diff --git a/cmd/porchctl/main.go b/cmd/porchctl/main.go index 285b0b136..2aaa9b80c 100644 --- a/cmd/porchctl/main.go +++ b/cmd/porchctl/main.go @@ -1,4 +1,4 @@ -// Copyright 2023 The kpt and Nephio Authors +// Copyright 2023,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import ( "github.com/nephio-project/porch/cmd/porchctl/run" "github.com/spf13/cobra" _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/component-base/cli" + "k8s.io/component-base/logs" "k8s.io/klog/v2" k8scmdutil "k8s.io/kubectl/pkg/cmd/util" ) @@ -49,13 +49,57 @@ func runMain() int { cmd := run.GetMain(ctx) - err = cli.RunNoErrOutput(cmd) + err = k8sCliRunNoErrOutput(cmd) if err != nil { return handleErr(cmd, err) } return 0 } +// k8sCliRunNoErrOutput is a copy of k8s.io/component-base/cli.RunNoErrOutput +// that does not mess up the global normalization func. This means that we lose +// the WordSepNormalize functionality in porchctl. +// +// TODO: is that a problem? +func k8sCliRunNoErrOutput(cmd *cobra.Command) error { + defer logs.FlushLogs() + + // cmd.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc) + + if !cmd.SilenceUsage { + cmd.SilenceUsage = true + cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error { + c.SilenceUsage = false + return err + }) + } + + cmd.SilenceErrors = true + + logs.AddFlags(cmd.PersistentFlags()) + + switch { + case cmd.PersistentPreRun != nil: + pre := cmd.PersistentPreRun + cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { + logs.InitLogs() + pre(cmd, args) + } + case cmd.PersistentPreRunE != nil: + pre := cmd.PersistentPreRunE + cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + logs.InitLogs() + return pre(cmd, args) + } + default: + cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { + logs.InitLogs() + } + } + + return cmd.Execute() +} + // handleErr takes care of printing an error message for a given error. func handleErr(cmd *cobra.Command, err error) int { // First attempt to see if we can resolve the error into a specific diff --git a/make/build.mk b/make/build.mk index 6d988ef55..2ef304d9e 100644 --- a/make/build.mk +++ b/make/build.mk @@ -49,6 +49,10 @@ porch: porchctl: go build -ldflags="-X github.com/nephio-project/porch/cmd/porchctl/run.version=$(PORCHCTL_VERSION)" -o $(PORCHCTL) ./cmd/porchctl +.PHONY: install-porchctl +install-porchctl: + go install -ldflags="-X github.com/nephio-project/porch/cmd/porchctl/run.version=$(PORCHCTL_VERSION)" ./cmd/porchctl + .PHONY: build-images build-images: ALPINE_VERSION="$(ALPINE_VERSION)" GOLANG_BOOKWORM_VERSION="$(GOLANG_BOOKWORM_VERSION)" IMAGE_NAME="$(PORCH_SERVER_IMAGE)" make -C build/ build-image diff --git a/pkg/cli/commands/repo/docs/docs.go b/pkg/cli/commands/repo/docs/docs.go index 23aff0573..0c95ae9c8 100644 --- a/pkg/cli/commands/repo/docs/docs.go +++ b/pkg/cli/commands/repo/docs/docs.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,33 +53,33 @@ Args: Flags: - --branch: + --branch Branch within the repository where finalized packages are commited. The default is to use the 'main' branch. - --deployment: + --deployment, --deploy Tags the repository as a deployment repository. Packages in a deployment repository are considered ready for deployment. - --description: + --description, --desc, -d Description of the repository. - --directory: + --directory, --dir Directory within the repository where packages are found. The default is the root of the repository. - --sync-schedule: + --sync-schedule Cron schedule for reconciling packages in the repository. If not specified, porch will use --repo-sync-frequency. - --name: + --name Name of the repository. By default the last segment of the repository URL will be used as the name. - --repo-basic-username: + --repo-basic-username, --username, --user Username for authenticating to a repository with basic auth. - --repo-basic-password: + --repo-basic-password, --password, --pass Password for authenticating to a repository with basic auth. ` var RegExamples = ` @@ -104,7 +104,7 @@ Args: Flags: - --keep-auth-secret: + --keep-auth-secret Keep the Secret object with auth information referenced by the repository. By default, it will be deleted when the repository is unregistered. ` diff --git a/pkg/cli/commands/repo/reg/command.go b/pkg/cli/commands/repo/reg/command.go index d2a7c573f..65178fe9a 100644 --- a/pkg/cli/commands/repo/reg/command.go +++ b/pkg/cli/commands/repo/reg/command.go @@ -1,4 +1,4 @@ -// Copyright 2022-2025 The kpt and Nephio Authors +// Copyright 2022-2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,8 +23,10 @@ import ( configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/repo/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/robfig/cron/v3" "github.com/spf13/cobra" + "github.com/spf13/pflag" coreapi "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -56,16 +58,37 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner c.Flags().StringVar(&r.branch, "branch", "main", "Branch in the repository where finalized packages are committed.") c.Flags().BoolVar(&r.createBranch, "create-branch", false, "Create the package branch if it doesn't already exist.") c.Flags().StringVar(&r.name, "name", "", "Name of the package repository. If unspecified, will use the name portion (last segment) of the repository URL.") - c.Flags().StringVar(&r.description, "description", "", "Brief description of the package repository.") + c.Flags().StringVarP(&r.description, "description", "d", "", "Brief description of the package repository.") c.Flags().BoolVar(&r.deployment, "deployment", false, "Repository is a deployment repository; packages in a deployment repository are considered deployment-ready.") c.Flags().StringVar(&r.username, "repo-basic-username", "", "Username for repository authentication using basic auth.") c.Flags().StringVar(&r.password, "repo-basic-password", "", "Password for repository authentication using basic auth.") c.Flags().BoolVar(&r.workloadIdentity, "repo-workload-identity", false, "Use workload identity for authentication with the repo") c.Flags().StringVar(&r.syncSchedule, "sync-schedule", "", "Cron schedule for reconciling packages in the repository.") + c.Flags().SetNormalizeFunc(aliasNormalizeFunc) + return r } +// aliasNormalizeFunc adds some sensible short versions of flags +// TODO: document +func aliasNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "user", "username": + name = "repo-basic-username" + case "pw", "pass", "password": + name = "repo-basic-password" + case "deploy": + name = "deployment" + case "desc": + name = "description" + case "dir", "folder": + name = "directory" + } + + return pflag.NormalizedName(name) +} + func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { return newRunner(ctx, rcg).Command } @@ -92,21 +115,11 @@ type runner struct { func (r *runner) preRunE(_ *cobra.Command, _ []string) error { const op errors.Op = command + ".preRunE" - // todo if namespace flag missing, use kubeconfig - if *r.cfg.Namespace == "" { - // Get the namespace from kubeconfig - namespace, _, err := r.cfg.ToRawKubeConfigLoader().Namespace() - if err != nil { - return fmt.Errorf("error getting namespace: %w", err) - } - r.cfg.Namespace = &namespace - } - - client, err := cliutils.CreateClientWithFlags(r.cfg) + var err error + r.client, err = cliutils.CreateClientWithFlags(r.cfg) if err != nil { return errors.E(op, err) } - r.client = client return nil } @@ -185,7 +198,7 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { }, ObjectMeta: metav1.ObjectMeta{ Name: r.name, - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Spec: configapi.RepositorySpec{ Description: r.description, @@ -225,7 +238,7 @@ func (r *runner) buildAuthSecret() (*coreapi.Secret, error) { }, ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s-auth", r.name), - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Data: map[string][]byte{}, Type: "kpt.dev/workload-identity-auth", @@ -238,7 +251,7 @@ func (r *runner) buildAuthSecret() (*coreapi.Secret, error) { }, ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s-auth", r.name), - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Data: map[string][]byte{ "username": []byte(r.username), diff --git a/pkg/cli/commands/repo/unreg/command.go b/pkg/cli/commands/repo/unreg/command.go index 87d257e85..7ff7c686a 100644 --- a/pkg/cli/commands/repo/unreg/command.go +++ b/pkg/cli/commands/repo/unreg/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import ( configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/repo/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" coreapi "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -72,15 +73,6 @@ type runner struct { func (r *runner) preRunE(_ *cobra.Command, _ []string) error { const op errors.Op = command + ".preRunE" - if *r.cfg.Namespace == "" { - // Get the namespace from kubeconfig - namespace, _, err := r.cfg.ToRawKubeConfigLoader().Namespace() - if err != nil { - return fmt.Errorf("error getting namespace: %w", err) - } - r.cfg.Namespace = &namespace - } - client, err := cliutils.CreateClientWithFlags(r.cfg) if err != nil { return errors.E(op, err) @@ -100,7 +92,7 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { var repo configapi.Repository if err := r.client.Get(r.ctx, client.ObjectKey{ - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), Name: repository, }, &repo); err != nil { return errors.E(op, err) diff --git a/pkg/cli/commands/rpkg/approve/command.go b/pkg/cli/commands/rpkg/approve/command.go index ae86e577b..ffe1cf8b9 100644 --- a/pkg/cli/commands/rpkg/approve/command.go +++ b/pkg/cli/commands/rpkg/approve/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/util/retry" @@ -81,11 +82,9 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { const op errors.Op = command + ".runE" var messages []string - namespace := *r.cfg.Namespace - for _, name := range args { key := client.ObjectKey{ - Namespace: namespace, + Namespace: util.EnsureNamespace(r.cfg), Name: name, } var lastErr error diff --git a/pkg/cli/commands/rpkg/clone/command.go b/pkg/cli/commands/rpkg/clone/command.go index 57b84ae43..c0d26c1ea 100644 --- a/pkg/cli/commands/rpkg/clone/command.go +++ b/pkg/cli/commands/rpkg/clone/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -169,7 +170,7 @@ func (r *runner) runE(cmd *cobra.Command, _ []string) error { APIVersion: porchapi.SchemeGroupVersion.Identifier(), }, ObjectMeta: metav1.ObjectMeta{ - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Spec: porchapi.PackageRevisionSpec{ PackageName: r.target, diff --git a/pkg/cli/commands/rpkg/copy/command.go b/pkg/cli/commands/rpkg/copy/command.go index 0ecbe1598..5b99ac455 100644 --- a/pkg/cli/commands/rpkg/copy/command.go +++ b/pkg/cli/commands/rpkg/copy/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,9 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" + "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -52,10 +54,24 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner RunE: r.runE, Hidden: cliutils.HidePorchCommands, } - r.Command.Flags().StringVar(&r.workspace, "workspace", "", "Workspace name of the copy of the package.") + + r.Command.Flags().StringVarP(&r.workspace, "workspace", "w", "", "Workspace name of the copy of the package.") + + r.Command.Flags().SetNormalizeFunc(aliasNormalizeFunc) + return r } +// aliasNormalizeFunc adds some sensible short versions of flags +func aliasNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "ws": + name = "workspace" + } + + return pflag.NormalizedName(name) +} + type runner struct { ctx context.Context cfg *genericclioptions.ConfigFlags @@ -102,7 +118,7 @@ func (r *runner) runE(cmd *cobra.Command, _ []string) error { APIVersion: porchapi.SchemeGroupVersion.Identifier(), }, ObjectMeta: metav1.ObjectMeta{ - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Spec: *revisionSpec, } @@ -117,7 +133,7 @@ func (r *runner) getPackageRevisionSpec() (*porchapi.PackageRevisionSpec, error) packageRevision := porchapi.PackageRevision{} err := r.client.Get(r.ctx, types.NamespacedName{ Name: r.copy.Source.Name, - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, &packageRevision) if err != nil { return nil, err diff --git a/pkg/cli/commands/rpkg/copy/command_test.go b/pkg/cli/commands/rpkg/copy/command_test.go index ce2161281..2142d8fa0 100644 --- a/pkg/cli/commands/rpkg/copy/command_test.go +++ b/pkg/cli/commands/rpkg/copy/command_test.go @@ -18,6 +18,7 @@ import ( "context" "io" "os" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -45,10 +46,16 @@ func createScheme() (*runtime.Scheme, error) { } func TestCmd(t *testing.T) { - repoName := "test-repo" - ns := "ns" - pkgRevName := "test-package" - var scheme, err = createScheme() + const ( + ns = "ns" + repoName = "test-repo" + pkgName = "test-package" + workspaceName = "v1" + ) + + pkgRevName := strings.Join([]string{repoName, pkgName, workspaceName}, ".") + scheme, err := createScheme() + if err != nil { t.Fatalf("error creating scheme: %v", err) } @@ -109,8 +116,11 @@ func TestCmd(t *testing.T) { APIVersion: porchapi.SchemeGroupVersion.Identifier(), }, Spec: porchapi.PackageRevisionSpec{ - Lifecycle: porchapi.PackageRevisionLifecycleProposed, + Lifecycle: porchapi.PackageRevisionLifecyclePublished, RepositoryName: repoName, + PackageName: pkgName, + WorkspaceName: workspaceName, + Revision: 1, }, ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -119,9 +129,8 @@ func TestCmd(t *testing.T) { }, } - for tn := range testCases { - tc := testCases[tn] - t.Run(tn, func(t *testing.T) { + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { cmd := &cobra.Command{} o := os.Stdout @@ -155,7 +164,7 @@ func TestCmd(t *testing.T) { os.Stdout = o os.Stderr = e - if diff := cmp.Diff(string(tc.output), string(out)); diff != "" { + if diff := cmp.Diff(tc.output, string(out)); diff != "" { t.Errorf("Unexpected result (-want, +got): %s", diff) } }) diff --git a/pkg/cli/commands/rpkg/del/command.go b/pkg/cli/commands/rpkg/del/command.go index 7dd292a11..e427b5c07 100644 --- a/pkg/cli/commands/rpkg/del/command.go +++ b/pkg/cli/commands/rpkg/del/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/spf13/cobra" @@ -90,7 +91,7 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { APIVersion: porchapi.SchemeGroupVersion.Identifier(), }, ObjectMeta: metav1.ObjectMeta{ - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), Name: pkg, }, } diff --git a/pkg/cli/commands/rpkg/docs/docs.go b/pkg/cli/commands/rpkg/docs/docs.go index 8f7a0200f..678a7c1f9 100644 --- a/pkg/cli/commands/rpkg/docs/docs.go +++ b/pkg/cli/commands/rpkg/docs/docs.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -139,14 +139,22 @@ Args: Flags: + --repository, --repo, -r + Repository of the packages to get. Any package residing in + the specified repository will be included in the results. + --name Name of the packages to get. Any package whose name contains this value will be included in the results. - --revision + --revision, --rev Revision of the package to get. Any package whose revision matches this value will be included in the results. + --workspace, --ws, -w + WorkspaceName of the packages to get. Any package whose + workspaceName matches this value will be included in the results. + --show-kptfile Display the root Kptfile of the specified package revision. Requires exactly one package revision name as an argument. @@ -180,13 +188,13 @@ Args: Flags: - --repository + --repository, --repo, -r Repository in which the new package will be created. - --workspace + --workspace, --ws, -w Workspace of the new package. - --description + --description, --desc, -d Short description of the package --keywords @@ -259,6 +267,12 @@ Args: DIR: A local directory with the new manifest. If the manifests have be read from stdin, use '-' in place of DIR. + +Flags: + + --force, -f + Overwrite the existing directory, even if it belongs to a different package. + ` var PushExamples = ` # update the package revision 'example-repo.example-package-name.example-workspace' with the resources diff --git a/pkg/cli/commands/rpkg/get/command.go b/pkg/cli/commands/rpkg/get/command.go index 44c6539e4..505e32bc9 100644 --- a/pkg/cli/commands/rpkg/get/command.go +++ b/pkg/cli/commands/rpkg/get/command.go @@ -27,15 +27,14 @@ import ( cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" "github.com/spf13/cobra" + "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/rest" - "k8s.io/klog/v2" "k8s.io/kubectl/pkg/cmd/get" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -67,15 +66,31 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner // Create flags cmd.Flags().StringVar(&r.packageName, "name", "", "Name of the packages to get. Any package whose name contains this value will be included in the results.") cmd.Flags().Int64Var(&r.revision, "revision", -2, "Revision of the packages to get. Any package whose revision matches this value will be included in the results.") - cmd.Flags().StringVar(&r.workspace, "workspace", "", - "WorkspaceName of the packages to get. Any package whose workspaceName matches this value will be included in the results.") + cmd.Flags().StringVarP(&r.workspace, "workspace", "w", "", "WorkspaceName of the packages to get. Any package whose workspaceName matches this value will be included in the results.") + cmd.Flags().StringVarP(&r.repository, "repository", "r", "", "Repository of the packages to get. Any package residing in the specified repository will be included in the results.") cmd.Flags().BoolVar(&r.showKptfile, "show-kptfile", false, "Display the root Kptfile of the specified package revision. Requires a single package revision name as an argument.") + cmd.Flags().SetNormalizeFunc(aliasNormalizeFunc) + r.getFlags.AddFlags(cmd) r.printFlags.AddFlags(cmd) return r } +// aliasNormalizeFunc adds some sensible short versions of flags +func aliasNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "repo": + name = "repository" + case "rev": + name = "revision" + case "ws": + name = "workspace" + } + + return pflag.NormalizedName(name) +} + func NewCommand(ctx context.Context, rcg *genericclioptions.ConfigFlags) *cobra.Command { return newRunner(ctx, rcg).Command } @@ -86,6 +101,7 @@ type runner struct { Command *cobra.Command // Flags + repository string packageName string revision int64 workspace string @@ -179,7 +195,6 @@ func (r *runner) runE(cmd *cobra.Command, args []string) error { return r.showKptfileContent(cmd, args[0]) } - var objs []runtime.Object b, err := r.getFlags.ResourceBuilder() if err != nil { return err @@ -208,18 +223,21 @@ func (r *runner) runE(cmd *cobra.Command, args []string) error { } if useSelectors { - fieldSelector := fields.Everything() + fieldSet := fields.Set{} if r.revision != -2 { - fieldSelector = fields.OneTermEqualSelector("spec.revision", strconv.FormatInt(r.revision, 10)) + fieldSet["spec.revision"] = strconv.FormatInt(r.revision, 10) } if r.workspace != "" { - fieldSelector = fields.OneTermEqualSelector("spec.workspaceName", r.workspace) + fieldSet["spec.workspaceName"] = r.workspace } if r.packageName != "" { - fieldSelector = fields.OneTermEqualSelector("spec.packageName", r.packageName) + fieldSet["spec.packageName"] = r.packageName + } + if r.repository != "" { + fieldSet["spec.repository"] = r.repository } - if s := fieldSelector.String(); s != "" { - b = b.FieldSelectorParam(s) + if len(fieldSet) > 0 { + b = b.FieldSelectorParam(fieldSet.AsSelector().String()) } else { b = b.SelectAllParam(true) } @@ -248,42 +266,10 @@ func (r *runner) runE(cmd *cobra.Command, args []string) error { return errors.E(op, err) } - // Decode json objects in tables (likely PartialObjectMetadata) - for _, i := range infos { - if table, ok := i.Object.(*metav1.Table); ok { - for i := range table.Rows { - row := &table.Rows[i] - if row.Object.Object == nil && row.Object.Raw != nil { - u := &unstructured.Unstructured{} - if err := u.UnmarshalJSON(row.Object.Raw); err != nil { - klog.Warningf("error parsing raw object: %v", err) - } - row.Object.Object = u - } - } - } - } - - // Apply any filters we couldn't pass down as field selectors - for _, i := range infos { - switch obj := i.Object.(type) { - case *unstructured.Unstructured: - match, err := r.packageRevisionMatches(obj) - if err != nil { - return errors.E(op, err) - } - if match { - objs = append(objs, obj) - } - case *metav1.Table: - // Technically we should have applied this as a field-selector, so this might not be necessary - if err := r.filterTableRows(obj); err != nil { - return err - } - objs = append(objs, obj) - default: - return errors.E(op, fmt.Sprintf("Unrecognized response %T", obj)) - } + // Collect infos into Objects + objs := make([]runtime.Object, 0, len(infos)) + for _, info := range infos { + objs = append(objs, info.Object) } printer, err := r.printFlags.ToPrinter() @@ -304,88 +290,6 @@ func (r *runner) runE(cmd *cobra.Command, args []string) error { return nil } -func (r *runner) packageRevisionMatches(o *unstructured.Unstructured) (bool, error) { - packageName, _, err := unstructured.NestedString(o.Object, "spec", "packageName") - if err != nil { - return false, err - } - revision, _, err := unstructured.NestedInt64(o.Object, "spec", "revision") - if err != nil { - return false, err - } - workspace, _, err := unstructured.NestedString(o.Object, "spec", "workspaceName") - if err != nil { - return false, err - } - if r.packageName != "" && r.packageName != packageName { - return false, nil - } - if r.revision != -2 && r.revision != revision { - return false, nil - } - if r.workspace != "" && r.workspace != workspace { - return false, nil - } - return true, nil -} - -func findColumn(cols []metav1.TableColumnDefinition, name string) int { - for i := range cols { - if cols[i].Name == name { - return i - } - } - return -1 -} - -func getStringCell(cells []interface{}, col int) (string, bool) { - if col < 0 { - return "", false - } - s, ok := cells[col].(string) - return s, ok -} - -func getInt64Cell(cells []interface{}, col int) (int64, bool) { - if col < 0 { - return 0, false - } - i, ok := cells[col].(int64) - return i, ok -} - -func (r *runner) filterTableRows(table *metav1.Table) error { - filtered := make([]metav1.TableRow, 0, len(table.Rows)) - packageNameCol := findColumn(table.ColumnDefinitions, "Package") - revisionCol := findColumn(table.ColumnDefinitions, "Revision") - workspaceCol := findColumn(table.ColumnDefinitions, "WorkspaceName") - - for i := range table.Rows { - row := &table.Rows[i] - - if packageName, ok := getStringCell(row.Cells, packageNameCol); ok { - if r.packageName != "" && r.packageName != packageName { - continue - } - } - if revision, ok := getInt64Cell(row.Cells, revisionCol); ok { - if r.revision != -2 && r.revision != revision { - continue - } - } - if workspace, ok := getStringCell(row.Cells, workspaceCol); ok { - if r.workspace != "" && r.workspace != workspace { - continue - } - } - - // Row matches - filtered = append(filtered, *row) - } - table.Rows = filtered - return nil -} - func (r *runner) showKptfileContent(cmd *cobra.Command, name string) error { const op errors.Op = command + ".showKptfileContent" diff --git a/pkg/cli/commands/rpkg/get/command_test.go b/pkg/cli/commands/rpkg/get/command_test.go index e2e2ff226..eeaa94b4c 100644 --- a/pkg/cli/commands/rpkg/get/command_test.go +++ b/pkg/cli/commands/rpkg/get/command_test.go @@ -28,25 +28,12 @@ import ( "github.com/spf13/cobra" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) -func TestGetters(t *testing.T) { - cells := make([]any, 1) - cells[0] = int64(1234) - - _, retVal1 := getInt64Cell(cells, -1) - assert.False(t, retVal1) - - val, retVal2 := getInt64Cell(cells, 0) - assert.True(t, retVal2) - assert.Equal(t, int64(1234), val) -} - func TestRunnerPreRunE(t *testing.T) { ns := "ns" ctx := context.Background() @@ -105,111 +92,6 @@ func TestGetCmd(t *testing.T) { } } -type MockRunner struct { - *runner -} - -func (r *MockRunner) packageRevisionMatches(o *unstructured.Unstructured) (bool, error) { - return r.runner.packageRevisionMatches(o) -} - -func TestPackageRevisionMatches(t *testing.T) { - tests := []struct { - name string - flags runner - object *unstructured.Unstructured - expected bool - shouldFail bool - }{ - { - name: "match all filters", - flags: runner{ - packageName: "pkg1", - revision: 1, - workspace: "ws1", - }, - object: &unstructured.Unstructured{ - Object: map[string]any{ - "spec": map[string]any{ - "packageName": "pkg1", - "revision": int64(1), - "workspaceName": "ws1", - }, - }, - }, - expected: true, - }, - { - name: "package name mismatch", - flags: runner{ - packageName: "pkg1", - revision: 1, - workspace: "ws1", - }, - object: &unstructured.Unstructured{ - Object: map[string]any{ - "spec": map[string]any{ - "packageName": "pkg2", - "revision": int64(1), - "workspaceName": "ws1", - }, - }, - }, - expected: false, - }, - { - name: "revision mismatch", - flags: runner{ - packageName: "pkg1", - revision: 2, - workspace: "ws1", - }, - object: &unstructured.Unstructured{ - Object: map[string]any{ - "spec": map[string]any{ - "packageName": "pkg1", - "revision": int64(1), - "workspaceName": "ws1", - }, - }, - }, - expected: false, - }, - { - name: "workspace mismatch", - flags: runner{ - packageName: "pkg1", - revision: 1, - workspace: "ws2", - }, - object: &unstructured.Unstructured{ - Object: map[string]any{ - "spec": map[string]any{ - "packageName": "pkg1", - "revision": int64(1), - "workspaceName": "ws1", - }, - }, - }, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockRunner := &MockRunner{runner: &tt.flags} - match, err := mockRunner.packageRevisionMatches(tt.object) - - if tt.shouldFail { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.expected, match) - } - }) - } -} - func TestNamespaceFlagWithoutValue(t *testing.T) { ctx := context.Background() ns := "" diff --git a/pkg/cli/commands/rpkg/init/command.go b/pkg/cli/commands/rpkg/init/command.go index 37ac8b43a..f31eb2e1d 100644 --- a/pkg/cli/commands/rpkg/init/command.go +++ b/pkg/cli/commands/rpkg/init/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,9 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" + "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "sigs.k8s.io/controller-runtime/pkg/client" @@ -52,15 +54,31 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner } r.Command = c - c.Flags().StringVar(&r.Description, "description", "sample description", "short description of the package.") + c.Flags().StringVarP(&r.Description, "description", "d", "sample description", "short description of the package.") c.Flags().StringSliceVar(&r.Keywords, "keywords", []string{}, "list of keywords for the package.") c.Flags().StringVar(&r.Site, "site", "", "link to page with information about the package.") - c.Flags().StringVar(&r.repository, "repository", "", "Repository to which package will be created.") - c.Flags().StringVar(&r.workspace, "workspace", "", "Workspace name of the package.") + c.Flags().StringVarP(&r.repository, "repository", "r", "", "Repository to which package will be created.") + c.Flags().StringVarP(&r.workspace, "workspace", "w", "", "Workspace name of the package.") + + c.Flags().SetNormalizeFunc(aliasNormalizeFunc) return r } +// aliasNormalizeFunc adds some sensible short versions of flags +func aliasNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { + switch name { + case "repo": + name = "repository" + case "desc": + name = "description" + case "ws": + name = "workspace" + } + + return pflag.NormalizedName(name) +} + type runner struct { ctx context.Context cfg *genericclioptions.ConfigFlags @@ -110,7 +128,7 @@ func (r *runner) runE(cmd *cobra.Command, _ []string) error { APIVersion: porchapi.SchemeGroupVersion.Identifier(), }, ObjectMeta: metav1.ObjectMeta{ - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Spec: porchapi.PackageRevisionSpec{ PackageName: r.name, diff --git a/pkg/cli/commands/rpkg/propose/command.go b/pkg/cli/commands/rpkg/propose/command.go index f274a6683..933ef23b7 100644 --- a/pkg/cli/commands/rpkg/propose/command.go +++ b/pkg/cli/commands/rpkg/propose/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/util/retry" @@ -81,11 +82,10 @@ func (r *runner) preRunE(_ *cobra.Command, _ []string) error { func (r *runner) runE(_ *cobra.Command, args []string) error { const op errors.Op = command + ".runE" var messages []string - namespace := *r.cfg.Namespace for _, name := range args { key := client.ObjectKey{ - Namespace: namespace, + Namespace: util.EnsureNamespace(r.cfg), Name: name, } var lastErr error diff --git a/pkg/cli/commands/rpkg/proposedelete/command.go b/pkg/cli/commands/rpkg/proposedelete/command.go index be6ab8c42..d394f1cf5 100644 --- a/pkg/cli/commands/rpkg/proposedelete/command.go +++ b/pkg/cli/commands/rpkg/proposedelete/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/util/retry" @@ -81,11 +82,10 @@ func (r *runner) preRunE(_ *cobra.Command, _ []string) error { func (r *runner) runE(_ *cobra.Command, args []string) error { const op errors.Op = command + ".runE" var messages []string - namespace := *r.cfg.Namespace for _, name := range args { key := client.ObjectKey{ - Namespace: namespace, + Namespace: util.EnsureNamespace(r.cfg), Name: name, } var lastErr error diff --git a/pkg/cli/commands/rpkg/pull/command.go b/pkg/cli/commands/rpkg/pull/command.go index 59c754462..aee7ea6c2 100644 --- a/pkg/cli/commands/rpkg/pull/command.go +++ b/pkg/cli/commands/rpkg/pull/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package pull import ( "context" + "fmt" "io" "os" "path/filepath" @@ -47,7 +48,7 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner ctx: ctx, cfg: rcg, } - c := &cobra.Command{ + cmd := &cobra.Command{ Use: "pull PACKAGE [DIR]", Aliases: []string{"source", "read"}, SuggestFor: []string{}, @@ -58,7 +59,10 @@ func newRunner(ctx context.Context, rcg *genericclioptions.ConfigFlags) *runner RunE: r.runE, Hidden: cliutils.HidePorchCommands, } - r.Command = c + r.Command = cmd + + cmd.Flags().BoolVarP(&r.force, "force", "f", false, "Overwrite the existing directory, even if it belongs to a different package.") + return r } @@ -72,6 +76,8 @@ type runner struct { client client.Client Command *cobra.Command printer printer.Printer + + force bool } func (r *runner) preRunE(_ *cobra.Command, _ []string) error { @@ -103,12 +109,12 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { return errors.E(op, "PACKAGE is a required positional argument") } - packageName := args[0] + packageRevisionName := args[0] var resources porchapi.PackageRevisionResources if err := r.client.Get(r.ctx, client.ObjectKey{ - Namespace: *r.cfg.Namespace, - Name: packageName, + Namespace: util.EnsureNamespace(r.cfg), + Name: packageRevisionName, }, &resources); err != nil { return errors.E(op, err) } @@ -118,7 +124,8 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { } if len(args) > 1 { - if err := writeToDir(resources.Spec.Resources, args[1]); err != nil { + overwrite := util.IsSamePackage(args[1], packageRevisionName) || r.force + if err := writeToDir(resources.Spec.Resources, args[1], overwrite); err != nil { return errors.E(op, err) } } else { @@ -129,9 +136,13 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { return nil } -func writeToDir(resources map[string]string, dir string) error { +func writeToDir(resources map[string]string, dir string, overwrite bool) error { if err := cmdutil.CheckDirectoryNotPresent(dir); err != nil { - return err + if !overwrite { + return fmt.Errorf("%w; you may overwrite the directory with --force", err) + } + + _ = os.RemoveAll(dir) } if err := os.MkdirAll(dir, 0750); err != nil { return err diff --git a/pkg/cli/commands/rpkg/push/command.go b/pkg/cli/commands/rpkg/push/command.go index 8f7ee41f2..ac255fcf0 100644 --- a/pkg/cli/commands/rpkg/push/command.go +++ b/pkg/cli/commands/rpkg/push/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -131,7 +131,7 @@ func (r *runner) runE(cmd *cobra.Command, args []string) error { }, ObjectMeta: metav1.ObjectMeta{ Name: packageName, - Namespace: *r.cfg.Namespace, + Namespace: util.EnsureNamespace(r.cfg), }, Spec: porchapi.PackageRevisionResourcesSpec{ Resources: resources, diff --git a/pkg/cli/commands/rpkg/reject/command.go b/pkg/cli/commands/rpkg/reject/command.go index 4d750dfb8..b71b0287c 100644 --- a/pkg/cli/commands/rpkg/reject/command.go +++ b/pkg/cli/commands/rpkg/reject/command.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt and Nephio Authors +// Copyright 2022,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" cliutils "github.com/nephio-project/porch/internal/cliutils" "github.com/nephio-project/porch/pkg/cli/commands/rpkg/docs" + "github.com/nephio-project/porch/pkg/cli/commands/rpkg/util" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/util/retry" @@ -85,11 +86,10 @@ func (r *runner) runE(_ *cobra.Command, args []string) error { const op errors.Op = command + ".runE" var messages []string - namespace := *r.cfg.Namespace var proposedFor string for _, name := range args { key := client.ObjectKey{ - Namespace: namespace, + Namespace: util.EnsureNamespace(r.cfg), Name: name, } var lastErr error diff --git a/pkg/cli/commands/rpkg/util/common.go b/pkg/cli/commands/rpkg/util/common.go index de8089c46..924279468 100644 --- a/pkg/cli/commands/rpkg/util/common.go +++ b/pkg/cli/commands/rpkg/util/common.go @@ -1,4 +1,4 @@ -// Copyright 2023 The kpt and Nephio Authors +// Copyright 2023,2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package util import ( "fmt" + "os" + "strings" kptfilev1 "github.com/kptdev/kpt/pkg/api/kptfile/v1" fnsdk "github.com/kptdev/krm-functions-sdk/go/fn" porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" -) - -const ( - ResourceVersionAnnotation = "internal.kpt.dev/resource-version" + "k8s.io/cli-runtime/pkg/genericclioptions" + "sigs.k8s.io/kustomize/kyaml/kio" ) func GetResourceFileKubeObject(prr *porchapi.PackageRevisionResources, file, kind, name string) (*fnsdk.KubeObject, error) { @@ -74,7 +74,72 @@ func AddRevisionMetadata(prr *porchapi.PackageRevisionResources) error { return nil } +func ReadRevisionMetadataFromDir(path string) (*fnsdk.KubeObject, error) { + reader := &kio.LocalPackageReader{ + PackagePath: path, + PackageFileName: kptfilev1.KptFileName, + MatchFilesGlob: []string{kptfilev1.RevisionMetaDataFileName}, + IncludeSubpackages: false, + ErrorIfNonResources: false, + OmitReaderAnnotations: false, + PreserveSeqIndent: true, + } + + rnodes, err := reader.Read() + if err != nil { + return nil, err + } + if len(rnodes) != 1 { + return nil, fmt.Errorf("expected exactly one rnode for file %q, got %d", + kptfilev1.RevisionMetaDataFileName, len(rnodes)) + } + + return fnsdk.MoveToKubeObject(rnodes[0]), nil +} + func RemoveRevisionMetadata(prr *porchapi.PackageRevisionResources) error { delete(prr.Spec.Resources, kptfilev1.RevisionMetaDataFileName) return nil } + +// EnsureNamespace tries to return a namespace from multiple different configs before defaulting to "default" +// +// Should only be used if the intention cannot be "all namespaces"! +func EnsureNamespace(cfg *genericclioptions.ConfigFlags) string { + // if --namespace is set just return that + if cfg.Namespace != nil && *cfg.Namespace != "" { + return *cfg.Namespace + } + + // try getting the namespace from the kubeconfig context + if kcfgNs, _, err := cfg.ToRawKubeConfigLoader().Namespace(); err == nil && kcfgNs != "" && kcfgNs != "default" { + return kcfgNs + } + + // try getting it from an env var + if envNs := os.Getenv("NAMESPACE"); envNs != "" { + return envNs + } + + // fall back to "default" + return "default" +} + +func IsSamePackage(dir, prName string) bool { + ko, err := ReadRevisionMetadataFromDir(dir) + if err != nil { + return false + } + + return trimWorkspace(ko.GetName()) == trimWorkspace(prName) +} + +func trimWorkspace(prName string) string { + i := strings.LastIndex(prName, ".") + + if i == -1 { + return prName + } + + return prName[:i] +} diff --git a/pkg/cli/commands/rpkg/util/common_test.go b/pkg/cli/commands/rpkg/util/common_test.go new file mode 100644 index 000000000..54e4a8f38 --- /dev/null +++ b/pkg/cli/commands/rpkg/util/common_test.go @@ -0,0 +1,529 @@ +// Copyright 2026 The kpt and Nephio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + kptfilev1 "github.com/kptdev/kpt/pkg/api/kptfile/v1" + fnsdk "github.com/kptdev/krm-functions-sdk/go/fn" + porchapi "github.com/nephio-project/porch/api/porch/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/utils/ptr" +) + +const ( + testNs = "ns" + testPRRName = "test-repo.test-package.v1" + + testKptfileMinimal = `apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: test-package +` +) + +func TestGetResourceFileKubeObject(t *testing.T) { + const testConfigMapYAML = `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-config + namespace: ns +` + + testCases := map[string]struct { + prr *porchapi.PackageRevisionResources + + file string + kind string + name string + + wantErr string + wantKind string + wantName string + }{ + "nil resources": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{Resources: nil}, + }, + file: "cm.yaml", + wantErr: "nil resources found", + }, + "file not found": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"other.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + wantErr: `"cm.yaml" not found`, + }, + "invalid yaml": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"bad.yaml": "@&:: not valid yaml"}, + }, + }, + file: "bad.yaml", + wantErr: "failed to parse", + }, + "kind not found": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"cm.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + kind: "Secret", + wantErr: `does not contain kind "Secret"`, + }, + "name not found": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"cm.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + kind: "ConfigMap", + name: "other", + wantErr: `does not contain resource named "other"`, + }, + "success no kind or name": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"cm.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + wantKind: "ConfigMap", + wantName: "test-config", + }, + "success with matching kind and name": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"cm.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + kind: "ConfigMap", + name: "test-config", + wantKind: "ConfigMap", + wantName: "test-config", + }, + "empty kind skips kind check, wrong kind in file": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"cm.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + kind: "", + wantKind: "ConfigMap", + wantName: "test-config", + }, + "empty name skips name check": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{"cm.yaml": testConfigMapYAML}, + }, + }, + file: "cm.yaml", + kind: "ConfigMap", + name: "", + wantKind: "ConfigMap", + wantName: "test-config", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ko, err := GetResourceFileKubeObject(tc.prr, tc.file, tc.kind, tc.name) + if tc.wantErr != "" { + require.ErrorContains(t, err, tc.wantErr) + assert.Nil(t, ko) + } else { + require.NoError(t, err) + require.NotNil(t, ko) + assert.Equal(t, tc.wantKind, ko.GetKind()) + assert.Equal(t, tc.wantName, ko.GetName()) + } + }) + } +} + +func TestGetResourceVersion(t *testing.T) { + const ( + testKptRevisionMetadataWithVersion = `apiVersion: config.kubernetes.io/v1 +kind: KptRevisionMetadata +metadata: + name: test-repo.test-package.v1 + namespace: ns + resourceVersion: "42" +` + + testConfigMapWrongKindYAML = `apiVersion: v1 +kind: ConfigMap +metadata: + name: other-cm +` + ) + + testCases := map[string]struct { + prr *porchapi.PackageRevisionResources + want string + wantErr string + }{ + "missing file": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{}, + }, + }, + wantErr: "not found", + }, + "success": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{ + kptfilev1.RevisionMetaDataFileName: testKptRevisionMetadataWithVersion, + }, + }, + }, + want: "42", + }, + "wrong kind": { + prr: &porchapi.PackageRevisionResources{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNs, Name: testPRRName}, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{ + kptfilev1.RevisionMetaDataFileName: testConfigMapWrongKindYAML, + }, + }, + }, + wantErr: `does not contain kind "KptRevisionMetadata"`, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got, err := GetResourceVersion(tc.prr) + if tc.wantErr != "" { + require.ErrorContains(t, err, tc.wantErr) + assert.Empty(t, got) + } else { + require.NoError(t, err) + assert.Equal(t, tc.want, got) + } + }) + } +} + +func TestAddRevisionMetadata(t *testing.T) { + prr := &porchapi.PackageRevisionResources{ + TypeMeta: metav1.TypeMeta{ + APIVersion: porchapi.SchemeGroupVersion.String(), + Kind: "PackageRevisionResources", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: testPRRName, + Namespace: testNs, + ResourceVersion: "1", + }, + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: map[string]string{ + "Kptfile": testKptfileMinimal, + }, + }, + } + + require.NoError(t, AddRevisionMetadata(prr)) + raw, ok := prr.Spec.Resources[kptfilev1.RevisionMetaDataFileName] + require.Truef(t, ok, "expected %q in resources", kptfilev1.RevisionMetaDataFileName) + ko, err := fnsdk.ParseKubeObject([]byte(raw)) + require.NoError(t, err) + assert.Equal(t, kptfilev1.RevisionMetaDataKind, ko.GetKind()) + assert.Equal(t, testPRRName, ko.GetName()) +} + +func TestReadRevisionMetadataFromDir(t *testing.T) { + testKptRevisionMetadata := fmt.Sprintf(`apiVersion: config.kubernetes.io/v1 +kind: KptRevisionMetadata +metadata: + name: %s + resourceVersion: "1" +`, testPRRName) + + testKptRevisionMetadataMultiDoc := testKptRevisionMetadata + "\n---\n" + strings.Replace(testKptRevisionMetadata, "1", "2", 1) + + testCases := map[string]struct { + files map[string]string + wantErr string + }{ + "empty path": { + files: nil, + wantErr: "must specify package path", + }, + "no matching files": { + files: map[string]string{ + "Kptfile": testKptfileMinimal, + }, + wantErr: "expected exactly one rnode", + }, + "multiple in same file": { + files: map[string]string{ + kptfilev1.RevisionMetaDataFileName: testKptRevisionMetadataMultiDoc, + }, + wantErr: "expected exactly one rnode", + }, + "success": { + files: map[string]string{ + kptfilev1.RevisionMetaDataFileName: testKptRevisionMetadata, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + dir := "" + if tc.files != nil { + dir = t.TempDir() + for name, content := range tc.files { + require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte(content), 0o600)) + } + } + ko, err := ReadRevisionMetadataFromDir(dir) + if tc.wantErr != "" { + require.ErrorContains(t, err, tc.wantErr) + assert.Nil(t, ko) + } else { + require.NoError(t, err) + require.NotNil(t, ko) + assert.Equal(t, testPRRName, ko.GetName()) + } + }) + } +} + +func TestRemoveRevisionMetadata(t *testing.T) { + testCases := map[string]struct { + resources map[string]string + }{ + "success": { + resources: map[string]string{ + kptfilev1.RevisionMetaDataFileName: "data", + }, + }, + "nil resources is no-op": { + resources: nil, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + prr := &porchapi.PackageRevisionResources{ + Spec: porchapi.PackageRevisionResourcesSpec{ + Resources: tc.resources, + }, + } + require.NoError(t, RemoveRevisionMetadata(prr)) + if tc.resources != nil { + require.NotNil(t, prr.Spec.Resources) + assert.NotContainsf(t, prr.Spec.Resources, kptfilev1.RevisionMetaDataFileName, "expected %q to be removed", kptfilev1.RevisionMetaDataFileName) + } else { + assert.Nil(t, prr.Spec.Resources) + } + }) + } +} + +func TestEnsureNamespace(t *testing.T) { + const kcfgNs = "ctx-ns" + + testKubeconfigWithContextNS := fmt.Sprintf(`apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://127.0.0.1:6443 + insecure-skip-tls-verify: true + name: test +contexts: +- context: + cluster: test + user: test + namespace: %s + name: test +current-context: test +users: +- name: test + user: {} +`, kcfgNs) + + const testKubeconfigNoNamespace = `apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://127.0.0.1:6443 + insecure-skip-tls-verify: true + name: test +contexts: +- context: + cluster: test + user: test + name: test +current-context: test +users: +- name: test + user: {} +` + + tempDir := t.TempDir() + withNsKubeconfigPath := filepath.Join(tempDir, "with-ns-kubeconfig") + require.NoError(t, os.WriteFile(withNsKubeconfigPath, []byte(testKubeconfigWithContextNS), 0o600)) + + noNsKubeconfigPath := filepath.Join(tempDir, "no-ns-kubeconfig") + require.NoError(t, os.WriteFile(noNsKubeconfigPath, []byte(testKubeconfigNoNamespace), 0o600)) + + testCases := map[string]struct { + kubeconfigPath string + envVar string + ns *string + + expectedNs string + }{ + "flag wins over kubeconfig": { + kubeconfigPath: withNsKubeconfigPath, + ns: ptr.To("flag-ns"), + expectedNs: "flag-ns", + }, + "flag with empty kubeconfig context": { + kubeconfigPath: noNsKubeconfigPath, + ns: ptr.To("explicit"), + expectedNs: "explicit", + }, + "empty flag": { + kubeconfigPath: withNsKubeconfigPath, + ns: ptr.To(""), + expectedNs: "ctx-ns", + }, + "nil flag": { + kubeconfigPath: withNsKubeconfigPath, + ns: nil, + expectedNs: "ctx-ns", + }, + "env var empty kubeconfig context": { + kubeconfigPath: noNsKubeconfigPath, + ns: ptr.To(""), + envVar: "env-ns", + expectedNs: "env-ns", + }, + "full fallback": { + kubeconfigPath: noNsKubeconfigPath, + ns: ptr.To(""), + expectedNs: "default", + }, + "invalid kubeconfig path": { + kubeconfigPath: "missing-kubeconfig", + ns: ptr.To(""), + expectedNs: "default", + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + if tc.envVar != "" { + t.Setenv("NAMESPACE", tc.envVar) + } + cfg := &genericclioptions.ConfigFlags{ + KubeConfig: &tc.kubeconfigPath, + Namespace: tc.ns, + } + assert.Equal(t, tc.expectedNs, EnsureNamespace(cfg)) + }) + } +} + +func TestIsSamePackage(t *testing.T) { + const impossibleName = "definitely-not-possible" + testMetadata := fmt.Sprintf(`apiVersion: config.kubernetes.io/v1 +kind: KptRevisionMetadata +metadata: + name: %s +`, testPRRName) + testMetadataImpossible := fmt.Sprintf(`apiVersion: config.kubernetes.io/v1 +kind: KptRevisionMetadata +metadata: + name: %s +`, impossibleName) + + testCases := map[string]struct { + prName string + metadataFileContent string + expectSame bool + }{ + "read error": { + prName: testPRRName, + expectSame: false, + }, + "equal": { + metadataFileContent: testMetadata, + prName: "test-repo.test-package.v2", + expectSame: true, + }, + "non-equal": { + metadataFileContent: testMetadata, + prName: "test-repo.test-package-2.v1", + expectSame: false, + }, + "full compare equal": { + metadataFileContent: testMetadataImpossible, + prName: "definitely-not-possible", + expectSame: true, + }, + "full compare non-equal": { + metadataFileContent: testMetadataImpossible, + prName: "definitely-not-possible-as-well", + expectSame: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + dir := "" + if tc.metadataFileContent != "" { + dir = t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, kptfilev1.RevisionMetaDataFileName), []byte(tc.metadataFileContent), 0o600)) + } + assert.Equal(t, tc.expectSame, IsSamePackage(dir, tc.prName)) + }) + } +} diff --git a/test/e2e/cli/cluster.go b/test/e2e/cli/cluster.go index 9c19854f1..746e25b8a 100644 --- a/test/e2e/cli/cluster.go +++ b/test/e2e/cli/cluster.go @@ -114,7 +114,7 @@ func KubectlWaitForRepoReady(t *testing.T, repoName, namespace string) { var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr - t.Logf("running command %v", strings.Join(cmd.Args, " ")) + t.Logf("running command `%s`", strings.Join(cmd.Args, " ")) err := cmd.Run() ready := stdout.String() if err == nil && string(ready) == "True" { diff --git a/test/e2e/cli/config.go b/test/e2e/cli/config.go index 9c95f623b..ce3d3d964 100644 --- a/test/e2e/cli/config.go +++ b/test/e2e/cli/config.go @@ -56,6 +56,8 @@ type TestCaseConfig struct { Commands []Command `yaml:"commands,omitempty"` // Skip the test? If the value is not empty, it will be used as a message with which to skip the test. Skip string `yaml:"skip,omitempty"` + // DefaultNamespace indicates whether the test is running in the "default" namespace + DefaultNamespace bool `yaml:"defaultNamespace,omitempty"` } func ReadTestCaseConfig(t *testing.T, name, path string) TestCaseConfig { diff --git a/test/e2e/cli/suite.go b/test/e2e/cli/suite.go index 46616c9a5..800a71067 100644 --- a/test/e2e/cli/suite.go +++ b/test/e2e/cli/suite.go @@ -111,7 +111,9 @@ func (s *CliTestSuite) RunTests(t *testing.T) { // RunTestCase runs a single test case. func (s *CliTestSuite) RunTestCase(t *testing.T, tc TestCaseConfig) { - KubectlCreateNamespace(t, tc.TestCase) + if !tc.DefaultNamespace { + KubectlCreateNamespace(t, tc.TestCase) + } // Setup signal handler for Ctrl-C cleanup sigChan := make(chan os.Signal, 1) @@ -119,7 +121,9 @@ func (s *CliTestSuite) RunTestCase(t *testing.T, tc TestCaseConfig) { go func() { <-sigChan t.Logf("Interrupt received, cleaning up namespace %s", tc.TestCase) - KubectlDeleteNamespace(t, tc.TestCase) + if !tc.DefaultNamespace { + KubectlDeleteNamespace(t, tc.TestCase) + } if tc.UsesPorchTestRepo { suiteutils.RecreateGiteaRepo(t, porchTestRepo) } @@ -128,14 +132,15 @@ func (s *CliTestSuite) RunTestCase(t *testing.T, tc TestCaseConfig) { t.Cleanup(func() { signal.Stop(sigChan) - KubectlDeleteNamespace(t, tc.TestCase) + if !tc.DefaultNamespace { + KubectlDeleteNamespace(t, tc.TestCase) + } if tc.UsesPorchTestRepo { suiteutils.RecreateGiteaRepo(t, porchTestRepo) } }) for i := range tc.Commands { - // time.Sleep(1 * time.Second) // TODO: why was this necessary? command := &tc.Commands[i] for i, arg := range command.Args { for search, replace := range s.SearchAndReplace { @@ -155,7 +160,7 @@ func (s *CliTestSuite) RunTestCase(t *testing.T, tc TestCaseConfig) { cmd.Stdout = &stdout cmd.Stderr = &stderr - t.Logf("running command %v", strings.Join(cmd.Args, " ")) + t.Logf("running command `%s`", strings.Join(cmd.Args, " ")) err := cmd.Run() if command.Yaml { @@ -207,10 +212,19 @@ func (s *CliTestSuite) RunTestCase(t *testing.T, tc TestCaseConfig) { } } - if slices.Contains(cmd.Args, "register") { + if slices.Contains(cmd.Args, "register") || slices.Contains(cmd.Args, "reg") { name, found := getRepoName(cmd.Args) if found { - KubectlWaitForRepoReady(t, name, tc.TestCase) + ns := tc.TestCase + if tc.DefaultNamespace { + ns = "default" + t.Cleanup(func() { + if err := runUtilityCommand(t, s.PorchctlCommand, "repo", "unregister", name); err != nil { + t.Logf("Warning: failed to unregister repository %s/%s: %s", ns, name, err) + } + }) + } + KubectlWaitForRepoReady(t, name, ns) } else { t.Fatalf("Failed to get repo name for registration: %s", tc.TestCase) } @@ -259,7 +273,7 @@ func (s *CliTestSuite) Porchctl(t *testing.T, args ...string) error { func runUtilityCommand(t *testing.T, command string, args ...string) error { cmd := exec.Command(command, args...) - t.Logf("running utility command %s %s", command, strings.Join(args, " ")) + t.Logf("running utility command `%s %s`", command, strings.Join(args, " ")) return cmd.Run() } diff --git a/test/e2e/cli/testdata/rpkg-default-namespace/config.yaml b/test/e2e/cli/testdata/rpkg-default-namespace/config.yaml new file mode 100644 index 000000000..704ef1c6b --- /dev/null +++ b/test/e2e/cli/testdata/rpkg-default-namespace/config.yaml @@ -0,0 +1,115 @@ +usesPorchTestRepo: true +defaultNamespace: true +commands: + - args: + - porchctl + - repo + - register + - --name=git + - --repo-basic-password=secret + - --repo-basic-username=nephio + - http://gitea.gitea.svc.cluster.local:3000/nephio/porch-test + - args: + - porchctl + - rpkg + - init + - --repository=git + - --workspace=v1 + - test-package + stdout: | + git.test-package.v1 created + - args: + - porchctl + - rpkg + - get + - --repository=git + stdout: | + NAME PACKAGE WORKSPACENAME REVISION LATEST LIFECYCLE REPOSITORY + git.test-package.v1 test-package v1 0 false Draft git + - args: + - porchctl + - rpkg + - pull + - git.test-package.v1 + - /tmp/porch-e2e/default-git.test-package.v1 + - args: + - kpt + - fn + - eval + - --image + - ghcr.io/kptdev/krm-functions-catalog/search-replace:v0.2.3 + - --match-kind + - Kptfile + - /tmp/porch-e2e/default-git.test-package.v1 + - -- + - by-path=info.description + - put-value=Updated Test Package Description + stderr: | + [RUNNING] "ghcr.io/kptdev/krm-functions-catalog/search-replace:v0.2.3" on 1 resource(s) + [Results]: [info] info.description: Mutated field value to "Updated Test Package Description" + - args: + - porchctl + - rpkg + - push + - git.test-package.v1 + - /tmp/porch-e2e/default-git.test-package.v1 + stdout: | + git.test-package.v1 pushed + - args: + - porchctl + - rpkg + - propose + - git.test-package.v1 + stdout: | + git.test-package.v1 proposed + - args: + - porchctl + - rpkg + - approve + - git.test-package.v1 + stdout: | + git.test-package.v1 approved + - args: + - porchctl + - rpkg + - copy + - git.test-package.v1 + - --workspace=v2 + stdout: | + git.test-package.v2 created + - args: + - porchctl + - rpkg + - get + - --repository=git + stdout: | + NAME PACKAGE WORKSPACENAME REVISION LATEST LIFECYCLE REPOSITORY + git.test-package.main test-package main -1 false Published git + git.test-package.v1 test-package v1 1 true Published git + git.test-package.v2 test-package v2 0 false Draft git + - args: + - porchctl + - rpkg + - delete + - git.test-package.v2 + stdout: | + git.test-package.v2 deleted + - args: + - porchctl + - rpkg + - propose-delete + - git.test-package.v1 + stdout: | + git.test-package.v1 proposed for deletion + - args: + - porchctl + - rpkg + - delete + - git.test-package.v1 + stdout: | + git.test-package.v1 deleted + - args: + - porchctl + - repo + - unreg + - git diff --git a/test/e2e/cli/testdata/rpkg-get-selectors/config.yaml b/test/e2e/cli/testdata/rpkg-get-selectors/config.yaml new file mode 100644 index 000000000..2482791fd --- /dev/null +++ b/test/e2e/cli/testdata/rpkg-get-selectors/config.yaml @@ -0,0 +1,57 @@ +commands: + - args: + - porchctl + - repo + - register + - --repo-basic-password=secret + - --repo-basic-username=nephio + - http://gitea.gitea.svc.cluster.local:3000/nephio/test-blueprints.git + - --namespace=rpkg-get-selectors + - --name=test-blueprints + - --description + - Test Blueprints + - args: + - porchctl + - rpkg + - get + - --namespace=rpkg-get-selectors + - --repository=test-blueprints + - --name=update + - --output=custom-columns=NAME:.metadata.name,PKG:.spec.packageName,REPO:.spec.repository,REV:.spec.revision + stdout: | + NAME PKG REPO REV + test-blueprints.update.main update test-blueprints -1 + test-blueprints.update.v1 update test-blueprints 1 + test-blueprints.update.v2 update test-blueprints 2 + - args: + - porchctl + - rpkg + - get + - --namespace=rpkg-get-selectors + - --repository=test-blueprints + - --name=update + - --workspace=v2 + stdout: | + NAME PACKAGE WORKSPACENAME REVISION LATEST LIFECYCLE REPOSITORY + test-blueprints.update.v2 update v2 2 true Published test-blueprints + - args: + - porchctl + - rpkg + - get + - --namespace=rpkg-get-selectors + - --name=basens + - --workspace=v2 + stdout: | + NAME PACKAGE WORKSPACENAME REVISION LATEST LIFECYCLE REPOSITORY + test-blueprints.basens.v2 basens v2 2 false Published test-blueprints + - args: + - porchctl + - rpkg + - get + - --namespace=rpkg-get-selectors + - --repository=test-blueprints + - --name=bucket + - --workspace=v1 + stdout: | + NAME PACKAGE WORKSPACENAME REVISION LATEST LIFECYCLE REPOSITORY + test-blueprints.bucket.v1 bucket v1 1 true Published test-blueprints diff --git a/test/e2e/cli/testdata/rpkg-pull-overwrite/config.yaml b/test/e2e/cli/testdata/rpkg-pull-overwrite/config.yaml new file mode 100644 index 000000000..15e7bfae1 --- /dev/null +++ b/test/e2e/cli/testdata/rpkg-pull-overwrite/config.yaml @@ -0,0 +1,112 @@ +usesPorchTestRepo: true +commands: + - args: + - porchctl + - repo + - register + - --namespace=rpkg-pull-overwrite + - --name=git + - --repo-basic-password=secret + - --repo-basic-username=nephio + - http://gitea.gitea.svc.cluster.local:3000/nephio/porch-test + - args: + - porchctl + - rpkg + - init + - --namespace=rpkg-pull-overwrite + - --repository=git + - --workspace=v1 + - test-package + stdout: | + git.test-package.v1 created + - args: + - porchctl + - rpkg + - pull + - --namespace=rpkg-pull-overwrite + - git.test-package.v1 + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package + - args: + - sed + - -i + - "s/sample description/updated description/" + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package/Kptfile + - args: + - porchctl + - rpkg + - push + - --namespace=rpkg-pull-overwrite + - git.test-package.v1 + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package + stdout: | + git.test-package.v1 pushed + - args: + - porchctl + - rpkg + - pull + - --namespace=rpkg-pull-overwrite + - git.test-package.v1 + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package + # no error + - args: + - porchctl + - rpkg + - propose + - --namespace=rpkg-pull-overwrite + - git.test-package.v1 + stdout: | + git.test-package.v1 proposed + - args: + - porchctl + - rpkg + - approve + - --namespace=rpkg-pull-overwrite + - git.test-package.v1 + stdout: | + git.test-package.v1 approved + - args: + - porchctl + - rpkg + - copy + - --namespace=rpkg-pull-overwrite + - git.test-package.v1 + - --workspace=v2 + stdout: | + git.test-package.v2 created + - args: + - porchctl + - rpkg + - pull + - --namespace=rpkg-pull-overwrite + - git.test-package.v2 + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package + # no error + - args: + - porchctl + - rpkg + - init + - --namespace=rpkg-pull-overwrite + - --repository=git + - --workspace=v1 + - test-package-2 + stdout: | + git.test-package-2.v1 created + - args: + - porchctl + - rpkg + - pull + - --namespace=rpkg-pull-overwrite + - git.test-package-2.v1 + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package + exitCode: 1 + stderr: | # extra space at the end because of a bug in kpt pkg/lib/errors + Error: directory "/tmp/porch-e2e/rpkg-pull-overwrite-git.test-package" already exists, please delete the directory and retry; you may overwrite the directory with --force + - args: + - porchctl + - rpkg + - pull + - --namespace=rpkg-pull-overwrite + - --force + - git.test-package-2.v1 + - /tmp/porch-e2e/rpkg-pull-overwrite-git.test-package + # no error