Skip to content

Commit

Permalink
feat(cli, client): install package glasskube#23 glasskube#28 glasskub…
Browse files Browse the repository at this point in the history
  • Loading branch information
christophenne authored and kosmoz committed Jan 22, 2024
1 parent 221a05a commit 54a5237
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ go.work
bin/

dist/

.idea/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Start the package manager:
glasskube serve
```

Open [`http://localhost:80805`](http://localhost:80805) and explore available packages.
Open [`http://localhost:8580`](http://localhost:8580) and explore available packages.

## 📦 Supported Packages

Expand Down
43 changes: 43 additions & 0 deletions cmd/glasskube/cmd/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package cmd

import (
"fmt"
"github.com/glasskube/glasskube/api/v1alpha1/condition"
"github.com/glasskube/glasskube/cmd/glasskube/config"
"github.com/glasskube/glasskube/pkg/client"
"github.com/glasskube/glasskube/pkg/install"
"github.com/spf13/cobra"
)

var installCmd = &cobra.Command{
Use: "install [package-name]",
Short: "Install a package",
Long: `Install a package.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
pkgClient, err := client.InitKubeClient(config.Kubeconfig)
if err != nil {
return err
}
status, err := install.Install(pkgClient, cmd.Context(), args[0])
if err != nil {
return err
}
if status != nil {
switch (*status).Status {
case condition.Ready:
fmt.Println("Installed successfully.")
default:
fmt.Printf("Installation has status %v, reason: %v\nMessage: %v\n",
(*status).Status, (*status).Reason, (*status).Message)
}
} else {
fmt.Println("Installation status unknown - no error and no status have been observed.")
}
return nil
},
}

func init() {
RootCmd.AddCommand(installCmd)
}
10 changes: 6 additions & 4 deletions cmd/glasskube/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cmd

import (
"fmt"
"github.com/glasskube/glasskube/cmd/glasskube/config"

"github.com/spf13/cobra"
)
Expand All @@ -11,8 +11,10 @@ var (
Use: "glasskube",
Version: "0.0.0",
Short: "Kubernetes Package Management the easy way 🔥",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("glasskube cli stub")
},
}
)

func init() {
RootCmd.PersistentFlags().StringVar(&config.Kubeconfig, "kubeconfig", "",
"path to the kubeconfig file, whose current-context will be used (defaults to ~/.kube/config)")
}
3 changes: 3 additions & 0 deletions cmd/glasskube/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package config

var Kubeconfig string
30 changes: 30 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package client

import (
"github.com/glasskube/glasskube/api/v1alpha1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/clientcmd"
)

var PackageGVR = v1alpha1.GroupVersion.WithResource("packages")

func InitKubeClient(kubeconfig string) (*PackageV1Alpha1Client, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
if kubeconfig != "" {
loadingRules.ExplicitPath = kubeconfig
}
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
config, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
err = v1alpha1.AddToScheme(scheme.Scheme)
if err != nil {
return nil, err
}
pkgClient, err := NewPackageClient(config)
if err != nil {
return nil, err
}
return pkgClient, nil
}
75 changes: 75 additions & 0 deletions pkg/client/package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package client

import (
"context"
"github.com/glasskube/glasskube/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)

type PackageV1Alpha1Client struct {
restClient rest.Interface
}

type PackageInterface interface {
Create(ctx context.Context, p *v1alpha1.Package) error
Watch(ctx context.Context) (watch.Interface, error)
}

type packageClient struct {
restClient rest.Interface
}

func NewPackageClient(cfg *rest.Config) (*PackageV1Alpha1Client, error) {
pkgRestConfig := *cfg
pkgRestConfig.ContentConfig.GroupVersion = &v1alpha1.GroupVersion
pkgRestConfig.APIPath = "/apis"
pkgRestConfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
restClient, err := rest.RESTClientFor(&pkgRestConfig)
if err != nil {
return nil, err
}
return &PackageV1Alpha1Client{restClient: restClient}, err
}

func (c *PackageV1Alpha1Client) Packages() PackageInterface {
return &packageClient{
restClient: c.restClient,
}
}

func (c *packageClient) Create(ctx context.Context, pkg *v1alpha1.Package) error {
return c.restClient.Post().
Resource(PackageGVR.Resource).
Body(pkg).Do(ctx).Into(pkg)
}

func (c *packageClient) Watch(ctx context.Context) (watch.Interface, error) {
opts := metav1.ListOptions{Watch: true}
return c.restClient.Get().
Resource(PackageGVR.Resource).
VersionedParams(&opts, scheme.ParameterCodec).
Watch(ctx)
}

// NewPackage instantiates a new v1alpha1.Package struct with the given package name
func NewPackage(packageName string) *v1alpha1.Package {
return &v1alpha1.Package{
ObjectMeta: metav1.ObjectMeta{
Name: packageName,
},
Spec: v1alpha1.PackageSpec{
PackageInfo: v1alpha1.PackageInfoTemplate{
Name: packageName,
},
},
}
}

type PackageStatus struct {
Status string
Reason string
Message string
}
73 changes: 73 additions & 0 deletions pkg/install/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package install

import (
"context"
"errors"
"fmt"
"github.com/glasskube/glasskube/api/v1alpha1"
"github.com/glasskube/glasskube/api/v1alpha1/condition"
"github.com/glasskube/glasskube/pkg/client"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
)

// Install creates a new v1alpha1.Package custom resource in the cluster, and blocks until this resource has either
// status Ready or Failed.
func Install(pkgClient *client.PackageV1Alpha1Client, ctx context.Context, packageName string) (*client.PackageStatus, error) {
pkg := client.NewPackage(packageName)
err := pkgClient.Packages().Create(ctx, pkg)
if err != nil {
return nil, err
}
fmt.Printf("Installing %v.\n", packageName)

status, err := awaitInstall(pkgClient, ctx, pkg.GetUID())
if err != nil {
return nil, err
}

return status, nil
}

func awaitInstall(pkgClient *client.PackageV1Alpha1Client, ctx context.Context, pkgUID types.UID) (*client.PackageStatus, error) {
watcher, err := pkgClient.Packages().Watch(ctx)
if err != nil {
return nil, err
}

defer watcher.Stop()
for event := range watcher.ResultChan() {
if obj, ok := event.Object.(*v1alpha1.Package); ok && obj.GetUID() == pkgUID {
if event.Type == watch.Added || event.Type == watch.Modified {
if status := getStatus(&obj.Status); status != nil {
return status, nil
}
} else if event.Type == watch.Deleted {
return nil, errors.New("created package has been deleted unexpectedly")
}
}
}
return nil, errors.New("failed to confirm package installation status")
}

func getStatus(status *v1alpha1.PackageStatus) *client.PackageStatus {
readyCnd := meta.FindStatusCondition((*status).Conditions, condition.Ready)
if readyCnd != nil && readyCnd.Status == v1.ConditionTrue {
return newPackageStatus(readyCnd)
}
failedCnd := meta.FindStatusCondition((*status).Conditions, condition.Failed)
if failedCnd != nil && failedCnd.Status == v1.ConditionTrue {
return newPackageStatus(failedCnd)
}
return nil
}

func newPackageStatus(cnd *v1.Condition) *client.PackageStatus {
return &client.PackageStatus{
Status: cnd.Type,
Reason: cnd.Reason,
Message: cnd.Message,
}
}
8 changes: 6 additions & 2 deletions website/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,18 @@ Currently only the glasskube packages repository is supported: [`glasskube/packa

## Commands

### `glasskube`
### `glasskube serve`

Will start the UI server and opens a local browser on localhost:80805.
Will start the UI server and opens a local browser on [http://localhost:8580](http://localhost:8580).

### `glasskube bootstrap`

### `glasskube install`

Install the given package in your cluster.
By default, the cluster given in `~/.kube/config` (`current-context`) will be used.
An alternative kube config can be passed with the `--kubeconfig` flag.

```
glasskube install <package>
Expand Down

0 comments on commit 54a5237

Please sign in to comment.