Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

running kubectl apply in higher level #211

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
136 changes: 83 additions & 53 deletions pkg/patterns/declarative/pkg/applier/direct.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
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"
"sigs.k8s.io/controller-runtime/pkg/log"
)

type DirectApplier struct {
Expand All @@ -27,78 +29,57 @@ func NewDirectApplier() *DirectApplier {
}

func (d *DirectApplier) Apply(ctx context.Context, opt ApplierOptions) error {
log := log.Log
ioReader := strings.NewReader(opt.Manifest)

buffer := &bytes.Buffer{}

ioStreams := genericclioptions.IOStreams{
In: os.Stdin,
Out: os.Stdout,
ErrOut: os.Stderr,
In: bytes.NewReader(nil),
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)
}
}
}
log.V(4).Info("applying manifests", "args", args, "manifests", opt.Manifest)

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
}
cmd, o := NewCmdApply(ioStreams)
if err := cmd.ParseFlags(args); err != nil {
return fmt.Errorf("parse kubectl apply args failed, args: %v, errors: %s", args, err)
}

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()
if err := o.Complete(cmdutil.NewFactory(restClientGetter), cmd); err != nil {
return fmt.Errorf("apply manifests failed, args: %v, errors: %s", args, err)
}
res := o.Builder.Unstructured().Stream(ioReader, "manifestString").Do()
if infos, err := res.Infos(); err != nil {
return err
} else {
o.SetObjects(infos)
}
applyOpts.DeleteOptions = &cmdDelete.DeleteOptions{
IOStreams: ioStreams,

if err := o.Run(); err != nil {
return fmt.Errorf("apply manifests failed, args: %v, errors: %s, msg:%s", args, err, buffer.String())
}

return applyOpts.Run()
log.Info("applying manifest succeed", "message", buffer.String())
return nil
}

// staticRESTClientGetter returns a fixed RESTClient
type staticRESTClientGetter struct {
RESTConfig *rest.Config
DiscoveryClient discovery.CachedDiscoveryInterface
RESTMapper meta.RESTMapper
namespace string
}

var _ resource.RESTClientGetter = &staticRESTClientGetter{}
Expand All @@ -121,3 +102,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you copy some codes from k/kubectl?
IMHO, it's good not to copy, but use modules.
I think DirectApplier is implementend with something tricky way.

Recently, we started these topics related with prune functionality at #201.
We may have to have discussion at #201.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, It's copied from k/kubectl. In the NewCmdApply function, ApplyOptions is never returned, so I didn't find a way to reuse that piece of code.

In the previous implementation, some codes are copied from Complete too, with some customize modification, Which can be very easy to make mistakes. I agreedDirectApplier is implemented very trickly indeed.

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 <group/version/kind> 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
}