diff --git a/README.md b/README.md index a7f15c9..d6511a5 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,9 @@ make ORG= build publish Have a working `k3s` installation with a working `$HOME/.kube/config` or `$KUBECONFIG`, then: ```bash -# Installation on a single-node cluster -./bin/kim install --agent-image=docker.io/${ORG}/kim -``` - -```bash +# Installation on a single-node cluster is automatic # Installation on a multi-node cluster, targeting a Node named "my-builder-node" -./bin/kim install --agent-image=docker.io/${ORG}/kim --selector k3s.io/hostname=my-builder-node +./bin/kim install --selector k3s.io/hostname=my-builder-node ``` @@ -76,19 +72,20 @@ Usage: kim [command] Examples: - kim build --tag your/image:tag . + kim image build --tag your/image:tag . Available Commands: - build Build an image help Help about any command + image Manage Images + system Manage KIM + +Images Shortcuts: + build Build an image images List images - info Display builder information - install Install builder component(s) pull Pull an image push Push an image rmi Remove an image tag Tag an image - uninstall Uninstall builder component(s) Flags: -x, --context string kubeconfig context for authentication @@ -96,7 +93,7 @@ Flags: --debug-level int -h, --help help for kim -k, --kubeconfig string kubeconfig for authentication - -n, --namespace string namespace (default "kim") + -n, --namespace string namespace (default "kube-image") -v, --version version for kim Use "kim [command] --help" for more information about a command. diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index ad7e13a..3491da9 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -1,16 +1,17 @@ package cli import ( - "github.com/rancher/kim/pkg/cli/commands/agent" - "github.com/rancher/kim/pkg/cli/commands/build" - "github.com/rancher/kim/pkg/cli/commands/images" - "github.com/rancher/kim/pkg/cli/commands/info" - "github.com/rancher/kim/pkg/cli/commands/install" - "github.com/rancher/kim/pkg/cli/commands/pull" - "github.com/rancher/kim/pkg/cli/commands/push" - "github.com/rancher/kim/pkg/cli/commands/rmi" - "github.com/rancher/kim/pkg/cli/commands/tag" - "github.com/rancher/kim/pkg/cli/commands/uninstall" + "strings" + + "github.com/rancher/kim/pkg/cli/command/agent" + "github.com/rancher/kim/pkg/cli/command/image" + "github.com/rancher/kim/pkg/cli/command/image/build" + "github.com/rancher/kim/pkg/cli/command/image/list" + "github.com/rancher/kim/pkg/cli/command/image/pull" + "github.com/rancher/kim/pkg/cli/command/image/push" + "github.com/rancher/kim/pkg/cli/command/image/remove" + "github.com/rancher/kim/pkg/cli/command/image/tag" + "github.com/rancher/kim/pkg/cli/command/system" "github.com/rancher/kim/pkg/client" "github.com/rancher/kim/pkg/credential/provider" "github.com/rancher/kim/pkg/version" @@ -30,8 +31,11 @@ Aliases: Examples: {{.Example}}{{end}}{{if .HasAvailableSubCommands}} -Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} +Available Commands:{{range .Commands}}{{if (and (not (index .Annotations "shortcut-root")) (or .IsAvailableCommand (eq .Name "help")))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}{{if (eq (index .Annotations "shortcuts") "image")}} + +Images Shortcuts:{{range .Commands}}{{if (and .IsAvailableCommand (eq (index .Annotations "shortcut-root") "image"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} @@ -55,25 +59,54 @@ func Main() *cobra.Command { Use: "kim [OPTIONS] COMMAND", Short: "Kubernetes Image Manager -- in ur kubernetes buildin ur imagez", Version: version.FriendlyVersion(), - Example: "kim build --tag your/image:tag .", + Example: "kim image build --tag your/image:tag .", DisableFlagsInUseLine: true, + Annotations: map[string]string{ + "shortcuts": "image", + }, }) cmd.AddCommand( agent.Command(), - info.Command(), - images.Command(), - install.Command(), - uninstall.Command(), - build.Command(), - pull.Command(), - push.Command(), - rmi.Command(), - tag.Command(), + image.Command(), + system.Command(), ) + // image subsystem shortcuts + AddShortcut(cmd, build.Use, "image", "build") + AddShortcut(cmd, list.Use("images"), "image", "list") + AddShortcut(cmd, pull.Use, "image", "pull") + AddShortcut(cmd, push.Use, "image", "push") + AddShortcut(cmd, remove.Use("rmi"), "image", "remove") + AddShortcut(cmd, tag.Use, "image", "tag") cmd.SetUsageTemplate(defaultUsageTemplate) return cmd } +func AddShortcut(cmd *cobra.Command, use string, path ...string) { + sub, _, err := cmd.Find(path) + if err != nil { + panic(err) + } + target := strings.Join(path, " ") + shortcut := *sub + shortcut.Use = use + //shortcut.Short = fmt.Sprintf("%s (shortcut to `%s %s`)", sub.Short, cmd.Name(), target) + shortcut.Aliases = []string{target} + shortcut.Annotations = map[string]string{ + "shortcut-root": path[0], + } + for pre := sub; ; pre = pre.Parent() { + if pre.Name() == path[0] { + if pre.PersistentPreRunE != nil { + shortcut.PersistentPreRunE = func(alias *cobra.Command, args []string) error { + return pre.PersistentPreRunE(alias, args) + } + } + break + } + } + cmd.AddCommand(&shortcut) +} + type App struct { wrangler.DebugConfig client.Config diff --git a/pkg/cli/commands/agent/agent.go b/pkg/cli/command/agent/agent.go similarity index 89% rename from pkg/cli/commands/agent/agent.go rename to pkg/cli/command/agent/agent.go index d1f3eb0..99c7d21 100644 --- a/pkg/cli/commands/agent/agent.go +++ b/pkg/cli/command/agent/agent.go @@ -10,7 +10,7 @@ import ( func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ Use: "agent [OPTIONS]", - Short: "Run the controller daemon", + Short: "Run the controller daemon (on supported platforms)", Hidden: true, DisableFlagsInUseLine: true, }) diff --git a/pkg/cli/commands/build/build.go b/pkg/cli/command/image/build/build.go similarity index 73% rename from pkg/cli/commands/build/build.go rename to pkg/cli/command/image/build/build.go index 4b8b390..1b32927 100644 --- a/pkg/cli/commands/build/build.go +++ b/pkg/cli/command/image/build/build.go @@ -5,21 +5,26 @@ import ( "os" "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/image" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) +const ( + Use = "build [OPTIONS] PATH" + Short = "Build an image" +) + func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "build [OPTIONS] PATH", - Short: "Build an image", + Use: Use, + Short: Short, DisableFlagsInUseLine: true, }) } type CommandSpec struct { - action.BuildImage + image.Build } func (c *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -38,5 +43,5 @@ func (c *CommandSpec) Run(cmd *cobra.Command, args []string) error { if err != nil { return err } - return c.BuildImage.Invoke(cmd.Context(), k8s, path) + return c.Build.Do(cmd.Context(), k8s, path) } diff --git a/pkg/cli/command/image/image.go b/pkg/cli/command/image/image.go new file mode 100644 index 0000000..5e4d916 --- /dev/null +++ b/pkg/cli/command/image/image.go @@ -0,0 +1,60 @@ +package image + +import ( + "github.com/rancher/kim/pkg/cli/command/image/build" + "github.com/rancher/kim/pkg/cli/command/image/list" + "github.com/rancher/kim/pkg/cli/command/image/pull" + "github.com/rancher/kim/pkg/cli/command/image/push" + "github.com/rancher/kim/pkg/cli/command/image/remove" + "github.com/rancher/kim/pkg/cli/command/image/tag" + "github.com/rancher/kim/pkg/cli/command/system/install" + "github.com/rancher/kim/pkg/client" + wrangler "github.com/rancher/wrangler-cli" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Command() *cobra.Command { + cmd := wrangler.Command(&CommandSpec{}, cobra.Command{ + Use: "image [OPTIONS] COMMAND", + Short: "Manage Images", + DisableFlagsInUseLine: true, + //TraverseChildren: true, + }) + cmd.AddCommand( + build.Command(), + list.Command(), + pull.Command(), + push.Command(), + remove.Command(), + tag.Command(), + ) + return cmd +} + +type CommandSpec struct { +} + +func (s *CommandSpec) PersistentPre(cmd *cobra.Command, _ []string) error { + pre := install.CommandSpec{} + // i've tried using subcommands from the cli command tree but there be dragons + wrangler.Command(&pre, cobra.Command{}) // initialize pre.Install defaults + k8s, err := client.DefaultConfig.Interface() + if err != nil { + return err + } + // if the daemon-set is available then we don't need to do anything + daemon, err := k8s.Apps.DaemonSet().Get(k8s.Namespace, "builder", metav1.GetOptions{}) + if err == nil && daemon.Status.NumberAvailable > 0 { + return nil + } + pre.NoWait = false + pre.NoFail = true + logrus.Warnf("Cannot find available builder daemon, attempting automatic installation...") + return pre.Install.Do(cmd.Context(), k8s) +} + +func (s *CommandSpec) Run(cmd *cobra.Command, _ []string) error { + return cmd.Help() +} diff --git a/pkg/cli/commands/images/images.go b/pkg/cli/command/image/list/list.go similarity index 54% rename from pkg/cli/commands/images/images.go rename to pkg/cli/command/image/list/list.go index c8f6694..1db8e93 100644 --- a/pkg/cli/commands/images/images.go +++ b/pkg/cli/command/image/list/list.go @@ -1,22 +1,33 @@ -package images +package list import ( + "fmt" + "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/image" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) +const ( + Short = "List images" +) + +func Use(sub string) string { + return fmt.Sprintf("%s [OPTIONS] [REPOSITORY[:TAG]]", sub) +} + func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "images [OPTIONS] [REPOSITORY[:TAG]]", - Short: "List images", + Use: Use("ls"), + Short: Short, DisableFlagsInUseLine: true, + Aliases: []string{"list"}, }) } type CommandSpec struct { - action.ListImages + image.List } func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -25,5 +36,5 @@ func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { return err } - return s.ListImages.Invoke(cmd.Context(), k8s, args) + return s.List.Do(cmd.Context(), k8s, args) } diff --git a/pkg/cli/commands/pull/pull.go b/pkg/cli/command/image/pull/pull.go similarity index 66% rename from pkg/cli/commands/pull/pull.go rename to pkg/cli/command/image/pull/pull.go index 55c76f4..03b47f2 100644 --- a/pkg/cli/commands/pull/pull.go +++ b/pkg/cli/command/image/pull/pull.go @@ -3,20 +3,26 @@ package pull import ( "github.com/pkg/errors" "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/image" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) +const ( + Use = "pull [OPTIONS] IMAGE" + Short = "Pull an image" +) + func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "pull [OPTIONS] IMAGE", - Short: "Pull an image", + Use: Use, + Short: Short, + DisableFlagsInUseLine: true, }) } type CommandSpec struct { - action.PullImage + image.Pull } func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -28,5 +34,5 @@ func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { if err != nil { return err } - return s.PullImage.Invoke(cmd.Context(), k8s, args[0]) + return s.Pull.Do(cmd.Context(), k8s, args[0]) } diff --git a/pkg/cli/commands/push/push.go b/pkg/cli/command/image/push/push.go similarity index 66% rename from pkg/cli/commands/push/push.go rename to pkg/cli/command/image/push/push.go index 9f9022d..f81a01a 100644 --- a/pkg/cli/commands/push/push.go +++ b/pkg/cli/command/image/push/push.go @@ -3,20 +3,26 @@ package push import ( "github.com/pkg/errors" "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/image" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) +const ( + Use = "push [OPTIONS] IMAGE" + Short = "Push an image" +) + func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "push [OPTIONS] IMAGE", - Short: "Push an image", + Use: Use, + Short: Short, + DisableFlagsInUseLine: true, }) } type CommandSpec struct { - action.PushImage + image.Push } func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -28,5 +34,5 @@ func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { if err != nil { return err } - return s.PushImage.Invoke(cmd.Context(), k8s, args[0]) + return s.Push.Do(cmd.Context(), k8s, args[0]) } diff --git a/pkg/cli/commands/rmi/rmi.go b/pkg/cli/command/image/remove/remove.go similarity index 55% rename from pkg/cli/commands/rmi/rmi.go rename to pkg/cli/command/image/remove/remove.go index 6cb6e02..b2bf77d 100644 --- a/pkg/cli/commands/rmi/rmi.go +++ b/pkg/cli/command/image/remove/remove.go @@ -1,22 +1,34 @@ -package rmi +package remove import ( + "fmt" + "github.com/pkg/errors" "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/image" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) +const ( + Short = "Remove an image" +) + +func Use(sub string) string { + return fmt.Sprintf("%s [OPTIONS] IMAGE [IMAGE...]", sub) +} + func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "rmi REF", - Short: "Remove an image", + Use: Use("rm"), + Short: Short, + Aliases: []string{"remove"}, + DisableFlagsInUseLine: true, }) } type CommandSpec struct { - action.RemoveImage + image.Remove } func (c *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -28,5 +40,5 @@ func (c *CommandSpec) Run(cmd *cobra.Command, args []string) error { if err != nil { return err } - return c.RemoveImage.Invoke(cmd.Context(), k8s, args[0]) + return c.Remove.Do(cmd.Context(), k8s, args[0]) } diff --git a/pkg/cli/commands/tag/tag.go b/pkg/cli/command/image/tag/tag.go similarity index 63% rename from pkg/cli/commands/tag/tag.go rename to pkg/cli/command/image/tag/tag.go index 0009619..26eff2d 100644 --- a/pkg/cli/commands/tag/tag.go +++ b/pkg/cli/command/image/tag/tag.go @@ -3,20 +3,26 @@ package tag import ( "github.com/pkg/errors" "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/image" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) +const ( + Use = "tag SOURCE_REF TARGET_REF [TARGET_REF, ...]" + Short = "Tag an image" +) + func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "tag SOURCE_REF TARGET_REF [TARGET_REF, ...]", - Short: "Tag an image", + Use: Use, + Short: Short, + DisableFlagsInUseLine: true, }) } type CommandSpec struct { - action.TagImage + image.Tag } func (c *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -28,5 +34,5 @@ func (c *CommandSpec) Run(cmd *cobra.Command, args []string) error { if err != nil { return err } - return c.TagImage.Invoke(cmd.Context(), k8s, args[0], args[1:]) + return c.Tag.Do(cmd.Context(), k8s, args[0], args[1:]) } diff --git a/pkg/cli/commands/info/info.go b/pkg/cli/command/system/info/info.go similarity index 73% rename from pkg/cli/commands/info/info.go rename to pkg/cli/command/system/info/info.go index 6e5d1ec..0f14608 100644 --- a/pkg/cli/commands/info/info.go +++ b/pkg/cli/command/system/info/info.go @@ -8,8 +8,9 @@ import ( func Command() *cobra.Command { return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "info [OPTIONS]", - Short: "Display builder information", + Use: "info [OPTIONS]", + Short: "Display builder information", + DisableFlagsInUseLine: true, }) } diff --git a/pkg/cli/command/system/install/install.go b/pkg/cli/command/system/install/install.go new file mode 100644 index 0000000..a2a0969 --- /dev/null +++ b/pkg/cli/command/system/install/install.go @@ -0,0 +1,28 @@ +package install + +import ( + "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/system/builder" + wrangler "github.com/rancher/wrangler-cli" + "github.com/spf13/cobra" +) + +func Command() *cobra.Command { + return wrangler.Command(&CommandSpec{}, cobra.Command{ + Use: "install [OPTIONS]", + Short: "Install builder component(s)", + DisableFlagsInUseLine: true, + }) +} + +type CommandSpec struct { + builder.Install +} + +func (s *CommandSpec) Run(cmd *cobra.Command, _ []string) error { + k8s, err := client.DefaultConfig.Interface() + if err != nil { + return err + } + return s.Install.Do(cmd.Context(), k8s) +} diff --git a/pkg/cli/command/system/system.go b/pkg/cli/command/system/system.go new file mode 100644 index 0000000..3a5325b --- /dev/null +++ b/pkg/cli/command/system/system.go @@ -0,0 +1,30 @@ +package system + +import ( + "github.com/rancher/kim/pkg/cli/command/system/info" + "github.com/rancher/kim/pkg/cli/command/system/install" + "github.com/rancher/kim/pkg/cli/command/system/uninstall" + wrangler "github.com/rancher/wrangler-cli" + "github.com/spf13/cobra" +) + +func Command() *cobra.Command { + cmd := wrangler.Command(&CommandSpec{}, cobra.Command{ + Use: "system [OPTIONS] COMMAND", + Short: "Manage KIM", + DisableFlagsInUseLine: true, + }) + cmd.AddCommand( + info.Command(), + install.Command(), + uninstall.Command(), + ) + return cmd +} + +type CommandSpec struct { +} + +func (s *CommandSpec) Run(cmd *cobra.Command, _ []string) error { + return cmd.Help() +} diff --git a/pkg/cli/commands/uninstall/uninstall.go b/pkg/cli/command/system/uninstall/uninstall.go similarity index 78% rename from pkg/cli/commands/uninstall/uninstall.go rename to pkg/cli/command/system/uninstall/uninstall.go index 9267480..5a7c77d 100644 --- a/pkg/cli/commands/uninstall/uninstall.go +++ b/pkg/cli/command/system/uninstall/uninstall.go @@ -2,7 +2,7 @@ package uninstall import ( "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" + "github.com/rancher/kim/pkg/client/system/builder" wrangler "github.com/rancher/wrangler-cli" "github.com/spf13/cobra" ) @@ -16,7 +16,7 @@ func Command() *cobra.Command { } type CommandSpec struct { - action.UninstallBuilder + builder.Uninstall } func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { @@ -25,9 +25,9 @@ func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { return err } ctx := cmd.Context() - err = s.UninstallBuilder.Namespace(ctx, k8s) + err = s.Uninstall.Namespace(ctx, k8s) if err != nil { return err } - return s.UninstallBuilder.NodeRole(ctx, k8s) + return s.Uninstall.NodeRole(ctx, k8s) } diff --git a/pkg/cli/commands/install/install.go b/pkg/cli/commands/install/install.go deleted file mode 100644 index fc678b8..0000000 --- a/pkg/cli/commands/install/install.go +++ /dev/null @@ -1,50 +0,0 @@ -package install - -import ( - "github.com/rancher/kim/pkg/client" - "github.com/rancher/kim/pkg/client/action" - wrangler "github.com/rancher/wrangler-cli" - "github.com/spf13/cobra" -) - -func Command() *cobra.Command { - return wrangler.Command(&CommandSpec{}, cobra.Command{ - Use: "install [OPTIONS]", - Short: "Install builder component(s)", - DisableFlagsInUseLine: true, - }) -} - -type CommandSpec struct { - action.InstallBuilder -} - -func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error { - k8s, err := client.DefaultConfig.Interface() - if err != nil { - return err - } - ctx := cmd.Context() - // assert namespace - err = s.InstallBuilder.Namespace(ctx, k8s) - if err != nil { - return err - } - // label the node(s) - err = s.InstallBuilder.NodeRole(ctx, k8s) - if err != nil { - return err - } - // assert secrets - err = s.InstallBuilder.Secrets(ctx, k8s) - if err != nil { - return err - } - // assert service - err = s.InstallBuilder.Service(ctx, k8s) - if err != nil { - return err - } - // assert daemonset - return s.InstallBuilder.DaemonSet(ctx, k8s) -} diff --git a/pkg/client/action/action.go b/pkg/client/action/action.go deleted file mode 100644 index 04c42db..0000000 --- a/pkg/client/action/action.go +++ /dev/null @@ -1,135 +0,0 @@ -package action - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "io/ioutil" - "net" - "path/filepath" - "strconv" - - buildkit "github.com/moby/buildkit/client" - "github.com/pkg/errors" - imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" - "github.com/rancher/kim/pkg/client" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type DoImagesFunc func(context.Context, imagesv1.ImagesClient) error -type DoControlFunc func(context.Context, *buildkit.Client) error - -func DoImages(ctx context.Context, k8s *client.Interface, fn DoImagesFunc) error { - addr, err := GetServiceAddress(ctx, k8s, "kim") - if err != nil { - return err - } - - tlsConfig := &tls.Config{} - - // ca cert - secret, err := k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-ca", metav1.GetOptions{}) - if err != nil { - return errors.Wrap(err, "failed to get ca cert") - } - if pem, ok := secret.Data[corev1.TLSCertKey]; ok { - tlsConfig.RootCAs = x509.NewCertPool() - tlsConfig.RootCAs.AppendCertsFromPEM(pem) - } - - // client cert+key - secret, err = k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-client", metav1.GetOptions{}) - if err != nil { - return errors.Wrap(err, "failed to get client cert+key") - } - certificate, err := tls.X509KeyPair(secret.Data[corev1.TLSCertKey], secret.Data[corev1.TLSPrivateKeyKey]) - if err != nil { - return errors.Wrap(err, "failed to setup client cert+key") - } - tlsConfig.Certificates = []tls.Certificate{certificate} - - conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) - if err != nil { - return err - } - defer conn.Close() - return fn(ctx, imagesv1.NewImagesClient(conn)) -} - -func DoControl(ctx context.Context, k8s *client.Interface, fn DoControlFunc) error { - addr, err := GetServiceAddress(ctx, k8s, "buildkit") - if err != nil { - return err - } - - tmp, err := ioutil.TempDir("", "kim-tls-*") - if err != nil { - return errors.Wrap(err, "failed to create temp directory") - } - - tmpCA := filepath.Join(tmp, "ca.pem") - tmpCert := filepath.Join(tmp, "cert.pem") - tmpKey := filepath.Join(tmp, "key.pem") - - options := []buildkit.ClientOpt{ - buildkit.WithCredentials( - fmt.Sprintf("builder.%s.svc", k8s.Namespace), - tmpCA, tmpCert, tmpKey, - ), - } - - // ca - secret, err := k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-ca", metav1.GetOptions{}) - if err != nil { - return errors.Wrap(err, "failed to get ca cert") - } - if pem, ok := secret.Data[corev1.TLSCertKey]; ok { - if err = ioutil.WriteFile(tmpCA, pem, 0600); err != nil { - return errors.Wrap(err, "failed to write temporary ca certificate") - } - } - // client - secret, err = k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-client", metav1.GetOptions{}) - if err != nil { - return errors.Wrap(err, "failed to get client cert+key") - } - if pem, ok := secret.Data[corev1.TLSCertKey]; ok { - if err = ioutil.WriteFile(tmpCert, pem, 0600); err != nil { - return errors.Wrap(err, "failed to write temporary client certificate") - } - } - if pem, ok := secret.Data[corev1.TLSPrivateKeyKey]; ok { - if err = ioutil.WriteFile(tmpKey, pem, 0600); err != nil { - return errors.Wrap(err, "failed to write temporary client key") - } - } - - bkc, err := buildkit.New(ctx, fmt.Sprintf("tcp://%s", addr), options...) - if err != nil { - return err - } - defer bkc.Close() - return fn(ctx, bkc) -} - -func GetServiceAddress(_ context.Context, k8s *client.Interface, port string) (string, error) { - // TODO handle multiple addresses - endpoints, err := k8s.Core.Endpoints().Get(k8s.Namespace, "builder", metav1.GetOptions{}) - if err != nil { - return "", err - } - for _, sub := range endpoints.Subsets { - if len(sub.Addresses) > 0 { - for _, p := range sub.Ports { - if p.Name == port { - return net.JoinHostPort(sub.Addresses[0].IP, strconv.FormatInt(int64(p.Port), 10)), nil - } - } - } - } - return "", errors.New("unknown service port") -} diff --git a/pkg/client/do/do-control.go b/pkg/client/do/do-control.go new file mode 100644 index 0000000..36488a7 --- /dev/null +++ b/pkg/client/do/do-control.go @@ -0,0 +1,74 @@ +package do + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + buildkit "github.com/moby/buildkit/client" + "github.com/pkg/errors" + "github.com/rancher/kim/pkg/client" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ControlFunc func(context.Context, *buildkit.Client) error + +func Control(ctx context.Context, k8s *client.Interface, fn ControlFunc) error { + addr, err := GetServiceAddress(ctx, k8s, "buildkit") + if err != nil { + return err + } + + tmp, err := ioutil.TempDir("", "kim-tls-*") + if err != nil { + return errors.Wrap(err, "failed to create temp directory") + } + defer os.RemoveAll(tmp) + + tmpCA := filepath.Join(tmp, "ca.pem") + tmpCert := filepath.Join(tmp, "cert.pem") + tmpKey := filepath.Join(tmp, "key.pem") + + options := []buildkit.ClientOpt{ + buildkit.WithCredentials( + fmt.Sprintf("builder.%s.svc", k8s.Namespace), + tmpCA, tmpCert, tmpKey, + ), + } + + // ca + secret, err := k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-ca", metav1.GetOptions{}) + if err != nil { + return errors.Wrap(err, "failed to get ca cert") + } + if pem, ok := secret.Data[corev1.TLSCertKey]; ok { + if err = ioutil.WriteFile(tmpCA, pem, 0600); err != nil { + return errors.Wrap(err, "failed to write temporary ca certificate") + } + } + // client + secret, err = k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-client", metav1.GetOptions{}) + if err != nil { + return errors.Wrap(err, "failed to get client cert+key") + } + if pem, ok := secret.Data[corev1.TLSCertKey]; ok { + if err = ioutil.WriteFile(tmpCert, pem, 0600); err != nil { + return errors.Wrap(err, "failed to write temporary client certificate") + } + } + if pem, ok := secret.Data[corev1.TLSPrivateKeyKey]; ok { + if err = ioutil.WriteFile(tmpKey, pem, 0600); err != nil { + return errors.Wrap(err, "failed to write temporary client key") + } + } + + bkc, err := buildkit.New(ctx, fmt.Sprintf("tcp://%s", addr), options...) + if err != nil { + return err + } + defer bkc.Close() + return fn(ctx, bkc) +} diff --git a/pkg/client/do/do-images.go b/pkg/client/do/do-images.go new file mode 100644 index 0000000..8a25de9 --- /dev/null +++ b/pkg/client/do/do-images.go @@ -0,0 +1,54 @@ +package do + +import ( + "context" + "crypto/tls" + "crypto/x509" + + "github.com/pkg/errors" + imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" + "github.com/rancher/kim/pkg/client" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ImagesFunc func(context.Context, imagesv1.ImagesClient) error + +func Images(ctx context.Context, k8s *client.Interface, fn ImagesFunc) error { + addr, err := GetServiceAddress(ctx, k8s, "kim") + if err != nil { + return err + } + + tlsConfig := &tls.Config{} + + // ca cert + secret, err := k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-ca", metav1.GetOptions{}) + if err != nil { + return errors.Wrap(err, "failed to get ca cert") + } + if pem, ok := secret.Data[corev1.TLSCertKey]; ok { + tlsConfig.RootCAs = x509.NewCertPool() + tlsConfig.RootCAs.AppendCertsFromPEM(pem) + } + + // client cert+key + secret, err = k8s.Core.Secret().Get(k8s.Namespace, "kim-tls-client", metav1.GetOptions{}) + if err != nil { + return errors.Wrap(err, "failed to get client cert+key") + } + certificate, err := tls.X509KeyPair(secret.Data[corev1.TLSCertKey], secret.Data[corev1.TLSPrivateKeyKey]) + if err != nil { + return errors.Wrap(err, "failed to setup client cert+key") + } + tlsConfig.Certificates = []tls.Certificate{certificate} + + conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + if err != nil { + return err + } + defer conn.Close() + return fn(ctx, imagesv1.NewImagesClient(conn)) +} diff --git a/pkg/client/do/do.go b/pkg/client/do/do.go new file mode 100644 index 0000000..7db4bf1 --- /dev/null +++ b/pkg/client/do/do.go @@ -0,0 +1,29 @@ +package do + +import ( + "context" + "net" + "strconv" + + "github.com/pkg/errors" + "github.com/rancher/kim/pkg/client" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func GetServiceAddress(_ context.Context, k8s *client.Interface, port string) (string, error) { + // TODO handle multiple addresses + endpoints, err := k8s.Core.Endpoints().Get(k8s.Namespace, "builder", metav1.GetOptions{}) + if err != nil { + return "", err + } + for _, sub := range endpoints.Subsets { + if len(sub.Addresses) > 0 { + for _, p := range sub.Ports { + if p.Name == port { + return net.JoinHostPort(sub.Addresses[0].IP, strconv.FormatInt(int64(p.Port), 10)), nil + } + } + } + } + return "", errors.New("unknown service port") +} diff --git a/pkg/client/action/action-build.go b/pkg/client/image/build.go similarity index 85% rename from pkg/client/action/action-build.go rename to pkg/client/image/build.go index 44306f0..768227e 100644 --- a/pkg/client/action/action-build.go +++ b/pkg/client/image/build.go @@ -1,4 +1,4 @@ -package action +package image import ( "context" @@ -13,11 +13,12 @@ import ( "github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/util/progress/progressui" "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/do" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" ) -type BuildImage struct { +type Build struct { AddHost []string `usage:"Add a custom host-to-IP mapping (host:ip)"` BuildArg []string `usage:"Set build-time variables"` //CacheFrom []string `usage:"Images to consider as cache sources"` @@ -34,12 +35,12 @@ type BuildImage struct { Pull bool `usage:"Always attempt to pull a newer version of the image"` } -func (s *BuildImage) Invoke(ctx context.Context, k8s *client.Interface, path string) error { - return DoControl(ctx, k8s, func(ctx context.Context, bkc *buildkit.Client) error { +func (s *Build) Do(ctx context.Context, k8s *client.Interface, path string) error { + return do.Control(ctx, k8s, func(ctx context.Context, bkc *buildkit.Client) error { options := buildkit.SolveOpt{ - Frontend: s.Frontend(), - FrontendAttrs: s.FrontendAttrs(), - LocalDirs: s.LocalDirs(path), + Frontend: "dockerfile.v0", + FrontendAttrs: s.frontendAttrs(), + LocalDirs: s.localDirs(path), Session: []session.Attachable{authprovider.NewDockerAuthProvider(os.Stdout)}, } if len(s.Tag) > 0 { @@ -58,11 +59,7 @@ func (s *BuildImage) Invoke(ctx context.Context, k8s *client.Interface, path str }) } -func (s *BuildImage) Frontend() string { - return "dockerfile.v0" -} - -func (s *BuildImage) FrontendAttrs() map[string]string { +func (s *Build) frontendAttrs() map[string]string { // --target m := map[string]string{ "target": s.Target, @@ -99,7 +96,7 @@ func (s *BuildImage) FrontendAttrs() map[string]string { return m } -func (s *BuildImage) LocalDirs(path string) map[string]string { +func (s *Build) localDirs(path string) map[string]string { m := map[string]string{ "context": path, } @@ -111,7 +108,7 @@ func (s *BuildImage) LocalDirs(path string) map[string]string { return m } -func (s *BuildImage) progress(group *errgroup.Group) chan *buildkit.SolveStatus { +func (s *Build) progress(group *errgroup.Group) chan *buildkit.SolveStatus { var ( c console.Console err error diff --git a/pkg/client/action/action-images.go b/pkg/client/image/list.go similarity index 93% rename from pkg/client/action/action-images.go rename to pkg/client/image/list.go index 7ec7098..e03182f 100644 --- a/pkg/client/action/action-images.go +++ b/pkg/client/image/list.go @@ -1,4 +1,4 @@ -package action +package image import ( "context" @@ -11,10 +11,11 @@ import ( "github.com/rancher/kim/pkg/apis/services/images" imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/do" criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) -type ListImages struct { +type List struct { All bool `usage:"Show all images (default hides tag-less images)" short:"a"` Digests bool `usage:"Show digests"` //Filter string `usage:"Filter output based on conditions provided" short:"f"` @@ -23,8 +24,8 @@ type ListImages struct { Quiet bool `usage:"Only show image IDs" short:"q"` } -func (s *ListImages) Invoke(ctx context.Context, k8s *client.Interface, names []string) error { - return DoImages(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { +func (s *List) Do(ctx context.Context, k8s *client.Interface, names []string) error { + return do.Images(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { req := &imagesv1.ImageListRequest{} // TODO filtering not working as expected if len(names) > 0 { diff --git a/pkg/client/action/action-pull.go b/pkg/client/image/pull.go similarity index 86% rename from pkg/client/action/action-pull.go rename to pkg/client/image/pull.go index 214d6a2..53ec838 100644 --- a/pkg/client/action/action-pull.go +++ b/pkg/client/image/pull.go @@ -1,4 +1,4 @@ -package action +package image import ( "context" @@ -7,6 +7,7 @@ import ( imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/do" "github.com/rancher/kim/pkg/progress" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -14,12 +15,12 @@ import ( "k8s.io/kubernetes/pkg/credentialprovider" ) -type PullImage struct { +type Pull struct { Platform string `usage:"Set platform if server is multi-platform capable"` } -func (s *PullImage) Invoke(ctx context.Context, k8s *client.Interface, image string) error { - return DoImages(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { +func (s *Pull) Do(ctx context.Context, k8s *client.Interface, image string) error { + return do.Images(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { ch := make(chan []imagesv1.ImageStatus) eg, ctx := errgroup.WithContext(ctx) // render output from the channel diff --git a/pkg/client/action/action-push.go b/pkg/client/image/push.go similarity index 85% rename from pkg/client/action/action-push.go rename to pkg/client/image/push.go index 9737e46..ced49fb 100644 --- a/pkg/client/action/action-push.go +++ b/pkg/client/image/push.go @@ -1,4 +1,4 @@ -package action +package image import ( "context" @@ -7,6 +7,7 @@ import ( imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/do" "github.com/rancher/kim/pkg/progress" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -14,11 +15,11 @@ import ( "k8s.io/kubernetes/pkg/credentialprovider" ) -type PushImage struct { +type Push struct { } -func (s *PushImage) Invoke(ctx context.Context, k8s *client.Interface, image string) error { - return DoImages(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { +func (s *Push) Do(ctx context.Context, k8s *client.Interface, image string) error { + return do.Images(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { ch := make(chan []imagesv1.ImageStatus) eg, ctx := errgroup.WithContext(ctx) // render output from the channel diff --git a/pkg/client/action/action-rmi.go b/pkg/client/image/remove.go similarity index 62% rename from pkg/client/action/action-rmi.go rename to pkg/client/image/remove.go index 93ab699..bed2014 100644 --- a/pkg/client/action/action-rmi.go +++ b/pkg/client/image/remove.go @@ -1,19 +1,20 @@ -package action +package image import ( "context" imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/do" "github.com/sirupsen/logrus" criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) -type RemoveImage struct { +type Remove struct { } -func (s *RemoveImage) Invoke(ctx context.Context, k8s *client.Interface, image string) error { - return DoImages(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { +func (s *Remove) Do(ctx context.Context, k8s *client.Interface, image string) error { + return do.Images(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { req := &imagesv1.ImageRemoveRequest{ Image: &criv1.ImageSpec{ Image: image, diff --git a/pkg/client/action/action-tag.go b/pkg/client/image/tag.go similarity index 62% rename from pkg/client/action/action-tag.go rename to pkg/client/image/tag.go index 0382811..fa45998 100644 --- a/pkg/client/action/action-tag.go +++ b/pkg/client/image/tag.go @@ -1,19 +1,20 @@ -package action +package image import ( "context" imagesv1 "github.com/rancher/kim/pkg/apis/services/images/v1alpha1" "github.com/rancher/kim/pkg/client" + "github.com/rancher/kim/pkg/client/do" "github.com/sirupsen/logrus" criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) -type TagImage struct { +type Tag struct { } -func (s *TagImage) Invoke(ctx context.Context, k8s *client.Interface, image string, tags []string) error { - return DoImages(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { +func (s *Tag) Do(ctx context.Context, k8s *client.Interface, image string, tags []string) error { + return do.Images(ctx, k8s, func(ctx context.Context, imagesClient imagesv1.ImagesClient) error { req := &imagesv1.ImageTagRequest{ Image: &criv1.ImageSpec{ Image: image, diff --git a/pkg/client/action/action-install.go b/pkg/client/system/builder/install.go similarity index 81% rename from pkg/client/action/action-install.go rename to pkg/client/system/builder/install.go index fac580a..b84a1ea 100644 --- a/pkg/client/action/action-install.go +++ b/pkg/client/system/builder/install.go @@ -1,9 +1,14 @@ -package action +package builder import ( "context" "fmt" "net" + "time" + + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/sirupsen/logrus" "github.com/pkg/errors" "github.com/rancher/kim/pkg/client" @@ -16,13 +21,82 @@ import ( "k8s.io/client-go/util/retry" ) -type InstallBuilder struct { +// Install the builder. +type Install struct { Force bool `usage:"Force installation by deleting existing builder"` Selector string `usage:"Selector for nodes (label query) to apply builder role"` + NoWait bool `usage:"Do not wait for backend to become available"` + NoFail bool `usage:"Do not fail if backend components are already installed"` server.Config } -func (_ *InstallBuilder) Namespace(_ context.Context, k *client.Interface) error { +func (a *Install) checkNoFail(err error) error { + if err == nil { + return nil + } + if a.NoFail { + logrus.Warn(err) + return nil + } + return err +} + +func (a *Install) Do(ctx context.Context, k8s *client.Interface) error { + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + + // assert node-role + if err := a.NodeRole(ctx, k8s); err != nil { + return a.checkNoFail(err) + } + // assert namespace + if err := a.Namespace(ctx, k8s); err != nil { + return a.checkNoFail(err) + } + // assert secrets + if err := a.Secrets(ctx, k8s); err != nil { + return a.checkNoFail(err) + } + // assert service + if err := a.Service(ctx, k8s); err != nil { + return a.checkNoFail(err) + } + // assert daemonset + if err := a.DaemonSet(ctx, k8s); err != nil { + return a.checkNoFail(err) + } + + if a.NoWait { + return nil + } + + retryMe := errors.New("retry me") + return retry.OnError( + wait.Backoff{ + Steps: 15, + Duration: 5 * time.Second, + Factor: 1.0, + Jitter: 0.3, + }, + func(err error) bool { + return err == retryMe + }, + func() error { + daemon, err := k8s.Apps.DaemonSet().Get(k8s.Namespace, "builder", metav1.GetOptions{}) + if err != nil { + return err + } + if daemon.Status.NumberReady == 0 { + logrus.Infof("Waiting on builder daemon availability...") + return retryMe + } + return nil + }, + ) +} + +func (_ *Install) Namespace(_ context.Context, k *client.Interface) error { + logrus.Infof("Asserting namespace `%s`", k.Namespace) return retry.RetryOnConflict(retry.DefaultRetry, func() error { ns, err := k.Core.Namespace().Get(k.Namespace, metav1.GetOptions{}) if apierr.IsNotFound(err) { @@ -48,7 +122,8 @@ func (_ *InstallBuilder) Namespace(_ context.Context, k *client.Interface) error }) } -func (a *InstallBuilder) Secrets(_ context.Context, k *client.Interface) error { +func (a *Install) Secrets(_ context.Context, k *client.Interface) error { + logrus.Info("Asserting TLS secrets") secrets := k.Core.Secret() if a.Force { deletePropagation := metav1.DeletePropagationBackground @@ -99,7 +174,8 @@ func (a *InstallBuilder) Secrets(_ context.Context, k *client.Interface) error { return nil } -func (a *InstallBuilder) Service(_ context.Context, k *client.Interface) error { +func (a *Install) Service(_ context.Context, k *client.Interface) error { + logrus.Info("Asserting service/endpoints") if a.Force { deletePropagation := metav1.DeletePropagationBackground deleteOptions := metav1.DeleteOptions{ @@ -147,7 +223,8 @@ func (a *InstallBuilder) Service(_ context.Context, k *client.Interface) error { }) } -func (a *InstallBuilder) DaemonSet(_ context.Context, k *client.Interface) error { +func (a *Install) DaemonSet(_ context.Context, k *client.Interface) error { + logrus.Info("Installing builder daemon") if a.Force { deletePropagation := metav1.DeletePropagationBackground deleteOptions := metav1.DeleteOptions{ @@ -346,12 +423,12 @@ func (a *InstallBuilder) DaemonSet(_ context.Context, k *client.Interface) error } _, err = k.Apps.DaemonSet().Create(daemon) if apierr.IsAlreadyExists(err) { - return errors.Errorf("buildkit already installed, specify --force to recreate") + return errors.Errorf("builder already installed") } - return err + return nil } -func (a *InstallBuilder) NodeRole(_ context.Context, k *client.Interface) error { +func (a *Install) NodeRole(_ context.Context, k *client.Interface) error { nodeList, err := k.Core.Node().List(metav1.ListOptions{ LabelSelector: a.Selector, }) @@ -359,6 +436,7 @@ func (a *InstallBuilder) NodeRole(_ context.Context, k *client.Interface) error return err } if len(nodeList.Items) == 1 { + logrus.Infof("Applying node-role `builder` to `%s`", nodeList.Items[0].Name) return retry.RetryOnConflict(retry.DefaultRetry, func() error { node, err := k.Core.Node().Get(nodeList.Items[0].Name, metav1.GetOptions{}) if err != nil { @@ -371,10 +449,15 @@ func (a *InstallBuilder) NodeRole(_ context.Context, k *client.Interface) error return err }) } - return errors.Errorf("too many nodes, please specify a selector, e.g. k3s.io/hostname=%s", nodeList.Items[0].Name) + + label := "k3s.io/hostname" + if _, k3s := nodeList.Items[0].Labels[label]; !k3s { + label = "kubernetes.io/hostname" + } + return errors.Errorf("too many nodes, please specify a selector, e.g. %s=%s", label, nodeList.Items[0].Name) } -func (a *InstallBuilder) containerPort(name string) corev1.ContainerPort { +func (a *Install) containerPort(name string) corev1.ContainerPort { switch name { case "buildkit": return corev1.ContainerPort{ @@ -393,7 +476,7 @@ func (a *InstallBuilder) containerPort(name string) corev1.ContainerPort { } } -func (a *InstallBuilder) servicePort(name string) corev1.ServicePort { +func (a *Install) servicePort(name string) corev1.ServicePort { switch name { case "buildkit": return corev1.ServicePort{ diff --git a/pkg/client/action/action-uninstall.go b/pkg/client/system/builder/uninstall.go similarity index 63% rename from pkg/client/action/action-uninstall.go rename to pkg/client/system/builder/uninstall.go index 8ce4491..aa5dbe7 100644 --- a/pkg/client/action/action-uninstall.go +++ b/pkg/client/system/builder/uninstall.go @@ -1,4 +1,4 @@ -package action +package builder import ( "context" @@ -13,17 +13,22 @@ import ( "k8s.io/client-go/util/retry" ) -type UninstallBuilder struct { +// Uninstall the builder. +type Uninstall struct { + Force bool `usage:"Force uninstallation by deleting namespace"` } -func (_ *UninstallBuilder) Namespace(ctx context.Context, k *client.Interface) error { +// Namespace uninstalls the builder namespace. +func (a *Uninstall) Namespace(ctx context.Context, k *client.Interface) error { ns, err := k.Core.Namespace().Get(k.Namespace, metav1.GetOptions{}) if err != nil { return err } - if ns.Labels == nil || ns.Labels["app.kubernetes.io/managed-by"] != "kim" { + + if !a.Force && (ns.Labels == nil || ns.Labels["app.kubernetes.io/managed-by"] != "kim") { return errors.Errorf("namespace not managed by kim") } + deletePropagation := metav1.DeletePropagationForeground deleteOptions := metav1.DeleteOptions{ PropagationPolicy: &deletePropagation, @@ -57,7 +62,8 @@ func (_ *UninstallBuilder) Namespace(ctx context.Context, k *client.Interface) e } } -func (a *UninstallBuilder) NodeRole(_ context.Context, k *client.Interface) error { +// NodeRole removes the builder role from all nodes with that role. +func (a *Uninstall) NodeRole(_ context.Context, k *client.Interface) error { nodeList, err := k.Core.Node().List(metav1.ListOptions{ LabelSelector: "node-role.kubernetes.io/builder", }) @@ -65,26 +71,24 @@ func (a *UninstallBuilder) NodeRole(_ context.Context, k *client.Interface) erro return err } for _, node := range nodeList.Items { - if err = retry.RetryOnConflict(retry.DefaultRetry, removeBuilderRole(k, node.Name)); err != nil { + if err = retry.RetryOnConflict(retry.DefaultRetry, removeNodeRole(k, node.Name)); err != nil { logrus.Warnf("failed to remove builder label from %s", node.Name) } } return nil } -func removeBuilderRole(k *client.Interface, nodeName string) func() error { +func removeNodeRole(k *client.Interface, nodeName string) func() error { return func() error { - return retry.RetryOnConflict(retry.DefaultRetry, func() error { - node, err := k.Core.Node().Get(nodeName, metav1.GetOptions{}) - if err != nil { - return err - } - if node.Labels == nil { - return nil - } - delete(node.Labels, "node-role.kubernetes.io/builder") - _, err = k.Core.Node().Update(node) + node, err := k.Core.Node().Get(nodeName, metav1.GetOptions{}) + if err != nil { return err - }) + } + if node.Labels == nil { + return nil + } + delete(node.Labels, "node-role.kubernetes.io/builder") + _, err = k.Core.Node().Update(node) + return err } } diff --git a/pkg/credential/provider/provider.go b/pkg/credential/provider/provider.go index 962e805..e0afcd7 100644 --- a/pkg/credential/provider/provider.go +++ b/pkg/credential/provider/provider.go @@ -10,8 +10,13 @@ import ( "k8s.io/kubernetes/pkg/credentialprovider" ) +var providers = map[string]bool{} + func RegisterDockerCredentialHelper(name string) { - credentialprovider.RegisterCredentialProvider(name, &dockerCredentialHelper{name: name}) + if registered := providers[name]; !registered { + credentialprovider.RegisterCredentialProvider(name, &dockerCredentialHelper{name: name}) + providers[name] = true + } } type dockerCredentialHelper struct { diff --git a/pkg/server/action/agent_other.go b/pkg/server/action/agent_other.go deleted file mode 100644 index f00f2bb..0000000 --- a/pkg/server/action/agent_other.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !linux - -package action - -import "context" - -func (s *Agent) Run(ctx context.Context) error { - panic("not supported on this platform") -} diff --git a/pkg/server/action/agent_unsupported.go b/pkg/server/action/agent_unsupported.go new file mode 100644 index 0000000..fda6c9b --- /dev/null +++ b/pkg/server/action/agent_unsupported.go @@ -0,0 +1,14 @@ +// +build !linux + +package action + +import ( + "context" + "runtime" + + "github.com/pkg/errors" +) + +func (_ *Agent) Run(_ context.Context) error { + return errors.Errorf("patform not supported: %s/%s", runtime.GOOS, runtime.GOARCH) +}