From 48d13e586bc0c2b8fd0cc54da9db06b5800e1677 Mon Sep 17 00:00:00 2001 From: Reficul Date: Thu, 24 Mar 2022 18:00:48 +0800 Subject: [PATCH] fix #201 running kubectl apply in higher level --- go.mod | 2 +- .../declarative/pkg/applier/direct.go | 129 +++++++++++------- 2 files changed, 80 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index e78946df..0286aa6a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/golang/glog v1.0.0 github.com/google/go-cmp v0.5.6 github.com/prometheus/client_golang v1.11.0 + github.com/spf13/cobra v1.2.1 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff k8s.io/api v0.23.0 @@ -80,7 +81,6 @@ require ( github.com/prometheus/procfs v0.6.0 // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/spf13/cobra v1.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.7.0 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect diff --git a/pkg/patterns/declarative/pkg/applier/direct.go b/pkg/patterns/declarative/pkg/applier/direct.go index b8c84583..e2156eba 100644 --- a/pkg/patterns/declarative/pkg/applier/direct.go +++ b/pkg/patterns/declarative/pkg/applier/direct.go @@ -1,19 +1,21 @@ package applier import ( + "bytes" "context" "fmt" "os" "strings" + "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" "k8s.io/kubectl/pkg/cmd/apply" - cmdDelete "k8s.io/kubectl/pkg/cmd/delete" cmdutil "k8s.io/kubectl/pkg/cmd/util" ) @@ -27,71 +29,48 @@ func NewDirectApplier() *DirectApplier { } func (d *DirectApplier) Apply(ctx context.Context, opt ApplierOptions) error { + ioReader := strings.NewReader(opt.Manifest) + + buffer := &bytes.Buffer{} + ioStreams := genericclioptions.IOStreams{ - In: os.Stdin, - Out: os.Stdout, - ErrOut: os.Stderr, + In: os.DevNull, + Out: buffer, + ErrOut: buffer, } - ioReader := strings.NewReader(opt.Manifest) restClientGetter := &staticRESTClientGetter{ RESTMapper: opt.RESTMapper, RESTConfig: opt.RESTConfig, } - b := resource.NewBuilder(restClientGetter) - - if opt.Validate { - // This potentially causes redundant work, but validation isn't the common path - v, err := cmdutil.NewFactory(&genericclioptions.ConfigFlags{}).Validator(true) - if err != nil { - return err - } - b.Schema(v) - } - res := b.Unstructured().Stream(ioReader, "manifestString").Do() - infos, err := res.Infos() - if err != nil { - return err + args := append(opt.ExtraArgs, "-f", "-") + if !opt.Validate { + args = append(args, "--validate=false") } - // Populate the namespace on any namespace-scoped objects - if opt.Namespace != "" { - visitor := resource.SetNamespace(opt.Namespace) - for _, info := range infos { - if err := info.Visit(visitor); err != nil { - return fmt.Errorf("error from SetNamespace: %w", err) - } - } + cmd, o := NewCmdApply(ioStreams) + + if err := cmd.ParseFlags(args); err != nil { + return fmt.Errorf("parse kubectl apply args failed, args: %v, errors: %s, msg:%s", args, err, buffer.String()) } - applyOpts := apply.NewApplyOptions(ioStreams) - - for i, arg := range opt.ExtraArgs { - switch arg { - case "--force": - applyOpts.ForceConflicts = true - case "--prune": - applyOpts.Prune = true - case "--selector": - applyOpts.Selector = opt.ExtraArgs[i + 1] - default: - continue - } + if err := o.Complete(cmdutil.NewFactory(restClientGetter), cmd); err != nil { + return fmt.Errorf("exec kubectl apply failed, args: %v, errors: %s, msg:%s", args, err, buffer.String()) } - applyOpts.Namespace = opt.Namespace - applyOpts.SetObjects(infos) - applyOpts.ToPrinter = func(operation string) (printers.ResourcePrinter, error) { - applyOpts.PrintFlags.NamePrintFlags.Operation = operation - cmdutil.PrintFlagsWithDryRunStrategy(applyOpts.PrintFlags, applyOpts.DryRunStrategy) - return applyOpts.PrintFlags.ToPrinter() + res := o.Builder.Unstructured().Stream(ioReader, "manifestString").Do() + infos, err := res.Infos() + if err != nil { + return err } - applyOpts.DeleteOptions = &cmdDelete.DeleteOptions{ - IOStreams: ioStreams, + o.SetObjects(infos) + + if err := o.Run(); err != nil { + return fmt.Errorf("exec kubectl apply failed, args: %v, errors: %s, msg:%s", args, err, buffer.String()) } - return applyOpts.Run() + return nil } // staticRESTClientGetter returns a fixed RESTClient @@ -99,6 +78,7 @@ type staticRESTClientGetter struct { RESTConfig *rest.Config DiscoveryClient discovery.CachedDiscoveryInterface RESTMapper meta.RESTMapper + namespace string } var _ resource.RESTClientGetter = &staticRESTClientGetter{} @@ -121,3 +101,52 @@ func (s *staticRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) { } return s.RESTMapper, nil } +func (s *staticRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + return namespaceStub(s.namespace) +} + +type namespaceStub string + +func (n namespaceStub) Namespace() (string, bool, error) { + return string(n), false, nil +} + +// below methods should never be called +func (namespaceStub) RawConfig() (api.Config, error) { + panic("implement me") +} + +func (namespaceStub) ClientConfig() (*rest.Config, error) { + panic("implement me") +} + +func (namespaceStub) ConfigAccess() clientcmd.ConfigAccess { + panic("implement me") +} + +func NewCmdApply(ioStreams genericclioptions.IOStreams) (*cobra.Command, *apply.ApplyOptions) { + o := apply.NewApplyOptions(ioStreams) + + // Store baseName for use in printing warnings / messages involving the base command name. + // This is useful for downstream command that wrap this one. + + cmd := &cobra.Command{} + + // bind flag structs + o.DeleteFlags.AddFlags(cmd) + o.RecordFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) + + cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration") + cmd.Flags().BoolVar(&o.Prune, "prune", o.Prune, "Automatically delete resource objects, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.") + cmdutil.AddValidateFlags(cmd) + cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().BoolVar(&o.All, "all", o.All, "Select all resources in the namespace of the specified resource types.") + cmd.Flags().StringArrayVar(&o.PruneWhitelist, "prune-whitelist", o.PruneWhitelist, "Overwrite the default whitelist with for --prune") + cmd.Flags().BoolVar(&o.OpenAPIPatch, "openapi-patch", o.OpenAPIPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.") + cmdutil.AddDryRunFlag(cmd) + cmdutil.AddServerSideApplyFlags(cmd) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, apply.FieldManagerClientSideApply) + + return cmd, o +}