From 4cd369b051b3e7fe646547d6dc085bfcd775a45e Mon Sep 17 00:00:00 2001 From: Erin Call Date: Wed, 25 Dec 2019 10:50:20 -0800 Subject: [PATCH 01/10] Auto-assign rewiewers with a CODEOWNERS [#12] --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..378c620 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @erincall @grinnellian @kav @josmo From 4f549ddf2a0e9944f235cc7aa6f3f81271f225f2 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Wed, 25 Dec 2019 11:20:14 -0800 Subject: [PATCH 02/10] Pre-populate pull request bodies with a template [#12] Note that as of this writing, docs/parameter_reference.md (and required- params documentation in README.md) doesn't actually exist; I created it in ef66bc0 but that commit hasn't been merged yet. --- .github/pull_request_template.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8985c6a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ +**Please replace this line with "fixes #ISSUE_NUMBER" (or "relates to #ISSUE_NUMBER", if it is not a complete fix)** + +Pre-merge checklist: + +* [ ] Code changes have tests +* [ ] Any changes to the config are documented in `docs/parameter_reference.md` +* [ ] Any new _required_ config is documented in `README.md` +* [ ] Any large changes have been verified by running a Drone job From 1a70a626ea1d81569e85f508497c0eda21928738 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Wed, 25 Dec 2019 11:46:56 -0800 Subject: [PATCH 03/10] Use the Contributor Covenant code of conduct [#12] --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..cc02111 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [workwithus@pelo.tech](mailto:workwithus@pelo.tech). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From f00f6a63292e246f7177fec3b7cd543de2354f48 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Wed, 25 Dec 2019 12:03:36 -0800 Subject: [PATCH 04/10] Encourage good issues with issue templates [#12] I've never used github's new multiple-option issue templates before! I'm excited to see them in action :) --- .github/ISSUE_TEMPLATE/bug_report.md | 17 +++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 14 ++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..aadc390 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,17 @@ +--- +name: Bug report +about: Unexpected or broken behavior +title: '' +labels: bug +assignees: '' + +--- + +**What I tried to do:** + + +**What happened:** + + +**More info:** + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..40932a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest a new feature +title: '' +labels: enhancement +assignees: '' + +--- + +**The problem I'm trying to solve:** + + +**How I imagine it working:** + From a71bba71fdf0fc56527ba5066247792d2f36fd82 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Wed, 25 Dec 2019 16:08:29 -0800 Subject: [PATCH 05/10] Give contributors some maybe-adequate information [#12] I honestly have no idea how useful this document is, but it's probably better than nothing? --- .drone.yml | 9 +++++++++ docs/contributing.md | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docs/contributing.md diff --git a/.drone.yml b/.drone.yml index 66984fa..08bcf70 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,3 +30,12 @@ steps: dockerfile: Dockerfile when: event: [ tag, push ] + + # Example configuration for publishing to a local registry for testing. + # - name: publish_locally + # image: plugins/docker + # settings: + # dockerfile: Dockerfile + # insecure: true + # registry: 0.0.0.0:5000 + # repo: 0.0.0.0:5000/drone-helm3 diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..ef70d84 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,48 @@ +# Contributing to drone-helm3 + +We're glad you're interested in contributing! Here are some guidelines that will help make sure everyone has a good experience: + +## Submitting a patch + +Before you start working on a change, please make sure there's an associated issue. It doesn't need to be thoroughly scrutinized and dissected, but it needs to exist. + +Please put the relevant issue number in the first line of your commit messages, e.g. `vorpalize the frabjulator [#42]`. Branch names do not need issue numbers, but feel free to include them if you like. + +We encourage you to follow [the guidelines in Pro Git](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines) when making commits. In short: + +* Commit early and commit often. +* Make the first line of the commit message concise--no more than 50 characters or so. +* Make the rest of the commit message verbose--information about _why_ you did what you did is particularly helpful. + +Once you're satisfied with your work, send us a pull request. If you'd like, you can send the pull request _before_ you're satisfied with your work; just be sure to mark the PR a draft or put `[WIP]` in the title. + +## How to run the tests + +We use `go test`, `go vet`, and `golint`: + +``` +go test ./cmd/... ./internal/... +go vet ./cmd/... ./internal/... +golint -set_exit_status ./cmd/... ./internal/... +``` + +If you have [the Drone cli tool](https://docs.drone.io/cli/install/) installed, you can also use `drone exec --include test --include lint`. + +## Using the plugin locally + +The internal tests can't test drone-helm3's integration with drone and helm themselves. However, you can build and run a local image to test a change end-to-end. + +You will need: + +* A Docker image registry. See [docs.docker.com/registry/](https://docs.docker.com/registry/) for information on standing up a local registry. +* You will also need [the Drone cli tool](https://docs.drone.io/cli/install/). +* A fixture repo--a repo with a `.drone.yml` and a helm chart. +* Access to a kubernetes cluster. + +Once you have a local registry, uncomment the `publish_locally` step in `.drone.yml` and replace the `0.0.0.0`s with your computer's local IP address. + +Now you can run `drone exec --include test --include lint --include publish_locally` to build an image and publish it to your local registry. + +Finally, configure your fixture repo to use the locally-published image, e.g. `image: 192.168.0.1:5000/drone-helm3`. + +Now you can use `drone exec` in the fixture repo to verify your changes. From 232bb5eb960a2220baef8d78df02814c657e8a13 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Thu, 26 Dec 2019 13:03:53 -0800 Subject: [PATCH 06/10] Rely on the PR template for docs/code consistency [#12] These comments were a reasonable attempt at ensuring the documentation matched reality, but the checkbox in the pull request template is much more likely to produce results. --- internal/helm/plan.go | 1 - internal/run/lint.go | 2 -- internal/run/upgrade.go | 2 -- 3 files changed, 5 deletions(-) diff --git a/internal/helm/plan.go b/internal/helm/plan.go index 54de589..e6c8721 100644 --- a/internal/helm/plan.go +++ b/internal/helm/plan.go @@ -69,7 +69,6 @@ func determineSteps(cfg Config) *func(Config) []Step { return &help default: switch cfg.DroneEvent { - // Note: These events are documented in docs/upgrade_settings.yml. Any changes here should be reflected there. case "push", "tag", "deployment", "pull_request", "promote", "rollback": return &upgrade case "delete": diff --git a/internal/run/lint.go b/internal/run/lint.go index 1993b49..e2843ca 100644 --- a/internal/run/lint.go +++ b/internal/run/lint.go @@ -16,8 +16,6 @@ func (l *Lint) Execute(_ Config) error { } // Prepare gets the Lint ready to execute. -// Note: mandatory settings are documented in README.md, and the full list of settings is in docs/lint_settings.yml. -// Any additions or deletions here should be reflected there. func (l *Lint) Prepare(cfg Config) error { if l.Chart == "" { return fmt.Errorf("chart is required") diff --git a/internal/run/upgrade.go b/internal/run/upgrade.go index fc561aa..dd50527 100644 --- a/internal/run/upgrade.go +++ b/internal/run/upgrade.go @@ -25,8 +25,6 @@ func (u *Upgrade) Execute(_ Config) error { } // Prepare gets the Upgrade ready to execute. -// Note: mandatory settings are documented in README.md, and the full list of settings is in docs/upgrade_settings.yml. -// Any additions or deletions here should be reflected there. func (u *Upgrade) Prepare(cfg Config) error { if u.Chart == "" { return fmt.Errorf("chart is required") From 24060c18fccf9036d4378a64d2a1f1928f3d56e9 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Thu, 26 Dec 2019 15:21:01 -0800 Subject: [PATCH 07/10] Provide better e2e-testing instructions [#12] The "get a kubernetes cluster" part could use some outfleshing, but this is certainly more straightforward than it was before. --- .drone.yml | 9 --------- .gitignore | 2 ++ docs/contributing.md | 24 ++++++++++++------------ docs/example.secrets | 3 +++ 4 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 docs/example.secrets diff --git a/.drone.yml b/.drone.yml index 08bcf70..66984fa 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,12 +30,3 @@ steps: dockerfile: Dockerfile when: event: [ tag, push ] - - # Example configuration for publishing to a local registry for testing. - # - name: publish_locally - # image: plugins/docker - # settings: - # dockerfile: Dockerfile - # insecure: true - # registry: 0.0.0.0:5000 - # repo: 0.0.0.0:5000/drone-helm3 diff --git a/.gitignore b/.gitignore index 66fd13c..feb6e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ +.env +.secrets diff --git a/docs/contributing.md b/docs/contributing.md index ef70d84..dae4001 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -28,21 +28,21 @@ golint -set_exit_status ./cmd/... ./internal/... If you have [the Drone cli tool](https://docs.drone.io/cli/install/) installed, you can also use `drone exec --include test --include lint`. -## Using the plugin locally +## Testing the plugin end-to-end -The internal tests can't test drone-helm3's integration with drone and helm themselves. However, you can build and run a local image to test a change end-to-end. +Although we aim to make the internal tests as thorough as possible, they can't test drone-helm3's integration with drone and helm themselves. However, you can test a change manually by building an image and running it with a fixture repository. You will need: -* A Docker image registry. See [docs.docker.com/registry/](https://docs.docker.com/registry/) for information on standing up a local registry. -* You will also need [the Drone cli tool](https://docs.drone.io/cli/install/). -* A fixture repo--a repo with a `.drone.yml` and a helm chart. -* Access to a kubernetes cluster. +* Access to a docker image registry. This document assumes you'll use [Docker Hub](https://hub.docker.com). +* [The Drone cli tool](https://docs.drone.io/cli/install/). +* A fixture repository--a directory with a `.drone.yml` and a helm chart. If you don't have one handy, try adding a `.drone.yml` to a chart from [Helm's "stable" repository](https://github.com/helm/charts/tree/master/stable/). +* Access to a kubernetes cluster (unless `lint` or `dry_run` is sufficient for your purposes). -Once you have a local registry, uncomment the `publish_locally` step in `.drone.yml` and replace the `0.0.0.0`s with your computer's local IP address. +Once you have what you need, you can publish and consume an image with your changes: -Now you can run `drone exec --include test --include lint --include publish_locally` to build an image and publish it to your local registry. - -Finally, configure your fixture repo to use the locally-published image, e.g. `image: 192.168.0.1:5000/drone-helm3`. - -Now you can use `drone exec` in the fixture repo to verify your changes. +1. [Create a repository on Docker Hub](https://hub.docker.com/repository/create). This document assumes you've called it drone-helm3-testing. +1. Create a `.secrets` file with your docker credentials (see [example.secrets](./example.secrets) for an example). While you can use your Docker Hub password, it's better to [generate an access token](https://hub.docker.com/settings/security) and use that instead. +1. Use Drone to build and publish an image with your changes: `drone exec --secret-file ./secrets --event push` +1. In the `.drone.yml` of your fixture repository, set the `image` for each relevant stanza to `your_dockerhub_username/drone-helm3-testing` +1. Use `drone exec` in the fixture repo to verify your changes. diff --git a/docs/example.secrets b/docs/example.secrets new file mode 100644 index 0000000..c9c99b7 --- /dev/null +++ b/docs/example.secrets @@ -0,0 +1,3 @@ +DOCKER_PASSWORD=your_access_token +DOCKER_USERNAME=your_dockerhub_username +PLUGIN_REPO=your_dockerhub_username/drone-helm3-testing From 181165cc51eab5c815dd8e5938d76a7f6359583a Mon Sep 17 00:00:00 2001 From: Erin Call Date: Fri, 27 Dec 2019 15:06:32 -0800 Subject: [PATCH 08/10] Call `helm dependency update` when so instructed [#25] As with Lint, I have no idea whether the --namespace flag actually matters here. I don't think it will hurt, though! --- docs/parameter_reference.md | 2 +- internal/helm/plan.go | 25 +++++-- internal/helm/plan_test.go | 48 ++++++++++++- internal/run/depupdate.go | 44 ++++++++++++ internal/run/depupdate_test.go | 128 +++++++++++++++++++++++++++++++++ 5 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 internal/run/depupdate.go create mode 100644 internal/run/depupdate_test.go diff --git a/docs/parameter_reference.md b/docs/parameter_reference.md index 07d8107..a5e02d6 100644 --- a/docs/parameter_reference.md +++ b/docs/parameter_reference.md @@ -4,7 +4,7 @@ | Param name | Type | Purpose | |---------------------|-----------------|---------| | helm_command | string | Indicates the operation to perform. Recommended, but not required. Valid options are `upgrade`, `uninstall`, `lint`, and `help`. | -| update_dependencies | boolean | Calls `helm dependency update` before running the main command. **Not currently implemented**; see [#25](https://github.com/pelotech/drone-helm3/issues/25).| +| update_dependencies | boolean | Calls `helm dependency update` before running the main command.| | helm_repos | list\ | Calls `helm repo add $repo` before running the main command. Each string should be formatted as `repo_name=https://repo.url/`. **Not currently implemented**; see [#26](https://github.com/pelotech/drone-helm3/issues/26). | | namespace | string | Kubernetes namespace to use for this operation. | | prefix | string | Expect environment variables to be prefixed with the given string. For more details, see "Using the prefix setting" below. **Not currently implemented**; see [#19](https://github.com/pelotech/drone-helm3/issues/19). | diff --git a/internal/helm/plan.go b/internal/helm/plan.go index e6c8721..ad2e5ae 100644 --- a/internal/helm/plan.go +++ b/internal/helm/plan.go @@ -96,7 +96,9 @@ func (p *Plan) Execute() error { var upgrade = func(cfg Config) []Step { steps := initKube(cfg) - + if cfg.UpdateDependencies { + steps = append(steps, depUpdate(cfg)...) + } steps = append(steps, &run.Upgrade{ Chart: cfg.Chart, Release: cfg.Release, @@ -113,6 +115,9 @@ var upgrade = func(cfg Config) []Step { var uninstall = func(cfg Config) []Step { steps := initKube(cfg) + if cfg.UpdateDependencies { + steps = append(steps, depUpdate(cfg)...) + } steps = append(steps, &run.Uninstall{ Release: cfg.Release, DryRun: cfg.DryRun, @@ -122,11 +127,15 @@ var uninstall = func(cfg Config) []Step { } var lint = func(cfg Config) []Step { - lint := &run.Lint{ - Chart: cfg.Chart, + steps := make([]Step, 0) + if cfg.UpdateDependencies { + steps = append(steps, depUpdate(cfg)...) } + steps = append(steps, &run.Lint{ + Chart: cfg.Chart, + }) - return []Step{lint} + return steps } var help = func(cfg Config) []Step { @@ -147,3 +156,11 @@ func initKube(cfg Config) []Step { }, } } + +func depUpdate(cfg Config) []Step { + return []Step{ + &run.DepUpdate{ + Chart: cfg.Chart, + }, + } +} diff --git a/internal/helm/plan_test.go b/internal/helm/plan_test.go index 4808aef..2cdde5c 100644 --- a/internal/helm/plan_test.go +++ b/internal/helm/plan_test.go @@ -167,7 +167,17 @@ func (suite *PlanTestSuite) TestUpgrade() { suite.Equal(expected, upgrade) } -func (suite *PlanTestSuite) TestDel() { +func (suite *PlanTestSuite) TestUpgradeWithUpdateDependencies() { + cfg := Config{ + UpdateDependencies: true, + } + steps := upgrade(cfg) + suite.Require().Equal(3, len(steps), "upgrade should have a third step when DepUpdate is true") + suite.IsType(&run.InitKube{}, steps[0]) + suite.IsType(&run.DepUpdate{}, steps[1]) +} + +func (suite *PlanTestSuite) TestUninstall() { cfg := Config{ KubeToken: "b2YgbXkgYWZmZWN0aW9u", SkipTLSVerify: true, @@ -205,6 +215,16 @@ func (suite *PlanTestSuite) TestDel() { suite.Equal(expected, actual) } +func (suite *PlanTestSuite) TestUninstallWithUpdateDependencies() { + cfg := Config{ + UpdateDependencies: true, + } + steps := uninstall(cfg) + suite.Require().Equal(3, len(steps), "uninstall should have a third step when DepUpdate is true") + suite.IsType(&run.InitKube{}, steps[0]) + suite.IsType(&run.DepUpdate{}, steps[1]) +} + func (suite *PlanTestSuite) TestInitKube() { cfg := Config{ KubeToken: "cXVlZXIgY2hhcmFjdGVyCg==", @@ -231,6 +251,23 @@ func (suite *PlanTestSuite) TestInitKube() { suite.Equal(expected, init) } +func (suite *PlanTestSuite) TestDepUpdate() { + cfg := Config{ + UpdateDependencies: true, + Chart: "scatterplot", + } + + steps := depUpdate(cfg) + suite.Require().Equal(1, len(steps), "depUpdate should return one step") + suite.Require().IsType(&run.DepUpdate{}, steps[0]) + update, _ := steps[0].(*run.DepUpdate) + + expected := &run.DepUpdate{ + Chart: "scatterplot", + } + suite.Equal(expected, update) +} + func (suite *PlanTestSuite) TestLint() { cfg := Config{ Chart: "./flow", @@ -245,6 +282,15 @@ func (suite *PlanTestSuite) TestLint() { suite.Equal(want, steps[0]) } +func (suite *PlanTestSuite) TestLintWithUpdateDependencies() { + cfg := Config{ + UpdateDependencies: true, + } + steps := lint(cfg) + suite.Require().Equal(2, len(steps), "lint should have a second step when DepUpdate is true") + suite.IsType(&run.DepUpdate{}, steps[0]) +} + func (suite *PlanTestSuite) TestDeterminePlanUpgradeCommand() { cfg := Config{ Command: "upgrade", diff --git a/internal/run/depupdate.go b/internal/run/depupdate.go new file mode 100644 index 0000000..a9b6c91 --- /dev/null +++ b/internal/run/depupdate.go @@ -0,0 +1,44 @@ +package run + +import ( + "fmt" +) + +// DepUpdate is an execution step that calls `helm dependency update` when executed. +type DepUpdate struct { + Chart string + cmd cmd +} + +// Execute executes the `helm upgrade` command. +func (d *DepUpdate) Execute(_ Config) error { + return d.cmd.Run() +} + +// Prepare gets the DepUpdate ready to execute. +func (d *DepUpdate) Prepare(cfg Config) error { + if d.Chart == "" { + return fmt.Errorf("chart is required") + } + + args := make([]string, 0) + + if cfg.Namespace != "" { + args = append(args, "--namespace", cfg.Namespace) + } + if cfg.Debug { + args = append(args, "--debug") + } + + args = append(args, "dependency", "update", d.Chart) + + d.cmd = command(helmBin, args...) + d.cmd.Stdout(cfg.Stdout) + d.cmd.Stderr(cfg.Stderr) + + if cfg.Debug { + fmt.Fprintf(cfg.Stderr, "Generated command: '%s'\n", d.cmd.String()) + } + + return nil +} diff --git a/internal/run/depupdate_test.go b/internal/run/depupdate_test.go new file mode 100644 index 0000000..315b351 --- /dev/null +++ b/internal/run/depupdate_test.go @@ -0,0 +1,128 @@ +package run + +import ( + "fmt" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" + "strings" + "testing" +) + +type DepUpdateTestSuite struct { + suite.Suite + ctrl *gomock.Controller + mockCmd *Mockcmd + originalCommand func(string, ...string) cmd +} + +func (suite *DepUpdateTestSuite) BeforeTest(_, _ string) { + suite.ctrl = gomock.NewController(suite.T()) + suite.mockCmd = NewMockcmd(suite.ctrl) + + suite.originalCommand = command + command = func(path string, args ...string) cmd { return suite.mockCmd } +} + +func (suite *DepUpdateTestSuite) AfterTest(_, _ string) { + command = suite.originalCommand +} + +func TestDepUpdateTestSuite(t *testing.T) { + suite.Run(t, new(DepUpdateTestSuite)) +} + +func (suite *DepUpdateTestSuite) TestPrepareAndExecute() { + defer suite.ctrl.Finish() + + stdout := strings.Builder{} + stderr := strings.Builder{} + cfg := Config{ + Stdout: &stdout, + Stderr: &stderr, + } + + command = func(path string, args ...string) cmd { + suite.Equal(helmBin, path) + suite.Equal([]string{"dependency", "update", "your_top_songs_2019"}, args) + + return suite.mockCmd + } + suite.mockCmd.EXPECT(). + Stdout(&stdout) + suite.mockCmd.EXPECT(). + Stderr(&stderr) + suite.mockCmd.EXPECT(). + Run(). + Times(1) + + d := DepUpdate{ + Chart: "your_top_songs_2019", + } + + suite.Require().NoError(d.Prepare(cfg)) + suite.NoError(d.Execute(cfg)) +} + +func (suite *DepUpdateTestSuite) TestPrepareNamespaceFlag() { + defer suite.ctrl.Finish() + + cfg := Config{ + Namespace: "spotify", + } + + command = func(path string, args ...string) cmd { + suite.Equal([]string{"--namespace", "spotify", "dependency", "update", "your_top_songs_2019"}, args) + + return suite.mockCmd + } + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + d := DepUpdate{ + Chart: "your_top_songs_2019", + } + + suite.Require().NoError(d.Prepare(cfg)) +} + +func (suite *DepUpdateTestSuite) TestPrepareDebugFlag() { + defer suite.ctrl.Finish() + + stdout := strings.Builder{} + stderr := strings.Builder{} + cfg := Config{ + Debug: true, + Stdout: &stdout, + Stderr: &stderr, + } + + command = func(path string, args ...string) cmd { + suite.mockCmd.EXPECT(). + String(). + Return(fmt.Sprintf("%s %s", path, strings.Join(args, " "))) + + return suite.mockCmd + } + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + d := DepUpdate{ + Chart: "your_top_songs_2019", + } + + suite.Require().NoError(d.Prepare(cfg)) + + want := fmt.Sprintf("Generated command: '%s --debug dependency update your_top_songs_2019'\n", helmBin) + suite.Equal(want, stderr.String()) + suite.Equal("", stdout.String()) +} + +func (suite *DepUpdateTestSuite) TestPrepareChartRequired() { + d := DepUpdate{} + + suite.mockCmd.EXPECT().Stdout(gomock.Any()).AnyTimes() + suite.mockCmd.EXPECT().Stderr(gomock.Any()).AnyTimes() + + err := d.Prepare(Config{}) + suite.EqualError(err, "chart is required") +} From 89ec9425b0938fd5b49344d245f05e63d84296af Mon Sep 17 00:00:00 2001 From: Erin Call Date: Fri, 27 Dec 2019 15:44:09 -0800 Subject: [PATCH 09/10] Mention the chart param for uninstalls [#25] --- docs/parameter_reference.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/parameter_reference.md b/docs/parameter_reference.md index a5e02d6..0073ddf 100644 --- a/docs/parameter_reference.md +++ b/docs/parameter_reference.md @@ -58,6 +58,7 @@ Uninstallations are triggered when the `helm_command` setting is "uninstall" or | dry_run | boolean | | Pass `--dry-run` to `helm uninstall`. | | timeout | duration | | Timeout for any *individual* Kubernetes operation. The uninstallation's full runtime may exceed this duration. | | skip_tls_verify | boolean | | Connect to the Kubernetes cluster without checking for a valid TLS certificate. Not recommended in production. | +| chart | string | | Required when the global `update_dependencies` parameter is true. No effect otherwise. | ### Where to put settings From d5a59590a129514340007bc06214ad297949e895 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Fri, 27 Dec 2019 16:18:10 -0800 Subject: [PATCH 10/10] Shim bare numbers into duration strings [#39] Helm2's --timeout took a number of seconds, rather than the ParseDuration-compatible string that helm3 uses. For backward- compatibility, update a bare number into a duration string. --- docs/parameter_reference.md | 1 + internal/helm/config.go | 7 +++++++ internal/helm/config_test.go | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/docs/parameter_reference.md b/docs/parameter_reference.md index 07d8107..c11aac5 100644 --- a/docs/parameter_reference.md +++ b/docs/parameter_reference.md @@ -67,6 +67,7 @@ Any setting (with the exception of `prefix`; [see below](#user-content-using-the * Booleans can be yaml's `true` and `false` literals or the strings `"true"` and `"false"`. * Durations are strings formatted with the syntax accepted by [golang's ParseDuration function](https://golang.org/pkg/time/#ParseDuration) (e.g. 5m30s) + * For backward-compatibility with drone-helm, a duration can also be an integer, in which case it will be interpreted to mean seconds. * List\s can be a yaml sequence or a comma-separated string. All of the following are equivalent: diff --git a/internal/helm/config.go b/internal/helm/config.go index 987b6de..cf351ae 100644 --- a/internal/helm/config.go +++ b/internal/helm/config.go @@ -4,8 +4,11 @@ import ( "fmt" "github.com/kelseyhightower/envconfig" "io" + "regexp" ) +var justNumbers = regexp.MustCompile(`^\d+$`) + // The Config struct captures the `settings` and `environment` blocks in the application's drone // config. Configuration in drone's `settings` block arrives as uppercase env vars matching the // config key, prefixed with `PLUGIN_`. Config from the `environment` block is uppercased, but does @@ -62,6 +65,10 @@ func NewConfig(stdout, stderr io.Writer) (*Config, error) { } } + if justNumbers.MatchString(cfg.Timeout) { + cfg.Timeout = fmt.Sprintf("%ss", cfg.Timeout) + } + if cfg.Debug && cfg.Stderr != nil { cfg.logDebug() } diff --git a/internal/helm/config_test.go b/internal/helm/config_test.go index 3ca2f91..f39dd0c 100644 --- a/internal/helm/config_test.go +++ b/internal/helm/config_test.go @@ -106,6 +106,13 @@ func (suite *ConfigTestSuite) TestNewConfigWithConflictingVariables() { suite.Equal("2m30s", cfg.Timeout) } +func (suite *ConfigTestSuite) TestNewConfigInfersNumbersAreSeconds() { + suite.setenv("PLUGIN_TIMEOUT", "42") + cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) + suite.Require().NoError(err) + suite.Equal("42s", cfg.Timeout) +} + func (suite *ConfigTestSuite) TestNewConfigSetsWriters() { stdout := &strings.Builder{} stderr := &strings.Builder{}