From e0c5206111b1b3ca9bb07ff8bc3b16ee079b8efd Mon Sep 17 00:00:00 2001 From: Akshit Agrawal Date: Mon, 22 Jan 2024 14:16:47 +0530 Subject: [PATCH] using boilr template --- plugin/pipeline.go | 149 ++++++++++++++++++++++++++++++ plugin/plugin.go | 172 +++++++++++++++++++++++++++++++++++ plugin/plugin_test.go | 88 ++++++++++++++++++ plugin/test-chart/Chart.yaml | 5 + plugin/util.go | 36 ++++++++ 5 files changed, 450 insertions(+) create mode 100644 plugin/pipeline.go create mode 100644 plugin/plugin.go create mode 100644 plugin/plugin_test.go create mode 100644 plugin/test-chart/Chart.yaml create mode 100644 plugin/util.go diff --git a/plugin/pipeline.go b/plugin/pipeline.go new file mode 100644 index 0000000..af82a23 --- /dev/null +++ b/plugin/pipeline.go @@ -0,0 +1,149 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +// Pipeline provides Pipeline metadata from the environment. +type Pipeline struct { + // Build provides build metadata. + Build struct { + Branch string `envconfig:"DRONE_BUILD_BRANCH"` + Number int `envconfig:"DRONE_BUILD_NUMBER"` + Parent int `envconfig:"DRONE_BUILD_PARENT"` + Event string `envconfig:"DRONE_BUILD_EVENT"` + Action string `envconfig:"DRONE_BUILD_ACTION"` + Status string `envconfig:"DRONE_BUILD_STATUS"` + Created int64 `envconfig:"DRONE_BUILD_CREATED"` + Started int64 `envconfig:"DRONE_BUILD_STARTED"` + Finished int64 `envconfig:"DRONE_BUILD_FINISHED"` + Link string `envconfig:"DRONE_BUILD_LINK"` + } + + // Calver provides the calver details parsed from the + // git tag. If the git tag is empty or is not a valid + // calver, the values will be empty. + Calver struct { + Version string `envconfig:"DRONE_CALVER"` + Short string `envconfig:"DRONE_CALVER_SHORT"` + MajorMinor string `envconfig:"DRONE_CALVER_MAJOR_MINOR"` + Major string `envconfig:"DRONE_CALVER_MAJOR"` + Minor string `envconfig:"DRONE_CALVER_MINOR"` + Micro string `envconfig:"DRONE_CALVER_MICRO"` + Modifier string `envconfig:"DRONE_CALVER_MODIFIER"` + } + + // Card provides adaptive card configuration options. + Card struct { + Path string `envconfig:"DRONE_CARD_PATH"` + } + + // Commit provides the commit metadata. + Commit struct { + Rev string `envconfig:"DRONE_COMMIT_SHA"` + Before string `envconfig:"DRONE_COMMIT_BEFORE"` + After string `envconfig:"DRONE_COMMIT_AFTER"` + Ref string `envconfig:"DRONE_COMMIT_REF"` + Branch string `envconfig:"DRONE_COMMIT_BRANCH"` + Source string `envconfig:"DRONE_COMMIT_SOURCE"` + Target string `envconfig:"DRONE_COMMIT_TARGET"` + Link string `envconfig:"DRONE_COMMIT_LINK"` + Message string `envconfig:"DRONE_COMMIT_MESSAGE"` + + Author struct { + Username string `envconfig:"DRONE_COMMIT_AUTHOR"` + Name string `envconfig:"DRONE_COMMIT_AUTHOR_NAME"` + Email string `envconfig:"DRONE_COMMIT_AUTHOR_EMAIL"` + Avatar string `envconfig:"DRONE_COMMIT_AUTHOR_AVATAR"` + } + } + + // Deploy provides the deployment metadata. + Deploy struct { + ID string `envconfig:"DRONE_DEPLOY_TO"` + Target string `envconfig:"DRONE_DEPLOY_ID"` + } + + // Failed provides a list of failed steps and failed stages + // for the current pipeline. + Failed struct { + Steps []string `envconfig:"DRONE_FAILED_STEPS"` + Stages []string `envconfig:"DRONE_FAILED_STAGES"` + } + + // Git provides the git repository metadata. + Git struct { + HTTPURL string `envconfig:"DRONE_GIT_HTTP_URL"` + SSHURL string `envconfig:"DRONE_GIT_SSH_URL"` + } + + // PullRequest provides the pull request metadata. + PullRequest struct { + Number int `envconfig:"DRONE_PULL_REQUEST"` + } + + // Repo provides the repository metadata. + Repo struct { + Branch string `envconfig:"DRONE_REPO_BRANCH"` + Link string `envconfig:"DRONE_REPO_LINK"` + Namespace string `envconfig:"DRONE_REPO_NAMESPACE"` + Name string `envconfig:"DRONE_REPO_NAME"` + Private bool `envconfig:"DRONE_REPO_PRIVATE"` + Remote string `envconfig:"DRONE_GIT_HTTP_URL"` + SCM string `envconfig:"DRONE_REPO_SCM"` + Slug string `envconfig:"DRONE_REPO"` + Visibility string `envconfig:"DRONE_REPO_VISIBILITY"` + } + + // Stage provides the stage metadata. + Stage struct { + Kind string `envconfig:"DRONE_STAGE_KIND"` + Type string `envconfig:"DRONE_STAGE_TYPE"` + Name string `envconfig:"DRONE_STAGE_NAME"` + Number int `envconfig:"DRONE_STAGE_NUMBER"` + Machine string `envconfig:"DRONE_STAGE_MACHINE"` + OS string `envconfig:"DRONE_STAGE_OS"` + Arch string `envconfig:"DRONE_STAGE_ARCH"` + Variant string `envconfig:"DRONE_STAGE_VARIANT"` + Status string `envconfig:"DRONE_STAGE_STATUS"` + Started int64 `envconfig:"DRONE_STAGE_STARTED"` + Finished int64 `envconfig:"DRONE_STAGE_FINISHED"` + DependsOn []string `envconfig:"DRONE_STAGE_DEPENDS_ON"` + } + + // Step provides the step metadata. + Step struct { + Number int `envconfig:"DRONE_STEP_NUMBER"` + Name string `envconfig:"DRONE_STEP_NAME"` + } + + // Semver provides the semver details parsed from the + // git tag. If the git tag is empty or is not a valid + // semver, the values will be empty and the error field + // will be populated with the parsing error. + Semver struct { + Version string `envconfig:"DRONE_SEMVER"` + Short string `envconfig:"DRONE_SEMVER_SHORT"` + Major string `envconfig:"DRONE_SEMVER_MAJOR"` + Minor string `envconfig:"DRONE_SEMVER_MINOR"` + Patch string `envconfig:"DRONE_SEMVER_PATCH"` + Build string `envconfig:"DRONE_SEMVER_BUILD"` + PreRelease string `envconfig:"DRONE_SEMVER_PRERELEASE"` + Error string `envconfig:"DRONE_SEMVER_ERROR"` + } + + // System provides the Drone system metadata, including + // the system version of details required to create the + // drone website address. + System struct { + Proto string `envconfig:"DRONE_SYSTEM_PROTO"` + Host string `envconfig:"DRONE_SYSTEM_HOST"` + Hostname string `envconfig:"DRONE_SYSTEM_HOSTNAME"` + Version string `envconfig:"DRONE_SYSTEM_VERSION"` + } + + // Tag provides the git tag details. + Tag struct { + Name string `envconfig:"DRONE_TAG"` + } +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..7f43441 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,172 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +import ( + "context" + "fmt" + "os" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/registry" +) + +// Args provides plugin execution arguments. +type Args struct { + Pipeline + + // Level defines the plugin log level. + Level string `envconfig:"PLUGIN_LOG_LEVEL"` + + RegistryUrl string `envconfig:"PLUGIN_REGISTRY_URL"` + Username string `envconfig:"PLUGIN_REGISTRY_USERNAME"` + Password string `envconfig:"PLUGIN_REGISTRY_PASSWORD"` + ChartPath string `envconfig:"PLUGIN_CHART_PATH"` + Namespace string `envconfig:"PLUGIN_REGISTRY_NAMESPACE"` + ProjectId string `envconfig:"PLUGIN_PROJECT_ID"` +} + +// Exec executes the plugin. +func Exec(ctx context.Context, args Args) error { + if err := VerifyArgs(&args); err != nil { + return err + } + + packageRun, err := packageChart(&args) + if err != nil { + return err + } + + opts := []registry.ClientOption{ + registry.ClientOptWriter(os.Stdout), + } + + err = registryLogin(&args, opts) + if err != nil { + return err + } + + err = registryPush(&args, opts, packageRun) + if err != nil { + return err + } + + return nil +} + +func VerifyArgs(args *Args) error { + if args.RegistryUrl == "" { + return fmt.Errorf("registry url is required") + } + if args.Username == "" { + return fmt.Errorf("username is required") + } + if args.Password == "" { + return fmt.Errorf("password is required") + } + if args.ChartPath == "" { + return fmt.Errorf("chart path is required") + } + if args.Namespace == "" { + return fmt.Errorf("namespace is required") + } + return nil +} + +func packageChart(args *Args) (string, error) { + helmClient := action.NewPackage() + helmClient.DependencyUpdate = true + helmClient.Destination = args.ChartPath + + settings := cli.New() + getters := getter.All(settings) + + registryClient, err := registry.NewClient() + + if err != nil { + return "", fmt.Errorf("failed to create registry client") + } + + downloadManager := &downloader.Manager{ + Out: os.Stdout, + ChartPath: args.ChartPath, + Debug: false, + Getters: getters, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + RegistryClient: registryClient, + } + + if err := downloadManager.Build(); err != nil { + return "", fmt.Errorf("failed to retrieve chart") + } + + packageRun, err := helmClient.Run(args.ChartPath, nil) + if err != nil { + return "", fmt.Errorf("failed to package chart") + } + + fmt.Print("Successfully packaged chart\n") + + return packageRun, nil +} + +func registryLogin(args *Args, opts []registry.ClientOption) error { + registryClient, err := registry.NewClient(opts...) + if err != nil { + return fmt.Errorf("failed to create registry client") + } + + cfg := new(action.Configuration) + cfg.RegistryClient = registryClient + + err = action.NewRegistryLogin(cfg).Run( + os.Stdout, + args.RegistryUrl, + args.Username, + args.Password, + ) + + if err != nil { + return fmt.Errorf("failed to login to registry") + } + + return nil +} + +func registryPush(args *Args, opts []registry.ClientOption, packageRun string) error { + registryClient, err := registry.NewClient(opts...) + if err != nil { + return fmt.Errorf("failed to create registry client") + } + + cfg := new(action.Configuration) + cfg.RegistryClient = registryClient + + client := action.NewPushWithOpts(action.WithPushConfig(cfg)) + + settings := new(cli.EnvSettings) + client.Settings = settings + + var remoteURL string + + if args.ProjectId != "" { + remoteURL = fmt.Sprintf("oci://%s/%s/%s", args.RegistryUrl, args.ProjectId, args.Namespace) + } else { + remoteURL = fmt.Sprintf("oci://%s/%s", args.RegistryUrl, args.Namespace) + } + + _, err = client.Run(packageRun, remoteURL) + if err != nil { + return fmt.Errorf("failed to push chart") + } + + fmt.Print("Successfully pushed chart\n") + + return nil +} diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go new file mode 100644 index 0000000..75543b1 --- /dev/null +++ b/plugin/plugin_test.go @@ -0,0 +1,88 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +import ( + "testing" + + "helm.sh/helm/v3/pkg/registry" +) + +func TestVerifyArgs(t *testing.T) { + var err error + + err = VerifyArgs(&Args{ + RegistryUrl: "https://registry.hub.docker.com", + }) + + if err == nil { + t.Error(err) + } + + err = VerifyArgs(&Args{ + Username: "octocat", + Password: "pass", + }) + + if err == nil { + t.Error(err) + } + + err = VerifyArgs(&Args{ + RegistryUrl: "https://registry.hub.docker.com", + Username: "octocat", + Password: "pass", + }) + + if err == nil { + t.Error(err) + } +} + +func TestPackageChart(t *testing.T) { + var err error + + _, err = packageChart(&Args{ + ChartPath: "test-chart", + }) + + if err != nil { + t.Error(err) + } + + _, err = packageChart(&Args{ + ChartPath: "test-chart-fail", + }) + + if err == nil { + t.Error(err) + } +} + +func TestRegistryLogin(t *testing.T) { + + err := registryLogin(&Args{ + RegistryUrl: "https://registry.hub.docker.com", + Username: "octocat", + Password: "pass", + }, []registry.ClientOption{}) + + if err == nil { + t.Error(err) + } +} + +func TestRegistryPush(t *testing.T) { + + err := registryPush(&Args{ + RegistryUrl: "https://registry.hub.docker.com", + Username: "octocat", + Password: "pass", + }, []registry.ClientOption{}, "test-chart") + + if err == nil { + t.Error(err) + } +} diff --git a/plugin/test-chart/Chart.yaml b/plugin/test-chart/Chart.yaml new file mode 100644 index 0000000..bb67fe1 --- /dev/null +++ b/plugin/test-chart/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: mywebapp +description: a simple web app +version: 5.0.0 +appVersion: 5.0.0 diff --git a/plugin/util.go b/plugin/util.go new file mode 100644 index 0000000..caf1a05 --- /dev/null +++ b/plugin/util.go @@ -0,0 +1,36 @@ +// Copyright 2020 the Drone Authors. All rights reserved. +// Use of this source code is governed by the Blue Oak Model License +// that can be found in the LICENSE file. + +package plugin + +import ( + "encoding/base64" + "encoding/json" + "io" + "io/ioutil" + "os" +) + +func writeCard(path, schema string, card interface{}) { + data, _ := json.Marshal(map[string]interface{}{ + "schema": schema, + "data": card, + }) + switch { + case path == "/dev/stdout": + writeCardTo(os.Stdout, data) + case path == "/dev/stderr": + writeCardTo(os.Stderr, data) + case path != "": + ioutil.WriteFile(path, data, 0644) + } +} + +func writeCardTo(out io.Writer, data []byte) { + encoded := base64.StdEncoding.EncodeToString(data) + io.WriteString(out, "\u001B]1338;") + io.WriteString(out, encoded) + io.WriteString(out, "\u001B]0m") + io.WriteString(out, "\n") +}