diff --git a/pkg/api/kpm_run.go b/pkg/api/kpm_run.go index d59ab8ba..146b1afb 100644 --- a/pkg/api/kpm_run.go +++ b/pkg/api/kpm_run.go @@ -66,7 +66,7 @@ func RunOci(ociRef, version string, entryFiles []string, vendorMode bool, kclArg // 2. Pull the tar. err = oci.Pull(localPath, ociOpts.Reg, ociOpts.Repo, ociOpts.Tag) - if err != nil { + if err != (*reporter.KpmEvent)(nil) { return "", err } diff --git a/pkg/cmd/cmd_pull.go b/pkg/cmd/cmd_pull.go index d828d1e7..db03bcf9 100644 --- a/pkg/cmd/cmd_pull.go +++ b/pkg/cmd/cmd_pull.go @@ -100,7 +100,7 @@ func KpmPull(c *cli.Context) error { // 2. Pull the tar. err = oci.Pull(localPath, ociOpt.Reg, ociOpt.Repo, ociOpt.Tag) - if err != nil { + if err != (*reporter.KpmEvent)(nil) { return err } diff --git a/pkg/cmd/cmd_push.go b/pkg/cmd/cmd_push.go index 0ad6c65c..81594dd4 100644 --- a/pkg/cmd/cmd_push.go +++ b/pkg/cmd/cmd_push.go @@ -3,6 +3,7 @@ package cmd import ( + "fmt" "net/url" "os" @@ -36,28 +37,31 @@ func NewPushCmd(settings *settings.Settings) *cli.Command { }, }, Action: func(c *cli.Context) error { + return KpmPush(c, settings) + }, + } +} - localTarPath := c.String(FLAG_TAR_PATH) - ociUrl := c.Args().First() - - var err error +func KpmPush(c *cli.Context, settings *settings.Settings) error { + localTarPath := c.String(FLAG_TAR_PATH) + ociUrl := c.Args().First() - if len(localTarPath) == 0 { - // If the tar package to be pushed is not specified, - // the current kcl package is packaged into tar and pushed. - err = pushCurrentPackage(ociUrl, c.Bool(FLAG_VENDOR), settings) - } else { - // Else push the tar package specified. - err = pushTarPackage(ociUrl, localTarPath, c.Bool(FLAG_VENDOR), settings) - } + var err error - if err != nil { - return err - } + if len(localTarPath) == 0 { + // If the tar package to be pushed is not specified, + // the current kcl package is packaged into tar and pushed. + err = pushCurrentPackage(ociUrl, c.Bool(FLAG_VENDOR), settings) + } else { + // Else push the tar package specified. + err = pushTarPackage(ociUrl, localTarPath, c.Bool(FLAG_VENDOR), settings) + } - return nil - }, + if err != nil { + return err } + + return nil } // genDefaultOciUrlForKclPkg will generate the default oci url from the current package. @@ -83,13 +87,14 @@ func pushCurrentPackage(ociUrl string, vendorMode bool, settings *settings.Setti pwd, err := os.Getwd() if err != nil { - reporter.ExitWithReport("kpm: internal bug: failed to load working directory") + reporter.ReportEventToStderr(reporter.NewEvent(reporter.Bug, "internal bug: failed to load working directory")) + return err } // 1. Load the current kcl packege. kclPkg, err := pkg.LoadKclPkg(pwd) if err != nil { - reporter.ExitWithReport("kpm: failed to load package in " + pwd + ".") + reporter.ReportEventToStderr(reporter.NewEvent(reporter.FailedLoadKclMod, fmt.Sprintf("failed to load package in '%s'", pwd))) return err } @@ -168,7 +173,7 @@ func pushPackage(ociUrl string, kclPkg *pkg.KclPkg, vendorMode bool, settings *s reporter.Report("kpm: package '" + kclPkg.GetPkgName() + "' will be pushed.") // 4. Push it. err = oci.Push(tarPath, ociOpts.Reg, ociOpts.Repo, ociOpts.Tag, settings) - if err != nil { + if err != (*reporter.KpmEvent)(nil) { return err } diff --git a/pkg/oci/oci.go b/pkg/oci/oci.go index e8544898..7a3fe421 100644 --- a/pkg/oci/oci.go +++ b/pkg/oci/oci.go @@ -6,6 +6,7 @@ import ( "path/filepath" v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/thoas/go-funk" "kcl-lang.io/kpm/pkg/errors" "kcl-lang.io/kpm/pkg/reporter" "kcl-lang.io/kpm/pkg/semver" @@ -18,6 +19,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/file" "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/errcode" "oras.land/oras-go/v2/registry/remote/retry" ) @@ -77,7 +79,7 @@ type OciClient struct { // NewOciClient will new an OciClient. // regName is the registry. e.g. ghcr.io or docker.io. // repoName is the repo name on registry. -func NewOciClient(regName, repoName string) (*OciClient, error) { +func NewOciClient(regName, repoName string) (*OciClient, *reporter.KpmEvent) { repoPath := utils.JoinPath(regName, repoName) repo, err := remote.NewRepository(repoPath) @@ -118,7 +120,7 @@ func NewOciClient(regName, repoName string) (*OciClient, error) { } // Pull will pull the oci artifacts from oci registry to local path. -func (ociClient *OciClient) Pull(localPath, tag string) error { +func (ociClient *OciClient) Pull(localPath, tag string) *reporter.KpmEvent { // Create a file store fs, err := file.New(localPath) if err != nil { @@ -140,7 +142,7 @@ func (ociClient *OciClient) Pull(localPath, tag string) error { } // TheLatestTag will return the latest tag of the kcl packages. -func (ociClient *OciClient) TheLatestTag() (string, error) { +func (ociClient *OciClient) TheLatestTag() (string, *reporter.KpmEvent) { var tagSelected string err := ociClient.repo.Tags(*ociClient.ctx, "", func(tags []string) error { @@ -164,12 +166,40 @@ func (ociClient *OciClient) TheLatestTag() (string, error) { return tagSelected, nil } +// ContainsTag will check if the tag exists in the repo. +func (ociClient *OciClient) ContainsTag(tag string) (bool, *reporter.KpmEvent) { + var exists bool + + err := ociClient.repo.Tags(*ociClient.ctx, "", func(tags []string) error { + exists = funk.ContainsString(tags, tag) + return nil + }) + + if err != nil { + // If the repo with tag is not found, return false. + errRes, ok := err.(*errcode.ErrorResponse) + if ok { + if len(errRes.Errors) == 1 && errRes.Errors[0].Code == errcode.ErrorCodeNameUnknown { + return false, nil + } + } + // If the user not login, return error. + return false, reporter.NewErrorEvent( + reporter.FailedGetPackageVersions, + err, + fmt.Sprintf("failed to access '%s'", ociClient.repo.Reference.String()), + ) + } + + return exists, nil +} + // Push will push the oci artifacts to oci registry from local path -func (ociClient *OciClient) Push(localPath, tag string) error { +func (ociClient *OciClient) Push(localPath, tag string) *reporter.KpmEvent { // 0. Create a file store fs, err := file.New(filepath.Dir(localPath)) if err != nil { - return err + return reporter.NewErrorEvent(reporter.FailedPush, err, "Failed to load store path ", localPath) } defer fs.Close() @@ -184,7 +214,7 @@ func (ociClient *OciClient) Push(localPath, tag string) error { // and a file path is created for each download, which is not good. fileDescriptor, err := fs.Add(*ociClient.ctx, filepath.Base(name), DEFAULT_OCI_ARTIFACT_TYPE, "") if err != nil { - return err + return reporter.NewErrorEvent(reporter.FailedPush, err, fmt.Sprintf("Failed to add file '%s'", name)) } fileDescriptors = append(fileDescriptors, fileDescriptor) } @@ -194,18 +224,18 @@ func (ociClient *OciClient) Push(localPath, tag string) error { PackImageManifest: true, }) if err != nil { - return err + return reporter.NewErrorEvent(reporter.FailedPush, err, fmt.Sprintf("Failed to pack package in '%s'", localPath)) } if err = fs.Tag(*ociClient.ctx, manifestDescriptor, tag); err != nil { - return err + return reporter.NewErrorEvent(reporter.FailedPush, err, fmt.Sprintf("Failed to tag package with tag '%s'", tag)) } // 3. Copy from the file store to the remote repository desc, err := oras.Copy(*ociClient.ctx, fs, tag, ociClient.repo, tag, oras.DefaultCopyOptions) if err != nil { - return err + return reporter.NewErrorEvent(reporter.FailedPush, err, fmt.Sprintf("Failed to push '%s'", ociClient.repo.Reference)) } reporter.Report("kpm: pushed [registry]", ociClient.repo.Reference) @@ -231,7 +261,7 @@ func loadCredential(hostName string, settings *settings.Settings) (*remoteauth.C } // Pull will pull the oci artifacts from oci registry to local path. -func Pull(localPath, hostName, repoName, tag string) error { +func Pull(localPath, hostName, repoName, tag string) *reporter.KpmEvent { ociClient, err := NewOciClient(hostName, repoName) if err != nil { return err @@ -260,13 +290,25 @@ func Pull(localPath, hostName, repoName, tag string) error { } // Push will push the oci artifacts to oci registry from local path -func Push(localPath, hostName, repoName, tag string, settings *settings.Settings) error { +func Push(localPath, hostName, repoName, tag string, settings *settings.Settings) *reporter.KpmEvent { // Create an oci client. ociClient, err := NewOciClient(hostName, repoName) if err != nil { return err } + exist, err := ociClient.ContainsTag(tag) + if err != (*reporter.KpmEvent)(nil) { + return err + } + + if exist { + return reporter.NewErrorEvent( + reporter.PkgTagExists, + fmt.Errorf("package version '%s' already exists", tag), + ) + } + // Push the oci package by the oci client. return ociClient.Push(localPath, tag) } diff --git a/pkg/package/modfile.go b/pkg/package/modfile.go index 3b2c51db..d9da7131 100644 --- a/pkg/package/modfile.go +++ b/pkg/package/modfile.go @@ -8,7 +8,6 @@ import ( "github.com/BurntSushi/toml" "kcl-lang.io/kcl-go/pkg/kcl" - "kcl-lang.io/kpm/pkg/errors" "kcl-lang.io/kpm/pkg/git" "kcl-lang.io/kpm/pkg/oci" "kcl-lang.io/kpm/pkg/opt" @@ -233,10 +232,14 @@ func (dep *Oci) Download(localPath string) (string, error) { return "", err } - matches, err := filepath.Glob(filepath.Join(localPath, "*.tar")) - if err != nil || len(matches) != 1 { - if err == nil { - err = errors.InvalidPkg + matches, finderr := filepath.Glob(filepath.Join(localPath, "*.tar")) + if finderr != nil || len(matches) != 1 { + if finderr == nil { + err = reporter.NewErrorEvent( + reporter.InvalidKclPkg, + err, + fmt.Sprintf("failed to find the kcl package tar from '%s'.", localPath), + ) } return "", reporter.NewErrorEvent( @@ -247,19 +250,19 @@ func (dep *Oci) Download(localPath string) (string, error) { } tarPath := matches[0] - err = utils.UnTarDir(tarPath, localPath) - if err != nil { + untarErr := utils.UnTarDir(tarPath, localPath) + if untarErr != nil { return "", reporter.NewErrorEvent( reporter.FailedUntarKclPkg, - err, + untarErr, fmt.Sprintf("failed to untar the kcl package tar from '%s' into '%s'.", tarPath, localPath), ) } // After untar the downloaded kcl package tar file, remove the tar file. if utils.DirExists(tarPath) { - err = os.Remove(tarPath) - if err != nil { + rmErr := os.Remove(tarPath) + if rmErr != nil { return "", reporter.NewErrorEvent( reporter.FailedUntarKclPkg, err, diff --git a/pkg/reporter/reporter.go b/pkg/reporter/reporter.go index 9fc7ebc1..b81f9059 100644 --- a/pkg/reporter/reporter.go +++ b/pkg/reporter/reporter.go @@ -55,7 +55,9 @@ const ( FailedLoadCredential FailedCreateOciClient FailedSelectLatestVersion + FailedGetPackageVersions FailedCreateStorePath + FailedPush FailedGetPkg FailedAccessPkgPath UnKnownPullWhat @@ -88,6 +90,7 @@ const ( LocalPathNotExist PathIsEmpty AddItselfAsDep + PkgTagExists ) // KpmEvent is the event used to show kpm logs to users. @@ -147,11 +150,16 @@ func NewEvent(errType EventType, args ...string) *KpmEvent { } } -// ReportEvent reports the event to users to stdout. +// ReportEventToStdout reports the event to users to stdout. func ReportEventToStdout(event *KpmEvent) { fmt.Fprintf(os.Stdout, "%v", event.Event()) } +// ReportEventToStderr reports the event to users to stderr. +func ReportEventToStderr(event *KpmEvent) { + fmt.Fprintf(os.Stderr, "%v", event.Event()) +} + // ReportEvent reports the event to users to stdout. func ReportEventTo(event *KpmEvent, w io.Writer) { fmt.Fprintf(w, "%v", event.Event()) diff --git a/scripts/reg.sh b/scripts/reg.sh index 2e024a01..09348d28 100755 --- a/scripts/reg.sh +++ b/scripts/reg.sh @@ -23,3 +23,6 @@ docker run -p 5001:5000 \ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \ -d registry + +# clean the registry +docker exec registry rm -rf /var/lib/registry/docker/registry/v2/repositories/ diff --git a/test/e2e/kpm_test.go b/test/e2e/kpm_test.go index 1ed55afb..97f9671c 100644 --- a/test/e2e/kpm_test.go +++ b/test/e2e/kpm_test.go @@ -103,10 +103,10 @@ var _ = ginkgo.Describe("Kpm CLI Testing", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) if !IsIgnore(expectedStdout) { - gomega.Expect(stdout).To(gomega.Equal(expectedStdout)) + gomega.Expect(stdout).To(gomega.ContainSubstring(expectedStdout)) } if !IsIgnore(expectedStderr) { - gomega.Expect(stderr).To(gomega.Equal(expectedStderr)) + gomega.Expect(stderr).To(gomega.ContainSubstring(expectedStderr)) } } } diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.env b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.env new file mode 100644 index 00000000..4c789529 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.env @@ -0,0 +1,2 @@ +KPM_HOME="" +KCLVM_VENDOR_HOME="" \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.input b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.input new file mode 100644 index 00000000..ca9a5d89 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.input @@ -0,0 +1 @@ +kpm push \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.stderr b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.stderr new file mode 100644 index 00000000..2e5310e4 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.stderr @@ -0,0 +1,2 @@ +kpm: package 'kcl1' will be pushed. +kpm: package version '0.0.1' already exists \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.stdout b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_again/test_suite.stdout new file mode 100644 index 00000000..e69de29b diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.env b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.env new file mode 100644 index 00000000..4c789529 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.env @@ -0,0 +1,2 @@ +KPM_HOME="" +KCLVM_VENDOR_HOME="" \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.input b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.input new file mode 100644 index 00000000..e7f08290 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.input @@ -0,0 +1 @@ +kpm push oci://not_exist_reg/not_exist_repo \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.stderr b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.stderr new file mode 100644 index 00000000..68e04602 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.stderr @@ -0,0 +1,2 @@ +kpm: package 'kcl1' will be pushed. +kpm: failed to access 'not_exist_reg/not_exist_repo' diff --git a/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.stdout b/test/e2e/test_suites/kpm/exec_inside_pkg/push_kcl1_reg_not_exist/test_suite.stdout new file mode 100644 index 00000000..e69de29b diff --git a/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.env b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.env new file mode 100644 index 00000000..4c789529 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.env @@ -0,0 +1,2 @@ +KPM_HOME="" +KCLVM_VENDOR_HOME="" \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.input b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.input new file mode 100644 index 00000000..ca9a5d89 --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.input @@ -0,0 +1 @@ +kpm push \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.stderr b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.stderr new file mode 100644 index 00000000..cab3e8da --- /dev/null +++ b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.stderr @@ -0,0 +1 @@ +kpm: failed to load package in '' \ No newline at end of file diff --git a/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.stdout b/test/e2e/test_suites/kpm/exec_outside_pkg/push_outside_pkg/test_suite.stdout new file mode 100644 index 00000000..e69de29b diff --git a/test_push/kcl.mod b/test_push/kcl.mod new file mode 100644 index 00000000..7bc61e19 --- /dev/null +++ b/test_push/kcl.mod @@ -0,0 +1,5 @@ +[package] +name = "test_push" +edition = "0.0.1" +version = "0.0.1" + diff --git a/test_push/kcl.mod.lock b/test_push/kcl.mod.lock new file mode 100644 index 00000000..e69de29b diff --git a/test_push/main.k b/test_push/main.k new file mode 100644 index 00000000..fa7048e6 --- /dev/null +++ b/test_push/main.k @@ -0,0 +1 @@ +The_first_kcl_program = 'Hello World!' \ No newline at end of file