diff --git a/pkg/utils/common/args.go b/pkg/utils/common/args.go index c370d148e..d1d3b68f2 100644 --- a/pkg/utils/common/args.go +++ b/pkg/utils/common/args.go @@ -21,13 +21,16 @@ import ( "k8s.io/client-go/discovery" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/util/flowcontrol" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/oam-dev/kubevela/pkg/cue/packages" + "github.com/oam-dev/kubevela/pkg/oam" "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" ) @@ -90,6 +93,25 @@ func (a *Args) GetClient() (client.Client, error) { return a.client, nil } +// GetFakeClient returns a fake client with the definition objects preloaded +func (a *Args) GetFakeClient(defs []oam.Object) (client.Client, error) { + if a.client != nil { + return a.client, nil + } + if a.config == nil { + if err := a.SetConfig(nil); err != nil { + return nil, err + } + } + objs := make([]client.Object, 0, len(defs)) + for _, def := range defs { + if unstructDef, ok := def.(*unstructured.Unstructured); ok { + objs = append(objs, unstructDef) + } + } + return fake.NewClientBuilder().WithObjects(objs...).WithScheme(a.Schema).Build(), nil +} + // GetDiscoveryMapper get discoveryMapper client if exist, create if not exist. func (a *Args) GetDiscoveryMapper() (discoverymapper.DiscoveryMapper, error) { if a.config == nil { diff --git a/references/cli/dryrun.go b/references/cli/dryrun.go index 5da57cc04..fa387b9fd 100644 --- a/references/cli/dryrun.go +++ b/references/cli/dryrun.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" @@ -44,6 +45,7 @@ type DryRunCmdOptions struct { cmdutil.IOStreams ApplicationFile string DefinitionFile string + OfflineMode bool } // NewDryRunCommand creates `dry-run` command @@ -61,8 +63,15 @@ func NewDryRunCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command RunE: func(cmd *cobra.Command, args []string) error { namespace, err := GetFlagNamespaceOrEnv(cmd, c) if err != nil { - return err + // We need to return an error only if not in offline mode + if !o.OfflineMode { + return err + } + + // Set the namespace to default to match behaviour of `GetFlagNamespaceOrEnv` + namespace = "default" } + buff, err := DryRunApplication(o, c, namespace) if err != nil { return err @@ -74,6 +83,7 @@ func NewDryRunCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command cmd.Flags().StringVarP(&o.ApplicationFile, "file", "f", "./app.yaml", "application file name") cmd.Flags().StringVarP(&o.DefinitionFile, "definition", "d", "", "specify a definition file or directory, it will only be used in dry-run rather than applied to K8s cluster") + cmd.Flags().BoolVar(&o.OfflineMode, "offline", false, "Run `dry-run` in offline / local mode, all validation steps will be skipped") addNamespaceAndEnvArg(cmd) cmd.SetOut(ioStreams.Out) return cmd @@ -81,12 +91,9 @@ func NewDryRunCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command // DryRunApplication will dry-run an application and return the render result func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace string) (bytes.Buffer, error) { + var err error var buff = bytes.Buffer{} - newClient, err := c.GetClient() - if err != nil { - return buff, err - } objs := []oam.Object{} if cmdOption.DefinitionFile != "" { objs, err = ReadObjectsFromFile(cmdOption.DefinitionFile) @@ -94,6 +101,20 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str return buff, err } } + + // Load a kubernetes client + var newClient client.Client + if cmdOption.OfflineMode { + // We will load a fake client with all the objects present in the definitions file preloaded + newClient, err = c.GetFakeClient(objs) + } else { + // Load an actual client here + newClient, err = c.GetClient() + } + if err != nil { + return buff, err + } + pd, err := c.GetPackageDiscover() if err != nil { return buff, err @@ -110,9 +131,12 @@ func DryRunApplication(cmdOption *DryRunCmdOptions, c common.Args, namespace str dryRunOpt := dryrun.NewDryRunOption(newClient, config, dm, pd, objs) ctx := oamutil.SetNamespaceInCtx(context.Background(), namespace) - err = dryRunOpt.ValidateApp(ctx, cmdOption.ApplicationFile) - if err != nil { - return buff, errors.WithMessagef(err, "validate application: %s by dry-run", cmdOption.ApplicationFile) + // Perform validation only if not in offline mode + if !cmdOption.OfflineMode { + err = dryRunOpt.ValidateApp(ctx, cmdOption.ApplicationFile) + if err != nil { + return buff, errors.WithMessagef(err, "validate application: %s by dry-run", cmdOption.ApplicationFile) + } } app, err := readApplicationFromFile(cmdOption.ApplicationFile)