From ea7e8b8d5a5086015dfb4d0638cb24448302d700 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Fri, 14 May 2021 07:20:44 +0200 Subject: [PATCH 01/15] chore(CODEOWNERS): add CODEOWNERS file --- CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..ef75b27f --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# Global. + +* @pregnor From 8332a37ef2acce5351cc9cf5afd3995071e9d9ce Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 09:30:39 +0200 Subject: [PATCH 02/15] chore(fork): updated license, removed assets Need to deep fork the repository, because we cannot wait any more for our dynamic bucket region feature PR to be merged. License: MIT -> Apache 2.0 To avoid licensing issues. Removed repository logo asset to avoid infringing intellectual property rights. --- .github/assets/icon_with_name.png | Bin 49863 -> 0 bytes LICENSE | 222 ++++++++++++++++++++++++++--- README.md | 2 - cmd/helms3/delete.go | 14 ++ cmd/helms3/init.go | 14 ++ cmd/helms3/main.go | 14 ++ cmd/helms3/proxy.go | 14 ++ cmd/helms3/push.go | 14 ++ cmd/helms3/reindex.go | 14 ++ internal/awss3/storage.go | 14 ++ internal/awsutil/session.go | 14 ++ internal/awsutil/session_test.go | 14 ++ internal/awsutil/token_provider.go | 14 ++ tests/e2e/delete_test.go | 14 ++ tests/e2e/doc.go | 14 ++ tests/e2e/init_test.go | 14 ++ tests/e2e/main_test.go | 14 ++ tests/e2e/push_test.go | 14 ++ 18 files changed, 411 insertions(+), 23 deletions(-) delete mode 100644 .github/assets/icon_with_name.png diff --git a/.github/assets/icon_with_name.png b/.github/assets/icon_with_name.png deleted file mode 100644 index bc7bf4868d2f4f483437982b9adfd5363b58e674..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49863 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 Uy{N3?$ls&LOTUq-kT4GTe@iQfrT_o{ diff --git a/LICENSE b/LICENSE index 96fb3747..261eeb9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2017 Igor Zibarev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md index 5ee073d2..2c002417 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -

helm-s3 Logo

- [![CircleCI](https://circleci.com/gh/hypnoglow/helm-s3/tree/master.svg?style=shield)](https://circleci.com/gh/hypnoglow/helm-s3/tree/master) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) [![GitHub release](https://img.shields.io/github/release/hypnoglow/helm-s3.svg)](https://github.com/hypnoglow/helm-s3/releases) diff --git a/cmd/helms3/delete.go b/cmd/helms3/delete.go index 5fd8ec79..c92c1d47 100644 --- a/cmd/helms3/delete.go +++ b/cmd/helms3/delete.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 main import ( diff --git a/cmd/helms3/init.go b/cmd/helms3/init.go index 0efd7a82..df36dd79 100644 --- a/cmd/helms3/init.go +++ b/cmd/helms3/init.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 main import ( diff --git a/cmd/helms3/main.go b/cmd/helms3/main.go index b0abd765..9ab0ff1d 100644 --- a/cmd/helms3/main.go +++ b/cmd/helms3/main.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 main import ( diff --git a/cmd/helms3/proxy.go b/cmd/helms3/proxy.go index 86a0b901..5ef53150 100644 --- a/cmd/helms3/proxy.go +++ b/cmd/helms3/proxy.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 main import ( diff --git a/cmd/helms3/push.go b/cmd/helms3/push.go index 0f9b6f4c..900a35a8 100644 --- a/cmd/helms3/push.go +++ b/cmd/helms3/push.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 main import ( diff --git a/cmd/helms3/reindex.go b/cmd/helms3/reindex.go index 0f7dd8b8..a483613f 100644 --- a/cmd/helms3/reindex.go +++ b/cmd/helms3/reindex.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 main import ( diff --git a/internal/awss3/storage.go b/internal/awss3/storage.go index 1ad588a0..1610214f 100644 --- a/internal/awss3/storage.go +++ b/internal/awss3/storage.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 awss3 import ( diff --git a/internal/awsutil/session.go b/internal/awsutil/session.go index 39eeab29..91b498ef 100644 --- a/internal/awsutil/session.go +++ b/internal/awsutil/session.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 awsutil import ( diff --git a/internal/awsutil/session_test.go b/internal/awsutil/session_test.go index c9ef4759..2a2f9998 100644 --- a/internal/awsutil/session_test.go +++ b/internal/awsutil/session_test.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 awsutil import ( diff --git a/internal/awsutil/token_provider.go b/internal/awsutil/token_provider.go index 3efc097d..8983ee0e 100644 --- a/internal/awsutil/token_provider.go +++ b/internal/awsutil/token_provider.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 awsutil import ( diff --git a/tests/e2e/delete_test.go b/tests/e2e/delete_test.go index 40dbe505..c376a429 100644 --- a/tests/e2e/delete_test.go +++ b/tests/e2e/delete_test.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e import ( diff --git a/tests/e2e/doc.go b/tests/e2e/doc.go index dcd58820..4ad08594 100644 --- a/tests/e2e/doc.go +++ b/tests/e2e/doc.go @@ -1,2 +1,16 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e contains end-to-end tests for helm-s3 plugin. package e2e diff --git a/tests/e2e/init_test.go b/tests/e2e/init_test.go index 264dd812..fc943977 100644 --- a/tests/e2e/init_test.go +++ b/tests/e2e/init_test.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e import ( diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go index 62db76d9..856076c3 100644 --- a/tests/e2e/main_test.go +++ b/tests/e2e/main_test.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e import ( diff --git a/tests/e2e/push_test.go b/tests/e2e/push_test.go index c7ef9acf..bb9c5915 100644 --- a/tests/e2e/push_test.go +++ b/tests/e2e/push_test.go @@ -1,3 +1,17 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e import ( From 895b63803eed7effe7a2612f9f758b55c8fa1ffa Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 09:23:31 +0200 Subject: [PATCH 03/15] chore(fork): updated references Updated repository "self" references. --- .circleci/config.yml | 30 +++++++++++++++--------------- .github/CONTRIBUTING.md | 2 +- CHANGELOG.md | 2 +- Dockerfile | 12 ++++++------ README.md | 34 ++++++++++++++++------------------ cmd/helms3/delete.go | 6 +++--- cmd/helms3/init.go | 6 +++--- cmd/helms3/main.go | 2 +- cmd/helms3/proxy.go | 4 ++-- cmd/helms3/push.go | 6 +++--- cmd/helms3/reindex.go | 6 +++--- go.mod | 2 +- hack/install.sh | 6 +++--- internal/awss3/storage.go | 2 +- plugin.yaml | 8 ++++---- tests/e2e/main_test.go | 2 +- 16 files changed, 64 insertions(+), 66 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 87dc8959..44d55e8d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ commands: type: string image_name: type: string - default: hypnoglow/helm-s3 + default: banzaicloud/helm-s3 steps: - run: name: Build & push Docker image @@ -79,7 +79,7 @@ commands: ./cmd/helms3 # Copy plugin directory to outside of the CircleCI workspace. - cp -r /go/src/github.com/hypnoglow/helm-s3 ${IT_TEMP_DIR} + cp -r /go/src/github.com/banzaicloud/helm-s3 ${IT_TEMP_DIR} # Correct the plugin manifest to make installation purely local cd ${IT_TEMP_DIR}/helm-s3 @@ -104,12 +104,12 @@ jobs: - image: circleci/golang:1.15 environment: GO111MODULE: "on" - working_directory: /go/src/github.com/hypnoglow/helm-s3 + working_directory: /go/src/github.com/banzaicloud/helm-s3 steps: - checkout - run: make deps - persist_to_workspace: - root: /go/src/github.com/hypnoglow + root: /go/src/github.com/banzaicloud paths: - helm-s3 test-unit: @@ -118,10 +118,10 @@ jobs: environment: GO111MODULE: "on" GOFLAGS: "-mod=vendor" - working_directory: /go/src/github.com/hypnoglow/helm-s3 + working_directory: /go/src/github.com/banzaicloud/helm-s3 steps: - attach_workspace: - at: /go/src/github.com/hypnoglow + at: /go/src/github.com/banzaicloud - run: ./.circleci/testcover.sh - run: bash <(curl -s https://codecov.io/bash) test-integration-helm-2_17: @@ -141,10 +141,10 @@ jobs: MINIO_ACCESS_KEY: EXAMPLEKEY123 MINIO_SECRET_KEY: EXAMPLESECRET123456 command: ["server", "/data"] - working_directory: /go/src/github.com/hypnoglow/helm-s3 + working_directory: /go/src/github.com/banzaicloud/helm-s3 steps: - attach_workspace: - at: /go/src/github.com/hypnoglow + at: /go/src/github.com/banzaicloud - run_integration_tests: helm_version: 2.17.0 test-integration-helm-3_4: @@ -164,10 +164,10 @@ jobs: MINIO_ACCESS_KEY: EXAMPLEKEY123 MINIO_SECRET_KEY: EXAMPLESECRET123456 command: ["server", "/data"] - working_directory: /go/src/github.com/hypnoglow/helm-s3 + working_directory: /go/src/github.com/banzaicloud/helm-s3 steps: - attach_workspace: - at: /go/src/github.com/hypnoglow + at: /go/src/github.com/banzaicloud - run_integration_tests: helm_version: 3.4.2 test-integration-helm-3_5: @@ -187,10 +187,10 @@ jobs: MINIO_ACCESS_KEY: EXAMPLEKEY123 MINIO_SECRET_KEY: EXAMPLESECRET123456 command: ["server", "/data"] - working_directory: /go/src/github.com/hypnoglow/helm-s3 + working_directory: /go/src/github.com/banzaicloud/helm-s3 steps: - attach_workspace: - at: /go/src/github.com/hypnoglow + at: /go/src/github.com/banzaicloud - run_integration_tests: helm_version: 3.5.2 test-install: @@ -222,17 +222,17 @@ jobs: fi echo "Check installation of version ${version}" - helm plugin install https://github.com/hypnoglow/helm-s3.git --version ${version} + helm plugin install https://github.com/banzaicloud/helm-s3.git --version ${version} release: docker: - image: circleci/golang:1.15 environment: GO111MODULE: "on" GOFLAGS: "-mod=vendor" - working_directory: /go/src/github.com/hypnoglow/helm-s3 + working_directory: /go/src/github.com/banzaicloud/helm-s3 steps: - attach_workspace: - at: /go/src/github.com/hypnoglow + at: /go/src/github.com/banzaicloud - deploy: name: goreleaser command: | diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6bea58c3..27d65992 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -7,7 +7,7 @@ that downloads prebuilt versioned release of the plugin binary and installs it. To disable this behavior, you need to pass `HELM_S3_PLUGIN_NO_INSTALL_HOOK=true` to the installer: - $ HELM_S3_PLUGIN_NO_INSTALL_HOOK=true helm plugin install https://github.com/hypnoglow/helm-s3.git + $ HELM_S3_PLUGIN_NO_INSTALL_HOOK=true helm plugin install https://github.com/banzaicloud/helm-s3.git Development mode: not downloading versioned release. Installed plugin: s3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e72b24a..368dd60d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,7 @@ that Helm v2 is still supported, and will be until the sunset of v2 (approximate - The plugin is now also distributed as Docker images. Images are pushed to Docker Hub tagged with plugin release version and suffixed with Helm version. The image built from master branch is also available, note that it should be only used for playing and testing, it is **strongly discouraged** to use that image for production use cases. -Refer to https://hub.docker.com/r/hypnoglow/helm-s3 for details and all available tags. +Refer to https://ghcr.io/banzaicloud/helm-s3 for details and all available tags. [Refs: [#79](https://github.com/hypnoglow/helm-s3/issues/79) [#88](https://github.com/hypnoglow/helm-s3/pull/88)] ### Changed diff --git a/Dockerfile b/Dockerfile index e6e6761b..ce5f75a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,12 +28,12 @@ ARG BUILD_DATE ARG VCS_REF ARG PLUGIN_VERSION LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="helm-s3" \ - org.label-schema.description="The Helm plugin that provides S3 protocol support and allows to use AWS S3 as a chart repository." \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/hypnoglow/helm-s3" \ - org.label-schema.version=$PLUGIN_VERSION \ - org.label-schema.schema-version="1.0" + org.label-schema.name="helm-s3" \ + org.label-schema.description="The Helm plugin that provides S3 protocol support and allows to use AWS S3 as a chart repository." \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vcs-url="https://github.com/banzaicloud/helm-s3" \ + org.label-schema.version=$PLUGIN_VERSION \ + org.label-schema.schema-version="1.0" COPY --from=build /workspace/helm-s3/plugin.yaml.fixed /root/.helm/cache/plugins/helm-s3/plugin.yaml COPY --from=build /workspace/helm-s3/bin/helms3 /root/.helm/cache/plugins/helm-s3/bin/helms3 diff --git a/README.md b/README.md index 2c002417..457eca1c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -[![CircleCI](https://circleci.com/gh/hypnoglow/helm-s3/tree/master.svg?style=shield)](https://circleci.com/gh/hypnoglow/helm-s3/tree/master) -[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) -[![GitHub release](https://img.shields.io/github/release/hypnoglow/helm-s3.svg)](https://github.com/hypnoglow/helm-s3/releases) +[![License](https://img.shields.io/github/license/banzaicloud/helm-s3.svg)](https://github.com/banzaicloud/helm-s3/tree/master/LICENSE) +[![Release](https://img.shields.io/github/v/release/banzaicloud/helm-s3?sort=semver)](https://github.com/banzaicloud/helm-s3/releases) +[![CI](https://github.com/banzaicloud/helm-s3/actions/workflows/ci.yml/badge.svg)](https://github.com/banzaicloud/helm-s3/actions/workflows/ci.yml) The Helm plugin that provides Amazon S3 protocol support. This allows you to have private or public Helm chart repositories hosted on Amazon S3. See [this guide](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/set-up-a-helm-v3-chart-repository-in-amazon-s3.html) to get a detailed example use case overview. -The plugin supports both Helm v2 and v3 (Helm v3 support is available since [v0.9.0](https://github.com/hypnoglow/helm-s3/releases/tag/v0.9.0)). +The plugin supports both Helm v2 and v3 (Helm v3 support is available since [v0.9.0](https://github.com/banzaicloud/helm-s3/releases/tag/v0.9.0)). ## Table of contents @@ -35,25 +35,23 @@ The plugin supports both Helm v2 and v3 (Helm v3 support is available since [v0. The installation itself is simple as: - $ helm plugin install https://github.com/hypnoglow/helm-s3.git + $ helm plugin install https://github.com/banzaicloud/helm-s3.git You can install a specific release version: - $ helm plugin install https://github.com/hypnoglow/helm-s3.git --version 0.10.0 + $ helm plugin install https://github.com/banzaicloud/helm-s3.git --version 0.10.0 To use the plugin, you do not need any special dependencies. The installer will -download versioned release with prebuilt binary from [github releases](https://github.com/hypnoglow/helm-s3/releases). +download versioned release with prebuilt binary from [github releases](https://github.com/banzaicloud/helm-s3/releases). However, if you want to build the plugin from source, or you want to contribute to the plugin, please see [these instructions](.github/CONTRIBUTING.md). ### Docker Images -[![Docker Pulls](https://img.shields.io/docker/pulls/hypnoglow/helm-s3)](https://hub.docker.com/r/hypnoglow/helm-s3) - -The plugin is also distributed as Docker images. Images are pushed to Docker Hub tagged with plugin release +The plugin is also distributed as Docker images. Images are pushed to Docker Hub tagged with plugin release version and suffixed with Helm version. The image built from master branch is also available, note that it should be -only used for playing and testing, it is **strongly discouraged** to use that image for production use cases. -Refer to https://hub.docker.com/r/hypnoglow/helm-s3 for details and all available tags. +only used for playing and testing, it is **strongly discouraged** to use that image for production use cases. +Refer to https://ghcr.io/banzaicloud/helm-s3 for details and all available tags. ## Configuration @@ -132,12 +130,12 @@ Example: # We have Helm version 3: $ helm version --short v3.0.2+g19e47ee - + # For some reason, the plugin detects Helm version badly: $ helm s3 version --mode helm-s3 plugin version: 0.9.2 Helm version mode: v2 - + # Force the plugin to operate in v3 mode: $ HELM_S3_MODE=3 helm s3 version --mode helm-s3 plugin version: 0.9.2 @@ -145,7 +143,7 @@ Example: ## Usage -*Note: some Helm CLI commands in v3 are incompatible with v2. Example commands below are provided for v2. For commands +*Note: some Helm CLI commands in v3 are incompatible with v2. Example commands below are provided for v2. For commands different in v3 there is a tip 💡 below each example.* For now let's omit the process of uploading repository index and charts to s3 and assume @@ -172,11 +170,11 @@ To install the chart: Fetching also works: $ helm fetch coolchart/epicservice --version "0.5.1" - + Alternatively: $ helm fetch s3://bucket-name/charts/epicservice-0.5.1.tgz - + 💡 *For Helm v3, use `helm pull coolchart/epicservice --version "0.5.1"`* ### Init @@ -280,7 +278,7 @@ and others. To configure the plugin to work alternative S3 backend, just define $ export AWS_ENDPOINT=localhost:9000 $ export AWS_DISABLE_SSL=true -See [these integration tests](https://github.com/hypnoglow/helm-s3/blob/master/hack/test-e2e-local.sh) that use local minio docker container for a complete example. +See [these integration tests](https://github.com/banzaicloud/helm-s3/blob/master/hack/test-e2e-local.sh) that use local minio docker container for a complete example. ### Using S3 bucket ServerSide Encryption diff --git a/cmd/helms3/delete.go b/cmd/helms3/delete.go index c92c1d47..f6948b1b 100644 --- a/cmd/helms3/delete.go +++ b/cmd/helms3/delete.go @@ -19,9 +19,9 @@ import ( "github.com/pkg/errors" - "github.com/hypnoglow/helm-s3/internal/awss3" - "github.com/hypnoglow/helm-s3/internal/awsutil" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/awss3" + "github.com/banzaicloud/helm-s3/internal/awsutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) type deleteAction struct { diff --git a/cmd/helms3/init.go b/cmd/helms3/init.go index df36dd79..a786edff 100644 --- a/cmd/helms3/init.go +++ b/cmd/helms3/init.go @@ -19,9 +19,9 @@ import ( "github.com/pkg/errors" - "github.com/hypnoglow/helm-s3/internal/awss3" - "github.com/hypnoglow/helm-s3/internal/awsutil" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/awss3" + "github.com/banzaicloud/helm-s3/internal/awsutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) type initAction struct { diff --git a/cmd/helms3/main.go b/cmd/helms3/main.go index 9ab0ff1d..aecea3e3 100644 --- a/cmd/helms3/main.go +++ b/cmd/helms3/main.go @@ -23,7 +23,7 @@ import ( "gopkg.in/alecthomas/kingpin.v2" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) var ( diff --git a/cmd/helms3/proxy.go b/cmd/helms3/proxy.go index 5ef53150..d4541b19 100644 --- a/cmd/helms3/proxy.go +++ b/cmd/helms3/proxy.go @@ -21,8 +21,8 @@ import ( "github.com/pkg/errors" - "github.com/hypnoglow/helm-s3/internal/awss3" - "github.com/hypnoglow/helm-s3/internal/awsutil" + "github.com/banzaicloud/helm-s3/internal/awss3" + "github.com/banzaicloud/helm-s3/internal/awsutil" ) type proxyCmd struct { diff --git a/cmd/helms3/push.go b/cmd/helms3/push.go index 900a35a8..fab3faac 100644 --- a/cmd/helms3/push.go +++ b/cmd/helms3/push.go @@ -21,9 +21,9 @@ import ( "github.com/pkg/errors" - "github.com/hypnoglow/helm-s3/internal/awss3" - "github.com/hypnoglow/helm-s3/internal/awsutil" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/awss3" + "github.com/banzaicloud/helm-s3/internal/awsutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) var ( diff --git a/cmd/helms3/reindex.go b/cmd/helms3/reindex.go index a483613f..cdddd4f8 100644 --- a/cmd/helms3/reindex.go +++ b/cmd/helms3/reindex.go @@ -20,9 +20,9 @@ import ( "github.com/pkg/errors" - "github.com/hypnoglow/helm-s3/internal/awss3" - "github.com/hypnoglow/helm-s3/internal/awsutil" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/awss3" + "github.com/banzaicloud/helm-s3/internal/awsutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) type reindexAction struct { diff --git a/go.mod b/go.mod index 1c75726a..30917765 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/hypnoglow/helm-s3 +module github.com/banzaicloud/helm-s3 go 1.15 diff --git a/hack/install.sh b/hack/install.sh index 0151e175..34368dbb 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -28,16 +28,16 @@ echo "Downloading and installing helm-s3 v${version} ..." binary_url="" if [ "$(uname)" == "Darwin" ]; then - binary_url="https://github.com/hypnoglow/helm-s3/releases/download/v${version}/helm-s3_${version}_darwin_amd64.tar.gz" + binary_url="https://github.com/banzaicloud/helm-s3/releases/download/v${version}/helm-s3_${version}_darwin_amd64.tar.gz" elif [ "$(uname)" == "Linux" ] ; then - binary_url="https://github.com/hypnoglow/helm-s3/releases/download/v${version}/helm-s3_${version}_linux_amd64.tar.gz" + binary_url="https://github.com/banzaicloud/helm-s3/releases/download/v${version}/helm-s3_${version}_linux_amd64.tar.gz" fi if [ -z "${binary_url}" ]; then echo "Unsupported OS type" exit 1 fi -checksum_url="https://github.com/hypnoglow/helm-s3/releases/download/v${version}/helm-s3_${version}_checksums.txt" +checksum_url="https://github.com/banzaicloud/helm-s3/releases/download/v${version}/helm-s3_${version}_checksums.txt" mkdir -p "bin" mkdir -p "releases/v${version}" diff --git a/internal/awss3/storage.go b/internal/awss3/storage.go index 1610214f..ec09bcbe 100644 --- a/internal/awss3/storage.go +++ b/internal/awss3/storage.go @@ -30,7 +30,7 @@ import ( "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/pkg/errors" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) const ( diff --git a/plugin.yaml b/plugin.yaml index 68ddc2aa..045adea9 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -3,12 +3,12 @@ version: "0.10.0" usage: "Manage chart repositories on Amazon S3" description: |- The plugin allows to use s3 protocol to upload, fetch charts and to work with repositories. - https://github.com/hypnoglow/helm-s3 + https://github.com/banzaicloud/helm-s3 command: "$HELM_PLUGIN_DIR/bin/helms3" downloaders: -- command: "bin/helms3" - protocols: - - "s3" + - command: "bin/helms3" + protocols: + - "s3" hooks: install: "cd $HELM_PLUGIN_DIR; ./hack/install.sh" update: "cd $HELM_PLUGIN_DIR; ./hack/install.sh" diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go index 856076c3..4a54db88 100644 --- a/tests/e2e/main_test.go +++ b/tests/e2e/main_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/hypnoglow/helm-s3/internal/helmutil" + "github.com/banzaicloud/helm-s3/internal/helmutil" ) var mc *minio.Client From c7eb73f16456b67e6b260c9a94e2e9318cf0ef20 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Wed, 5 May 2021 11:35:42 +0200 Subject: [PATCH 04/15] style: reformatted files Run Codium's Rewrap extension to format the files. https://github.com/stkb/Rewrap --- .github/CONTRIBUTING.md | 12 +-- CHANGELOG.md | 145 ++++++++++++++++++------------ Makefile | 12 +-- README.md | 192 +++++++++++++++++++++++----------------- docs/best-practice.md | 58 ++++++------ docs/usage-cost.md | 17 ++-- plugin.yaml | 17 ++-- 7 files changed, 261 insertions(+), 192 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 27d65992..2b8254fa 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,17 +2,17 @@ ## Development -On regular plugin installation, helm triggers post-install hook -that downloads prebuilt versioned release of the plugin binary and installs it. -To disable this behavior, you need to pass `HELM_S3_PLUGIN_NO_INSTALL_HOOK=true` -to the installer: +On regular plugin installation, helm triggers post-install hook that downloads +prebuilt versioned release of the plugin binary and installs it. To disable this +behavior, you need to pass `HELM_S3_PLUGIN_NO_INSTALL_HOOK=true` to the +installer: $ HELM_S3_PLUGIN_NO_INSTALL_HOOK=true helm plugin install https://github.com/banzaicloud/helm-s3.git Development mode: not downloading versioned release. Installed plugin: s3 -Next, you may want to ensure if you have all prerequisites to build -the plugin from source: +Next, you may want to ensure if you have all prerequisites to build the plugin +from source: cd ~/.helm/plugins/helm-s3 make deps build-local diff --git a/CHANGELOG.md b/CHANGELOG.md index 368dd60d..70c17679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +and this project adheres to [Semantic +Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] @@ -13,117 +14,143 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Added support for `HELM_S3_REGION` environment variable to override AWS region for bucket location. -[Refs: [#51](https://github.com/hypnoglow/helm-s3/issues/51) [#117](https://github.com/hypnoglow/helm-s3/pull/117)] +- Added support for `HELM_S3_REGION` environment variable to override AWS region + for bucket location. [Refs: + [#51](https://github.com/hypnoglow/helm-s3/issues/51) + [#117](https://github.com/hypnoglow/helm-s3/pull/117)] -- Added support for relative URLs in repository index: charts can be pushed with `--relative` flag. -[Refs: [#121](https://github.com/hypnoglow/helm-s3/pull/121) [#122](https://github.com/hypnoglow/helm-s3/pull/122)] +- Added support for relative URLs in repository index: charts can be pushed with + `--relative` flag. [Refs: + [#121](https://github.com/hypnoglow/helm-s3/pull/121) + [#122](https://github.com/hypnoglow/helm-s3/pull/122)] ### Changed - Update Helm versions the plugin is tested against: v2.16, v2.17, v3.3, v3.4. -[Refs: [#125](https://github.com/hypnoglow/helm-s3/pull/125)] + [Refs: [#125](https://github.com/hypnoglow/helm-s3/pull/125)] ### Fixed -- Fixed issues when pushing large charts. -[Refs: [#112](https://github.com/hypnoglow/helm-s3/issues/112) [#120](https://github.com/hypnoglow/helm-s3/issues/120) [#124](https://github.com/hypnoglow/helm-s3/pull/124)] +- Fixed issues when pushing large charts. [Refs: + [#112](https://github.com/hypnoglow/helm-s3/issues/112) + [#120](https://github.com/hypnoglow/helm-s3/issues/120) + [#124](https://github.com/hypnoglow/helm-s3/pull/124)] ## [0.9.2] - 2020-01-23 ### Changed - Updated AWS SDK to v1.25.50, allowing to use IAM roles for service accounts. -[Refs: [#109](https://github.com/hypnoglow/helm-s3/issues/109) [#110](https://github.com/hypnoglow/helm-s3/pull/110)] + [Refs: [#109](https://github.com/hypnoglow/helm-s3/issues/109) + [#110](https://github.com/hypnoglow/helm-s3/pull/110)] ## [0.9.1] - 2020-01-15 ### Added -- `helm version` now has optional flag `--mode` that additionally prints the mode (Helm version) in which the plugin operates, -either v2 or v3. -- Added `HELM_S3_MODE` that can be used to forcefully change the mode (Helm version), in case when the plugin does not detect Helm version properly. +- `helm version` now has optional flag `--mode` that additionally prints the + mode (Helm version) in which the plugin operates, either v2 or v3. +- Added `HELM_S3_MODE` that can be used to forcefully change the mode (Helm + version), in case when the plugin does not detect Helm version properly. ### Changed -- Changed the way the plugin detects Helm version. Now it parses `helm version` output instead of checking `helm env` -command existence. +- Changed the way the plugin detects Helm version. Now it parses `helm version` + output instead of checking `helm env` command existence. ## [0.9.0] - 2019-12-27 ### Added -- Helm v3 support. The plugin can detect Helm version and use the corresponding "mode" to operate properly. This means -that Helm v2 is still supported, and will be until the sunset of v2 (approximately until the summer of 2020). -[Refs: [#95](https://github.com/hypnoglow/helm-s3/pull/95) [#98](https://github.com/hypnoglow/helm-s3/pull/98)] - -- The plugin is now also distributed as Docker images. Images are pushed to Docker Hub tagged with plugin release -version and suffixed with Helm version. The image built from master branch is also available, note that it should be -only used for playing and testing, it is **strongly discouraged** to use that image for production use cases. -Refer to https://ghcr.io/banzaicloud/helm-s3 for details and all available tags. -[Refs: [#79](https://github.com/hypnoglow/helm-s3/issues/79) [#88](https://github.com/hypnoglow/helm-s3/pull/88)] +- Helm v3 support. The plugin can detect Helm version and use the corresponding + "mode" to operate properly. This means that Helm v2 is still supported, and + will be until the sunset of v2 (approximately until the summer of 2020). + [Refs: [#95](https://github.com/hypnoglow/helm-s3/pull/95) + [#98](https://github.com/hypnoglow/helm-s3/pull/98)] + +- The plugin is now also distributed as Docker images. Images are pushed to + Docker Hub tagged with plugin release version and suffixed with Helm version. + The image built from master branch is also available, note that it should be + only used for playing and testing, it is **strongly discouraged** to use that + image for production use cases. Refer to https://ghcr.io/banzaicloud/helm-s3 + for details and all available tags. [Refs: + [#79](https://github.com/hypnoglow/helm-s3/issues/79) + [#88](https://github.com/hypnoglow/helm-s3/pull/88)] ### Changed -- Migrate to go modules & update Go to 1.12. -[Refs: [#86](https://github.com/hypnoglow/helm-s3/pull/86)] [@moeryomenko](https://github.com/moeryomenko) +- Migrate to go modules & update Go to 1.12. [Refs: + [#86](https://github.com/hypnoglow/helm-s3/pull/86)] + [@moeryomenko](https://github.com/moeryomenko) -- CI now runs tests on multiple Helm versions: v2.14, v2.15, v2.16, v3.0. -[Refs: [#89](https://github.com/hypnoglow/helm-s3/pull/89) [#97](https://github.com/hypnoglow/helm-s3/pull/97)] +- CI now runs tests on multiple Helm versions: v2.14, v2.15, v2.16, v3.0. [Refs: + [#89](https://github.com/hypnoglow/helm-s3/pull/89) + [#97](https://github.com/hypnoglow/helm-s3/pull/97)] -- Huge rework on internal Helm integration code to provide support for both Helm v2 and v3. -[Refs: [#95](https://github.com/hypnoglow/helm-s3/pull/95) [#98](https://github.com/hypnoglow/helm-s3/pull/98)] +- Huge rework on internal Helm integration code to provide support for both Helm + v2 and v3. [Refs: [#95](https://github.com/hypnoglow/helm-s3/pull/95) + [#98](https://github.com/hypnoglow/helm-s3/pull/98)] -- Bumped almost all dependencies to more actual versions. Helm SDK now includes both v2.16.1 and v3.0.0. -[Refs: [#74](https://github.com/hypnoglow/helm-s3/pull/74) [#69](https://github.com/hypnoglow/helm-s3/issues/69) [#87](https://github.com/hypnoglow/helm-s3/pull/87)] [@willejs](https://github.com/willejs) +- Bumped almost all dependencies to more actual versions. Helm SDK now includes + both v2.16.1 and v3.0.0. [Refs: + [#74](https://github.com/hypnoglow/helm-s3/pull/74) + [#69](https://github.com/hypnoglow/helm-s3/issues/69) + [#87](https://github.com/hypnoglow/helm-s3/pull/87)] + [@willejs](https://github.com/willejs) ### Fixed -- Fixed incorrect s3 url when "proxy" runs on uninitialized repository. -[Refs: [#77](https://github.com/hypnoglow/helm-s3/issues/77) [#78](https://github.com/hypnoglow/helm-s3/pull/78)] [@horacimacias](https://github.com/horacimacias) +- Fixed incorrect s3 url when "proxy" runs on uninitialized repository. [Refs: + [#77](https://github.com/hypnoglow/helm-s3/issues/77) + [#78](https://github.com/hypnoglow/helm-s3/pull/78)] + [@horacimacias](https://github.com/horacimacias) ## [0.8.0] ### Added -- Added possibility to enable S3 serverside encryption. -[Refs: [#52](https://github.com/hypnoglow/helm-s3/pull/52)] @nexusix +- Added possibility to enable S3 serverside encryption. [Refs: + [#52](https://github.com/hypnoglow/helm-s3/pull/52)] @nexusix -- Added possibility to specify Content-Type for uploaded charts. -[Refs: [#59](https://github.com/hypnoglow/helm-s3/issues/59) [#60](https://github.com/hypnoglow/helm-s3/pull/60)] @bashims +- Added possibility to specify Content-Type for uploaded charts. [Refs: + [#59](https://github.com/hypnoglow/helm-s3/issues/59) + [#60](https://github.com/hypnoglow/helm-s3/pull/60)] @bashims -- Added checksum verification on plugin installation. -[Refs: [#63](https://github.com/hypnoglow/helm-s3/pull/63)] +- Added checksum verification on plugin installation. [Refs: + [#63](https://github.com/hypnoglow/helm-s3/pull/63)] ### Changed -- On `helm s3 reindex`, only `*.tgz` files in the bucket directory are taken into -account, everything else is ignored. -[Refs: [#57](https://github.com/hypnoglow/helm-s3/issues/57) [#58](https://github.com/hypnoglow/helm-s3/pull/58)] @kylehodgetts +- On `helm s3 reindex`, only `*.tgz` files in the bucket directory are taken + into account, everything else is ignored. [Refs: + [#57](https://github.com/hypnoglow/helm-s3/issues/57) + [#58](https://github.com/hypnoglow/helm-s3/pull/58)] @kylehodgetts -- Default Content-Type for uploaded charts is set to `application/gzip`. -[Refs: [#59](https://github.com/hypnoglow/helm-s3/issues/59) [#60](https://github.com/hypnoglow/helm-s3/pull/60)] @bashims +- Default Content-Type for uploaded charts is set to `application/gzip`. [Refs: + [#59](https://github.com/hypnoglow/helm-s3/issues/59) + [#60](https://github.com/hypnoglow/helm-s3/pull/60)] @bashims -- `make` is no longer required to install the plugin. -[Refs: [#62](https://github.com/hypnoglow/helm-s3/issues/62) [#64](https://github.com/hypnoglow/helm-s3/pull/64)] @willhayslett +- `make` is no longer required to install the plugin. [Refs: + [#62](https://github.com/hypnoglow/helm-s3/issues/62) + [#64](https://github.com/hypnoglow/helm-s3/pull/64)] @willhayslett ## [0.7.0] ### Added -- Added global `--acl` flag to address issues for setups with multiple Amazon -accounts. Thanks to [@razaj92](https://github.com/razaj92) for the Pull Request! -[Ref: [#37](https://github.com/hypnoglow/helm-s3/issues/37)] -- Added `--dry-run` flag to `helm s3 push` command. It simulates a push, but doesn't -actually touch anything. This option is useful, for example, to indicate if -a chart upload would fail due to the version not being changed. -[Ref: [#44](https://github.com/hypnoglow/helm-s3/issues/44)] -- Added `--ignore-if-exists` flag to `helm s3 push` command. It allows to exit -normally without triggering an error if the pushed chart already exists. A clean -exit code may be useful to avoid some error management in the CI/CD. -[Ref: [#41](https://github.com/hypnoglow/helm-s3/issues/41)] +- Added global `--acl` flag to address issues for setups with multiple Amazon + accounts. Thanks to [@razaj92](https://github.com/razaj92) for the Pull + Request! [Ref: [#37](https://github.com/hypnoglow/helm-s3/issues/37)] +- Added `--dry-run` flag to `helm s3 push` command. It simulates a push, but + doesn't actually touch anything. This option is useful, for example, to + indicate if a chart upload would fail due to the version not being changed. + [Ref: [#44](https://github.com/hypnoglow/helm-s3/issues/44)] +- Added `--ignore-if-exists` flag to `helm s3 push` command. It allows to exit + normally without triggering an error if the pushed chart already exists. A + clean exit code may be useful to avoid some error management in the CI/CD. + [Ref: [#41](https://github.com/hypnoglow/helm-s3/issues/41)] ### Changed -- Moved `helm s3 reindex` command out of beta, as it seems there are no more -issues related to it. +- Moved `helm s3 reindex` command out of beta, as it seems there are no more + issues related to it. diff --git a/Makefile b/Makefile index 7d681412..2aec2a36 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,13 @@ all: deps build .PHONY: deps deps: - @go mod download - @go mod vendor - @go mod tidy + @ go mod download + @ go mod vendor + @ go mod tidy .PHONY: build build: - @./hack/build.sh $(CURDIR) $(PKG) + @ ./hack/build.sh $(CURDIR) $(PKG) .PHONY: build-local build-local: @@ -22,7 +22,7 @@ build-local: .PHONY: install install: - @./hack/install.sh + @ ./hack/install.sh .PHONY: test-unit test-unit: @@ -34,4 +34,4 @@ test-e2e: .PHONY: test-e2e-local test-e2e-local: - @./hack/test-e2e-local.sh + @ ./hack/test-e2e-local.sh diff --git a/README.md b/README.md index 457eca1c..a97601a9 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,38 @@ The Helm plugin that provides Amazon S3 protocol support. -This allows you to have private or public Helm chart repositories hosted on Amazon S3. See [this guide](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/set-up-a-helm-v3-chart-repository-in-amazon-s3.html) to get a detailed example use case overview. +This allows you to have private or public Helm chart repositories hosted on +Amazon S3. See [this +guide](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/set-up-a-helm-v3-chart-repository-in-amazon-s3.html) +to get a detailed example use case overview. -The plugin supports both Helm v2 and v3 (Helm v3 support is available since [v0.9.0](https://github.com/banzaicloud/helm-s3/releases/tag/v0.9.0)). +The plugin supports both Helm v2 and v3 (Helm v3 support is available since +[v0.9.0](https://github.com/banzaicloud/helm-s3/releases/tag/v0.9.0)). ## Table of contents - * [Install](#install) - * [Docker Images](#docker-images) - * [Configuration](#configuration) - * [AWS Access](#aws-access) - * [Helm version mode](#helm-version-mode) - * [Usage](#usage) - * [Init](#init) - * [Push](#push) - * [Delete](#delete) - * [Reindex](#reindex) - * [Uninstall](#uninstall) - * [Advanced Features](#advanced-features) - * [ACLs](#acls) - * [Using alternative S3-compatible vendors](#using-alternative-s3-compatible-vendors) - * [Using S3 bucket ServerSide Encryption](#using-s3-bucket-serverside-encryption) - * [S3 bucket location](#s3-bucket-location) - * [Additional Documentation](#additional-documentation) - * [Community and Related Projects](#community-and-related-projects) - * [Contributing](#contributing) - * [License](#license) +* [Install](#install) + * [Docker Images](#docker-images) +* [Configuration](#configuration) + * [AWS Access](#aws-access) + * [Helm version mode](#helm-version-mode) +* [Usage](#usage) + * [Init](#init) + * [Push](#push) + * [Delete](#delete) + * [Reindex](#reindex) +* [Uninstall](#uninstall) +* [Advanced Features](#advanced-features) + * [ACLs](#acls) + * [Using alternative S3-compatible + vendors](#using-alternative-s3-compatible-vendors) + * [Using S3 bucket ServerSide + Encryption](#using-s3-bucket-serverside-encryption) + * [S3 bucket location](#s3-bucket-location) +* [Additional Documentation](#additional-documentation) +* [Community and Related Projects](#community-and-related-projects) +* [Contributing](#contributing) +* [License](#license) ## Install @@ -42,31 +48,38 @@ You can install a specific release version: $ helm plugin install https://github.com/banzaicloud/helm-s3.git --version 0.10.0 To use the plugin, you do not need any special dependencies. The installer will -download versioned release with prebuilt binary from [github releases](https://github.com/banzaicloud/helm-s3/releases). -However, if you want to build the plugin from source, or you want to contribute -to the plugin, please see [these instructions](.github/CONTRIBUTING.md). +download versioned release with prebuilt binary from [github +releases](https://github.com/banzaicloud/helm-s3/releases). However, if you want +to build the plugin from source, or you want to contribute to the plugin, please +see [these instructions](.github/CONTRIBUTING.md). ### Docker Images -The plugin is also distributed as Docker images. Images are pushed to Docker Hub tagged with plugin release -version and suffixed with Helm version. The image built from master branch is also available, note that it should be -only used for playing and testing, it is **strongly discouraged** to use that image for production use cases. -Refer to https://ghcr.io/banzaicloud/helm-s3 for details and all available tags. +The plugin is also distributed as Docker images. Images are pushed to Docker Hub +tagged with plugin release version and suffixed with Helm version. The image +built from master branch is also available, note that it should be only used for +playing and testing, it is **strongly discouraged** to use that image for +production use cases. Refer to https://ghcr.io/banzaicloud/helm-s3 for details +and all available tags. ## Configuration ### AWS Access -To publish charts to buckets and to fetch from private buckets, you need to provide valid AWS credentials. -You can do this in [the same manner](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) as for `AWS CLI` tool. +To publish charts to buckets and to fetch from private buckets, you need to +provide valid AWS credentials. You can do this in [the same +manner](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) +as for `AWS CLI` tool. So, if you want to use the plugin and you are already using `AWS CLI` - you are -good to go, no additional configuration required. Otherwise, follow [the official guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) +good to go, no additional configuration required. Otherwise, follow [the +official +guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) to set up credentials. -To minimize security issues, remember to configure your IAM user policies properly. -As an example, a setup can provide only read access for users, and write access -for a CI that builds and pushes charts to your repository. +To minimize security issues, remember to configure your IAM user policies +properly. As an example, a setup can provide only read access for users, and +write access for a CI that builds and pushes charts to your repository. **Example Read Only IAM policy** @@ -122,8 +135,9 @@ for a CI that builds and pushes charts to your repository. ### Helm version mode -The plugin is able to detect if you are using Helm v2 or v3 automatically. If, for some reason, the plugin does not -detect Helm version properly, you can set `HELM_S3_MODE` environment variable to value `2` or `3` to force the mode. +The plugin is able to detect if you are using Helm v2 or v3 automatically. If, +for some reason, the plugin does not detect Helm version properly, you can set +`HELM_S3_MODE` environment variable to value `2` or `3` to force the mode. Example: @@ -143,23 +157,24 @@ Example: ## Usage -*Note: some Helm CLI commands in v3 are incompatible with v2. Example commands below are provided for v2. For commands -different in v3 there is a tip 💡 below each example.* +*Note: some Helm CLI commands in v3 are incompatible with v2. Example commands +below are provided for v2. For commands different in v3 there is a tip 💡 below +each example.* -For now let's omit the process of uploading repository index and charts to s3 and assume -you already have your repository `index.yaml` file on s3 under path `s3://bucket-name/charts/index.yaml` -and a chart archive `epicservice-0.5.1.tgz` under path `s3://bucket-name/charts/epicservice-0.5.1.tgz`. +For now let's omit the process of uploading repository index and charts to s3 +and assume you already have your repository `index.yaml` file on s3 under path +`s3://bucket-name/charts/index.yaml` and a chart archive `epicservice-0.5.1.tgz` +under path `s3://bucket-name/charts/epicservice-0.5.1.tgz`. Add your repository: $ helm repo add coolcharts s3://bucket-name/charts -Now you can use it as any other Helm chart repository. -Try: +Now you can use it as any other Helm chart repository. Try: $ helm search coolcharts - NAME VERSION DESCRIPTION - coolcharts/epicservice 0.5.1 A Helm chart. + NAME VERSION DESCRIPTION + coolcharts/epicservice 0.5.1 A Helm chart. 💡 *For Helm v3, use `helm search repo coolcharts`* @@ -186,7 +201,8 @@ To create a new repository, use **init**: This command generates an empty **index.yaml** and uploads it to the S3 bucket under `/charts` key. -To work with this repo by it's name, first you need to add it using native helm command: +To work with this repo by it's name, first you need to add it using native helm +command: $ helm repo add mynewrepo s3://bucket-name/charts @@ -196,28 +212,29 @@ Now you can push your chart to this repo: $ helm s3 push ./epicservice-0.7.2.tgz mynewrepo -When the bucket is replicated you should make the index's URLs relative so that the charts can be accessed from a replica bucket. +When the bucket is replicated you should make the index's URLs relative so that +the charts can be accessed from a replica bucket. $ helm s3 push --relative ./epicservice-0.7.2.tgz mynewrepo -On push, both remote and local repo indexes are automatically updated (that means -you don't need to run `helm repo update`). +On push, both remote and local repo indexes are automatically updated (that +means you don't need to run `helm repo update`). Your pushed chart is available: $ helm search mynewrepo - NAME VERSION DESCRIPTION + NAME VERSION DESCRIPTION mynewrepo/epicservice 0.7.2 A Helm chart. 💡 *For Helm v3, use `helm search repo mynewrepo`* -Note that the plugin denies push when the chart with the same version already exists -in the repository. This behavior is intentional. It is useful, for example, in -CI automated pushing: if someone forgets to bump chart version - the chart would -not be overwritten. +Note that the plugin denies push when the chart with the same version already +exists in the repository. This behavior is intentional. It is useful, for +example, in CI automated pushing: if someone forgets to bump chart version - the +chart would not be overwritten. -However, in some cases you want to replace existing chart version. To do so, -add `--force` flag to a push command: +However, in some cases you want to replace existing chart version. To do so, add +`--force` flag to a push command: $ helm s3 push --force ./epicservice-0.7.2.tgz mynewrepo @@ -242,12 +259,13 @@ The chart is deleted from the repo: ### Reindex -If your repository somehow became inconsistent or broken, you can use reindex to recreate -the index in accordance with the charts in the repository. +If your repository somehow became inconsistent or broken, you can use reindex to +recreate the index in accordance with the charts in the repository. $ helm s3 reindex mynewrepo -When the bucket is replicated you should make the index's URLs relative so that the charts can be accessed from a replica bucket. +When the bucket is replicated you should make the index's URLs relative so that +the charts can be accessed from a replica bucket. $ helm s3 reindex --relative mynewrepo @@ -259,10 +277,11 @@ When the bucket is replicated you should make the index's URLs relative so that ### ACLs -In use cases where you share a repo across multiple AWS accounts, -you may want the ability to define object ACLS to allow charts to persist there -permissions across accounts. -To do so, add the flag `--acl="ACL_POLICY"`. The list of ACLs can be [found here](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl): +In use cases where you share a repo across multiple AWS accounts, you may want +the ability to define object ACLS to allow charts to persist there permissions +across accounts. To do so, add the flag `--acl="ACL_POLICY"`. The list of ACLs +can be [found +here](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl): $ helm s3 push --acl="bucket-owner-full-control" ./epicservice-0.7.2.tgz mynewrepo @@ -270,44 +289,57 @@ You can also set the default ACL be setting the `S3_ACL` environment variable. ### Using alternative S3-compatible vendors -The plugin assumes Amazon S3 by default. However, it can work with any S3-compatible -object storage, like [minio](https://www.minio.io/), [DreamObjects](https://www.dreamhost.com/cloud/storage/) -and others. To configure the plugin to work alternative S3 backend, just define -`AWS_ENDPOINT` (and optionally `AWS_DISABLE_SSL`): +The plugin assumes Amazon S3 by default. However, it can work with any +S3-compatible object storage, like [minio](https://www.minio.io/), +[DreamObjects](https://www.dreamhost.com/cloud/storage/) and others. To +configure the plugin to work alternative S3 backend, just define `AWS_ENDPOINT` +(and optionally `AWS_DISABLE_SSL`): $ export AWS_ENDPOINT=localhost:9000 $ export AWS_DISABLE_SSL=true -See [these integration tests](https://github.com/banzaicloud/helm-s3/blob/master/hack/test-e2e-local.sh) that use local minio docker container for a complete example. +See [these integration +tests](https://github.com/banzaicloud/helm-s3/blob/master/hack/test-e2e-local.sh) +that use local minio docker container for a complete example. ### Using S3 bucket ServerSide Encryption -To enable S3 SSE export environment variable `AWS_S3_SSE` and set it to desired type for example `AES256`. +To enable S3 SSE export environment variable `AWS_S3_SSE` and set it to desired +type for example `AES256`. ### S3 bucket location -The plugin will look for the bucket in the region inferred by the environment. If the bucket is in another region, the commands will fail. +The plugin will look for the bucket in the region inferred by the environment. +If the bucket is in another region, the commands will fail. -This can be controlled by exporting one of `HELM_S3_REGION`, `AWS_REGION` or `AWS_DEFAULT_REGION`, in order of precedence. +This can be controlled by exporting one of `HELM_S3_REGION`, `AWS_REGION` or +`AWS_DEFAULT_REGION`, in order of precedence. ## Additional Documentation -Additional documentation is available in the [docs](docs) directory. This currently includes: +Additional documentation is available in the [docs](docs) directory. This +currently includes: - estimated [usage cost calculation](docs/usage-cost.md) - [best practices](docs/best-practice.md) for organizing your repositories. ## Community and Related Projects -- [Helm | Related Projects and Documentation](https://helm.sh/docs/community/related/) -- [Set up a Helm v3 chart repository in Amazon S3 - AWS Prescriptive Guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/set-up-a-helm-v3-chart-repository-in-amazon-s3.html) -- [Deploy Kubernetes resources and packages using Amazon EKS and a Helm chart repository in Amazon S3 - AWS Prescriptive Guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/deploy-kubernetes-resources-and-packages-using-amazon-eks-and-a-helm-chart-repository-in-amazon-s3.html) -- [Chart sources - Flux Helm Operator](https://docs.fluxcd.io/projects/helm-operator/en/stable/helmrelease-guide/chart-sources/#extending-the-supported-helm-repository-protocols) -- [How to create a Helm chart repository using Amazon S3](https://andrewlock.net/how-to-create-a-helm-chart-repository-using-amazon-s3/) +- [Helm | Related Projects and + Documentation](https://helm.sh/docs/community/related/) +- [Set up a Helm v3 chart repository in Amazon S3 - AWS Prescriptive + Guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/set-up-a-helm-v3-chart-repository-in-amazon-s3.html) +- [Deploy Kubernetes resources and packages using Amazon EKS and a Helm chart + repository in Amazon S3 - AWS Prescriptive + Guidance](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/deploy-kubernetes-resources-and-packages-using-amazon-eks-and-a-helm-chart-repository-in-amazon-s3.html) +- [Chart sources - Flux Helm + Operator](https://docs.fluxcd.io/projects/helm-operator/en/stable/helmrelease-guide/chart-sources/#extending-the-supported-helm-repository-protocols) +- [How to create a Helm chart repository using Amazon + S3](https://andrewlock.net/how-to-create-a-helm-chart-repository-using-amazon-s3/) ## Contributing -Contributions are welcome. Please see [these instructions](.github/CONTRIBUTING.md) -that will help you to develop the plugin. +Contributions are welcome. Please see [these +instructions](.github/CONTRIBUTING.md) that will help you to develop the plugin. ## License diff --git a/docs/best-practice.md b/docs/best-practice.md index 0dfd0201..b143507f 100644 --- a/docs/best-practice.md +++ b/docs/best-practice.md @@ -2,40 +2,46 @@ ## Reindexing your repository -In short, due to limitations of AWS your chart repository index can be broken -by accident. This means that it may not reflect the "real" state of your chart +In short, due to limitations of AWS your chart repository index can be broken by +accident. This means that it may not reflect the "real" state of your chart files in S3 bucket. Nothing serious, but can be annoying. -To workaround this, the `helm s3 reindex ` command is available. *Note: this -operation is is [much more expensive](usage-cost.md#reindex) than other in this plugin*. +To workaround this, the `helm s3 reindex ` command is available. *Note: +this operation is is [much more expensive](usage-cost.md#reindex) than other in +this plugin*. ## Organizing your repositories -A chart repository file structure is always flat. -It cannot contain nested directories. +A chart repository file structure is always flat. It cannot contain nested +directories. -The number of AWS S3 requests for reindex operation depends on your repository structure. -Due to limitations of AWS S3 API you cannot list objects of the folder under the key - excluding subfolders. `ListObjects` only can lists objects under the key recursively. - -The plugin code makes its best to ignore subfolders, because chart repository is always flat. -But still, not all cases are covered. +The number of AWS S3 requests for reindex operation depends on your repository +structure. Due to limitations of AWS S3 API you cannot list objects of the +folder under the key excluding subfolders. `ListObjects` only can lists objects +under the key recursively. -Imagine the worst case scenario: you have 100 chart files in your repository, which is the -bucket root. And 1 million files in the "foo-bar" subfolder, which are not related to -the chart repository. In this case the plugin **have to** call `ListObjects` -about 1000 times (1000 objects per call) to make sure it did not miss any chart file. +The plugin code makes its best to ignore subfolders, because chart repository is +always flat. But still, not all cases are covered. -By that, the golden rule is to **never have subfolders in your chart repository folder**. +Imagine the worst case scenario: you have 100 chart files in your repository, +which is the bucket root. And 1 million files in the "foo-bar" subfolder, which +are not related to the chart repository. In this case the plugin **have to** +call `ListObjects` about 1000 times (1000 objects per call) to make sure it did +not miss any chart file. -So, there are two good options for your chart repository file structure inside S3 bucket: +By that, the golden rule is to **never have subfolders in your chart repository +folder**. + +So, there are two good options for your chart repository file structure inside +S3 bucket: 1. One bucket - one repository. Create a bucket "yourcompany-charts-stable", or -"yourcompany-productname-charts" and use the bucket root as your chart repository. -In this case, never put any other files in that bucket. - -2. One bucket - many repositories, each in separate subfolder. Create a bucket -"yourcompany-charts". Create a subfolder in it for each repository you need, for -example "stable" and "testing". Another option is to separate the repositories -by the product or by group of services, for example "backoffice", "order-processing", etc. -And again, never put any other files in the repository folder. \ No newline at end of file + "yourcompany-productname-charts" and use the bucket root as your chart + repository. In this case, never put any other files in that bucket. + +2. One bucket - many repositories, each in separate subfolder. Create a bucket + "yourcompany-charts". Create a subfolder in it for each repository you need, + for example "stable" and "testing". Another option is to separate the + repositories by the product or by group of services, for example + "backoffice", "order-processing", etc. And again, never put any other files + in the repository folder. \ No newline at end of file diff --git a/docs/usage-cost.md b/docs/usage-cost.md index ca1b2fc0..aae3f6b2 100644 --- a/docs/usage-cost.md +++ b/docs/usage-cost.md @@ -1,19 +1,22 @@ # Usage pricing -I hope this document helps you to calculate the AWS S3 usage cost for your use case. +I hope this document helps you to calculate the AWS S3 usage cost for your use +case. Disclaimer: the plugin author is not responsible for your unexpected expenses. -**Make sure to consult the pricing for your region [here](https://aws.amazon.com/s3/pricing)!** +**Make sure to consult the pricing for your region +[here](https://aws.amazon.com/s3/pricing)!** ## Reindex `helm s3 reindex ` command is much more expensive operation than other in -this plugin. For example, reindexing a repository with 1000 chart files in it +this plugin. For example, reindexing a repository with 1000 chart files in it results in 1 GET (`ListObjects`) request and 1000 HEAD (`HeadObject`) requests. -Plus it can make additional GET (`GetObject`) requests if it did not found +Plus it can make additional GET (`GetObject`) requests if it did not found required metadata in the HEAD request response. -At the moment of writing this document the price for HEAD/GET requests in `eu-central-1` is `$0.0043 for 10 000 requests`. -So the whole reindex operation for this case may cost approximately **$0.00043** or even **$0.00086**. -This seems small, but multiple reindex operations per day may hurt your budget. \ No newline at end of file +At the moment of writing this document the price for HEAD/GET requests in +`eu-central-1` is `$0.0043 for 10 000 requests`. So the whole reindex operation +for this case may cost approximately **$0.00043** or even **$0.00086**. This +seems small, but multiple reindex operations per day may hurt your budget. \ No newline at end of file diff --git a/plugin.yaml b/plugin.yaml index 045adea9..938fe0b4 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,14 +1,15 @@ -name: "s3" -version: "0.10.0" -usage: "Manage chart repositories on Amazon S3" +name: s3 +version: 0.10.0 +usage: Manage chart repositories on Amazon S3 description: |- The plugin allows to use s3 protocol to upload, fetch charts and to work with repositories. https://github.com/banzaicloud/helm-s3 -command: "$HELM_PLUGIN_DIR/bin/helms3" +ignoreFlags: false +command: $HELM_PLUGIN_DIR/bin/helms3 downloaders: - - command: "bin/helms3" + - command: bin/helms3 protocols: - - "s3" + - s3 hooks: - install: "cd $HELM_PLUGIN_DIR; ./hack/install.sh" - update: "cd $HELM_PLUGIN_DIR; ./hack/install.sh" + install: cd $HELM_PLUGIN_DIR && ./hack/install.sh + update: cd $HELM_PLUGIN_DIR && ./hack/install.sh From 0355a4517c65cf2237826f469e50bdb9ebcf7314 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 10:22:11 +0200 Subject: [PATCH 05/15] chore: run go mod tidy Updated dependency references before modifying files. --- go.sum | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/go.sum b/go.sum index 68e0acc7..4fcff16f 100644 --- a/go.sum +++ b/go.sum @@ -67,11 +67,9 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -86,7 +84,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.18 h1:SRdWLg+DqMFWX8HB3UvXyAoZpw9IDIUYnSTwgzOYbqg= github.com/aws/aws-sdk-go v1.37.18/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= @@ -158,7 +155,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -202,7 +198,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= @@ -210,7 +205,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -279,7 +273,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -289,7 +282,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= @@ -302,14 +294,11 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -383,7 +372,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -393,7 +381,6 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= @@ -406,12 +393,10 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -464,7 +449,6 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -544,7 +528,6 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rK github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -594,7 +577,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -605,7 +587,6 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= @@ -624,7 +605,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= @@ -645,10 +625,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -698,11 +676,9 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -710,7 +686,6 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= @@ -755,12 +730,10 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -781,9 +754,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2l golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -804,11 +775,9 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -845,19 +814,15 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -906,7 +871,6 @@ golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -974,7 +938,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= @@ -1004,12 +967,9 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1054,7 +1014,6 @@ k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAE k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao= k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= @@ -1072,7 +1031,6 @@ sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbL sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From 8001c392fa4a4c37c6db91923bdaf7985543f074 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 16:29:51 +0200 Subject: [PATCH 06/15] chore(lint): updated golangci-lint configuration Personalized linting. --- .golangci.yml | 306 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b045e5ac..e2f2e2e3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,312 @@ +# options for analysis running +run: + # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + modules-download-mode: readonly + +# output configuration options +output: + # sorts results by: filepath, line and column + sort-results: true + +# all available settings of specific linters +linters-settings: + cyclop: + # the maximal code complexity to report + max-complexity: 20 + + dupl: + # tokens count to trigger issue, 150 by default + threshold: 100 + + errcheck: + # report about not checking of errors in type assertions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: true + + exhaustive: + # check switch statements in generated files also + check-generated: true + # indicates that switch statements are to be considered exhaustive if a + # 'default' case is present, even if all enum members aren't listed in the + # switch + default-signifies-exhaustive: true + + forbidigo: + # Forbid the following identifiers + forbid: + - ^$ # Note: turning off default fmt\.Print.* rule. + + funlen: + lines: 60 + statements: 40 + + gci: + # put imports beginning with prefix after 3rd-party packages; + # only support one prefix + # if not set, use goimports.local-prefixes + local-prefixes: github.com/banzaicloud/helm-s3 + + gocognit: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 20 + + nestif: + # minimal complexity of if statements to report, 5 by default + min-complexity: 4 + + goconst: + # minimal length of string constant, 3 by default + min-len: 1 + # minimal occurrences count to trigger, 3 by default + min-occurrences: 2 + + gocritic: + # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. + # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + rangeValCopy: + # size in bytes that makes the warning trigger (default 128) + sizeThreshold: 32 + unnamedResult: + # whether to check exported functions + checkExported: true + + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 20 + + godot: + # comments to be checked: `declarations`, `toplevel`, or `all` + scope: all + # check that each sentence starts with a capital letter + capital: true + + gofumpt: + # Choose whether or not to use the extra rules that are disabled + # by default + extra-rules: true + + goheader: + values: + const: + # define here const type values in format k:v, for example: + # COMPANY: MY COMPANY + company: Banzai Cloud + regexp: + # define here regexp type values, for example + # AUTHOR: .*@mycompany\.com + later-year: "20(2[1-9]|[3-9][0-9])" + template: |- + Copyright © {{ later-year }} {{ company }} + + 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. + + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: github.com/banzaicloud/helm-s3 + + gomoddirectives: + # List of allowed `replace` directives. Default is empty. + replace-allow-list: + - github.com/docker/distribution # Note: legacy, temporary. + - github.com/docker/docker # Note: legacy, temporary. + + gosec: + # To specify a set of rules to explicitly exclude. + # Available rules: https://github.com/securego/gosec#available-rules + excludes: [] + + config: + G306: "0600" + + gosimple: + # Select the Go version to target. The default is '1.13'. + go: "1.16" + + govet: + # report about shadowed variables + check-shadowing: true + enable-all: true + + ifshort: + # Maximum length of variable declaration measured in number of lines, after which linter won't suggest using short syntax. + # Has higher priority than max-decl-chars. + max-decl-lines: 1 + # Maximum length of variable declaration measured in number of characters, after which linter won't suggest using short syntax. + max-decl-chars: 30 + + nakedret: + # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 + max-func-lines: 0 + + predeclared: + # include method names and field names (i.e., qualified names) in checks + q: true + + nolintlint: + # Enable to ensure that nolint directives are all used. Default is true. + allow-unused: false + # Enable to require an explanation of nonzero length after each nolint directive. Default is false. + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. Default is false. + require-specific: true + + revive: + # see https://github.com/mgechev/revive#available-rules for details. + ignore-generated-header: false + severity: error + + staticcheck: + # Select the Go version to target. The default is '1.13'. + go: "1.16" + + stylecheck: + # Select the Go version to target. The default is '1.13'. + go: "1.16" + + unused: + # Select the Go version to target. The default is '1.13'. + go: "1.16" + + wrapcheck: + # An array of strings that specify substrings of signatures to ignore. + # If this set, it will override the default set of ignored signatures. + # See https://github.com/tomarrell/wrapcheck#configuration for more information. + ignoreSigs: + - errors.New( + - errors.Unwrap( + - .Errorf( + - .Wrap( + - .Wrapf( + - .WrapWithDetails( + - .WithDetails( + - .WithMessage( + + wsl: + force-err-cuddling: true + +# Last updated: 1.40.1 linters: - enable-all: true - disable: + enable: + - asciicheck + - bodyclose + - cyclop + - deadcode + - depguard + - dogsled - dupl + - durationcheck + - errcheck + - errorlint + - exhaustive + - exhaustivestruct + - exportloopref + - forbidigo + - forcetypeassert - funlen + - gci - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot - godox + - goerr113 + - gofmt + - gofumpt + - goheader + - goimports + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - govet + - ifshort + - importas + - ineffassign - lll + - makezero + - megacheck + - misspell + - nakedret + - nestif + - nilerr + - nlreturn + - noctx + - nolintlint + - paralleltest + - prealloc + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - structcheck + - stylecheck + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - varcheck + - wastedassign + - whitespace + - wrapcheck - wsl + disable: + - golint # Note: deprecated, archived since v1.41.0. + - interfacer # Note: deprecated, also prone to false positives. + - maligned # Note: replaced by govet 'fieldalignment'. + - scopelint # Note: replaced by exportloopref. + - tagliatelle # Note: unfortunately YAML casing is varying and struct keys are not always under our control. + - testpackage # Note: I prefer using whitebox unit testing methods as well. + disable-all: false + fast: false issues: - new-from-rev: master + # Fix found issues (if it's supported by the linter) + fix: true + +severity: + # Default value is empty string. + # Set the default severity for issues. If severity rules are defined and the issues + # do not match or no severity is provided to the rule this will be the default + # severity applied. Severities should match the supported severity names of the + # selected out format. + # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity + # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity + # - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message + default-severity: error From 22f8c5ca58089302cb2670383f7f0d8dd9666699 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 16:37:16 +0200 Subject: [PATCH 07/15] style(lint): auto-fixed issues Run golangci-lint --fix on the repo. --- cmd/helms3/main.go | 6 ++--- cmd/helms3/push.go | 10 +++---- internal/awss3/storage.go | 36 +++++++++++++++---------- internal/awsutil/session.go | 12 ++++----- internal/helmutil/helm_v2.go | 2 +- internal/helmutil/helm_v3.go | 2 +- internal/helmutil/index_v2.go | 2 +- internal/helmutil/index_v2_test.go | 4 +-- internal/helmutil/index_v3.go | 6 +++-- internal/helmutil/index_v3_test.go | 4 +-- internal/helmutil/repo_entry.go | 6 ++--- internal/helmutil/repo_entry_v2_test.go | 2 +- internal/helmutil/repo_entry_v3_test.go | 5 ++-- internal/helmutil/testing_test.go | 2 +- internal/helmutil/version.go | 2 +- tests/e2e/main_test.go | 5 ++-- tests/e2e/push_test.go | 2 +- 17 files changed, 57 insertions(+), 51 deletions(-) diff --git a/cmd/helms3/main.go b/cmd/helms3/main.go index aecea3e3..0e7a233e 100644 --- a/cmd/helms3/main.go +++ b/cmd/helms3/main.go @@ -26,9 +26,7 @@ import ( "github.com/banzaicloud/helm-s3/internal/helmutil" ) -var ( - version = "master" -) +var version = "master" const ( actionVersion = "version" @@ -40,7 +38,7 @@ const ( defaultTimeout = time.Minute * 5 defaultTimeoutString = "5m" - // duplicated in e2e package for testing. + // Duplicated in e2e package for testing. defaultChartsContentType = "application/gzip" helpFlagTimeout = `Timeout for the whole operation to complete. Defaults to 5 minutes. diff --git a/cmd/helms3/push.go b/cmd/helms3/push.go index fab3faac..e61748a8 100644 --- a/cmd/helms3/push.go +++ b/cmd/helms3/push.go @@ -37,12 +37,12 @@ var ( ) type pushAction struct { - // required parameters + // Required parameters. chartPath string repoName string - // optional parameters and flags + // Optional parameters and flags. force bool dryRun bool @@ -90,7 +90,7 @@ func (act pushAction) Run(ctx context.Context) error { } if cachedIndex, err := helmutil.LoadIndex(repoEntry.CacheFile()); err == nil { - // if cached index exists, check if the same chart version exists in it. + // If cached index exists, check if the same chart version exists in it. if cachedIndex.Has(chart.Name(), chart.Version()) { if act.ignoreIfExists { return nil @@ -99,7 +99,7 @@ func (act pushAction) Run(ctx context.Context) error { return ErrChartExists } - // fallthrough on --force + // Fallthrough on --force. } } @@ -126,7 +126,7 @@ func (act pushAction) Run(ctx context.Context) error { return ErrChartExists } - // fallthrough on --force + // Fallthrough on --force. } if !act.dryRun { diff --git a/internal/awss3/storage.go b/internal/awss3/storage.go index ec09bcbe..c9e70778 100644 --- a/internal/awss3/storage.go +++ b/internal/awss3/storage.go @@ -34,10 +34,10 @@ import ( ) const ( - // selects serverside encryption for bucket + // Selects serverside encryption for bucket. awsS3encryption = "AWS_S3_SSE" - // s3MetadataSoftLimitBytes is application-specific soft limit + // S3MetadataSoftLimitBytes is application-specific soft limit // for the number of bytes in S3 object metadata. s3MetadataSoftLimitBytes = 1900 ) @@ -55,7 +55,7 @@ func New(session *session.Session) *Storage { return &Storage{session: session} } -// Returns desired encryption +// Returns desired encryption. func getSSE() *string { sse := os.Getenv(awsS3encryption) if sse == "" { @@ -120,7 +120,7 @@ func (s *Storage) traverse(ctx context.Context, repoURI string, items chan<- Cha if !strings.HasSuffix(key, ".tgz") { // Ignore any file that isn't a chart // This could include index.yaml - // or any other kind of file that might be in the repo + // or any other kind of file that might be in the repo. continue } @@ -186,7 +186,7 @@ func (s *Storage) traverse(ctx context.Context, repoURI string, items chan<- Cha reindexItem.Hash = *chartDigest } - // process meta and hash + // Process meta and hash. items <- reindexItem } @@ -206,7 +206,7 @@ type ChartInfo struct { } // FetchRaw downloads the object from URI and returns it in the form of byte slice. -// uri must be in the form of s3 protocol: s3://bucket-name/key[...]. +// Uri must be in the form of s3 protocol: s3://bucket-name/key[...]. func (s *Storage) FetchRaw(ctx context.Context, uri string) ([]byte, error) { bucket, key, err := parseURI(uri) if err != nil { @@ -259,8 +259,16 @@ func (s *Storage) Exists(ctx context.Context, uri string) (bool, error) { } // PutChart puts the chart file to the storage. -// uri must be in the form of s3 protocol: s3://bucket-name/key[...]. -func (s *Storage) PutChart(ctx context.Context, uri string, r io.Reader, chartMeta, acl string, chartDigest string, contentType string) (string, error) { +// Uri must be in the form of s3 protocol: s3://bucket-name/key[...]. +func (s *Storage) PutChart( + ctx context.Context, + uri string, + r io.Reader, + chartMeta string, + acl string, + chartDigest string, + contentType string, +) (string, error) { bucket, key, err := parseURI(uri) if err != nil { return "", err @@ -285,8 +293,8 @@ func (s *Storage) PutChart(ctx context.Context, uri string, r io.Reader, chartMe } // PutIndex puts the index file to the storage. -// uri must be in the form of s3 protocol: s3://bucket-name/key[...]. -func (s *Storage) PutIndex(ctx context.Context, uri string, acl string, r io.Reader) error { +// Uri must be in the form of s3 protocol: s3://bucket-name/key[...]. +func (s *Storage) PutIndex(ctx context.Context, uri, acl string, r io.Reader) error { if strings.HasPrefix(uri, "index.yaml") { return errors.New("uri must not contain \"index.yaml\" suffix, it appends automatically") } @@ -313,7 +321,7 @@ func (s *Storage) PutIndex(ctx context.Context, uri string, acl string, r io.Rea } // Delete deletes the object by uri. -// uri must be in the form of s3 protocol: s3://bucket-name/key[...]. +// Uri must be in the form of s3 protocol: s3://bucket-name/key[...]. func (s *Storage) Delete(ctx context.Context, uri string) error { bucket, key, err := parseURI(uri) if err != nil { @@ -336,7 +344,7 @@ func (s *Storage) Delete(ctx context.Context, uri string) error { // parseURI returns bucket and key from URIs like: // - s3://bucket-name/dir -// - s3://bucket-name/dir/file.ext +// - s3://bucket-name/dir/file.ext. func parseURI(uri string) (bucket, key string, err error) { if !strings.HasPrefix(uri, "s3://") { return "", "", fmt.Errorf("uri %s protocol is not s3", uri) @@ -385,9 +393,9 @@ func objectMetadataSize(m map[string]*string) int { } const ( - // metaChartMetadata is a s3 object metadata key that represents chart metadata. + // MetaChartMetadata is a s3 object metadata key that represents chart metadata. metaChartMetadata = "chart-metadata" - // metaChartDigest is a s3 object metadata key that represents chart digest. + // MetaChartDigest is a s3 object metadata key that represents chart digest. metaChartDigest = "chart-digest" ) diff --git a/internal/awsutil/session.go b/internal/awsutil/session.go index 91b498ef..565b5e7a 100644 --- a/internal/awsutil/session.go +++ b/internal/awsutil/session.go @@ -22,15 +22,15 @@ import ( ) const ( - // awsEndpoint can be set to a custom endpoint to use alternative AWS S3 + // AwsEndpoint can be set to a custom endpoint to use alternative AWS S3 // server like minio (https://minio.io). awsEndpoint = "AWS_ENDPOINT" - // awsDisableSSL can be set to true to disable SSL for AWS S3 server. + // AwsDisableSSL can be set to true to disable SSL for AWS S3 server. awsDisableSSL = "AWS_DISABLE_SSL" - // awsBucketLocation can be set to an AWS region to force the session region - // if AWS_DEFAULT_REGION and AWS_REGION cannot be trusted + // AwsBucketLocation can be set to an AWS region to force the session region + // if AWS_DEFAULT_REGION and AWS_REGION cannot be trusted. awsBucketLocation = "HELM_S3_REGION" ) @@ -62,8 +62,8 @@ func Session(opts ...SessionOption) (*session.Session, error) { } bucketRegion := os.Getenv(awsBucketLocation) - // if not set, we don't update the config, - // so that the AWS SDK can still rely on either AWS_REGION or AWS_DEFAULT_REGION + // If not set, we don't update the config, + // so that the AWS SDK can still rely on either AWS_REGION or AWS_DEFAULT_REGION. if bucketRegion != "" { so.Config.Region = aws.String(bucketRegion) } diff --git a/internal/helmutil/helm_v2.go b/internal/helmutil/helm_v2.go index d53c286a..6c1e9f5b 100644 --- a/internal/helmutil/helm_v2.go +++ b/internal/helmutil/helm_v2.go @@ -19,7 +19,7 @@ func setupHelm2() { var ( helm2Home helmpath.Home - // func that loads helm repo file. + // Func that loads helm repo file. // Defined for testing purposes. helm2LoadRepoFile func(path string) (*repo.RepoFile, error) ) diff --git a/internal/helmutil/helm_v3.go b/internal/helmutil/helm_v3.go index d011c350..1eee143d 100644 --- a/internal/helmutil/helm_v3.go +++ b/internal/helmutil/helm_v3.go @@ -16,7 +16,7 @@ func setupHelm3() { var ( helm3Env *cli.EnvSettings - // func that loads helm repo file. + // Func that loads helm repo file. // Defined for testing purposes. helm3LoadRepoFile func(path string) (*repo.File, error) ) diff --git a/internal/helmutil/index_v2.go b/internal/helmutil/index_v2.go index 3de96b8e..d01d967a 100644 --- a/internal/helmutil/index_v2.go +++ b/internal/helmutil/index_v2.go @@ -85,7 +85,7 @@ func (idx *IndexV2) AddOrReplace(metadata interface{}, filename, baseURL, digest } } - // Otherwise just add to the list of versions + // Otherwise just add to the list of versions. idx.index.Entries[md.Name] = append(entry, cr) return nil } diff --git a/internal/helmutil/index_v2_test.go b/internal/helmutil/index_v2_test.go index 3009f994..363e7248 100644 --- a/internal/helmutil/index_v2_test.go +++ b/internal/helmutil/index_v2_test.go @@ -38,8 +38,8 @@ generated: 2018-01-01T00:00:00Z err := idx.UnmarshalBinary(input) require.NoError(t, err) - assert.Equal(t, "foo", idx.index.APIVersion) - assert.Equal(t, time.Date(2018, 01, 01, 0, 0, 0, 0, time.UTC), idx.index.Generated) + require.Equal(t, "foo", idx.index.APIVersion) + require.Equal(t, time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), idx.index.Generated) } func TestIndexV2_AddOrReplace(t *testing.T) { diff --git a/internal/helmutil/index_v3.go b/internal/helmutil/index_v3.go index d95a6ea4..fe797087 100644 --- a/internal/helmutil/index_v3.go +++ b/internal/helmutil/index_v3.go @@ -12,7 +12,9 @@ import ( "github.com/pkg/errors" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/repo" - "k8s.io/helm/pkg/urlutil" // Note that this is from Helm v2 SDK because in Helm v3 this package is internal. + + // Note that this is from Helm v2 SDK because in Helm v3 this package is internal. + "k8s.io/helm/pkg/urlutil" "sigs.k8s.io/yaml" ) @@ -85,7 +87,7 @@ func (idx *IndexV3) AddOrReplace(metadata interface{}, filename, baseURL, digest } } - // Otherwise just add to the list of versions + // Otherwise just add to the list of versions. idx.index.Entries[md.Name] = append(entry, cr) return nil } diff --git a/internal/helmutil/index_v3_test.go b/internal/helmutil/index_v3_test.go index 395ecf56..b0f07a90 100644 --- a/internal/helmutil/index_v3_test.go +++ b/internal/helmutil/index_v3_test.go @@ -38,8 +38,8 @@ generated: 2018-01-01T00:00:00Z err := idx.UnmarshalBinary(input) require.NoError(t, err) - assert.Equal(t, "foo", idx.index.APIVersion) - assert.Equal(t, time.Date(2018, 01, 01, 0, 0, 0, 0, time.UTC), idx.index.Generated) + require.Equal(t, "foo", idx.index.APIVersion) + require.Equal(t, time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), idx.index.Generated) } func TestIndexV3_AddOrReplace(t *testing.T) { diff --git a/internal/helmutil/repo_entry.go b/internal/helmutil/repo_entry.go index fc966dfd..c3199b24 100644 --- a/internal/helmutil/repo_entry.go +++ b/internal/helmutil/repo_entry.go @@ -4,19 +4,19 @@ type RepoEntry interface { // URL returns repo URL. // Examples: // - https://kubernetes-charts.storage.googleapis.com/ - // - s3://my-charts + // - s3://my-charts. URL() string // IndexURI returns repo index file URL. // Examples: // - https://kubernetes-charts.storage.googleapis.com/index.yaml - // - s3://my-charts/index.yaml + // - s3://my-charts/index.yaml. IndexURL() string // CacheFile returns repo local cache file path. // Examples: // - /Users/foo/Library/Caches/helm/repository/my-charts-index.yaml (on macOS) - // - /home/foo/.cache/helm/repository/my-charts-index.yaml (on Linux) + // - /home/foo/.cache/helm/repository/my-charts-index.yaml (on Linux). CacheFile() string } diff --git a/internal/helmutil/repo_entry_v2_test.go b/internal/helmutil/repo_entry_v2_test.go index 68c81427..b82b9a50 100644 --- a/internal/helmutil/repo_entry_v2_test.go +++ b/internal/helmutil/repo_entry_v2_test.go @@ -75,7 +75,7 @@ func TestRepoEntryV2_IndexURL(t *testing.T) { } func TestRepoEntryV2_CacheFile(t *testing.T) { - // mock helm2 home + // Mock helm2 home. helm2Home = "/home/foo/.helm" testCases := map[string]struct { diff --git a/internal/helmutil/repo_entry_v3_test.go b/internal/helmutil/repo_entry_v3_test.go index 7e4f902a..b6cca85d 100644 --- a/internal/helmutil/repo_entry_v3_test.go +++ b/internal/helmutil/repo_entry_v3_test.go @@ -4,9 +4,8 @@ import ( "fmt" "testing" - "helm.sh/helm/v3/pkg/cli" - "github.com/stretchr/testify/assert" + "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/repo" ) @@ -77,7 +76,7 @@ func TestRepoEntryV3_IndexURL(t *testing.T) { } func TestRepoEntryV3_CacheFile(t *testing.T) { - // mock helm3 env + // Mock helm3 env. helm3Env = cli.New() helm3Env.RepositoryCache = "/home/foo/.cache/helm/repository" diff --git a/internal/helmutil/testing_test.go b/internal/helmutil/testing_test.go index ddf19f0b..3a1031a5 100644 --- a/internal/helmutil/testing_test.go +++ b/internal/helmutil/testing_test.go @@ -1,6 +1,6 @@ package helmutil -// this file contains utilities for testing code in this package. +// This file contains utilities for testing code in this package. import ( "os" diff --git a/internal/helmutil/version.go b/internal/helmutil/version.go index 4278e2f4..8f21057b 100644 --- a/internal/helmutil/version.go +++ b/internal/helmutil/version.go @@ -15,7 +15,7 @@ func IsHelm3() bool { case "3", "v3": return true default: - // continue to other detection methods. + // Continue to other detection methods. } if os.Getenv("TILLER_HOST") != "" { diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go index 4a54db88..32fb3437 100644 --- a/tests/e2e/main_test.go +++ b/tests/e2e/main_test.go @@ -64,10 +64,9 @@ func setup() { } func teardown() { - } -// helper functions +// Helper functions. func setupBucket(t *testing.T, name string) { t.Helper() @@ -136,7 +135,7 @@ func command(c string) (cmd *exec.Cmd, stdout, stderr *bytes.Buffer) { } // For helm v2, the command is `helm search foo/bar` -// For helm v3, the command is `helm search repo foo/bar` +// For helm v3, the command is `helm search repo foo/bar`. func makeSearchCommand(repoName, chartName string) string { c := "helm search" diff --git a/tests/e2e/push_test.go b/tests/e2e/push_test.go index bb9c5915..1284c114 100644 --- a/tests/e2e/push_test.go +++ b/tests/e2e/push_test.go @@ -32,7 +32,7 @@ import ( ) const ( - // copied from main + // Copied from main. defaultChartsContentType = "application/gzip" ) From a47a8f22b75d6a16b29e6640771b81978e83758d Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 19:12:42 +0200 Subject: [PATCH 08/15] chore(git): updated .gitignore Removed unnecessary ignores. --- .gitignore | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 0bef4c23..d5e21080 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,5 @@ -bin/* -tmp -releases -vendor +# Note: local build. +bin/** -.env -coverage.txt - -.idea -*.iml \ No newline at end of file +# Note: optional local dependency cache. +vendor/** From 6df16fbc1cb0a34f523b939bfb270c2ef30034f5 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Thu, 6 May 2021 19:13:37 +0200 Subject: [PATCH 09/15] chore(Docker): removed .dockerignore Not necessary anymore. --- .dockerignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 22e2992e..00000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ref: https://docs.docker.com/engine/reference/builder/#dockerignore-file -.git -bin From d6a9012ee0515927dd93a8b38a4640efebfb0849 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Tue, 27 Apr 2021 23:04:40 +0200 Subject: [PATCH 10/15] refactor(Makefile): refactored build Updated/simplified/personalized build tasks. --- .circleci/config.yml | 13 --------- .github/CONTRIBUTING.md | 2 +- Makefile | 61 ++++++++++++++++++++++++++++------------- cmd/helms3/main.go | 2 +- hack/build.sh | 20 -------------- 5 files changed, 44 insertions(+), 54 deletions(-) delete mode 100755 hack/build.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 44d55e8d..ac85c26f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,19 +99,6 @@ commands: command: | go test -v ./tests/e2e/... jobs: - dep: - docker: - - image: circleci/golang:1.15 - environment: - GO111MODULE: "on" - working_directory: /go/src/github.com/banzaicloud/helm-s3 - steps: - - checkout - - run: make deps - - persist_to_workspace: - root: /go/src/github.com/banzaicloud - paths: - - helm-s3 test-unit: docker: - image: circleci/golang:1.15 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2b8254fa..8d63a242 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -15,7 +15,7 @@ Next, you may want to ensure if you have all prerequisites to build the plugin from source: cd ~/.helm/plugins/helm-s3 - make deps build-local + make build-local If you see no messages - build was successful. Try to run some helm commands that involve the plugin, or jump straight into plugin development. diff --git a/Makefile b/Makefile index 2aec2a36..0e5e1ee0 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,51 @@ -PKG := github.com/hypnoglow/helm-s3 -GO111MODULE := on +# A Self-Documenting Makefile: +# http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html -.EXPORT_ALL_VARIABLES: +# Git. +GIT_DEFAULT_BRANCH = origin/main +GIT_REF = $$(git show-ref --head | awk '/HEAD/ {print $$1}') + +# Go. +# Note: explicitly setting GOBIN for global build install (required for GitHub +# Actions environment). +export GOBIN ?= $(shell go env GOPATH)/bin +GO_ROOT_MODULE_PKG ?= $$(awk 'NR == 1 {print $$2 ; exit}' go.mod) + +# Helm S3 plugin. +HELM_S3_PLUGIN_LATEST_VERSION ?= $$(awk '/^version:/ {print $$2 ; exit}' plugin.yaml) +HELM_S3_PLUGIN_VERSION ?= $(GIT_REF) .PHONY: all -all: deps build +all: analyze build ## all runs the entire toolchain configured for local development. -.PHONY: deps -deps: - @ go mod download - @ go mod vendor - @ go mod tidy +.PHONY: analyze +analyze: ## analyze runs the code analysis tools for new code. + @ echo "- Analyzing new code" + @ golangci-lint run --new-from-rev $(GIT_DEFAULT_BRANCH) ./... + +.PHONY: analyze-full +analyze-full: ## analyze-full runs the code analysis tools for all code. + @ echo "- Analyzing code" + @ golangci-lint run ./... .PHONY: build -build: - @ ./hack/build.sh $(CURDIR) $(PKG) +build: ## build builds the local packages. You can set the version through the HELM_S3_PLUGIN_VERSION environment variable, defaults to 'local'. + @ echo "- Building project binaries and libraries" + @ go install -ldflags "-X main.version=$(HELM_S3_PLUGIN_VERSION)" ./... + @ export GOBIN="$${PWD}/bin" ; go install -ldflags "-X main.version=$(HELM_S3_PLUGIN_VERSION)" ./... -.PHONY: build-local -build-local: - HELM_S3_PLUGIN_VERSION=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") $(MAKE) build +.PHONY: build-latest +build-latest: HELM_S3_PLUGIN_VERSION=$(HELM_S3_PLUGIN_LATEST_VERSION) ## build-latest builds the local packages with the latest version based on the plugin.yaml. +build-latest: build -.PHONY: install -install: - @ ./hack/install.sh +.PHONY: help +help: ## help displays the help message. + @ grep -E '^[0-9a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' .PHONY: test-unit -test-unit: - go test $$(go list ./... | grep -v e2e) +test-unit: ## test-unit runs the unit tests in the repository. + @ echo "- Running unit tests" + @ go test -count 1 -race $$(go list ./... | grep -v $(GO_ROOT_MODULE_PKG)/test/e2e) .PHONY: test-e2e test-e2e: @@ -35,3 +54,7 @@ test-e2e: .PHONY: test-e2e-local test-e2e-local: @ ./hack/test-e2e-local.sh + +.PHONY: vendor +vendor: ## vendor downloads the dependencies to a local vendor folder. + @ go mod vendor diff --git a/cmd/helms3/main.go b/cmd/helms3/main.go index 0e7a233e..08daeb6d 100644 --- a/cmd/helms3/main.go +++ b/cmd/helms3/main.go @@ -26,7 +26,7 @@ import ( "github.com/banzaicloud/helm-s3/internal/helmutil" ) -var version = "master" +var version = "local" const ( actionVersion = "version" diff --git a/hack/build.sh b/hack/build.sh deleted file mode 100755 index 8df07ebc..00000000 --- a/hack/build.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env sh - -# This emulates GOPATH presence for go tool. -# This is need because helm installs plugins into ~/.helm/plugins. - -projectRoot="$1" -pkg="$2" - -if [ ! -e "${GOPATH}/src/${pkg}" ]; then - mkdir -p $(dirname "${GOPATH}/src/${pkg}") - ln -sfn "${projectRoot}" "${GOPATH}/src/${pkg}" -fi - -version="${HELM_S3_PLUGIN_VERSION:-}" -if [ -z "${version}" ]; then - version="$(cat plugin.yaml | grep "version" | cut -d '"' -f 2)" -fi - -cd "${GOPATH}/src/${pkg}" -go build -o bin/helms3 -ldflags "-X main.version=${version}" ./cmd/helms3 From 376f22ec020fe773f1f0932cedccdff682ccc453 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Fri, 7 May 2021 01:21:31 +0200 Subject: [PATCH 11/15] build(Container): refactored Dockerfile Updated/simplified/personalized container related files. --- Dockerfile | 51 ++++++++++++++++++++++++--------------------------- Makefile | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index ce5f75a3..fcb2e326 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,38 @@ -ARG HELM_VERSION +ARG GO_VERSION=1.16 +ARG HELM_VERSION=3.5.4 -FROM golang:1.15-alpine as build +FROM golang:${GO_VERSION}-alpine as build -ARG PLUGIN_VERSION +ARG ARCH=amd64 +ARG HELM_PLUGIN_VERSION=local +ARG YQ_VERSION=v4.7.1 + +ENV YQ_BINARY="yq_linux_${ARCH}" + +RUN apk add --no-cache \ + git \ + wget + +RUN wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY} -O /usr/bin/yq && \ + chmod +x /usr/bin/yq WORKDIR /workspace/helm-s3 COPY . . -RUN apk add --no-cache git - -RUN go build -o bin/helms3 \ - -mod=vendor \ - -ldflags "-X main.version=${PLUGIN_VERSION}" \ - ./cmd/helms3 +# Note: Using argument version. +# +# Note: Not using install hooks in container context. +RUN yq eval --inplace ".version = \"${HELM_PLUGIN_VERSION}\"" plugin.yaml && \ + yq eval --inplace "del(.hooks)" plugin.yaml -# Correct the plugin manifest with docker-specific fixes: -# - remove hooks, because we are building everything locally from source -# - update version -RUN sed "/^hooks:/,+2 d" plugin.yaml > plugin.yaml.fixed \ - && sed -i "s/^version:.*$/version: ${PLUGIN_VERSION}/" plugin.yaml.fixed +RUN mkdir -p ./bin +RUN go build -ldflags "-X main.version=${HELM_PLUGIN_VERSION}" -o ./bin/helms3 ./cmd/helms3 FROM alpine/helm:${HELM_VERSION} -# Build-time metadata as defined at http://label-schema.org -ARG BUILD_DATE -ARG VCS_REF -ARG PLUGIN_VERSION -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="helm-s3" \ - org.label-schema.description="The Helm plugin that provides S3 protocol support and allows to use AWS S3 as a chart repository." \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/banzaicloud/helm-s3" \ - org.label-schema.version=$PLUGIN_VERSION \ - org.label-schema.schema-version="1.0" - -COPY --from=build /workspace/helm-s3/plugin.yaml.fixed /root/.helm/cache/plugins/helm-s3/plugin.yaml +COPY --from=build /workspace/helm-s3/LICENSE /root/.helm/cache/plugins/helm-s3/LICENSE +COPY --from=build /workspace/helm-s3/plugin.yaml /root/.helm/cache/plugins/helm-s3/plugin.yaml COPY --from=build /workspace/helm-s3/bin/helms3 /root/.helm/cache/plugins/helm-s3/bin/helms3 RUN mkdir -p /root/.helm/plugins \ diff --git a/Makefile b/Makefile index 0e5e1ee0..6aa50c57 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,15 @@ # A Self-Documenting Makefile: # http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +# Generic. +ORGANIZATION ?= $$(basename $$(dirname $${PWD})) +REPOSITORY ?= $$(basename $${PWD}) + +# --- + +# Container. +CONTAINER_IMAGE_NAME = $(ORGANIZATION)/$(REPOSITORY) + # Git. GIT_DEFAULT_BRANCH = origin/main GIT_REF = $$(git show-ref --head | awk '/HEAD/ {print $$1}') @@ -34,6 +43,11 @@ build: ## build builds the local packages. You can set the version through the H @ go install -ldflags "-X main.version=$(HELM_S3_PLUGIN_VERSION)" ./... @ export GOBIN="$${PWD}/bin" ; go install -ldflags "-X main.version=$(HELM_S3_PLUGIN_VERSION)" ./... +.PHONY: build-container +build-container: ## build-container builds the project's container with the ${VERSION} tag (defaults to local). + @ echo "- Building container" + @ docker build --tag "$(CONTAINER_IMAGE_NAME):$(HELM_S3_PLUGIN_VERSION)" . + .PHONY: build-latest build-latest: HELM_S3_PLUGIN_VERSION=$(HELM_S3_PLUGIN_LATEST_VERSION) ## build-latest builds the local packages with the latest version based on the plugin.yaml. build-latest: build @@ -42,6 +56,11 @@ build-latest: build help: ## help displays the help message. @ grep -E '^[0-9a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' +.PHONY: run-container +run-container: ## run-container runs the projects container in a throw-away context with the ${CMD} command as argument. + @ echo "- Running container" + @ docker run --interactive --rm --tty "$(CONTAINER_IMAGE_NAME):$(HELM_S3_PLUGIN_VERSION)" $(CMD) + .PHONY: test-unit test-unit: ## test-unit runs the unit tests in the repository. @ echo "- Running unit tests" From c85f07a38f78a3ba0b852016edac3ff1b5f08987 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Fri, 7 May 2021 03:22:03 +0200 Subject: [PATCH 12/15] refactor(install): refactored plugin install Updated/simplified/personalized plugin install. --- Makefile | 10 +++ hack/install.sh | 76 ---------------- plugin.yaml | 16 +++- scripts/install_plugin.bash | 174 ++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 80 deletions(-) delete mode 100755 hack/install.sh create mode 100755 scripts/install_plugin.bash diff --git a/Makefile b/Makefile index 6aa50c57..c991d970 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ GO_ROOT_MODULE_PKG ?= $$(awk 'NR == 1 {print $$2 ; exit}' go.mod) # Helm S3 plugin. HELM_S3_PLUGIN_LATEST_VERSION ?= $$(awk '/^version:/ {print $$2 ; exit}' plugin.yaml) +HELM_S3_PLUGIN_NAME ?= s3 HELM_S3_PLUGIN_VERSION ?= $(GIT_REF) .PHONY: all @@ -56,6 +57,15 @@ build-latest: build help: ## help displays the help message. @ grep -E '^[0-9a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' +.PHONY: install-plugin-local +install-plugin-local: ## install-plugin-local installs the Helm plugin with 'local' version. + @ echo "- Installing plugin locally" + @ if helm plugin list | grep -q $(HELM_S3_PLUGIN_NAME); then \ + helm plugin remove $(HELM_S3_PLUGIN_NAME) ; \ + fi + @ export HELM_PLUGIN_INSTALL_LOCAL=1 ; \ + helm plugin install . + .PHONY: run-container run-container: ## run-container runs the projects container in a throw-away context with the ${CMD} command as argument. @ echo "- Running container" diff --git a/hack/install.sh b/hack/install.sh deleted file mode 100755 index 34368dbb..00000000 --- a/hack/install.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [ -n "${HELM_S3_PLUGIN_NO_INSTALL_HOOK:-}" ]; then - echo "Development mode: not downloading versioned release." - exit 0 -fi - -validate_checksum() { - if ! grep -q ${1} ${2}; then - echo "Invalid checksum" > /dev/stderr - exit 1 - fi - echo "Checksum is valid." -} - -on_exit() { - exit_code=$? - if [ ${exit_code} -ne 0 ]; then - echo "helm-s3 install hook failed. Please remove the plugin using 'helm plugin remove s3' and install again." > /dev/stderr - fi - exit ${exit_code} -} -trap on_exit EXIT - -version="$(cat plugin.yaml | grep "version" | cut -d '"' -f 2)" -echo "Downloading and installing helm-s3 v${version} ..." - -binary_url="" -if [ "$(uname)" == "Darwin" ]; then - binary_url="https://github.com/banzaicloud/helm-s3/releases/download/v${version}/helm-s3_${version}_darwin_amd64.tar.gz" -elif [ "$(uname)" == "Linux" ] ; then - binary_url="https://github.com/banzaicloud/helm-s3/releases/download/v${version}/helm-s3_${version}_linux_amd64.tar.gz" -fi - -if [ -z "${binary_url}" ]; then - echo "Unsupported OS type" - exit 1 -fi -checksum_url="https://github.com/banzaicloud/helm-s3/releases/download/v${version}/helm-s3_${version}_checksums.txt" - -mkdir -p "bin" -mkdir -p "releases/v${version}" -binary_filename="releases/v${version}.tar.gz" -checksums_filename="releases/v${version}_checksums.txt" - -# Download binary and checksums files. -( - if [ -x "$(which curl 2>/dev/null)" ]; then - curl -sSL "${binary_url}" -o "${binary_filename}" - curl -sSL "${checksum_url}" -o "${checksums_filename}" - elif [ -x "$(which wget 2>/dev/null)" ]; then - wget -q "${binary_url}" -O "${binary_filename}" - wget -q "${checksum_url}" -O "${checksums_filename}" - else - echo "ERROR: no curl or wget found to download files." > /dev/stderr - fi -) - -# Verify checksum. -( - if [ -x "$(which sha256sum 2>/dev/null)" ]; then - checksum=$(sha256sum ${binary_filename} | awk '{ print $1 }') - validate_checksum ${checksum} ${checksums_filename} - elif [ -x "$(which openssl 2>/dev/null)" ]; then - checksum=$(openssl dgst -sha256 ${binary_filename} | awk '{ print $2 }') - validate_checksum ${checksum} ${checksums_filename} - else - echo "WARNING: no tool found to verify checksum" > /dev/stderr - fi -) - -# Unpack the binary. -tar xzf "${binary_filename}" -C "releases/v${version}" -mv "releases/v${version}/bin/helms3" "bin/helms3" -exit 0 diff --git a/plugin.yaml b/plugin.yaml index 938fe0b4..36c734d9 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -2,14 +2,22 @@ name: s3 version: 0.10.0 usage: Manage chart repositories on Amazon S3 description: |- - The plugin allows to use s3 protocol to upload, fetch charts and to work with repositories. + Adds s3 protocol support to Helm operations. https://github.com/banzaicloud/helm-s3 + + Supported architectures: + - amd64 + - arm64 + + Supported operating systems: + - darwin + - linux ignoreFlags: false -command: $HELM_PLUGIN_DIR/bin/helms3 +command: "${HELM_PLUGIN_DIR}/bin/helms3" downloaders: - command: bin/helms3 protocols: - s3 hooks: - install: cd $HELM_PLUGIN_DIR && ./hack/install.sh - update: cd $HELM_PLUGIN_DIR && ./hack/install.sh + install: (cd ${HELM_PLUGIN_DIR} && ./scripts/install_plugin.bash ;) + update: (cd ${HELM_PLUGIN_DIR} && ./scripts/install_plugin.bash ;) diff --git a/scripts/install_plugin.bash b/scripts/install_plugin.bash new file mode 100755 index 00000000..617c979b --- /dev/null +++ b/scripts/install_plugin.bash @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +set -eo pipefail + +exit_code_success=0 +exit_code_fatal=1 + +function download_file() { + local -r downloader="${1}" + local -r source="${2}" + local -r destination="${3}" + + test ! -f "${destination}" || + log_fatal "destination file '%s' already exists" "${destination}" + + case "${downloader}" in + curl) + curl -sSL "${source}" -o "${destination}" || log_fatal "downloading '%s' failed" "${source}" + ;; + wget) + wget -q "${source}" -O "${destination}" || log_fatal "downloading '%s' failed" "${source}" + ;; + *) + log_fatal "unknown downloader: '%s'" "${downloader}" + ;; + esac +} + +function get_architecture() { + # shellcheck disable=SC2155 # Note: not using exit status. + local architecture="$(uname -m)" + + case "${architecture}" in + aarch64_be | aarch64 | armv6l | armv7l | armv8b | armv8l) + architecture=arm64 + ;; + x86_64) + architecture=amd64 + ;; + *) ;; + esac + + printf "%s" "${architecture}" +} + +function get_first_binary() { + local -r candidates=("${@}") + + local binary="" + for candidate in "${candidates[@]}"; do + if which -s "${candidate}"; then + binary="${candidate}" + + break + fi + done + + if [ "${binary}" == "" ]; then + log_fatal "required binary not found of candidates %s" "${candidates[*]}" + fi + + printf "%s" "${binary}" +} + +function log_fatal() { + local -r format_string="${1}" + local -r arguments=("${@:2}") + + # shellcheck disable=SC2059,SC2086 + printf >&2 "${format_string}\n" ${arguments[*]} + + exit ${exit_code_fatal} +} + +function main() { + local -r binary_path="bin/helms3" + + if [ -n "${HELM_PLUGIN_INSTALL_LOCAL}" ]; then + if test -f "${binary_path}" && "${binary_path}" &>/dev/null; then + exit ${exit_code_success} # Note: local plugin install with existing, working binary, nothing else to do. + fi + + make build || log_fatal "building Helm S3 plugin locally failed" + test -f "${binary_path}" || log_fatal "expected binary at '%s' cannot be found" "${binary_path}" + ${binary_path} &>/dev/null || log_fatal "test running binary '%s' failed" "${binary_path}" + + exit ${exit_code_success} # Note: local plugin install with existing, working binary, nothing else to do. + fi + + local -r checksum_verifier="$(get_first_binary shasum openssl sha512sum)" + local -r downloader="$(get_first_binary curl wget)" + local -r plugin_manifest_file_path="plugin.yaml" + local -r project_name="helm-s3" + local -r temporary_directory_path="$(mktemp -d)" + local -r version_tag_prefix="v" + # + local -r repository_name="${project_name}" + + test -f "${plugin_manifest_file_path}" || + log_fatal "required plugin manifest file '%s' is missing" "${plugin_manifest_file_path}" + + local -r architecture="$(get_architecture)" + local -r operating_system="$(uname | tr "[:upper:]" "[:lower:]")" + local version + + version="$(awk '/^version:/ {print $2 ; exit}' "${plugin_manifest_file_path}")" || + log_fatal "required plugin manifest entry 'version' not found" + + grep -E -q "^\s+- ${architecture}$" "${plugin_manifest_file_path}" || + log_fatal "unsupported architecture: '%s'" "${architecture}" + + grep -E -q "^\s+- ${operating_system}$" "${plugin_manifest_file_path}" || + log_fatal "unsupported operating system '%s'" "${operating_system}" + + local -r download_url="https://github.com/banzaicloud/${repository_name}/releases/download" + # + local -r plugin_archive_name="${project_name}_${version}_${operating_system}_${architecture}" + local -r plugin_checksums_file_name="${project_name}_${version}_sha512_checksums.txt" + # + local -r plugin_archive_file_name="${plugin_archive_name}.tar.gz" + local -r plugin_checksums_download_path="${temporary_directory_path}/${plugin_checksums_file_name}" + local -r plugin_checksums_url="${download_url}/${version_tag_prefix}${version}/${plugin_checksums_file_name}" + # + local -r plugin_archive_download_path="${temporary_directory_path}/${plugin_archive_file_name}" + local -r plugin_archive_url="${download_url}/${version_tag_prefix}${version}/${plugin_archive_file_name}" + + download_file "${downloader}" "${plugin_archive_url}" "${plugin_archive_download_path}" + download_file "${downloader}" "${plugin_checksums_url}" "${plugin_checksums_download_path}" + + verify_checksum "${checksum_verifier}" "${plugin_archive_download_path}" "${plugin_checksums_download_path}" + + mkdir -p "${temporary_directory_path}/${plugin_archive_name}" + tar -xzf "${plugin_archive_download_path}" -C "${temporary_directory_path}/${plugin_archive_name}" + mkdir -p "$(dirname "${binary_path}")" + mv "${temporary_directory_path}/${plugin_archive_name}/${binary_path}" "${binary_path}" + + rm -fr "${temporary_directory_path}" +} + +function verify_checksum() { + local -r checker="${1}" + local -r verified_file_path="${2}" + local -r checksums_file_path="${3}" + + test -f "${verified_file_path}" || log_fatal "file '%s' to verify is missing" "${verified_file_path}" + test -f "${checksums_file_path}" || log_fatal "checksums file '%s' to verify with is missing" "${checksums_file_path}" + + local -r verified_file_name="$(basename "${verified_file_path}")" + local expected_checksum + expected_checksum="$(awk "/^[0-9a-z]+ ${verified_file_name}$/ { print \$1 ; exit }" "${checksums_file_path}")" || + log_fatal "%s file not found in checksums file %s" "${verified_file_name}" "${checksums_file_path}" + + local actual_checksum="" + case "${checker}" in + openssl) + actual_checksum="$(openssl dgst -sha512 "${verified_file_path}" | awk '{print $2}')" + ;; + sha512sum) + actual_checksum="$(sha512sum "${verified_file_path}" | awk '{print $1}')" + ;; + shasum) + actual_checksum="$(shasum -a 512 "${verified_file_path}" | awk '{print $1}')" + ;; + *) + log_fatal "unknown checksum checker: %s" "${checker}" + ;; + esac + + test "${actual_checksum}" == "${expected_checksum}" || + log_fatal "'%s' file's checksum '%s' does not match recorded expected checksum '%s'" \ + "${verified_file_path}" "${actual_checksum}" "${expected_checksum}" +} + +main "${@}" From 69606a488b58443519abaadc46aa069432b4f4a7 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Fri, 7 May 2021 10:38:45 +0200 Subject: [PATCH 13/15] test(e2e): refactored, Minio->AWS, LocalStack Changed the end to end tests' Minio S3 API provider to AWS and LocalStack providers to support both local and remote end to end testing out of the box. AWS works with a regular AWS account with an access to the S3 service. (Tested.) LocalStack works with no additional requirements than the LocalStack container. (Tested.) Also refactored the tests (assert->require, testify/suite, linter fixes). --- Makefile | 63 +- docker-compose.yml | 23 + go.mod | 14 +- go.sum | 87 +- hack/test-e2e-local.sh | 56 - internal/helmutil/index_v2_test.go | 13 +- internal/helmutil/index_v3_test.go | 13 +- internal/helmutil/repo_entry_test.go | 4 +- internal/helmutil/repo_entry_v2_test.go | 10 +- internal/helmutil/repo_entry_v3_test.go | 10 +- internal/helmutil/testing_test.go | 5 +- internal/helmutil/version_test.go | 4 +- test/e2e/aws_log_level_value_adapter_test.go | 102 ++ .../testdata => test/e2e/data}/foo-1.2.3.tgz | Bin .../e2e/data}/foo/.helmignore | 0 .../testdata => test/e2e/data}/foo/Chart.yaml | 0 .../e2e/data}/foo/templates/NOTES.txt | 0 .../e2e/data}/foo/templates/_helpers.tpl | 0 .../e2e/data}/foo/templates/deployment.yaml | 0 .../e2e/data}/foo/templates/ingress.yaml | 0 .../e2e/data}/foo/templates/service.yaml | 0 .../e2e/data}/foo/values.yaml | 0 test/e2e/end_to_end_suite_test.go | 120 ++ test/e2e/fixture_test.go | 1039 +++++++++++++++++ test/e2e/helm_fetch_test.go | 39 + test/e2e/helm_repo_add_test.go | 33 + .../e2e/helm_repo_remove_test.go | 15 +- test/e2e/helm_s3_delete_test.go | 45 + test/e2e/helm_s3_init_test.go | 36 + test/e2e/helm_s3_push_test.go | 190 +++ test/e2e/helm_s3_reindex_test.go | 50 + test/e2e/require_test.go | 60 + test/e2e/string_value_adapter_test.go | 129 ++ tests/e2e/delete_test.go | 85 -- tests/e2e/init_test.go | 67 -- tests/e2e/main_test.go | 148 --- tests/e2e/push_test.go | 328 ------ 37 files changed, 2017 insertions(+), 771 deletions(-) create mode 100644 docker-compose.yml delete mode 100755 hack/test-e2e-local.sh create mode 100644 test/e2e/aws_log_level_value_adapter_test.go rename {tests/e2e/testdata => test/e2e/data}/foo-1.2.3.tgz (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/.helmignore (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/Chart.yaml (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/templates/NOTES.txt (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/templates/_helpers.tpl (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/templates/deployment.yaml (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/templates/ingress.yaml (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/templates/service.yaml (100%) rename {tests/e2e/testdata => test/e2e/data}/foo/values.yaml (100%) create mode 100644 test/e2e/end_to_end_suite_test.go create mode 100644 test/e2e/fixture_test.go create mode 100644 test/e2e/helm_fetch_test.go create mode 100644 test/e2e/helm_repo_add_test.go rename tests/e2e/doc.go => test/e2e/helm_repo_remove_test.go (69%) create mode 100644 test/e2e/helm_s3_delete_test.go create mode 100644 test/e2e/helm_s3_init_test.go create mode 100644 test/e2e/helm_s3_push_test.go create mode 100644 test/e2e/helm_s3_reindex_test.go create mode 100644 test/e2e/require_test.go create mode 100644 test/e2e/string_value_adapter_test.go delete mode 100644 tests/e2e/delete_test.go delete mode 100644 tests/e2e/init_test.go delete mode 100644 tests/e2e/main_test.go delete mode 100644 tests/e2e/push_test.go diff --git a/Makefile b/Makefile index c991d970..e4c80874 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,19 @@ HELM_S3_PLUGIN_LATEST_VERSION ?= $$(awk '/^version:/ {print $$2 ; exit}' plugin. HELM_S3_PLUGIN_NAME ?= s3 HELM_S3_PLUGIN_VERSION ?= $(GIT_REF) +# LocalStack. +LOCALSTACK_HEALTH = $$( \ + ( curl -s $(LOCALSTACK_URL)/health || echo "{}" ) \ + | jq --arg ls_services "$(LOCALSTACK_SERVICES)" \ + '{ "services": ( $$ls_services | split(",") | sort | reduce .[] as $$service ({}; . + { ($$service): "down" } ) ) } * .' \ +) +LOCALSTACK_HEALTH_LINE_COUNT = $$(echo "$(LOCALSTACK_HEALTH)" | jq | wc -l | grep -E -o "[0-9]+") +export LOCALSTACK_SERVICES ?= s3 +LOCALSTACK_STATUS = $$(docker-compose ps | awk '$$1 ~ /localstack_main/ {found=1 ; print $$3} ; END { if (!found) {print "Down" } }') +LOCALSTACK_URL ?= http://localhost.localstack.cloud:4566 + .PHONY: all -all: analyze build ## all runs the entire toolchain configured for local development. +all: analyze build test ## all runs the entire toolchain configured for local development. .PHONY: analyze analyze: ## analyze runs the code analysis tools for new code. @@ -66,23 +77,63 @@ install-plugin-local: ## install-plugin-local installs the Helm plugin with 'loc @ export HELM_PLUGIN_INSTALL_LOCAL=1 ; \ helm plugin install . +.PHONY: reset-e2e-test-env +reset-e2e-test-env: teardown-e2e-test-env-force setup-e2e-test-env ## reset-e22e-test-env sets up a new end to end test environment. + .PHONY: run-container run-container: ## run-container runs the projects container in a throw-away context with the ${CMD} command as argument. @ echo "- Running container" @ docker run --interactive --rm --tty "$(CONTAINER_IMAGE_NAME):$(HELM_S3_PLUGIN_VERSION)" $(CMD) +.PHONY: setup-e2e-test-env +setup-e2e-test-env: ## setup-e2e-test-env sets up the end to end test environment. + @ echo "- Setting up end to end test environment" + @ if [ "$(LOCALSTACK_STATUS)" != "Up" ]; then \ + docker-compose up --detach ; \ + fi + @ echo "Waithing for services to start:" + @ printf "%b%b\n" "$(LOCALSTACK_HEALTH)" "\033[$(LOCALSTACK_HEALTH_LINE_COUNT)F" + @ while $$(echo "$(LOCALSTACK_HEALTH)" | jq '.services | any(. != "running")') ; do \ + printf "%b%b\n" "$(LOCALSTACK_HEALTH)" "\033[$(LOCALSTACK_HEALTH_LINE_COUNT)F" ; \ + sleep 1 ; \ + done + @ echo "$(LOCALSTACK_HEALTH)" | jq + +.PHONY: status-e2e-test-env +status-e2e-test-env: ## status-e2e-test-env returns the current state of the end to end test environment. + @ echo $(LOCALSTACK_STATUS) + +.PHONY: status-e2e-test-env-localstack +status-e2e-test-env-localstack: ## status-e2e-test-env-localstack returns the current state of the end to end test environment's LocalStack instance. + @ echo $(LOCALSTACK_HEALTH) | jq + +.PHONY: teardown-e2e-test-env +teardown-e2e-test-env: ## teardown-e2e-test-env tears down the end to end test environment. + @ echo "- Tearing down end to end test environment" + @ if [ "$(LOCALSTACK_STATUS)" != "Down" ]; then \ + docker-compose down ; \ + fi + +.PHONY: teardown-e2e-test-env-force +teardown-e2e-test-env-force: ## teardown-e2e-test-env-force tears down the end to end test environment. (Required to run 2 teardowns in a single rule.) + @ echo "- Tearing down end to end test environment" + @ docker-compose down + +.PHONY: test +test: test-unit test-e2e ## test runs all tests in the repository. + .PHONY: test-unit test-unit: ## test-unit runs the unit tests in the repository. @ echo "- Running unit tests" @ go test -count 1 -race $$(go list ./... | grep -v $(GO_ROOT_MODULE_PKG)/test/e2e) .PHONY: test-e2e -test-e2e: - go test -v ./tests/e2e/... +test-e2e: reset-e2e-test-env install-plugin-local test-e2e-no-env teardown-e2e-test-env ## test-e2e sets up the end to end testing environment and runs the end to end tests in the repository. -.PHONY: test-e2e-local -test-e2e-local: - @ ./hack/test-e2e-local.sh +.PHONY: test-e2e-no-env +test-e2e-no-env: ## test-e2e-no-env runs the end to end tests without any modifications to the testing environment. + @ echo "- Running end to end tests" + @ go test -count 1 -v $(GO_ROOT_MODULE_PKG)/test/e2e .PHONY: vendor vendor: ## vendor downloads the dependencies to a local vendor folder. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..241f5423 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" + +services: + localstack: + container_name: "${LOCALSTACK_DOCKER_NAME-helm-s3-localstack_main}" + environment: + - LOCALSTACK_DATA_DIR=${LOCALSTACK_DATA_DIR- } + - LOCALSTACK_DEBUG=${LOCALSTACK_DEBUG- } + - LOCALSTACK_DOCKER_HOST=unix:///var/run/docker.sock + - LOCALSTACK_HOST_TMP_FOLDER=${TMPDIR:-/tmp/localstack} + - LOCALSTACK_KINESIS_ERROR_PROBABILITY=${LOCALSTACK_KINESIS_ERROR_PROBABILITY- } + - LOCALSTACK_LAMBDA_EXECUTOR=${LOCALSTACK_LAMBDA_EXECUTOR- } + - LOCALSTACK_PORT_WEB_UI=${LOCALSTACK_PORT_WEB_UI- } + - LOCALSTACK_SERVICES=${LOCALSTACK_SERVICES- } + image: localstack/localstack:0.12.10 + network_mode: bridge + ports: + - "${LOCALSTACK_PORT_WEB_UI-8080}:${LOCALSTACK_PORT_WEB_UI-8080}" + - "4566:4566" + - "4571:4571" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "${TMPDIR:-/tmp/localstack}:/tmp/localstack" diff --git a/go.mod b/go.mod index 30917765..4bca6d31 100644 --- a/go.mod +++ b/go.mod @@ -9,16 +9,22 @@ replace ( ) require ( + emperror.dev/errors v0.8.0 github.com/Masterminds/semver v1.5.0 github.com/Masterminds/semver/v3 v3.1.1 - github.com/aws/aws-sdk-go v1.37.18 + github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect + github.com/aws/aws-sdk-go v1.38.35 github.com/ghodss/yaml v1.0.0 - github.com/google/go-cmp v0.5.2 - github.com/minio/minio-go/v6 v6.0.40 + github.com/google/go-cmp v0.5.5 // indirect + github.com/google/uuid v1.1.2 + github.com/mitchellh/mapstructure v1.4.1 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.0 + go.uber.org/multierr v1.7.0 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 - helm.sh/helm/v3 v3.5.2 + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + helm.sh/helm/v3 v3.5.4 k8s.io/helm v2.17.0+incompatible sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 4fcff16f..444e54a8 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +emperror.dev/errors v0.8.0 h1:4lycVEx0sdJkwDUfQ9pdu6SR0x7rgympt5f4+ok8jDk= +emperror.dev/errors v0.8.0/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -71,8 +73,9 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4= +github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -85,8 +88,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.37.18 h1:SRdWLg+DqMFWX8HB3UvXyAoZpw9IDIUYnSTwgzOYbqg= -github.com/aws/aws-sdk-go v1.37.18/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.38.35 h1:7AlAO0FC+8nFjxiGKEmq0QLpiA8/XFr6eIxgRTwkdTg= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -225,8 +228,9 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -297,8 +301,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -311,12 +316,12 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= @@ -387,7 +392,6 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -438,15 +442,10 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/minio/minio-go/v6 v6.0.40 h1:MlSCSXvItiu2jINMxYdhUU99KR4446Db+0iAU1IKaZ0= -github.com/minio/minio-go/v6 v6.0.40/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= -github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= @@ -454,6 +453,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= @@ -591,10 +592,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= @@ -668,8 +667,13 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -679,7 +683,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -734,7 +737,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -956,8 +958,6 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -971,17 +971,19 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.5.2 h1:Us7qDuUuPYDJhkCo5tVVjfZmC7JlNnEmiqCJHAZVEj0= -helm.sh/helm/v3 v3.5.2/go.mod h1:7+CqT745B1Sy/4dzhzbbY9U08pGnJfrJXBkoEEFj18c= +helm.sh/helm/v3 v3.5.4 h1:FUx2L831YESvMcoNoPTicV0oW/6+es+Tnojw5yGvyVM= +helm.sh/helm/v3 v3.5.4/go.mod h1:44SeYdnTImrEArjDazqgVQVRitFpLEZNYX97NFJyq4k= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -989,38 +991,33 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw= -k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= -k8s.io/apiextensions-apiserver v0.20.2 h1:rfrMWQ87lhd8EzQWRnbQ4gXrniL/yTRBgYH1x1+BLlo= -k8s.io/apiextensions-apiserver v0.20.2/go.mod h1:F6TXp389Xntt+LUq3vw6HFOLttPa0V8821ogLGwb6Zs= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg= -k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apiserver v0.20.2/go.mod h1:2nKd93WyMhZx4Hp3RfgH2K5PhwyTrprrkWYnI7id7jA= -k8s.io/cli-runtime v0.20.1/go.mod h1:6wkMM16ZXTi7Ow3JLYPe10bS+XBnIkL6V9dmEz0mbuY= -k8s.io/cli-runtime v0.20.2 h1:W0/FHdbApnl9oB7xdG643c/Zaf7TZT+43I+zKxwqvhU= -k8s.io/cli-runtime v0.20.2/go.mod h1:FjH6uIZZZP3XmwrXWeeYCbgxcrD6YXxoAykBaWH0VdM= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ= -k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= -k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= -k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0= -k8s.io/component-helpers v0.20.1/go.mod h1:Q8trCj1zyLNdeur6pD2QvsF8d/nWVfK71YjN5+qVXy4= +k8s.io/api v0.20.4 h1:xZjKidCirayzX6tHONRQyTNDVIR55TYVqgATqo6ZULY= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/apiextensions-apiserver v0.20.4 h1:VO/Y5PwBdznMIctX/vvgSNhxffikEmcLC/V1bpbhHhU= +k8s.io/apiextensions-apiserver v0.20.4/go.mod h1:Hzebis/9c6Io5yzHp24Vg4XOkTp1ViMwKP/6gmpsfA4= +k8s.io/apimachinery v0.20.4 h1:vhxQ0PPUUU2Ns1b9r4/UFp13UPs8cw2iOoTjnY9faa0= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/cli-runtime v0.20.4 h1:jVU13lBeebHLtarHeHkoIi3uRONFzccmP7hHLzEoQ4w= +k8s.io/cli-runtime v0.20.4/go.mod h1:dz38e1CM4uuIhy8PMFUZv7qsvIdoE3ByZYlmbHNCkt4= +k8s.io/client-go v0.20.4 h1:85crgh1IotNkLpKYKZHVNI1JT86nr/iDCvq2iWKsql4= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/code-generator v0.20.4/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-helpers v0.20.4/go.mod h1:S7jGg8zQp3kwvSzfuGtNaQAMVmvzomXDioTm5vABn9g= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao= k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.5.0 h1:8mOnjf1RmUPW6KRqQCfYSZq/K20Unmp3IhuZUhxl8KI= +k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kubectl v0.20.1/go.mod h1:2bE0JLYTRDVKDiTREFsjLAx4R2GvUtL/mGYFXfFFMzY= -k8s.io/metrics v0.20.1/go.mod h1:JhpBE/fad3yRGsgEpiZz5FQQM5wJ18OTLkD7Tv40c0s= +k8s.io/kubectl v0.20.4/go.mod h1:yCC5lUQyXRmmtwyxfaakryh9ezzp/bT0O14LeoFLbGo= +k8s.io/metrics v0.20.4/go.mod h1:DDXS+Ls+2NAxRcVhXKghRPa3csljyJRjDRjPe6EOg/g= k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/hack/test-e2e-local.sh b/hack/test-e2e-local.sh deleted file mode 100755 index 80b28dc2..00000000 --- a/hack/test-e2e-local.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -set -e -uo pipefail - -[ -n "${DEBUG:-}" ] && set -x - -## Set up - -export AWS_ACCESS_KEY_ID=EXAMPLEKEY123 -export AWS_SECRET_ACCESS_KEY=EXAMPLESECRET123456 -export AWS_DEFAULT_REGION=us-east-1 -export AWS_ENDPOINT=localhost:9000 -export AWS_DISABLE_SSL=true - -DOCKER_NAME='helm-s3-minio' - -cleanup() { - if docker container ls | grep -q "${DOCKER_NAME}$" ; then - docker container rm --force --volumes "${DOCKER_NAME}" &>/dev/null || : - fi -} - -cleanup - -on_exit() { - if [ -z "${SKIP_CLEANUP:-}" ]; then - cleanup - fi -} -trap on_exit EXIT - -docker container run -d --rm --name "${DOCKER_NAME}" \ - -p 9000:9000 \ - -e MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \ - -e MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \ - minio/minio:latest server /data >/dev/null - -PATH=${GOPATH}/bin:${PATH} -if [ ! -x "$(which mc 2>/dev/null)" ]; then - pushd /tmp > /dev/null - go get github.com/minio/mc - popd > /dev/null -fi - -# give minio time to become service available. -sleep 3 -mc config host add helm-s3-minio http://localhost:9000 $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY -mc mb helm-s3-minio/test-bucket - -go build -o bin/helms3 ./cmd/helms3 - -## Test - -go test -v ./tests/e2e/... -if [ $? -eq 0 ] ; then - echo -e "\nAll tests passed!" -fi diff --git a/internal/helmutil/index_v2_test.go b/internal/helmutil/index_v2_test.go index 363e7248..8c8f50e0 100644 --- a/internal/helmutil/index_v2_test.go +++ b/internal/helmutil/index_v2_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/repo" @@ -25,7 +24,7 @@ func TestIndexV2_MarshalBinary(t *testing.T) { entries: null generated: "2018-01-01T00:00:00Z" ` - assert.Equal(t, expected, string(b)) + require.Equal(t, expected, string(b)) } func TestIndexV2_UnmarshalBinary(t *testing.T) { @@ -57,7 +56,7 @@ func TestIndexV2_AddOrReplace(t *testing.T) { ) require.NoError(t, err) - assert.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) + require.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) }) t.Run("should add a new version of a chart", func(t *testing.T) { @@ -87,8 +86,8 @@ func TestIndexV2_AddOrReplace(t *testing.T) { i.SortEntries() - assert.Equal(t, "http://example.com/charts/foo-0.1.1.tgz", i.index.Entries["foo"][0].URLs[0]) - assert.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) + require.Equal(t, "http://example.com/charts/foo-0.1.1.tgz", i.index.Entries["foo"][0].URLs[0]) + require.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) }) t.Run("should replace existing chart version", func(t *testing.T) { @@ -118,7 +117,7 @@ func TestIndexV2_AddOrReplace(t *testing.T) { require.Len(t, i.index.Entries, 1) - assert.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) - assert.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) + require.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) + require.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) }) } diff --git a/internal/helmutil/index_v3_test.go b/internal/helmutil/index_v3_test.go index b0f07a90..e131620f 100644 --- a/internal/helmutil/index_v3_test.go +++ b/internal/helmutil/index_v3_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/repo" @@ -25,7 +24,7 @@ func TestIndexV3_MarshalBinary(t *testing.T) { entries: null generated: "2018-01-01T00:00:00Z" ` - assert.Equal(t, expected, string(b)) + require.Equal(t, expected, string(b)) } func TestIndexV3_UnmarshalBinary(t *testing.T) { @@ -57,7 +56,7 @@ func TestIndexV3_AddOrReplace(t *testing.T) { ) require.NoError(t, err) - assert.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) + require.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) }) t.Run("should add a new version of a chart", func(t *testing.T) { @@ -87,8 +86,8 @@ func TestIndexV3_AddOrReplace(t *testing.T) { i.SortEntries() - assert.Equal(t, "http://example.com/charts/foo-0.1.1.tgz", i.index.Entries["foo"][0].URLs[0]) - assert.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) + require.Equal(t, "http://example.com/charts/foo-0.1.1.tgz", i.index.Entries["foo"][0].URLs[0]) + require.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) }) t.Run("should replace existing chart version", func(t *testing.T) { @@ -118,7 +117,7 @@ func TestIndexV3_AddOrReplace(t *testing.T) { require.Len(t, i.index.Entries, 1) - assert.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) - assert.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) + require.Equal(t, "http://example.com/charts/foo-0.1.0.tgz", i.index.Entries["foo"][0].URLs[0]) + require.Equal(t, "sha256:222", i.index.Entries["foo"][0].Digest) }) } diff --git a/internal/helmutil/repo_entry_test.go b/internal/helmutil/repo_entry_test.go index 42e40339..4cc936e5 100644 --- a/internal/helmutil/repo_entry_test.go +++ b/internal/helmutil/repo_entry_test.go @@ -3,7 +3,7 @@ package helmutil import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v3/pkg/cli" repo3 "helm.sh/helm/v3/pkg/repo" repo2 "k8s.io/helm/pkg/repo" @@ -84,7 +84,7 @@ func TestLookupRepoEntry(t *testing.T) { entry, err := LookupRepoEntry(tc.name) assertError(t, err, tc.expectError) - assert.Equal(t, tc.expectedEntry, entry) + require.Equal(t, tc.expectedEntry, entry) }) } } diff --git a/internal/helmutil/repo_entry_v2_test.go b/internal/helmutil/repo_entry_v2_test.go index b82b9a50..ab868bed 100644 --- a/internal/helmutil/repo_entry_v2_test.go +++ b/internal/helmutil/repo_entry_v2_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/helm/pkg/repo" ) @@ -36,7 +36,7 @@ func TestRepoEntryV2_URL(t *testing.T) { for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.url, tc.entry.URL()) + require.Equal(t, tc.url, tc.entry.URL()) }) } } @@ -69,7 +69,7 @@ func TestRepoEntryV2_IndexURL(t *testing.T) { for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.url, tc.entry.IndexURL()) + require.Equal(t, tc.url, tc.entry.IndexURL()) }) } } @@ -127,7 +127,7 @@ func TestRepoEntryV2_CacheFile(t *testing.T) { for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.cacheFile, tc.entry.CacheFile()) + require.Equal(t, tc.cacheFile, tc.entry.CacheFile()) }) } } @@ -209,7 +209,7 @@ func TestLookupV2(t *testing.T) { entry, err := lookupV2(tc.name) assertError(t, err, tc.expectError) - assert.Equal(t, tc.expectedEntry, entry) + require.Equal(t, tc.expectedEntry, entry) }) } } diff --git a/internal/helmutil/repo_entry_v3_test.go b/internal/helmutil/repo_entry_v3_test.go index b6cca85d..b5243f0c 100644 --- a/internal/helmutil/repo_entry_v3_test.go +++ b/internal/helmutil/repo_entry_v3_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/repo" ) @@ -37,7 +37,7 @@ func TestRepoEntryV3_URL(t *testing.T) { for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.url, tc.entry.URL()) + require.Equal(t, tc.url, tc.entry.URL()) }) } } @@ -70,7 +70,7 @@ func TestRepoEntryV3_IndexURL(t *testing.T) { for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.url, tc.entry.IndexURL()) + require.Equal(t, tc.url, tc.entry.IndexURL()) }) } } @@ -125,7 +125,7 @@ func TestRepoEntryV3_CacheFile(t *testing.T) { for name, tc := range testCases { tc := tc t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.cacheFile, tc.entry.CacheFile()) + require.Equal(t, tc.cacheFile, tc.entry.CacheFile()) }) } } @@ -210,7 +210,7 @@ func TestLookupV3(t *testing.T) { entry, err := lookupV3(tc.name) assertError(t, err, tc.expectError) - assert.Equal(t, tc.expectedEntry, entry) + require.Equal(t, tc.expectedEntry, entry) }) } } diff --git a/internal/helmutil/testing_test.go b/internal/helmutil/testing_test.go index 3a1031a5..48bb63ac 100644 --- a/internal/helmutil/testing_test.go +++ b/internal/helmutil/testing_test.go @@ -6,7 +6,6 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -56,8 +55,8 @@ func mockEnvs(t *testing.T, nameValue ...string) func() { func assertError(t *testing.T, err error, expected bool) { if expected { - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } } diff --git a/internal/helmutil/version_test.go b/internal/helmutil/version_test.go index 806f6feb..009cf440 100644 --- a/internal/helmutil/version_test.go +++ b/internal/helmutil/version_test.go @@ -3,7 +3,7 @@ package helmutil import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIsHelm3(t *testing.T) { @@ -91,7 +91,7 @@ func TestIsHelm3(t *testing.T) { teardown := tc.setup() defer teardown() - assert.Equal(t, tc.isHelm3, IsHelm3()) + require.Equal(t, tc.isHelm3, IsHelm3()) }) } } diff --git a/test/e2e/aws_log_level_value_adapter_test.go b/test/e2e/aws_log_level_value_adapter_test.go new file mode 100644 index 00000000..653adb1c --- /dev/null +++ b/test/e2e/aws_log_level_value_adapter_test.go @@ -0,0 +1,102 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "os" + "strconv" + + "github.com/aws/aws-sdk-go/aws" +) + +// awsLogLevelValueAdapter describes an interface to obtain AWS log level values +// from any source. +type awsLogLevelValueAdapter interface { + // AWSLogLevelValue returns an AWS log level value obtained from a custom + // source. + AWSLogLevelValue() (value aws.LogLevelType, isSet bool) +} + +// dynamicAWSLogLevelValueAdapter implements a stringValueAdapter for dynamic +// sources. +type dynamicAWSLogLevelValueAdapter struct { + valueFunction func() (aws.LogLevelType, bool) +} + +// AWSLogLevel returns an AWS log level value obtained from a custom source. +func (adapter *dynamicAWSLogLevelValueAdapter) AWSLogLevelValue() (value aws.LogLevelType, isSet bool) { + if adapter == nil { + return aws.LogOff, false + } + + return adapter.valueFunction() +} + +// newDynamicAWSLogLevelValueAdapter returns an awsLogLevelValueAdapter object +// from the specified function to use as a dynamic AWS log level value source. +func newDynamicAWSLogLevelValueAdapter(valueFunction func() (aws.LogLevelType, bool)) awsLogLevelValueAdapter { + return &dynamicAWSLogLevelValueAdapter{ + valueFunction: valueFunction, + } +} + +// newEnvironmentAWSLogLevelValueAdapter returns an awsLogLevelValueAdapter +// object from the specified environment key to use as an OS environment +// AWSLogLevel value source. +func newEnvironmentAWSLogLevelValueAdapter(key string) awsLogLevelValueAdapter { + return newDynamicAWSLogLevelValueAdapter( + func() (aws.LogLevelType, bool) { + awsLogLevelString, isSet := os.LookupEnv(key) + if !isSet { + return aws.LogOff, false + } + + awsLogLevelUint64, err := strconv.ParseUint(awsLogLevelString, 10, 64) + if err != nil { + return aws.LogOff, false + } + + return aws.LogLevelType(awsLogLevelUint64), true + }, + ) +} + +// newFirstAWSLogLevelValueAdapter returns an awsLogLevelValueAdapter object +// from the specified adapters to use the first set value among the adapters as +// a AWSLogLevel source. +func newFirstAWSLogLevelValueAdapter(adapters ...awsLogLevelValueAdapter) awsLogLevelValueAdapter { + return newDynamicAWSLogLevelValueAdapter( + func() (aws.LogLevelType, bool) { + for _, adapter := range adapters { + if value, isSet := adapter.AWSLogLevelValue(); isSet { + return value, true + } + } + + return aws.LogOff, false + }, + ) +} + +// newStaticAWSLogLevelValueAdapter returns an awsLogLevelValueAdapter object +// from the specified AWSLogLevel to use the static value as a AWSLogLevel value +// source. +func newStaticAWSLogLevelValueAdapter(value aws.LogLevelType) awsLogLevelValueAdapter { + return newDynamicAWSLogLevelValueAdapter( + func() (aws.LogLevelType, bool) { + return value, true + }, + ) +} diff --git a/tests/e2e/testdata/foo-1.2.3.tgz b/test/e2e/data/foo-1.2.3.tgz similarity index 100% rename from tests/e2e/testdata/foo-1.2.3.tgz rename to test/e2e/data/foo-1.2.3.tgz diff --git a/tests/e2e/testdata/foo/.helmignore b/test/e2e/data/foo/.helmignore similarity index 100% rename from tests/e2e/testdata/foo/.helmignore rename to test/e2e/data/foo/.helmignore diff --git a/tests/e2e/testdata/foo/Chart.yaml b/test/e2e/data/foo/Chart.yaml similarity index 100% rename from tests/e2e/testdata/foo/Chart.yaml rename to test/e2e/data/foo/Chart.yaml diff --git a/tests/e2e/testdata/foo/templates/NOTES.txt b/test/e2e/data/foo/templates/NOTES.txt similarity index 100% rename from tests/e2e/testdata/foo/templates/NOTES.txt rename to test/e2e/data/foo/templates/NOTES.txt diff --git a/tests/e2e/testdata/foo/templates/_helpers.tpl b/test/e2e/data/foo/templates/_helpers.tpl similarity index 100% rename from tests/e2e/testdata/foo/templates/_helpers.tpl rename to test/e2e/data/foo/templates/_helpers.tpl diff --git a/tests/e2e/testdata/foo/templates/deployment.yaml b/test/e2e/data/foo/templates/deployment.yaml similarity index 100% rename from tests/e2e/testdata/foo/templates/deployment.yaml rename to test/e2e/data/foo/templates/deployment.yaml diff --git a/tests/e2e/testdata/foo/templates/ingress.yaml b/test/e2e/data/foo/templates/ingress.yaml similarity index 100% rename from tests/e2e/testdata/foo/templates/ingress.yaml rename to test/e2e/data/foo/templates/ingress.yaml diff --git a/tests/e2e/testdata/foo/templates/service.yaml b/test/e2e/data/foo/templates/service.yaml similarity index 100% rename from tests/e2e/testdata/foo/templates/service.yaml rename to test/e2e/data/foo/templates/service.yaml diff --git a/tests/e2e/testdata/foo/values.yaml b/test/e2e/data/foo/values.yaml similarity index 100% rename from tests/e2e/testdata/foo/values.yaml rename to test/e2e/data/foo/values.yaml diff --git a/test/e2e/end_to_end_suite_test.go b/test/e2e/end_to_end_suite_test.go new file mode 100644 index 00000000..cb4ff367 --- /dev/null +++ b/test/e2e/end_to_end_suite_test.go @@ -0,0 +1,120 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/stretchr/testify/suite" +) + +// EndToEndSuite collects the end to end tests in a test suite. +type EndToEndSuite struct { + // Suite provides testify test suite functionality as base class. + suite.Suite + + s3Client *s3.S3 + testBucketNames map[string]string +} + +// TestEndToEndSuite initiates the end to end test suite. Required for go test +// to run the testify test suite. +func TestEndToEndSuite(t *testing.T) { + t.Parallel() + + suite.Run(t, new(EndToEndSuite)) +} + +// AfterTest executes test-specific behavior right after a test is run. +func (testSuite *EndToEndSuite) AfterTest(suiteName, testName string) { + if testSuite == nil { + return + } + + bucketName := testSuite.AWSS3BucketName(testName) + + removeHelmRepository(testSuite.T(), bucketName) + deleteAWSS3Bucket(testSuite.T(), testSuite.AWSS3Client(), bucketName) + deleteDirectory(testSuite.T(), temporaryDirectoryPath(bucketName)) + testSuite.testBucketNames[testName] = "" +} + +// AWSS3BucketName returns the name of the bucket corresponding of the specified +// test names's test. +func (testSuite *EndToEndSuite) AWSS3BucketName(testName string) string { + if testSuite == nil || + testSuite.testBucketNames == nil { + return "" + } + + return testSuite.testBucketNames[testName] +} + +// AWSS3Client returns the AWS S3 client object associated with the test suite. +func (testSuite *EndToEndSuite) AWSS3Client() *s3.S3 { + if testSuite == nil { + return nil + } + + return testSuite.s3Client +} + +// BeforeTest executes test-specific behavior right before a test is run. +func (testSuite *EndToEndSuite) BeforeTest(suiteName, testName string) { + if testSuite == nil { + return + } + + bucketName := newUniqueBucketName(toLowerWordsFromCamelOrPascalCase(testName)...) + repositoryURI := helmS3RepositoryURI(bucketName) + + testSuite.testBucketNames[testName] = bucketName + createDirectory(testSuite.T(), temporaryDirectoryPath(bucketName), 0o755) // nolint:gocritic // Note: intentional. + createAWSS3Bucket(testSuite.T(), testSuite.AWSS3Client(), bucketName) + initializeHelmS3Repository(testSuite.T(), testSuite.s3Client, bucketName, repositoryURI) + addHelmRepository(testSuite.T(), bucketName, repositoryURI) +} + +// SetupSuite executes suite-independent behavior right before a suite is run. +func (testSuite *EndToEndSuite) SetupSuite() { + if testSuite == nil { + return + } + + testSuite.testBucketNames = make(map[string]string) + + awsConfiguration := newAWSConfiguration() + + checkAWSEnvironment(testSuite.T(), awsConfiguration) + initializeAWSEnvironment(testSuite.T(), awsConfiguration) + + testSuite.s3Client = s3.New(session.Must(session.NewSession(awsConfiguration))) + + _ = listAWSS3Buckets(testSuite.T(), testSuite.s3Client) // Note: API connection test. + + initializeHelmEnvironment(testSuite.T()) +} + +// TemporaryPath returns a path pointing into the test's temporary directory +// with a subpath based on the specified path elements. +func (testSuite *EndToEndSuite) TemporaryPath(testName string, pathElements ...string) string { + if testSuite == nil { + return "" + } + + return temporaryDirectoryPath(append([]string{testSuite.AWSS3BucketName(testName)}, pathElements...)...) +} diff --git a/test/e2e/fixture_test.go b/test/e2e/fixture_test.go new file mode 100644 index 00000000..0f7107ae --- /dev/null +++ b/test/e2e/fixture_test.go @@ -0,0 +1,1039 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + "unicode" + + "emperror.dev/errors" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/google/uuid" + "github.com/mitchellh/mapstructure" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/repo" +) + +const ( + // AwsOperationConfirmationKey is the environment key to use to confirm AWS + // operations. + awsOperationConfirmationKey string = "HELM_S3_CONFIRM_AWS_OPERATIONS" + + // AwsS3BucketRepositoryPath is the relative path to the Helm S3 repository + // from the AWS S3 bucket root. + awsS3BucketRepositoryPath string = "charts" + + // DefaultHelmChartContentType is the default content type of a Helm chart + // S3 object. + defaultHelmChartContentType string = "application/gzip" + + // DefaultRegion describes the default region to use when no explicit region is + // specified. Required for bucket creation. + defaultRegion string = "eu-central-1" + + // Helmv2StructTag is the struct tag used for Helm major version 2. + helmv2StructTag string = "helmv2" + + // Helmv3StructTag is the struct tag used for Helm major version 3. + helmv3StructTag string = "helmv3" + + // LocalStackAccessKeyID is the access key ID for a default configured + // LocalStack instance. + localStackAccessKeyID string = "test" + + // LocalStackStatusRunning defines the running status of the LocalStack server. + localStackStatusRunning localStackStatusType = "running" + + // MakeEndToEndTestEnvironmentSetupRule is the make rule which sets up the + // end to end test environment. + makeEndToEndTestEnvironmentSetupRule string = "make setup-e2e-test-env" + + // TestDataRootDirectory is the relative path to the directory containing + // the generic test data files. + testDataRootDirectory = "data" +) + +// exampleChart returns an example chart. +var exampleChart helmChart = helmChart{ // nolint:gochecknoglobals // Note: intentional. + Name: "foo", + Version: "1.2.3", + AppVersion: "1.2.3", + Description: "A Helm chart for Kubernetes", +} + +// helmChart collects information about a Helm chart known to the Helm binary. +type helmChart struct { + Name string `helmv2:"Name" helmv3:"name"` + Version string `helmv2:"Version" helmv3:"version"` + AppVersion string `helmv2:"AppVersion" helmv3:"app_version"` + Description string `helmv2:"Description" helmv3:"description"` +} + +// helmRepository collects information about a Helm repository known to the Helm +// binary. +type helmRepository struct { + Name string `helmv2:"Name" helmv3:"name"` + URL string `helmv2:"URL" helmv3:"url"` +} + +// localStackStatusType collects the possible status values for the LocalStack +// instance. +type localStackStatusType string + +// addHelmRepository adds a Helm repository to the local helm cache. +func addHelmRepository(t *testing.T, repositoryName, repositoryURI string) { + t.Helper() + + require.NotContains(t, listHelmRepositoryNames(t), repositoryName) + + output, errorOutput, err := runCommand("helm", "repo", "add", repositoryName, repositoryURI) + expectedOutput := fmt.Sprintf("\"%s\" has been added to your repositories\n", repositoryName) + requireCommandOutput(t, expectedOutput, "", nil, output, errorOutput, err) + + require.Contains(t, listHelmRepositoryNames(t), repositoryName) +} + +// checkAWSEnvironment checks whether the test is run in a real AWS environment +// or against Localstack. +func checkAWSEnvironment(t *testing.T, awsConfiguration *aws.Config) { + t.Helper() + + awsCredentials, err := awsConfiguration.Credentials.Get() + require.NoError(t, err, "retrieving AWS credentials failed") + + if awsCredentials.AccessKeyID != localStackAccessKeyID && + os.Getenv(awsOperationConfirmationKey) != "1" { + fmt.Printf( + "WARNING: the AWS access key ID '%s' seems to be a non-LocalStack ID (!= 'test')."+ + "\nTests might execute real AWS calls and create/read/update/delete actual AWS buckets with costs."+ + "\n\nIf you want to run the end to end tests in LocalStack, set its environment up with `%s`."+ + " If you want to use the AWS environment, set the `%s` environment variable to 1 to proceed.\n\n", + awsCredentials.AccessKeyID, + makeEndToEndTestEnvironmentSetupRule, + awsOperationConfirmationKey, + ) + + t.Fatal( + errors.Errorf( + "not proceeding with AWS operations without confirmation (`%s`)", + awsOperationConfirmationKey, + ).Error(), + ) + + return + } +} + +// containsString determines whether the specified collection contains the +// provided string. +func containsString(collection []string, text string) bool { + for _, item := range collection { + if item == text { + return true + } + } + + return false +} + +// createAWSS3Bucket creates an AWS bucket. +func createAWSS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) { + t.Helper() + + require.NotContains(t, listAWSS3Buckets(t, s3Client), bucketName) + + _, err := s3Client.CreateBucket( + &s3.CreateBucketInput{ // nolint:exhaustivestruct // Note: optional query values. + Bucket: aws.String(bucketName), + CreateBucketConfiguration: &s3.CreateBucketConfiguration{ + LocationConstraint: aws.String(defaultRegion), + }, + }) + require.NoError(t, err, "creating bucket failed, bucket: %s", bucketName) + + require.Contains(t, listAWSS3Buckets(t, s3Client), bucketName) +} + +// createDirectory creates a local directory. +func createDirectory(t *testing.T, directoryPath string, mode os.FileMode) { // nolint:lll // Note: temporary. // Postpone: replace with fs.FileMode at Go 1.18. + t.Helper() + + require.NoDirExists(t, directoryPath) + + err := os.MkdirAll(directoryPath, mode) + require.NoError(t, err, "creating directories failed, path: %s", directoryPath) + + require.DirExists(t, directoryPath) +} + +// deleteAWSS3Bucket deletes the specified AWS bucket. +func deleteAWSS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) { + t.Helper() + + if !containsString(listAWSS3Buckets(t, s3Client), bucketName) { + return + } + + deleteAWSS3Objects(t, s3Client, bucketName, listAWSS3ObjectKeys(t, s3Client, bucketName)...) + + _, err := s3Client.DeleteBucket( + &s3.DeleteBucketInput{ // nolint:exhaustivestruct // Note: optional query values. + Bucket: aws.String(bucketName), + }, + ) + require.NoError(t, err, "deleting bucket failed, bucket: %s", bucketName) + + require.NotContains(t, listAWSS3Buckets(t, s3Client), bucketName) +} + +// deleteAWSS3Objects deletes the bucket objects behind the specified keys. +func deleteAWSS3Objects(t *testing.T, s3Client *s3.S3, bucketName string, keys ...string) { + t.Helper() + + if len(keys) == 0 { + return + } + + require.Contains(t, listAWSS3Buckets(t, s3Client), bucketName) + + identifiers := make([]*s3.ObjectIdentifier, 0, len(keys)) + + for _, key := range keys { + _ = getAWSS3Object(t, s3Client, bucketName, key) + + identifiers = append(identifiers, + &s3.ObjectIdentifier{ // nolint:exhaustivestruct // Note: optional query options. + Key: aws.String(key), + }, + ) + } + + _, err := s3Client.DeleteObjects( + &s3.DeleteObjectsInput{ // nolint:exhaustivestruct // Note: optional query values. + Bucket: aws.String(bucketName), + Delete: &s3.Delete{ // nolint:exhaustivestruct // Note: optional query values. + Objects: identifiers, + }, + }, + ) + require.NoError(t, err, "deleting objects failed, bucket: %s, keys: %+v", bucketName, keys) + + for _, key := range keys { + getNoAWSS3Object(t, s3Client, bucketName, key) + } +} + +// DeleteDirectory deletes a local directory. +func deleteDirectory(t *testing.T, directoryPath string) { + t.Helper() + + _, err := os.Stat(directoryPath) + if err != nil { + return + } + + err = os.RemoveAll(directoryPath) + require.NoError(t, err, "removing directories failed, path: %s", directoryPath) + + require.NoDirExists(t, directoryPath) +} + +// deleteFile deletes a local file. +func deleteFile(t *testing.T, filePath string) { + t.Helper() + + _, err := os.Stat(filePath) + if err != nil { + return + } + + err = os.Remove(filePath) + require.NoError(t, err, "removing file failed, path: %s", filePath) + + require.NoFileExists(t, filePath) +} + +// deleteHelmS3Chart deletes the specified chart from the provided repository. +func deleteHelmS3Chart(t *testing.T, repositoryName, chartName, chartVersion string) { + t.Helper() + + var chart helmChart + + charts := searchHelmCharts(t, repositoryName, chartName) + + for chartIndex := range charts { + if charts[chartIndex].Version == chartVersion { + chart = charts[chartIndex] + + break + } + } + + if chart.Name == "" { + return + } + + output, errorOutput, err := runCommand( + "helm", "s3", "delete", + chart.Name, + "--version", chart.Version, + repositoryName, + ) + requireCommandOutput(t, "", "", nil, output, errorOutput, err) + + require.Empty(t, searchHelmCharts(t, repositoryName, chart.Name)) +} + +// FetchHelmChart fetches the specified Helm chart. +func fetchHelmChart(t *testing.T, repositoryName, chartName, chartVersion, destination string) { + t.Helper() + + if destination == "" { + destination = "." + } + + require.NoFileExists(t, destination) + + var chart helmChart + + charts := searchHelmCharts(t, repositoryName, chartName) + + for chartIndex := range charts { + if charts[chartIndex].Version == chartVersion { + chart = charts[chartIndex] + + break + } + } + + require.NotEmpty(t, chart, "chart not found among charts, chart: %+v, charts: %+v", chart, charts) + + _, err := os.Stat(destination) + if os.IsNotExist(err) { + createDirectory(t, destination, 0o755) // nolint:gocritic // Note: intentional. + } + + output, errorOutput, err := runCommand( + "helm", "fetch", + path.Join(repositoryName, chartName), + "--destination", destination, + "--version", chartVersion, + ) + requireCommandOutput(t, "", "", nil, output, errorOutput, err) + + require.FileExists(t, path.Join(destination, helmChartFileName(chartName, chartVersion))) +} + +// GetAWSS3Object retrieves an AWS S3 bucket object. +func getAWSS3Object(t *testing.T, s3Client *s3.S3, bucketName, objectKey string) *s3.GetObjectOutput { + t.Helper() + + require.Contains(t, listAWSS3Buckets(t, s3Client), bucketName) + + getObjectOutput, err := s3Client.GetObject( + &s3.GetObjectInput{ // nolint:exhaustivestruct // Note: optional query values. + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + }, + ) + require.NoError(t, err, "retrieving AWS S3 object failed, bucket: %s, key: %s", bucketName, objectKey) + + return getObjectOutput +} + +// getNoAWSS3Object ensures no AWS S3 bucket object can be retrieved with the +// specified key. +func getNoAWSS3Object(t *testing.T, s3Client *s3.S3, bucketName, objectKey string) { + t.Helper() + + require.Contains(t, listAWSS3Buckets(t, s3Client), bucketName) + + _, err := s3Client.GetObject( + &s3.GetObjectInput{ // nolint:exhaustivestruct // Note: optional query values. + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + }, + ) + requireAWSErrorCode(t, s3.ErrCodeNoSuchKey, err) +} + +// helmChartFileName returns the name for the helm chart file corresponding to +// the specified chart. +func helmChartFileName(chartName, chartVersion string) string { + return fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) +} + +// helmIndexChartVersion ireturns the Helm index's corresponding chart version +// for the specified chart name and provided chart version. +func helmIndexChartVersion(t *testing.T, indexPath, chartName, chartVersion string) *repo.ChartVersion { + t.Helper() + + index, err := repo.LoadIndexFile(indexPath) + require.NoError(t, err, "loading index file failed, path: %s", indexPath) + + indexChartVersion, err := index.Get(chartName, chartVersion) + require.NoError( + t, + err, + "retrieving chert version from index failed, chart: %s, version: %s, index: %s", + chartName, chartVersion, index.Entries, + ) + + return indexChartVersion +} + +// helmS3RepositoryChartPath returns the relative path to the specified chart on +// the Helm repository. Existence of the chart is not ensured. +func helmS3RepositoryChartPath(chartName, chartVersion string) string { + return helmS3RepositoryFilePath(helmChartFileName(chartName, chartVersion)) +} + +// helmS3RepositoryFilePath returns the relative path to the Helm repository's +// file with the specified name. +func helmS3RepositoryFilePath(fileName string) string { + return path.Join(awsS3BucketRepositoryPath, fileName) +} + +// helmS3RepositoryURI returns the URI of the Helm repository corresponding to +// the specified bucket name and repository subpath.. +func helmS3RepositoryURI(bucketName string) string { + return fmt.Sprintf("s3://%s/%s", bucketName, awsS3BucketRepositoryPath) +} + +// helmSearchCommand returns the Helm search command based on the Helm major +// version. +func helmSearchCommand(t *testing.T) []string { + t.Helper() + + version := helmVersion(t) + + switch { + case strings.HasPrefix(version, "v2."): + return []string{"helm", "search"} + case strings.HasPrefix(version, "v3."): + return []string{"helm", "search", "repo"} + default: + t.Fatalf("unsupported Helm version, version: %s", version) + + return nil // Note: for compiler code analysis. + } +} + +// helmStructTag returns the struct tag for the current Helm binary version. +func helmStructTag(t *testing.T) string { + t.Helper() + + version := helmVersion(t) + + switch { + case strings.HasPrefix(version, "v2."): + return helmv2StructTag + case strings.HasPrefix(version, "v3."): + return helmv3StructTag + default: + t.Fatalf("unsupported Helm version, version: %s", version) + + return "" // Note: for compiler code analysis. + } +} + +// helmVersion returns the version of the Helm binary. +func helmVersion(t *testing.T) string { + t.Helper() + + versionRawRegexp := `^.+:"(v?[0-9]+\.[0-9]+\.[0-9]+)".+` + versionRegexp, err := regexp.Compile(versionRawRegexp) + require.NoError(t, err, "raw regular expression: %s", versionRawRegexp) + + output, errorOutput, err := runCommand("helm", "version", "--client") + require.NoError(t, err, "output: %s, errorOutput: %s", output, errorOutput) + require.Empty(t, errorOutput, "output: %s", output) + + groups := versionRegexp.FindStringSubmatch(output) + if len(groups) < 2 { + t.Fatalf("Helm version cannot be determined, version output: %s", output) + } + + return groups[1] +} + +// initializeAWSEnvironment initializes the AWS environment. (AWS credentials, +// endpoint, region are required for Helm.) +func initializeAWSEnvironment(t *testing.T, awsConfiguration *aws.Config) { + t.Helper() + + awsCredentials, err := awsConfiguration.Credentials.Get() + require.NoError(t, err, "retrieving AWS credentials failed") + + err = os.Setenv("AWS_ACCESS_KEY_ID", awsCredentials.AccessKeyID) + require.NoError(t, err, "setting environment variable AWS_ACCESS_KEY_ID failed") + + if awsConfiguration.Endpoint != nil { + err = os.Setenv("AWS_ENDPOINT", aws.StringValue(awsConfiguration.Endpoint)) + require.NoError(t, err, "setting environment variable AWS_ENDPOINT failed") + } + + if awsConfiguration.Region != nil { + err = os.Setenv("AWS_REGION", aws.StringValue(awsConfiguration.Region)) + require.NoError(t, err, "setting environment variable AWS_REGION failed") + } + + err = os.Setenv("AWS_SECRET_ACCESS_KEY", awsCredentials.SecretAccessKey) + require.NoError(t, err, "setting environment variable AWS_SECRET_ACCESS_KEY failed") +} + +// initializeHelmEnvironment initializes the Helm environment. (Helm repo list +// requires an existing repository config file even for formatted output to +// return no error on empty repository list.) +func initializeHelmEnvironment(t *testing.T) { + t.Helper() + + version := helmVersion(t) + + switch { + case strings.HasPrefix(version, "v2."): + output, errorOutput, err := runCommand("helm", "init", "--client-only") + require.NoError(t, err, "output: %s, errorOutput: %s", output, errorOutput) + require.Empty(t, errorOutput, "output: %s", output) + case strings.HasPrefix(version, "v3."): + output, errorOutput, err := runCommand("helm", "env", "HELM_REPOSITORY_CONFIG") + require.NoError(t, err, "output: %s, errorOutput: %s", output, errorOutput) + + _, err = os.Stat(output) + if os.IsNotExist(err) { + expectedAddedOutput := "\"stable\" has been added to your repositories\n" + expectedAlreadyExistsOutput := "\"stable\" already exists with the same configuration, skipping\n" + expectedRemovedOutput := "\"stable\" has been removed from your repositories\n" + + output, errorOutput, err = runCommand("helm", "repo", "add", "stable", "https://charts.helm.sh/stable") + require.NoError(t, err, "output: %s, errorOutput: %s") + require.Equal(t, "", errorOutput) + require.Contains(t, []string{expectedAddedOutput, expectedAlreadyExistsOutput}, output, "output: %s", output) + + if output == expectedAddedOutput { + output, errorOutput, err = runCommand("helm", "repo", "remove", "stable") + requireCommandOutput(t, expectedRemovedOutput, "", nil, output, errorOutput, err) + } + } + default: + t.Fatalf("invalid Helm version, Helm version: %s", version) + } +} + +// initializeHelmS3Repository initializes an AWS S3 Helm repository at the +// specified URI. +func initializeHelmS3Repository(t *testing.T, s3Client *s3.S3, bucketName, repositoryURI string) { + t.Helper() + + require.Len(t, listAWSS3ObjectKeys(t, s3Client, bucketName), 0) + + output, errorOutput, err := runCommand("helm", "s3", "init", repositoryURI) + expectedOutput := fmt.Sprintf("Initialized empty repository at %s\n", repositoryURI) + requireCommandOutput(t, expectedOutput, "", nil, output, errorOutput, err) + + _ = getAWSS3Object(t, s3Client, bucketName, helmS3RepositoryFilePath("index.yaml")) +} + +// listAWSS3Buckets returns the list of AWS buckets. +func listAWSS3Buckets(t *testing.T, s3Client *s3.S3) []string { + t.Helper() + + listBucketsOutput, err := s3Client.ListBuckets(&s3.ListBucketsInput{}) + require.NoError(t, err, "listing buckets failed") + + buckets := make([]string, 0, len(listBucketsOutput.Buckets)) + for _, bucket := range listBucketsOutput.Buckets { + buckets = append(buckets, aws.StringValue(bucket.Name)) + } + + return buckets +} + +// listAWSS3ObjectKeys returns the specified bucket's AWS bucket object keys. +func listAWSS3ObjectKeys(t *testing.T, s3Client *s3.S3, bucketName string) []string { + t.Helper() + + require.Contains(t, listAWSS3Buckets(t, s3Client), bucketName) + + listObjectsOutput, err := s3Client.ListObjectsV2( + &s3.ListObjectsV2Input{ // nolint:exhaustivestruct // Note: optional query values. + Bucket: aws.String(bucketName), + }, + ) + require.NoError(t, err, "listing objects failed, bucket: %s", bucketName) + + objectKeys := make([]string, 0, len(listObjectsOutput.Contents)) + for _, object := range listObjectsOutput.Contents { + objectKeys = append(objectKeys, aws.StringValue(object.Key)) + } + + return objectKeys +} + +// listHelmRepositoryNames returns the names of the Helm repositories. +func listHelmRepositoryNames(t *testing.T) []string { + t.Helper() + + output, errorOutput, err := runCommand("helm", "repo", "list", "--output", "yaml") + require.NoError(t, err, "output: %s, errorOutput: %s", output, errorOutput) + + var yamlOutput interface{} + err = yaml.Unmarshal([]byte(output), &yamlOutput) + require.NoError(t, err, "parsing Helm repo list YAML failed, YAML: %s", output) + + var repositories []helmRepository + + decoderConfig := &mapstructure.DecoderConfig{ + DecodeHook: nil, + ErrorUnused: true, + ZeroFields: true, + WeaklyTypedInput: false, + Squash: true, + Metadata: nil, + Result: &repositories, + TagName: helmStructTag(t), + } + decoder, err := mapstructure.NewDecoder(decoderConfig) + require.NoError(t, err, "creating Helm repo list YAML decoder failed, config: %+v", decoderConfig) + + err = decoder.Decode(yamlOutput) + require.NoError(t, err, "decoding Helm repo list YAML failed, YAML: %s, config: %s", yamlOutput, decoderConfig) + + repositoryNames := make([]string, 0, len(repositories)) + + for repositoryIndex := range repositories { + repositoryNames = append(repositoryNames, repositories[repositoryIndex].Name) + } + + return repositoryNames +} + +// localStackStatus returns the current status of the LocalStack instance +// behind the specified URL or alternatively an error. +func localStackStatus(localStackURL string) (localStackStatusType, error) { + request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, localStackURL, nil) + if err != nil { + return "", errors.WrapWithDetails( + err, + "creating LocalStack check request failed", + "localStackURL", localStackURL, + ) + } + + response, err := http.DefaultClient.Do(request) + if err != nil { + return "", errors.WrapWithDetails(err, "checking LocalStack request failed", "localStackURL", localStackURL) + } else if response == nil { + return "", errors.WithDetails( + errors.Errorf("receiving LocalStack check response failed"), + "localStackURL", localStackURL, + ) + } + + defer func() { _ = response.Body.Close() }() + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + responseDump, _ := httputil.DumpResponse(response, true) + + return "", errors.WrapWithDetails( + err, + "reading LocalStack check response failed", + "localStackURL", localStackURL, + "response", string(responseDump), + ) + } + + var parsedData map[string]interface{} + if err = json.Unmarshal(data, &parsedData); err != nil { + return "", errors.WrapWithDetails( + err, + "parsing LocalStack check response data failed", + "localStackURL", localStackURL, + "data", string(data), + ) + } + + stringStatus, isOk := parsedData["status"].(string) + if !isOk { + return "", errors.WithDetails( + errors.Errorf("status field is not a string"), + "status", parsedData["status"], + "localStackURL", localStackURL, + ) + } + + return localStackStatusType(stringStatus), nil +} + +// newTestAWSConfiguration initializes an AWS configuration context for testing +// purposes based on the executing environment. +func newAWSConfiguration() *aws.Config { // nolint:funlen // Note: easier to understand. + awsCredentialsFilePath, _ := newFirstStringValueAdapter( + newEnvironmentStringValueAdapter("AWS_SHARED_CREDENTIALS_FILE"), + newFilePathStringValueAdapter(filepath.Join(os.Getenv("HOME"), ".aws", "credentials")), + ).StringValue() + + awsEndpoint, hasAWSEndpoint := newFirstStringValueAdapter( + newEnvironmentStringValueAdapter("AWS_ENDPOINT"), + newEnvironmentStringValueAdapter("LOCALSTACK_AWS_ENDPOINT"), + newDynamicStringValueAdapter(func() (string, bool) { + defaultURL := "http://s3.localhost.localstack.cloud:4566" // Note: for AWS S3 HEAD bucket. + if status, err := localStackStatus(defaultURL); err != nil || status != localStackStatusRunning { + return "", false + } + + return defaultURL, true + }), + ).StringValue() + + isUsingLocalStack := false + if status, err := localStackStatus(awsEndpoint); err == nil && status == localStackStatusRunning { + isUsingLocalStack = true + } + + awsLogLevel, hasAWSLogLevel := newFirstAWSLogLevelValueAdapter( + newEnvironmentAWSLogLevelValueAdapter("AWS_LOG_LEVEL"), + newEnvironmentAWSLogLevelValueAdapter("LOCALSTACK_AWS_LOG_LEVEL"), + newDynamicAWSLogLevelValueAdapter(func() (aws.LogLevelType, bool) { + _, isSet := os.LookupEnv("DEBUG") + if !isSet { + return aws.LogOff, false + } + + return aws.LogDebugWithHTTPBody, true + }), + newStaticAWSLogLevelValueAdapter(aws.LogOff), + ).AWSLogLevelValue() + + awsProfile, _ := newFirstStringValueAdapter( + newEnvironmentStringValueAdapter("AWS_PROFILE"), + newEnvironmentStringValueAdapter("LOCALSTACK_AWS_PROFILE"), + newStaticStringValueAdapter("default"), + ).StringValue() + + awsRegion, hasAWSRegion := newFirstStringValueAdapter( + newEnvironmentStringValueAdapter("HELM_S3_REGION"), + newEnvironmentStringValueAdapter("AWS_REGION"), + newEnvironmentStringValueAdapter("AWS_DEFAULT_REGION"), + newEnvironmentStringValueAdapter("LOCALSTACK_AWS_REGION"), + newEnvironmentStringValueAdapter("LOCALSTACK_AWS_DEFAULT_REGION"), + newConfigurationFileStringValueAdapter("region", awsProfile), + newStaticStringValueAdapter("us-east-1"), + ).StringValue() + + awsRoleARN, _ := newFirstStringValueAdapter( + newConfigurationFileStringValueAdapter("role_arn", awsProfile), + ).StringValue() + + awsRoleARNWebIdentity, _ := newFirstStringValueAdapter( + newEnvironmentStringValueAdapter("AWS_ROLE_ARN"), + ).StringValue() + + awsWebIdentityTokenFilePath, _ := newFirstStringValueAdapter( + newConfigurationFileStringValueAdapter("web_identity_token_file", awsProfile), + ).StringValue() + + var awsCredentialsProviders []credentials.Provider + if isUsingLocalStack { + awsCredentialsProviders = []credentials.Provider{ + newConditionalAWSCredentialsProvider( + isUsingLocalStack, + &credentials.StaticProvider{ + Value: credentials.Value{ + AccessKeyID: "test", + SecretAccessKey: "test", + SessionToken: "", + ProviderName: "", + }, + }, + ), + } + } else { + awsCredentialsProviders = newNotNilAWSCredentialsProviders( + newConditionalAWSCredentialsProvider(!isUsingLocalStack, &credentials.EnvProvider{}), + newConditionalAWSCredentialsProvider( + awsCredentialsFilePath != "", + &credentials.SharedCredentialsProvider{ + Filename: awsCredentialsFilePath, + Profile: awsProfile, + }, + ), + newConditionalAWSCredentialsProvider( + awsRoleARN != "", + &stscreds.AssumeRoleProvider{ // nolint:exhaustivestruct // Note: complex structure. + Client: sts.New(session.Must(session.NewSession())), + RoleARN: awsRoleARN, + Duration: stscreds.DefaultDuration, + TokenProvider: stscreds.StdinTokenProvider, + }, + ), + newConditionalAWSCredentialsProvider( + awsRoleARNWebIdentity != "" && awsWebIdentityTokenFilePath != "", + stscreds.NewWebIdentityRoleProvider( + sts.New(session.Must(session.NewSession())), + awsRoleARNWebIdentity, + "helm-s3-end-to-end-test-"+time.Now().Format(time.RFC3339Nano), + awsWebIdentityTokenFilePath, + ), + ), + ) + } + + awsConfiguration := aws.NewConfig(). + WithCredentials(credentials.NewChainCredentials(awsCredentialsProviders)) + + if hasAWSEndpoint { + awsConfiguration = awsConfiguration.WithEndpoint(awsEndpoint) + } + + if hasAWSLogLevel { + awsConfiguration = awsConfiguration.WithLogLevel(awsLogLevel) + } + + if hasAWSRegion { + awsConfiguration = awsConfiguration.WithRegion(awsRegion) + } + + return awsConfiguration +} + +// newConditionalAWSCredentialsProvider returns the specified provider if the +// condition evaluates to true, otherwise it returns nil. +func newConditionalAWSCredentialsProvider(condition bool, provider credentials.Provider) credentials.Provider { + if !condition { + return nil + } + + return provider +} + +// newNotNilAWSCredentialsProviders returns a collection of credentials +// providers based on the specified providers, excluding nil providers. +func newNotNilAWSCredentialsProviders(providers ...credentials.Provider) []credentials.Provider { + notNilProviders := make([]credentials.Provider, 0, len(providers)) + + for _, provider := range providers { + if provider != nil { + notNilProviders = append(notNilProviders, provider) + } + } + + return notNilProviders +} + +// newUniqueBucketName tries to create a universally unique bucket name using the +// specified prefix. +func newUniqueBucketName(prefixes ...string) string { + name := strings.Join(append(prefixes, uuid.New().String()), "-") + if len(name) > 63 { + name = name[:63] + } + + return name +} + +// pushHelmS3Chart pushes the specified chart to the provided repository. +func pushHelmS3Chart(t *testing.T, repositoryName, chartPath string, options ...string) { + t.Helper() + + output, errorOutput, err := tryPushHelmS3Chart(repositoryName, chartPath, options...) + requireCommandOutput(t, "", "", nil, output, errorOutput, err) +} + +// reindexHelmS3 reindexes the Helm S3 repository. +func reindexHelmS3(t *testing.T, repositoryName string) { + t.Helper() + + output, errorOutput, err := runCommand("helm", "s3", "reindex", repositoryName) + expectedOutput := fmt.Sprintf("Repository %s was successfully reindexed.\n", repositoryName) + requireCommandOutput(t, expectedOutput, "", nil, output, errorOutput, err) +} + +// removeHelmRepository removes the specified Helm S3 repository. +func removeHelmRepository(t *testing.T, repositoryName string) { + t.Helper() + + repositoryNames := listHelmRepositoryNames(t) + if !containsString(repositoryNames, repositoryName) { + return + } + + output, errorOutput, err := runCommand("helm", "repo", "remove", repositoryName) + expectedOutput := fmt.Sprintf("\"%s\" has been removed from your repositories\n", repositoryName) + requireCommandOutput(t, expectedOutput, "", nil, output, errorOutput, err) + + repositoryNames = listHelmRepositoryNames(t) + require.NotContains(t, repositoryNames, repositoryName) +} + +// runCommand runs the specified command with the provided arguments and returns +// its result. +func runCommand(commandAndArguments ...string) (output, errorOutput string, err error) { + if len(commandAndArguments) == 0 { + return "", "", errors.Errorf("missing required command argument") + } + + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) + + cmd := exec.Command( // nolint:gosec // Note: reported only for audit purposes. + commandAndArguments[0], + commandAndArguments[1:]..., + ) + cmd.Stdout = stdout + cmd.Stderr = stderr + + err = cmd.Run() + + return stdout.String(), stderr.String(), err +} + +// saveAWSS3ObjectLocally saves the specified object locally to the provided +// path. +func saveAWSS3ObjectLocally(t *testing.T, object *s3.GetObjectOutput, filePath string, mode os.FileMode) { // nolint:lll // Note: temporary. // Postpone: replace with fs.FileMode at Go 1.18. + t.Helper() + + data, err := ioutil.ReadAll(object.Body) + require.NoError(t, err, "reading AWS object body failed, local path: %s", filePath) + + _, err = os.Stat(path.Dir(filePath)) + if os.IsNotExist(err) { + createDirectory(t, path.Dir(filePath), 0o755) // nolint:gocritic // Note: intentional. + } + + err = ioutil.WriteFile(filePath, data, mode) + require.NoError(t, err, "writing file failed, path: %s", filePath) +} + +// SearchHelmCharts returns the corresponding chart to the specified repository +// and chart name if it can be found. +func searchHelmCharts(t *testing.T, repositoryName, chartName string) []helmChart { + t.Helper() + + updateHelmRepositories(t) + + output, errorOutput, err := runCommand( + append(helmSearchCommand(t), path.Join(repositoryName, chartName), "--output", "yaml")..., + ) + require.NoError(t, err, "output: %s, error output: %s", output, errorOutput) + + var yamlOutput interface{} + err = yaml.Unmarshal([]byte(output), &yamlOutput) + require.NoError(t, err, "parsing Helm search repo YAML failed, YAML: %s", output) + + var charts []helmChart + + decoderConfig := &mapstructure.DecoderConfig{ + DecodeHook: nil, + ErrorUnused: true, + ZeroFields: true, + WeaklyTypedInput: false, + Squash: true, + Metadata: nil, + Result: &charts, + TagName: helmStructTag(t), + } + decoder, err := mapstructure.NewDecoder(decoderConfig) + require.NoError(t, err, "creating Helm search repo YAML decoder failed, config: %+v", decoderConfig) + + err = decoder.Decode(yamlOutput) + require.NoError(t, err, "decoding Helm search repo YAML failed, YAML: %s, config: %s", yamlOutput, decoderConfig) + + for chartIndex := range charts { + charts[chartIndex].Name = path.Base(charts[chartIndex].Name) // Note: removing repository prefix. + } + + return charts +} + +// temporaryDirectoryPath returns a temporary directory path for the specified +// path elements. +func temporaryDirectoryPath(pathElements ...string) string { + return path.Join(append([]string{os.TempDir(), "helm-s3"}, pathElements...)...) +} + +// testChartPath returns a path to the specified helm chart's local test chart +// package file. +func testChartPath(t *testing.T, chartName, chartVersion string) string { + t.Helper() + + chartPath := path.Join(testDataRootDirectory, helmChartFileName(chartName, chartVersion)) + require.FileExists(t, chartPath) + + return chartPath +} + +// toLowerWordsFromCamelOrPascalCase returns the collection of words from a +// camel or Pascal cased text. +// +// WARNING: this fails on joint acronym expressions like HTTPAPI (becomes +// []string{"httpapi"}), because the word boundary cannot be determined without +// contextual knowledge, but otherwise is a good approximation. +func toLowerWordsFromCamelOrPascalCase(text string) []string { + words := make([]string, 0, 4) + + lastWord := "" + lastCharacter := 'A' + + for _, character := range text { + if unicode.IsUpper(character) && + !unicode.IsUpper(lastCharacter) { + words = append(words, lastWord) + lastWord = "" + } + + lastWord += string(unicode.ToLower(character)) + lastCharacter = character + } + + words = append(words, lastWord) + + return words +} + +// tryPushHelmS3Chart attempts to push the specified chart to the provided +// repository and returns the result. +func tryPushHelmS3Chart(repositoryName, chartPath string, options ...string) (output, errorOutput string, err error) { + return runCommand(append([]string{"helm", "s3", "push", chartPath, repositoryName}, options...)...) +} + +// updateHelmRepositories updates the known Helm repositories in the local cache. +func updateHelmRepositories(t *testing.T) { + t.Helper() + + output, errorOutput, err := runCommand("helm", "repo", "update") + require.NoError(t, err, "output: %s, errorOutput: %s", output, errorOutput) +} diff --git a/test/e2e/helm_fetch_test.go b/test/e2e/helm_fetch_test.go new file mode 100644 index 00000000..f8c60863 --- /dev/null +++ b/test/e2e/helm_fetch_test.go @@ -0,0 +1,39 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "path" +) + +func (testSuite *EndToEndSuite) TestHelmFetch() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + temporaryLocalChartPath := testSuite.TemporaryPath(testName, helmChartFileName(chart.Name, chart.Version)) + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath) + + _ = getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + + fetchHelmChart(testSuite.T(), repositoryName, chart.Name, chart.Version, path.Dir(temporaryLocalChartPath)) + deleteFile(testSuite.T(), temporaryLocalChartPath) +} diff --git a/test/e2e/helm_repo_add_test.go b/test/e2e/helm_repo_add_test.go new file mode 100644 index 00000000..1f3a5bd2 --- /dev/null +++ b/test/e2e/helm_repo_add_test.go @@ -0,0 +1,33 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "path" +) + +func (testSuite *EndToEndSuite) TestHelmRepoAdd() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + + repositoryName := bucketName + repositoryURI := helmS3RepositoryURI(bucketName) + + // Note: undoing test setup. + removeHelmRepository(testSuite.T(), repositoryName) + + addHelmRepository(testSuite.T(), repositoryName, repositoryURI) +} diff --git a/tests/e2e/doc.go b/test/e2e/helm_repo_remove_test.go similarity index 69% rename from tests/e2e/doc.go rename to test/e2e/helm_repo_remove_test.go index 4ad08594..3d1f3949 100644 --- a/tests/e2e/doc.go +++ b/test/e2e/helm_repo_remove_test.go @@ -12,5 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package e2e contains end-to-end tests for helm-s3 plugin. package e2e + +import ( + "path" +) + +func (testSuite *EndToEndSuite) TestHelmRepoRemove() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + + repositoryName := bucketName + + removeHelmRepository(testSuite.T(), repositoryName) +} diff --git a/test/e2e/helm_s3_delete_test.go b/test/e2e/helm_s3_delete_test.go new file mode 100644 index 00000000..53c39b56 --- /dev/null +++ b/test/e2e/helm_s3_delete_test.go @@ -0,0 +1,45 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "path" + + "github.com/stretchr/testify/require" +) + +func (testSuite *EndToEndSuite) TestHelmS3Delete() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + repositoryName := bucketName + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath) + + _ = getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + + require.Contains(testSuite.T(), searchHelmCharts(testSuite.T(), repositoryName, chart.Name), chart) + + deleteHelmS3Chart(testSuite.T(), repositoryName, chart.Name, chart.Version) + + getNoAWSS3Object(testSuite.T(), s3Client, repositoryName, bucketRepositoryChartPath) + + require.Empty(testSuite.T(), searchHelmCharts(testSuite.T(), repositoryName, chart.Name)) +} diff --git a/test/e2e/helm_s3_init_test.go b/test/e2e/helm_s3_init_test.go new file mode 100644 index 00000000..21477d47 --- /dev/null +++ b/test/e2e/helm_s3_init_test.go @@ -0,0 +1,36 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "path" +) + +func (testSuite *EndToEndSuite) TestHelmS3Init() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + bucketRepositoryIndexPath := helmS3RepositoryFilePath("index.yaml") + s3Client := testSuite.AWSS3Client() + + repositoryName := bucketName + repositoryURI := helmS3RepositoryURI(bucketName) + + // Note: undoing test setup. + removeHelmRepository(testSuite.T(), repositoryName) + deleteAWSS3Objects(testSuite.T(), s3Client, bucketName, bucketRepositoryIndexPath) + + initializeHelmS3Repository(testSuite.T(), s3Client, bucketName, repositoryURI) +} diff --git a/test/e2e/helm_s3_push_test.go b/test/e2e/helm_s3_push_test.go new file mode 100644 index 00000000..c9579098 --- /dev/null +++ b/test/e2e/helm_s3_push_test.go @@ -0,0 +1,190 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "path" + "time" + + "emperror.dev/errors" + "github.com/aws/aws-sdk-go/aws" + "github.com/stretchr/testify/require" +) + +func (testSuite *EndToEndSuite) TestHelmS3Push() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + temporaryLocalChartPath := testSuite.TemporaryPath(testName, helmChartFileName(chart.Name, chart.Version)) + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath) + + object := getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + require.Equal(testSuite.T(), defaultHelmChartContentType, aws.StringValue(object.ContentType)) + + require.Contains(testSuite.T(), searchHelmCharts(testSuite.T(), repositoryName, chart.Name), chart) + + fetchHelmChart(testSuite.T(), repositoryName, chart.Name, chart.Version, path.Dir(temporaryLocalChartPath)) + require.FileExists(testSuite.T(), temporaryLocalChartPath) + + output, errorOutput, err := tryPushHelmS3Chart(repositoryName, localChartPath) + expectedErrorOutput := + "The chart already exists in the repository and cannot be overwritten without an explicit intent." + + " If you want to replace existing chart, use --force flag:" + + "\n\n\thelm s3 push --force " + localChartPath + " " + repositoryName + + "\n\nError: plugin \"s3\" exited with error\n" + requireCommandOutput(testSuite.T(), "", expectedErrorOutput, errors.Errorf("exit status 1"), output, errorOutput, err) +} + +func (testSuite *EndToEndSuite) TestHelmS3PushContentType() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + contentType := defaultHelmChartContentType + "-test" + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath, "--content-type", contentType) + + object := getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + require.Equal(testSuite.T(), contentType, aws.StringValue(object.ContentType)) +} + +func (testSuite *EndToEndSuite) TestHelmS3PushDryRun() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath, "--dry-run") + + getNoAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) +} + +func (testSuite *EndToEndSuite) TestHelmS3PushForce() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath) + + object := getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + lastModified := aws.TimeValue(object.LastModified) + + time.Sleep(1 * time.Second) // Note: ensuring lastModified timestamp changes. + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath, "--force") + + object = getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + require.NotEqual(testSuite.T(), lastModified, aws.TimeValue(object.LastModified)) +} + +func (testSuite *EndToEndSuite) TestHelmS3PushIgnoreIfExists() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath) + + object := getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + lastModified := aws.TimeValue(object.LastModified) + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath, "--ignore-if-exists") + + object = getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + require.Equal(testSuite.T(), lastModified, aws.TimeValue(object.LastModified)) +} + +func (testSuite *EndToEndSuite) TestHelmS3PushForceAndIgnoreIfExists() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + chart := exampleChart + + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + + output, errorOutput, err := tryPushHelmS3Chart(repositoryName, localChartPath, "--force", "--ignore-if-exists") + expectedErrorOutput := "The --force and --ignore-if-exists flags are mutually exclusive and " + + "cannot be specified together.\n" + + "Error: plugin \"s3\" exited with error\n" + requireCommandOutput( + testSuite.T(), + "", + expectedErrorOutput, + errors.Errorf("exit status 1"), + output, + errorOutput, + err, + ) +} + +func (testSuite *EndToEndSuite) TestHelmS3PushRelative() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + bucketRepositoryIndexPath := helmS3RepositoryFilePath("index.yaml") + chart := exampleChart + temporaryLocalIndexPath := testSuite.TemporaryPath(testName, "index.yaml") + s3Client := testSuite.AWSS3Client() + + chartFileName := helmChartFileName(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + temporaryLocalChartPath := testSuite.TemporaryPath(testName, helmChartFileName(chart.Name, chart.Version)) + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath, "--relative") + + object := getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryIndexPath) + + saveAWSS3ObjectLocally( // nolint:gocritic // Note: intentional. + testSuite.T(), + object, + temporaryLocalIndexPath, + 0o444, + ) + + chartVersion := helmIndexChartVersion(testSuite.T(), temporaryLocalIndexPath, chart.Name, chart.Version) + require.Equal(testSuite.T(), []string{chartFileName}, chartVersion.URLs) + + fetchHelmChart(testSuite.T(), repositoryName, chart.Name, chart.Version, path.Dir(temporaryLocalChartPath)) +} diff --git a/test/e2e/helm_s3_reindex_test.go b/test/e2e/helm_s3_reindex_test.go new file mode 100644 index 00000000..5773254c --- /dev/null +++ b/test/e2e/helm_s3_reindex_test.go @@ -0,0 +1,50 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "path" + + "github.com/stretchr/testify/require" +) + +func (testSuite *EndToEndSuite) TestHelmS3Reindex() { + testName := path.Base(testSuite.T().Name()) + + bucketName := testSuite.AWSS3BucketName(testName) + bucketRepositoryIndexPath := helmS3RepositoryFilePath("index.yaml") + chart := exampleChart + s3Client := testSuite.AWSS3Client() + + bucketRepositoryChartPath := helmS3RepositoryChartPath(chart.Name, chart.Version) + localChartPath := testChartPath(testSuite.T(), chart.Name, chart.Version) + repositoryName := bucketName + + pushHelmS3Chart(testSuite.T(), repositoryName, localChartPath) + + _ = getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryChartPath) + + require.Contains(testSuite.T(), searchHelmCharts(testSuite.T(), repositoryName, chart.Name), chart) + + deleteAWSS3Objects(testSuite.T(), s3Client, bucketName, bucketRepositoryIndexPath) + + getNoAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryIndexPath) + + reindexHelmS3(testSuite.T(), repositoryName) + + _ = getAWSS3Object(testSuite.T(), s3Client, bucketName, bucketRepositoryIndexPath) + + require.Contains(testSuite.T(), searchHelmCharts(testSuite.T(), repositoryName, chart.Name), chart) +} diff --git a/test/e2e/require_test.go b/test/e2e/require_test.go new file mode 100644 index 00000000..733829ef --- /dev/null +++ b/test/e2e/require_test.go @@ -0,0 +1,60 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "testing" + + "emperror.dev/errors" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/stretchr/testify/require" +) + +// requireAWSErrorCode extracts the AWS error code from the specified actual +// error and compares it to the expected value. +func requireAWSErrorCode(t *testing.T, expectedAWSErrorCode string, actualError error) { + t.Helper() + + require.Error(t, actualError, "expected AWS error, received nil, expected AWS error code: %s", expectedAWSErrorCode) + + var actualAWSError awserr.Error + isOk := errors.As(actualError, &actualAWSError) + require.True(t, isOk, "actual error is not an AWS error, actual error: %s", actualError) + + require.Equal(t, expectedAWSErrorCode, actualAWSError.Code(), "unexpected AWS error: %s", actualAWSError) +} + +// requireCommandOutput compares expected and actual outputs of a command. +func requireCommandOutput( + t *testing.T, + expectedOutput string, + expectedErrorOutput string, + expectedError error, + actualOutput string, + actualErrorOutput string, + actualError error, +) { + t.Helper() + + if expectedError != nil { + errorMessage := expectedError.Error() + require.EqualError(t, actualError, errorMessage, "output: %s, errorOutput: %s", actualOutput, actualErrorOutput) + } else { + require.NoError(t, actualError, "output: %s, errorOutput: %s", actualOutput, actualErrorOutput) + } + + require.Equal(t, expectedErrorOutput, actualErrorOutput, "output: %s", actualOutput) + require.Equal(t, expectedOutput, actualOutput) +} diff --git a/test/e2e/string_value_adapter_test.go b/test/e2e/string_value_adapter_test.go new file mode 100644 index 00000000..2e5e8506 --- /dev/null +++ b/test/e2e/string_value_adapter_test.go @@ -0,0 +1,129 @@ +// Copyright © 2021 Banzai Cloud +// +// 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 e2e + +import ( + "os" + "os/exec" + "strings" +) + +// stringValueAdapter describes an interface to obtain string values from any +// source. +type stringValueAdapter interface { + // StringValue returns a string value obtained from a custom source. + StringValue() (value string, isSet bool) +} + +// dynamicStringValueAdapter implements a stringValueAdapter for dynamic +// sources. +type dynamicStringValueAdapter struct { + valueFunction func() (string, bool) +} + +// StringValue returns a string value obtained from a custom source. +func (adapter *dynamicStringValueAdapter) StringValue() (value string, isSet bool) { + if adapter == nil { + return "", false + } + + return adapter.valueFunction() +} + +// newDynamicStringValueAdapter returns a stringValueAdapter object from the +// specified function to use as a dynamic string value source. +func newDynamicStringValueAdapter(valueFunction func() (string, bool)) stringValueAdapter { + return &dynamicStringValueAdapter{ + valueFunction: valueFunction, + } +} + +// newConfigurationFileStringValueAdapter returns a stringValueAdapter object +// from the specified profile with the provided key to use as a configuration +// file string value source. +func newConfigurationFileStringValueAdapter(key, profile string) stringValueAdapter { + return newDynamicStringValueAdapter( + func() (string, bool) { + if profile == "" { + profile = "default" + } + + command := exec.Command("aws", "configure", "get", key, "--profile", profile) + + output, err := command.CombinedOutput() + if err != nil { + return "", false + } + + value := strings.TrimSpace(string(output)) + if value == "" { + return "", false + } + + return value, true + }, + ) +} + +// newEnvironmentStringValueAdapter returns a stringValueAdapter object from the +// specified environment key to use as an OS environment string value source. +func newEnvironmentStringValueAdapter(key string) stringValueAdapter { + return newDynamicStringValueAdapter( + func() (string, bool) { + return os.LookupEnv(key) + }, + ) +} + +// newFilePathStringValueAdapter returns a stringValueAdapter object from the +// specified string to use the file's path as a string value source. +func newFilePathStringValueAdapter(path string) stringValueAdapter { + return newDynamicStringValueAdapter( + func() (string, bool) { + if _, err := os.Stat(path); err != nil { + return "", false + } + + return path, true + }, + ) +} + +// newFirstStringValueAdapter returns a stringValueAdapter object from the +// specified adapters to use the first set value among the adapters as a string +// source. +func newFirstStringValueAdapter(adapters ...stringValueAdapter) stringValueAdapter { + return newDynamicStringValueAdapter( + func() (string, bool) { + for _, adapter := range adapters { + if value, isSet := adapter.StringValue(); isSet { + return value, true + } + } + + return "", false + }, + ) +} + +// newStaticStringValueAdapter returns a stringValueAdapter object from the +// specified string to use the static value as a string value source. +func newStaticStringValueAdapter(value string) stringValueAdapter { + return newDynamicStringValueAdapter( + func() (string, bool) { + return value, true + }, + ) +} diff --git a/tests/e2e/delete_test.go b/tests/e2e/delete_test.go deleted file mode 100644 index c376a429..00000000 --- a/tests/e2e/delete_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © 2021 Banzai Cloud -// -// 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 e2e - -import ( - "fmt" - "testing" - - "github.com/minio/minio-go/v6" - "github.com/stretchr/testify/assert" -) - -func TestDelete(t *testing.T) { - t.Log("Test basic delete action") - - const ( - repoName = "test-delete" - repoDir = "charts" - chartName = "foo" - chartVersion = "1.2.3" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - chartObjectName = repoDir + "/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - // Push chart to be deleted. - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push %s %s", chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that pushed chart exists in the bucket. - - obj, err := mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, chartObjectName, obj.Key) - - // Check that pushed chart can be searched, which means it exists in the index. - - cmd, stdout, stderr = command(makeSearchCommand(repoName, chartName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, nil, stderr) - - expected := `test-delete/foo 1.2.3 1.2.3 A Helm chart for Kubernetes` - assert.Contains(t, stdout.String(), expected) - - // Delete chart. - - cmd, stdout, stderr = command(fmt.Sprintf("helm s3 delete %s --version %s %s", chartName, chartVersion, repoName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was actually deleted from the bucket. - - _, err = mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.Equal(t, "NoSuchKey", minio.ToErrorResponse(err).Code) - - // Check that deleted chart cannot be searched, which means it was deleted from the index. - - cmd, stdout, stderr = command(makeSearchCommand(repoName, chartName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, nil, stderr) - - expected = `No results found` - assert.Contains(t, stdout.String(), expected) -} diff --git a/tests/e2e/init_test.go b/tests/e2e/init_test.go deleted file mode 100644 index fc943977..00000000 --- a/tests/e2e/init_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2021 Banzai Cloud -// -// 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 e2e - -import ( - "fmt" - "testing" - - "github.com/minio/minio-go/v6" - "github.com/stretchr/testify/assert" -) - -func TestInit(t *testing.T) { - t.Log("Test basic init action") - - const ( - repoName = "test-init" - repoDir = "charts" - indexObjectName = repoDir + "/index.yaml" - uri = "s3://test-init/charts" - ) - - setupBucket(t, repoName) - defer teardownBucket(t, repoName) - - // Run init. - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 init %s", uri)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, nil, stderr) - assert.Contains(t, stdout.String(), "Initialized empty repository at s3://test-init/charts") - - // Check that initialized repository has index file. - - obj, err := mc.StatObject(repoName, indexObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, indexObjectName, obj.Key) - - // Check that `helm repo add` works. - - cmd, stdout, stderr = command(fmt.Sprintf("helm repo add %s %s", repoName, uri)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, nil, stderr) - assert.Contains(t, stdout.String(), `"test-init" has been added to your repositories`) - - // Check that `helm repo remove` works. - - cmd, stdout, stderr = command(fmt.Sprintf("helm repo remove %s", repoName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, nil, stderr) - assert.Contains(t, stdout.String(), `"test-init" has been removed from your repositories`) -} diff --git a/tests/e2e/main_test.go b/tests/e2e/main_test.go deleted file mode 100644 index 32fb3437..00000000 --- a/tests/e2e/main_test.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright © 2021 Banzai Cloud -// -// 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 e2e - -import ( - "bytes" - "fmt" - "os" - "os/exec" - "strings" - "testing" - - "github.com/minio/minio-go/v6" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/banzaicloud/helm-s3/internal/helmutil" -) - -var mc *minio.Client - -func TestMain(m *testing.M) { - setup() - defer teardown() - - os.Exit(m.Run()) -} - -func setup() { - if os.Getenv("AWS_ENDPOINT") == "" { - panic("AWS_ENDPOINT is empty") - } - - if os.Getenv("AWS_ACCESS_KEY_ID") == "" { - panic("AWS_ACCESS_KEY_ID is empty") - } - - if os.Getenv("AWS_SECRET_ACCESS_KEY") == "" { - panic("AWS_SECRET_ACCESS_KEY is empty") - } - - var err error - mc, err = minio.New( - os.Getenv("AWS_ENDPOINT"), - os.Getenv("AWS_ACCESS_KEY_ID"), - os.Getenv("AWS_SECRET_ACCESS_KEY"), - false, - ) - if err != nil { - panic("create minio client: " + err.Error()) - } -} - -func teardown() { -} - -// Helper functions. - -func setupBucket(t *testing.T, name string) { - t.Helper() - - exists, err := mc.BucketExists(name) - require.NoError(t, err, "check if bucket exists") - if exists { - teardownBucket(t, name) - } - - err = mc.MakeBucket(name, "") - require.NoError(t, err, "create bucket") -} - -func teardownBucket(t *testing.T, name string) { - t.Helper() - - done := make(chan struct{}) - defer close(done) - - for obj := range mc.ListObjectsV2(name, "", true, done) { - err := mc.RemoveObject(name, obj.Key) - assert.NoError(t, err) - } - - err := mc.RemoveBucket(name) - require.NoError(t, err, "remove bucket") -} - -func setupRepo(t *testing.T, name, dir string) { - t.Helper() - - setupBucket(t, name) - - url := fmt.Sprintf("s3://%s", name) - if dir != "" { - url += "/" + dir - } - - out, err := exec.Command("helm", "s3", "init", url).CombinedOutput() - require.NoError(t, err, "helm s3 init: %s", string(out)) - - out, err = exec.Command("helm", "repo", "add", name, url).CombinedOutput() - require.NoError(t, err, "helm repo add: %s", string(out)) -} - -func teardownRepo(t *testing.T, name string) { - t.Helper() - - err := exec.Command("helm", "repo", "remove", name).Run() - require.NoError(t, err) - - teardownBucket(t, name) -} - -func command(c string) (cmd *exec.Cmd, stdout, stderr *bytes.Buffer) { - stdout = &bytes.Buffer{} - stderr = &bytes.Buffer{} - args := strings.Split(c, " ") - - cmd = exec.Command(args[0], args[1:]...) - cmd.Stdout = stdout - cmd.Stderr = stderr - - return -} - -// For helm v2, the command is `helm search foo/bar` -// For helm v3, the command is `helm search repo foo/bar`. -func makeSearchCommand(repoName, chartName string) string { - c := "helm search" - - helmutil.SetupHelm() - if helmutil.IsHelm3() { - c += " repo" - } - - return fmt.Sprintf("%s %s/%s", c, repoName, chartName) -} diff --git a/tests/e2e/push_test.go b/tests/e2e/push_test.go deleted file mode 100644 index 1284c114..00000000 --- a/tests/e2e/push_test.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright © 2021 Banzai Cloud -// -// 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 e2e - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/minio/minio-go/v6" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "helm.sh/helm/v3/pkg/repo" -) - -const ( - // Copied from main. - defaultChartsContentType = "application/gzip" -) - -func TestPush(t *testing.T) { - t.Log("Test basic push action") - - const ( - repoName = "test-push" - repoDir = "charts" - chartName = "foo" - chartVersion = "1.2.3" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - chartObjectName = repoDir + "/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push %s %s", chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was actually pushed. - - obj, err := mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, chartObjectName, obj.Key) - - // Check that chart has proper content type. - - assert.Equal(t, defaultChartsContentType, obj.ContentType) - - // Check that pushed chart can be searched. - - cmd, stdout, stderr = command(makeSearchCommand(repoName, chartName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, nil, stderr) - - expected := `test-push/foo 1.2.3 1.2.3 A Helm chart for Kubernetes` - assert.Contains(t, stdout.String(), expected) - - // Check that pushed chart can be fetched. - - tmpdir, err := ioutil.TempDir("", t.Name()) - require.NoError(t, err) - defer os.RemoveAll(tmpdir) - - cmd, stdout, stderr = command(fmt.Sprintf("helm fetch %s/%s --version %s --destination %s", repoName, chartName, chartVersion, tmpdir)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - assert.FileExists(t, filepath.Join(tmpdir, chartFilename)) - - // Check that pushing the same chart again fails. - - cmd, stdout, stderr = command(fmt.Sprintf("helm s3 push %s %s", chartFilepath, repoName)) - err = cmd.Run() - assert.Error(t, err) - assertEmptyOutput(t, stdout, nil) - - expected = `The chart already exists in the repository and cannot be overwritten without an explicit intent. If you want to replace existing chart, use --force flag` - assert.Contains(t, stderr.String(), expected) -} - -func TestPushContentType(t *testing.T) { - t.Log("Test push action with --content-type flag") - - const ( - repoName = "test-push-content-type" - repoDir = "charts" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - chartObjectName = repoDir + "/" + chartFilename - - contentType = defaultChartsContentType + "-test" - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push --content-type=%s %s %s", contentType, chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was actually pushed. - - obj, err := mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, chartObjectName, obj.Key) - - // Check that chart has proper content type. - - assert.Equal(t, contentType, obj.ContentType) -} - -func TestPushDryRun(t *testing.T) { - t.Log("Test push action with --dry-run flag") - - const ( - repoName = "test-push-dry-run" - repoDir = "charts" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - chartObjectName = repoDir + "/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push %s %s --dry-run", chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that actually nothing got pushed. - - _, err = mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.Equal(t, "NoSuchKey", minio.ToErrorResponse(err).Code) -} - -func TestPushForce(t *testing.T) { - t.Log("Test push action with --force flag") - - const ( - repoName = "test-push-force" - repoDir = "charts" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - chartObjectName = repoDir + "/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push %s %s", chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was actually pushed and remember last modification time. - - obj, err := mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, chartObjectName, obj.Key) - - lastModified := obj.LastModified - - // Push chart again with --force. - - time.Sleep(time.Second) - - cmd, stdout, stderr = command(fmt.Sprintf("helm s3 push %s %s --force", chartFilepath, repoName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was overwritten. - - obj, err = mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.True(t, obj.LastModified.After(lastModified), "Expected %s less than %s", lastModified.String(), obj.LastModified.String()) -} - -func TestPushIgnoreIfExists(t *testing.T) { - t.Log("Test push action with --ignore-if-exists flag") - - const ( - repoName = "test-push-ignore-if-exists" - repoDir = "charts" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - chartObjectName = repoDir + "/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push %s %s", chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was actually pushed and remember last modification time. - - obj, err := mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, chartObjectName, obj.Key) - - lastModified := obj.LastModified - - // Push chart again with --ignore-if-exists. - - cmd, stdout, stderr = command(fmt.Sprintf("helm s3 push %s %s --ignore-if-exists", chartFilepath, repoName)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Check that chart was not overwritten. - - obj, err = mc.StatObject(repoName, chartObjectName, minio.StatObjectOptions{}) - assert.NoError(t, err) - assert.Equal(t, lastModified, obj.LastModified) -} - -func TestPushForceAndIgnoreIfExists(t *testing.T) { - t.Log("Test push action with both --force and --ignore-if-exists flags") - - const ( - repoName = "test-push-force-and-ignore-if-exists" - repoDir = "charts" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push %s %s --force --ignore-if-exists", chartFilepath, repoName)) - err := cmd.Run() - assert.Error(t, err) - assertEmptyOutput(t, stdout, nil) - - expectedErrorMessage := "The --force and --ignore-if-exists flags are mutually exclusive and cannot be specified together." - if !strings.HasPrefix(stderr.String(), expectedErrorMessage) { - t.Errorf("Expected stderr to begin with %q, but got %q", expectedErrorMessage, stderr.String()) - } -} - -func TestPushRelative(t *testing.T) { - t.Log("Test push action with --relative flag") - - const ( - repoName = "test-push-relative" - repoDir = "charts" - chartName = "foo" - chartVersion = "1.2.3" - chartFilename = "foo-1.2.3.tgz" - chartFilepath = "testdata/" + chartFilename - ) - - setupRepo(t, repoName, repoDir) - defer teardownRepo(t, repoName) - - cmd, stdout, stderr := command(fmt.Sprintf("helm s3 push --relative %s %s", chartFilepath, repoName)) - err := cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - - // Fetch the repo index and check that chart uri is relative. - - tmpdir, err := ioutil.TempDir("", t.Name()) - require.NoError(t, err) - defer os.RemoveAll(tmpdir) - - indexFile := filepath.Join(tmpdir, "index.yaml") - - err = mc.FGetObject(repoName, repoDir+"/index.yaml", indexFile, minio.GetObjectOptions{}) - require.NoError(t, err) - - idx, err := repo.LoadIndexFile(indexFile) - require.NoError(t, err) - - cv, err := idx.Get(chartName, chartVersion) - require.NoError(t, err) - - expected := []string{chartFilename} - if diff := cmp.Diff(expected, cv.URLs); diff != "" { - t.Errorf("mismatch (-want +got):\n%s", diff) - } - - // Check that chart can be successfully fetched. - - cmd, stdout, stderr = command(fmt.Sprintf("helm fetch %s/%s --version %s --destination %s", repoName, chartName, chartVersion, tmpdir)) - err = cmd.Run() - assert.NoError(t, err) - assertEmptyOutput(t, stdout, stderr) - assert.FileExists(t, filepath.Join(tmpdir, chartFilename)) -} - -func assertEmptyOutput(t *testing.T, stdout, stderr *bytes.Buffer) { - t.Helper() - - if stdout != nil { - assert.Empty(t, stdout.String(), "Expected stdout to be empty") - } - if stderr != nil { - assert.Empty(t, stderr.String(), "Expected stderr to be empty") - } -} From b49eeba69564fb93a0ee9031dc8ab8eb7c3096be Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Tue, 11 May 2021 11:22:58 +0200 Subject: [PATCH 14/15] chore(goreleaser): reconfigured --- .gitignore | 3 ++ .goreleaser.yml | 106 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d5e21080..7e3af9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # Note: local build. bin/** +# Note: goreleaser artifacts. +dist/** + # Note: optional local dependency cache. vendor/** diff --git a/.goreleaser.yml b/.goreleaser.yml index 8a91a5ff..d8d67ad7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,6 +1,16 @@ +project_name: helm-s3 + builds: - - main: ./cmd/helms3 + - id: helm-s3 + dir: "." + main: ./cmd/helms3 binary: ./bin/helms3 + flags: + - -trimpath + asmflags: [] + gcflags: [] + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser env: - CGO_ENABLED=0 goos: @@ -8,10 +18,102 @@ builds: - linux goarch: - amd64 + - arm64 + goarm: [] + gomips: [] + gobinary: go + mod_timestamp: "{{ .CommitTimestamp }}" + hooks: + pre: [] + post: [] + skip: false archives: - - id: tar + - id: archive + builds: + - helm-s3 format: tar.gz + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" + wrap_in_directory: false files: - LICENSE - plugin.yaml + allow_different_binary_count: false + + - id: binary + builds: + - helm-s3 + format: binary + name_template: "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" + wrap_in_directory: false + files: + - binary_only* # Note: workaround to exclude default files such as LICENSE. + allow_different_binary_count: false + +checksum: + name_template: "{{ .ProjectName }}_{{ .Version }}_sha512_checksums.txt" + algorithm: sha512 + ids: [] # Note: all. + disable: false + +# Note: currently not working with replacements. +# gomod: +# proxy: true +# env: +# - GOPROXY=https://proxy.golang.org,direct +# - GOSUMDB=sum.golang.org +# gobinary: go + +snapshot: + name_template: "{{ .Tag }}-{{ .ShortCommit }}" +# +# Note: container and manifest building and pushing is done in CI. This +# reference is kept for intent "documentation" purposes. +# dockers: +# - goos: linux +# goarch: amd64 +# goarm: "" +# ids: +# - plugin +# image_templates: +# - ghcr.io/banzaicloud/{{ .ProjectName }}:{{ .Tag }}-amd64 +# skip_push: false +# dockerfile: Dockerfile +# use_buildx: false +# build_flag_templates: +# - --pull +# - --label=org.opencontainers.image.created={{.Date}} +# - --label=org.opencontainers.image.title={{.ProjectName}} +# - --label=org.opencontainers.image.revision={{.FullCommit}} +# - --label=org.opencontainers.image.version={{.Version}} +# extra_files: +# - LICENSE +# - plugin.yaml + +# # - goos: linux +# # goarch: arm64 +# # goarm: "" +# # ids: +# # - plugin +# # image_templates: +# # - ghcr.io/banzaicloud/{{ .ProjectName }}:{{ .Tag }}-arm64 +# # skip_push: false +# # dockerfile: Dockerfile +# # use_buildx: false +# # build_flag_templates: +# # - --pull +# # - --label=org.opencontainers.image.created={{.Date}} +# # - --label=org.opencontainers.image.title={{.ProjectName}} +# # - --label=org.opencontainers.image.revision={{.FullCommit}} +# # - --label=org.opencontainers.image.version={{.Version}} +# # extra_files: +# # - LICENSE +# # - plugin.yaml +# +# docker_manifests: +# - name_template: banzaicloud/{{ .ProjectName }}:{{ .Tag }} +# image_templates: +# - ghcr.io/banzaicloud/{{ .ProjectName }}:{{ .Tag }}-amd64 +# # - ghcr.io/banzaicloud/{{ .ProjectName }}:{{ .Tag }}-arm64 +# create_flags: [] +# push_flags: [] From ef593a207ffcad9476f27dc7957d81a19b0bfb37 Mon Sep 17 00:00:00 2001 From: Patrik Egyed Date: Fri, 14 May 2021 07:47:18 +0200 Subject: [PATCH 15/15] ci: refactored CircleCI->GitHub Actions --- .circleci/codecov.yml | 4 - .circleci/config.yml | 364 ---------------------------------- .circleci/testcover.sh | 12 -- .github/CONTRIBUTING.md | 35 ---- .github/workflows/ci.yml | 311 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 244 +++++++++++++++++++++++ Makefile | 58 +++++- 7 files changed, 609 insertions(+), 419 deletions(-) delete mode 100644 .circleci/codecov.yml delete mode 100644 .circleci/config.yml delete mode 100755 .circleci/testcover.sh delete mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.circleci/codecov.yml b/.circleci/codecov.yml deleted file mode 100644 index 95b24ecb..00000000 --- a/.circleci/codecov.yml +++ /dev/null @@ -1,4 +0,0 @@ -comment: off - -ignore: - - "tests" diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index ac85c26f..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,364 +0,0 @@ -# CircleCI configuration file -# Ref: https://circleci.com/docs/2.0/configuration-reference/ -version: 2.1 - -commands: - build_push_docker_image: - description: "Builds and pushes Docker image" - parameters: - helm_version: - type: string - image_name: - type: string - default: banzaicloud/helm-s3 - steps: - - run: - name: Build & push Docker image - command: | - HELM_VERSION="<< parameters.helm_version >>" - IMAGE_NAME="<< parameters.image_name >>" - - PLUGIN_VERSION="commit.${CIRCLE_SHA1}" - if [ "${CIRCLE_BRANCH}" == "master" ]; then - PLUGIN_VERSION="master" - fi - if [ -n "${CIRCLE_TAG}" ]; then - PLUGIN_VERSION="${CIRCLE_TAG#v*}" - fi - - docker build \ - --build-arg HELM_VERSION=${HELM_VERSION} \ - --build-arg PLUGIN_VERSION=${PLUGIN_VERSION} \ - --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ - --build-arg VCS_REF=$(git rev-parse --short HEAD) \ - -t ${IMAGE_NAME}:local . - - echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin - - IMAGE_TAG="${PLUGIN_VERSION}-helm${HELM_VERSION}" - docker tag ${IMAGE_NAME}:local ${IMAGE_NAME}:${IMAGE_TAG} - docker push ${IMAGE_NAME}:${IMAGE_TAG} - - IMAGE_TAG_HELM_MINOR="${PLUGIN_VERSION}-helm${HELM_VERSION%*.*}" - docker tag ${IMAGE_NAME}:local ${IMAGE_NAME}:${IMAGE_TAG_HELM_MINOR} - docker push ${IMAGE_NAME}:${IMAGE_TAG_HELM_MINOR} - run_integration_tests: - description: "Runs integration tests" - parameters: - helm_version: - type: string - steps: - - run: - name: Prepare environment - command: | - tmp_dir="$(mktemp -d)" - echo "export IT_PLUGIN_VERSION=commit.${CIRCLE_SHA1}" >> $BASH_ENV - echo "export IT_TEMP_DIR=${tmp_dir}" >> $BASH_ENV - echo "export IT_HELM_VERSION=<< parameters.helm_version >>" >> $BASH_ENV - - run: - name: Install helm - command: | - curl -sSL https://get.helm.sh/helm-v${IT_HELM_VERSION}-linux-amd64.tar.gz | tar xz - mv linux-amd64/helm ${GOPATH}/bin/helm - rm -rf linux-amd64 - - # Run `helm init` only for helm v2 - if [ "${IT_HELM_VERSION:0:1}" == "2" ]; then - helm init --client-only - fi - - # Add `stable` repo only for helm v3 - if [ "${IT_HELM_VERSION:0:1}" == "3" ]; then - helm repo add stable https://charts.helm.sh/stable - fi - - run: - name: Build and install the plugin - command: | - go build -o bin/helms3 \ - -ldflags "-X main.version=${IT_PLUGIN_VERSION}" \ - ./cmd/helms3 - - # Copy plugin directory to outside of the CircleCI workspace. - cp -r /go/src/github.com/banzaicloud/helm-s3 ${IT_TEMP_DIR} - - # Correct the plugin manifest to make installation purely local - cd ${IT_TEMP_DIR}/helm-s3 - sed -i "/^hooks:/,+2 d" plugin.yaml - sed -i "s/^version:.*$/version: ${IT_PLUGIN_VERSION}/" plugin.yaml - - helm plugin install ${IT_TEMP_DIR}/helm-s3 - - run: - name: Install minio client, prepare minio - command: | - curl -sSL https://dl.minio.io/client/mc/release/linux-amd64/mc -o ${GOPATH}/bin/mc - chmod +x ${GOPATH}/bin/mc - mc config host add helm-s3-minio http://${AWS_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} - mc mb helm-s3-minio/test-bucket - - run: - name: Run e2e tests - command: | - go test -v ./tests/e2e/... -jobs: - test-unit: - docker: - - image: circleci/golang:1.15 - environment: - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - working_directory: /go/src/github.com/banzaicloud/helm-s3 - steps: - - attach_workspace: - at: /go/src/github.com/banzaicloud - - run: ./.circleci/testcover.sh - - run: bash <(curl -s https://codecov.io/bash) - test-integration-helm-2_17: - docker: - - image: circleci/golang:1.15 - environment: - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - AWS_ACCESS_KEY_ID: EXAMPLEKEY123 - AWS_SECRET_ACCESS_KEY: EXAMPLESECRET123456 - AWS_DEFAULT_REGION: us-east-1 - AWS_ENDPOINT: helm-s3-minio:9000 - AWS_DISABLE_SSL: true - - image: minio/minio:latest - name: helm-s3-minio - environment: - MINIO_ACCESS_KEY: EXAMPLEKEY123 - MINIO_SECRET_KEY: EXAMPLESECRET123456 - command: ["server", "/data"] - working_directory: /go/src/github.com/banzaicloud/helm-s3 - steps: - - attach_workspace: - at: /go/src/github.com/banzaicloud - - run_integration_tests: - helm_version: 2.17.0 - test-integration-helm-3_4: - docker: - - image: circleci/golang:1.15 - environment: - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - AWS_ACCESS_KEY_ID: EXAMPLEKEY123 - AWS_SECRET_ACCESS_KEY: EXAMPLESECRET123456 - AWS_DEFAULT_REGION: us-east-1 - AWS_ENDPOINT: helm-s3-minio:9000 - AWS_DISABLE_SSL: true - - image: minio/minio:latest - name: helm-s3-minio - environment: - MINIO_ACCESS_KEY: EXAMPLEKEY123 - MINIO_SECRET_KEY: EXAMPLESECRET123456 - command: ["server", "/data"] - working_directory: /go/src/github.com/banzaicloud/helm-s3 - steps: - - attach_workspace: - at: /go/src/github.com/banzaicloud - - run_integration_tests: - helm_version: 3.4.2 - test-integration-helm-3_5: - docker: - - image: circleci/golang:1.15 - environment: - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - AWS_ACCESS_KEY_ID: EXAMPLEKEY123 - AWS_SECRET_ACCESS_KEY: EXAMPLESECRET123456 - AWS_DEFAULT_REGION: us-east-1 - AWS_ENDPOINT: helm-s3-minio:9000 - AWS_DISABLE_SSL: true - - image: minio/minio:latest - name: helm-s3-minio - environment: - MINIO_ACCESS_KEY: EXAMPLEKEY123 - MINIO_SECRET_KEY: EXAMPLESECRET123456 - command: ["server", "/data"] - working_directory: /go/src/github.com/banzaicloud/helm-s3 - steps: - - attach_workspace: - at: /go/src/github.com/banzaicloud - - run_integration_tests: - helm_version: 3.5.2 - test-install: - docker: - - image: circleci/buildpack-deps:stretch-curl - working_directory: /tmp - steps: - - run: - name: Install helm - command: | - tar_filename="helm-v3.4.0-linux-amd64.tar.gz" - checksum_filename="helm-v3.4.0-linux-amd64.tar.gz.sha256sum" - curl -sSL https://get.helm.sh/${tar_filename} -O - curl -sSL https://get.helm.sh/${checksum_filename} -O - cat ${checksum_filename} | sha256sum -c - tar xzf ${tar_filename} - sudo mv linux-amd64/helm /usr/local/bin/helm - rm -rf linux-amd64 ${tar_filename} ${checksum_filename} - - run: - name: Install helm-s3 plugin - command: | - sudo apt-get install -y make - - version="${CIRCLE_SHA1}" - export HELM_S3_PLUGIN_NO_INSTALL_HOOK=true - if [ -n "${CIRCLE_TAG}" ]; then - version="${CIRCLE_TAG#v*}" - export HELM_S3_PLUGIN_NO_INSTALL_HOOK= - fi - - echo "Check installation of version ${version}" - helm plugin install https://github.com/banzaicloud/helm-s3.git --version ${version} - release: - docker: - - image: circleci/golang:1.15 - environment: - GO111MODULE: "on" - GOFLAGS: "-mod=vendor" - working_directory: /go/src/github.com/banzaicloud/helm-s3 - steps: - - attach_workspace: - at: /go/src/github.com/banzaicloud - - deploy: - name: goreleaser - command: | - if [ -n "$CIRCLE_TAG" ]; then - curl -sL https://git.io/goreleaser | bash - fi - docker-helm-2_17: - docker: - - image: circleci/buildpack-deps:stretch - working_directory: ~/workspace/helm-s3 - steps: - - attach_workspace: - at: ~/workspace - - setup_remote_docker: - version: 18.06.0-ce - - build_push_docker_image: - helm_version: 2.17.0 - docker-helm-3_4: - docker: - - image: circleci/buildpack-deps:stretch - working_directory: ~/workspace/helm-s3 - steps: - - attach_workspace: - at: ~/workspace - - setup_remote_docker: - version: 18.06.0-ce - - build_push_docker_image: - helm_version: 3.4.2 - docker-helm-3_5: - docker: - - image: circleci/buildpack-deps:stretch - working_directory: ~/workspace/helm-s3 - steps: - - attach_workspace: - at: ~/workspace - - setup_remote_docker: - version: 18.06.0-ce - - build_push_docker_image: - helm_version: 3.5.2 - -workflows: - version: 2 - # test-pipeline runs on each push and merge, and does not run on tags. - test-pipeline: - jobs: - - dep - - test-unit: - requires: - - dep - - test-integration-helm-2_17: - requires: - - dep - - test-integration-helm-3_4: - requires: - - dep - - test-integration-helm-3_5: - requires: - - dep - - docker-helm-2_17: - requires: - - dep - - test-unit - - test-integration-helm-2_17 - filters: - branches: - only: master - - docker-helm-3_4: - requires: - - dep - - test-unit - - test-integration-helm-3_4 - filters: - branches: - only: master - - docker-helm-3_5: - requires: - - dep - - test-unit - - test-integration-helm-3_5 - filters: - branches: - only: master - - test-install: - requires: - - test-integration-helm-2_17 - - test-integration-helm-3_4 - - test-integration-helm-3_5 - filters: - branches: - only: master - # release-pipeline runs only on tags. - release-pipeline: - jobs: - - dep: - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ - - docker-helm-2_17: - requires: - - dep - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ - - docker-helm-3_4: - requires: - - dep - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ - - docker-helm-3_5: - requires: - - dep - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ - - release: - requires: - - dep - - docker-helm-2_17 - - docker-helm-3_4 - - docker-helm-3_5 - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ - - test-install: - requires: - - release - filters: - tags: - only: /.*/ - branches: - ignore: /.*/ diff --git a/.circleci/testcover.sh b/.circleci/testcover.sh deleted file mode 100755 index f00743c3..00000000 --- a/.circleci/testcover.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./... | grep -v vendor | grep -v e2e); do - go test -race -coverprofile=profile.out -covermode=atomic $d - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 8d63a242..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,35 +0,0 @@ -# Contributing to helm s3 plugin - -## Development - -On regular plugin installation, helm triggers post-install hook that downloads -prebuilt versioned release of the plugin binary and installs it. To disable this -behavior, you need to pass `HELM_S3_PLUGIN_NO_INSTALL_HOOK=true` to the -installer: - - $ HELM_S3_PLUGIN_NO_INSTALL_HOOK=true helm plugin install https://github.com/banzaicloud/helm-s3.git - Development mode: not downloading versioned release. - Installed plugin: s3 - -Next, you may want to ensure if you have all prerequisites to build the plugin -from source: - - cd ~/.helm/plugins/helm-s3 - make build-local - -If you see no messages - build was successful. Try to run some helm commands -that involve the plugin, or jump straight into plugin development. - -## Testing - -Run unit tests: - -```shell -make test-unit -``` - -Run e2e tests locally: - -```shell -make test-e2e-local -``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6192093b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,311 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + tags: + - "*" + schedule: + - cron: "0 9 * * *" + +# Note: currently jobs..(runs-on|strategy) cannot use '${{ env }}'. +# Until this is fixed, I'm moving static environment configuration into the +# configure-environment job's outputs (originally it handled dynamically +# determined values only). +# +# env: + +jobs: + configure-environment: + name: Configure environment + runs-on: ${{ matrix.os }} + outputs: + git-default-branch: origin/main + git-refname: ${{ steps.set-git-refname.outputs.git-refname }} + github-api-host: https://api.github.com + github-api-version: "3" + github-organization: banzaicloud + github-repository: helm-s3 + github-runner-default-os-json: '["ubuntu-latest"]' # Note: used for OS-independent jobs. + github-runner-oses-json: '["ubuntu-latest"]' + go-latest-3-minor-versions-json: ${{ steps.set-go-latest-3-minor-versions-json.outputs.go-latest-3-minor-versions-json }} + go-latest-version-json: ${{ steps.set-go-latest-version-json.outputs.go-latest-version-json }} + goflags: -mod=readonly + golangci-lint-version: v1.40.1 + helm-latest-version-json: ${{ steps.set-helm-latest-version-json.outputs.helm-latest-version-json }} + helm-versions-json: ${{ steps.set-helm-versions-json.outputs.helm-versions-json }} + strategy: + fail-fast: true + matrix: + os: ["ubuntu-latest"] # Note: OS independent job. # TODO: until static global env can be used through ${{ env }}. + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set Git refname + id: set-git-refname + run: echo ::set-output name=git-refname::$(echo "${{ github.ref }}" | sed -E 's@refs/(heads|pull|tags)/@@g') + + - name: Set Go latest 3 minor versions JSON array + id: set-go-latest-3-minor-versions-json + run: echo ::set-output name=go-latest-3-minor-versions-json::$(make get-go-latest-3-minor-versions-json) + + - name: Set Go latest version JSON array + id: set-go-latest-version-json + run: echo ::set-output name=go-latest-version-json::$(make get-go-latest-version-json) + + - name: Set Helm latest version JSON string + id: set-helm-latest-version-json + run: echo ::set-output name=helm-latest-version-json::$(make get-helm-latest-version-json) + + - name: Set Helm versions JSON array + id: set-helm-versions-json + run: echo ::set-output name=helm-versions-json::[\"v2.17.0\", \"${{ fromJSON(steps.set-helm-latest-version-json.outputs.helm-latest-version-json) }}\"] + + check-git: + name: Check Git constraints + needs: + - configure-environment + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-default-os-json) }} # Note: OS independent job. + steps: + - name: Check unnecessary (rebase-removable) auto-generated merge commits + uses: gsactions/commit-message-checker@v1 + with: + pattern: ^(?!Merge (branch '[^']+' into |pull request \#[1-9][0-9]* from ))(.*)$ # https://regex101.com/r/9CYsBr/1 + error: There is an auto-generated merge commit on the branch/pull request which is unnecessary noise and should be removed by rebasing the branch. + excludeDescription: "true" # Note: we don't care about the PR description, only care about the automatic short message. + excludeTitle: "true" # Note: we don't care about the PR title, only care about the automatic short message. + checkAllCommitMessages: "true" # Note: all commit messages should be checked for a PR to prevent merging. + accessToken: ${{ secrets.GITHUB_TOKEN }} # Note: required to check older commit messages. + + analyze-code: + name: Analyze code + needs: + - configure-environment + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-version-json) }} # Note: Go version independent job. + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-default-os-json) }} # Note: OS independent job. + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: ${{ needs.configure-environment.outputs.golangci-lint-version }} + args: --new-from-rev ${{ needs.configure-environment.outputs.git-default-branch }} --timeout 10m + + build: + name: Build project binaries and libraries + needs: + - configure-environment + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-3-minor-versions-json) }} + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-oses-json) }} + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Cache Go module dependencies + id: cache-go-module-dependencies + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: go-mod-cache-${{ runner.os }}-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + go-mod-cache-${{ runner.os }}-${{ matrix.go-version }} + go-mod-cache-${{ runner.os }} + go-mod-cache + + - name: Check Go modules dependency file integrity + run: make check-go-mod-integrity + + - name: Build project binaries + run: make build + + - name: Upload project binaries artifact + uses: actions/upload-artifact@v2 + with: + name: go-bins-${{ runner.os }}-${{ matrix.go-version }} + path: bin + if-no-files-found: error + retention-days: 1 + + test-unit: + name: Run unit tests + needs: + - configure-environment + - build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-3-minor-versions-json) }} + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-oses-json) }} + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Cache Go module dependencies + id: cache-go-module-dependencies + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: go-mod-cache-${{ runner.os }}-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + go-mod-cache-${{ runner.os }}-${{ matrix.go-version }} + go-mod-cache-${{ runner.os }} + go-mod-cache + + - name: Test (unit) + run: make test-unit + + check-test-coverage-change: + name: Check test coverage change + needs: + - configure-environment + - build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-version-json) }} # Note: Go version independent job. + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-default-os-json) }} # Note: OS independent job. + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Cache Go module dependencies + id: cache-go-module-dependencies + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: go-mod-cache-${{ runner.os }}-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + go-mod-cache-${{ runner.os }}-${{ matrix.go-version }} + go-mod-cache-${{ runner.os }} + go-mod-cache + + - name: Check test coverage integrity + run: | + current_test_coverage=$(make test-coverage) + + git checkout ${{ needs.configure-environment.outputs.git-default-branch }} + default_test_coverage=$(make test-coverage|| echo "{}") + + jq --argjson CURRENT_COVERAGE "${current_test_coverage}" --argjson DEFAULT_COVERAGE "${default_test_coverage}" --exit-status --null-input \ + '$CURRENT_COVERAGE | keys | all(. as $key | ($DEFAULT_COVERAGE[$key] // 0.0) <= $CURRENT_COVERAGE[$key])' >/dev/null || \ + ( \ + printf >&2 '%s test coverage decreased compared to %s (default branch) test coverage\n%s (default branch) test coverage: %s\n%s test coverage: %s\n\n' \ + "${{ needs.configure-environment.outputs.git-refname }}" "${{ needs.configure-environment.outputs.git-default-branch }}" \ + "${{ needs.configure-environment.outputs.git-default-branch }}" "${default_test_coverage}" ; \ + "${{ needs.configure-environment.outputs.git-refname }}" "${current_test_coverage}" \ + exit 1 ; \ + ) + + test-e2e: + name: Run end to end tests + needs: + - configure-environment + - build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-version-json) }} + helm-version: ${{ fromJSON(needs.configure-environment.outputs.helm-versions-json) }} + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-oses-json) }} + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Setup Helm + run: | + helm_archive_name="helm-${{ matrix.helm-version }}-linux-amd64.tar.gz" + curl -sSL https://get.helm.sh/${helm_archive_name} -o ./${helm_archive_name} + mkdir -p bin + tar -xzf ${helm_archive_name} -C bin + echo "${{ github.workspace }}/bin/linux-amd64" >> $GITHUB_PATH + + - name: Initialize Helm v2 (required for plugin install before end to end test) + if: startsWith(matrix.helm-version, 'v2.') + run: helm init --client-only + + - name: Cache Go module dependencies + id: cache-go-module-dependencies + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: go-mod-cache-${{ runner.os }}-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + go-mod-cache-${{ runner.os }}-${{ matrix.go-version }} + go-mod-cache-${{ runner.os }} + go-mod-cache + + - name: Download project binaries artifact + uses: actions/download-artifact@v2 + with: + name: go-bins-${{ runner.os }}-${{ matrix.go-version }} + path: bin + + - name: Set binary file permissions to executable + run: chmod +x bin/helms3 + + - name: Test (end to end) + run: make test-e2e + + remove-temporary-artifacts: + name: Remove temporary artifacts + if: always() + needs: + - configure-environment + - build + - test-e2e + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-3-minor-versions-json) }} # Note: for GO version specific artifacts. + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-oses-json) }} # Note: for OS specific artifacts. + steps: + - name: Remove project binaries artifact + uses: geekyeggo/delete-artifact@v1 + if: ${{ needs.build.result }} == "success" + with: + name: go-bins-${{ runner.os }}-${{ matrix.go-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..ff3969d5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,244 @@ +name: Release + +on: + push: # Note: I'm dissatisfied with the workflow_run event's current UX (depending workflow not attached to commit/tag). + branches: + - main + tags: + - "*" + schedule: + - cron: "0 9 * * *" + +# Note: currently jobs..(runs-on|strategy) cannot use '${{ env }}'. +# Until this is fixed, I'm moving static environment configuration into the +# configure-environment job's outputs (originally it handled dynamically +# determined values only). +# env: + +jobs: + configure-environment: + name: Configure environment + runs-on: ${{ matrix.os }} + outputs: + architectures-json: '["amd64"]' + ci-workflow-name: CI + git-refname: ${{ steps.set-git-refname.outputs.git-refname }} + github-organization: banzaicloud + github-repository: helm-s3 + github-runner-default-os-json: '["ubuntu-latest"]' # Note: used for OS-independent jobs. + go-latest-version-json: ${{ steps.set-go-latest-version-json.outputs.go-latest-version-json }} + goreleaser-version: v0.164.0 + helm-latest-version-json: ${{ steps.set-helm-latest-version-json.outputs.helm-latest-version-json }} + helm-versions-json: ${{ steps.set-helm-versions-json.outputs.helm-versions-json }} + image-name: "banzaicloud/helm-s3" + strategy: + fail-fast: true + matrix: + os: ["ubuntu-latest"] # Note: OS independent job. # TODO: until static global env can be used through ${{ env }}. + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set Git refname + id: set-git-refname + run: echo ::set-output name=git-refname::$(echo "${{ github.ref }}" | sed -E 's@refs/(heads|tags)/@@g') + + - name: Set Go latest version JSON array + id: set-go-latest-version-json + run: echo ::set-output name=go-latest-version-json::$(make get-go-latest-version-json) + + - name: Set Helm latest version JSON string + id: set-helm-latest-version-json + run: echo ::set-output name=helm-latest-version-json::$(make get-helm-latest-version-json) + + - name: Set Helm versions JSON array + id: set-helm-versions-json + run: echo ::set-output name=helm-versions-json::[\"v2.17.0\", \"${{ fromJSON(steps.set-helm-latest-version-json.outputs.helm-latest-version-json) }}\"] + + # Note: I'm dissatisfied with the workflow_run event's current UX (depending workflow not attached to commit/tag). + wait-successful-ci-workflow: + name: Waiting for CI workflow to finish successfully + needs: + - configure-environment + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-default-os-json) }} # Note: OS independent job. + steps: + - name: Wait for CI workflow to complete + run: | + check_interval_seconds=10 + initial_wait_time_seconds=10 + organization=${{ needs.configure-environment.outputs.github-organization }} + ref_basename=${{ needs.configure-environment.outputs.git-refname }} + repository=${{ needs.configure-environment.outputs.github-repository }} + + echo "${{ github.token }}" | gh auth login --with-token + + # Note: waiting to ensure simultaneous workflow triggers have a chance to complete before accessing parallel workflow. + sleep ${initial_wait_time_seconds} + + ci_workflow_run=$(gh api "repos/${organization}/${repository}/actions/runs?branch=${ref_basename}&event=push" | jq '[ .workflow_runs[] | select((.name == "${{ needs.configure-environment.outputs.ci-workflow-name }}") and (.head_sha == "${{ github.sha }}")) ] | first') + echo "${ci_workflow_run}" | jq + + while echo "${ci_workflow_run}" | jq --exit-status '.status != "completed"' &>/dev/null;do + echo "Waiting for the workflow to complete" + sleep ${check_interval_seconds} + + ci_workflow_run=$(gh api "repos/${organization}/${repository}/actions/runs?branch=${ref_basename}&event=push" | jq '[ .workflow_runs[] | select((.name == "${{ needs.configure-environment.outputs.ci-workflow-name }}") and (.head_sha == "${{ github.sha }}")) ] | first') + echo "${ci_workflow_run}" | jq + done + + - name: Check successful CI workflow before release + run: | + organization=${{ needs.configure-environment.outputs.github-organization }} + ref_basename=${{ needs.configure-environment.outputs.git-refname }} + repository=${{ needs.configure-environment.outputs.github-repository }} + + echo "${{ github.token }}" | gh auth login --with-token + + ci_workflow_run=$(gh api "repos/${organization}/${repository}/actions/runs?branch=${ref_basename}&event=push" | jq '[ .workflow_runs[] | select((.name == "${{ needs.configure-environment.outputs.ci-workflow-name }}") and (.head_sha == "${{ github.sha }}")) ] | first') + echo "${ci_workflow_run}" | jq + + echo "${ci_workflow_run}" | jq --exit-status '(.status == "completed") and (.conclusion == "success")' + + build-and-push-container: + name: Build and push container + needs: + - configure-environment + - wait-successful-ci-workflow + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + architecture: ${{ fromJSON(needs.configure-environment.outputs.architectures-json) }} + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-version-json) }} + helm-version: ${{ fromJSON(needs.configure-environment.outputs.helm-versions-json) }} + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-default-os-json) }} # Note: OS independent job. + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set image tag + id: set-image-tag + run: | + echo ::set-output name=helm-image-version::$(helm_image_version=${{ matrix.helm-version }} && echo "${helm_image_version//v/}") + + if ${{ startsWith(matrix.helm-version, 'v2.') }}; then + helm_major_version="v2" + elif ${{ startsWith(matrix.helm-version, 'v3.') }}; then + helm_major_version="v3" + fi + + if [ "${{ github.event_name }}" == "schedule" ]; then + tag="scheduled-helm${{ matrix.helm-version }}" + echo ::set-output name=tag::${tag} + echo ::set-output name=image-tag::ghcr.io/${{ needs.configure-environment.outputs.image-name }}:${tag} + elif ${{ startsWith(github.ref, 'refs/heads/') }}; then + tag="${{ needs.configure-environment.outputs.git-refname }}-helm${helm_major_version}" + tag="${tag/\//-}" + echo ::set-output name=tag::${tag} + echo ::set-output name=image-tag::ghcr.io/${{ needs.configure-environment.outputs.image-name }}:${tag} + elif ${{ startsWith(github.ref, 'refs/tags/') }}; then + tag="${{ needs.configure-environment.outputs.git-refname }}-helm${helm_major_version}" + echo ::set-output name=tag::${tag} + echo ::set-output name=image-tag::ghcr.io/${{ needs.configure-environment.outputs.image-name }}:${tag} + else + printf >&2 "unexpected event or ref, event: %s, ref: %s, sha: %s" "${{ github.event_name }}" "${{ github.ref }}" "${{ github.sha }}" + exit 1 + fi + + - name: Setup Docker metadata + id: setup-docker-metadata + uses: docker/metadata-action@v3 + with: + images: ghcr.io/${{ needs.configure-environment.outputs.image-name }} + + - name: Setup Docker QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + + - name: Setup BuildX + id: setup-buildx + uses: docker/setup-buildx-action@v1 + with: + install: true + + - name: Log in to ghcr.io + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Build and push image + uses: docker/build-push-action@v2 + with: + build-args: | + ARCH=${{ matrix.architecture }} + GO_VERSION=${{ matrix.go-version }} + HELM_PLUGIN_VERSION=${{ steps.set-image-tag.outputs.tag }} + HELM_VERSION=${{ steps.set-image-tag.outputs.helm-image-version }} + builder: ${{ steps.setup-buildx.outputs.name }} + context: "." + file: ./Dockerfile + platforms: linux/${{ matrix.architecture }} + push: true + tags: ${{ steps.set-image-tag.outputs.image-tag }} + + create-git-tag-artifacts: + name: Create tag artifacts + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + needs: + - configure-environment + - wait-successful-ci-workflow + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + architecture: ${{ fromJSON(needs.configure-environment.outputs.architectures-json) }} + go-version: ${{ fromJSON(needs.configure-environment.outputs.go-latest-version-json) }} + os: ${{ fromJSON(needs.configure-environment.outputs.github-runner-default-os-json) }} + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Set tag + id: set-tag + run: | + organization=${{ needs.configure-environment.outputs.github-organization }} + repository=${{ needs.configure-environment.outputs.github-repository }} + tag=${{ needs.configure-environment.outputs.git-ref-basename }} + + echo "${{ github.token }}" | gh auth login --with-token + + tag_sha=$(gh api repos/${organization}/${repository}/git/matching-refs/tags/${tag} | jq --raw-output '.[0].object.sha') + tag_details=$(gh api repos/${organization}/${repository}/git/tags/${tag_sha}) + body=$(echo "${tag_details}" | jq --raw-output '.message' | awk '{ if($0 == "-----BEGIN PGP SIGNATURE-----") { exit } else { print $0 } }') + is_prerelease=$(echo "${tag}" | (grep -E -q "v?[0-9]+\.[0-9]+\.[0-9]+.+" && printf true) || printf false) + + # Note: preparing release notes file for GoReleaser, but also keeping Git in a clean state. + echo "${body}" > /tmp/release_notes.md + + echo ::set-output name=body::${body} + echo ::set-output name=is-prerelease::${is_prerelease} + echo ::set-output name=tag::${tag} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: ${{ needs.configure-environment.outputs.goreleaser-version }} + args: release --config .goreleaser.yml --release-notes /tmp/release_notes.md --rm-dist + workdir: "." + install-only: false + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/Makefile b/Makefile index e4c80874..fbb26413 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,27 @@ GIT_REF = $$(git show-ref --head | awk '/HEAD/ {print $$1}') # Note: explicitly setting GOBIN for global build install (required for GitHub # Actions environment). export GOBIN ?= $(shell go env GOPATH)/bin +GO_MAJOR_VERSION ?= 1 GO_ROOT_MODULE_PKG ?= $$(awk 'NR == 1 {print $$2 ; exit}' go.mod) +GO_TEST_COVERAGES = $$( \ + go test -cover -covermode atomic $$(go list ./... | grep -v $(GO_ROOT_MODULE_PKG)/test/e2e) | \ + jq --raw-input --slurp \ + '[ split("\n") | .[] | select(. != "") | capture("(\\?|ok)\\s+(?\\S+)\\s+([0-9.hmnµs]+\\s+coverage: (?[0-9]+.[0-9]+)% of statements|\\[no test files\\])"; "gins") | .coverage = ( .coverage // "0.0" | tonumber ) ] | reduce .[] as $$entry ({}; . + { ($$entry.package): ($$entry.coverage) }) | . as $$coverages | $$coverages * { "average": ( $$coverages | add * 1000 / length | round / 1000 ) }' \ +) +GO_VERSIONS = $$( \ + curl -sSL \ + -H "Accept: application/vnd.github.v3+json" \ + https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | \ + jq \ +) + +# Helm. +HELM_VERSIONS ?= $$( \ + curl -sSL \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/helm/helm/tags | \ + jq '[ .[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$$"; "gins")) | .name ]' \ +) # Helm S3 plugin. HELM_S3_PLUGIN_LATEST_VERSION ?= $$(awk '/^version:/ {print $$2 ; exit}' plugin.yaml) @@ -64,6 +84,36 @@ build-container: ## build-container builds the project's container with the ${VE build-latest: HELM_S3_PLUGIN_VERSION=$(HELM_S3_PLUGIN_LATEST_VERSION) ## build-latest builds the local packages with the latest version based on the plugin.yaml. build-latest: build +.PHONY: check-clean-git-state +check-git-state-clean: ## check-git-state-clean ensures the Git state is clean (no file changes occurred compared to the current reference). + @ echo "- Checking Git state cleanliness" + @ if [ "$$(git status --porcelain)" != "" ]; then \ + printf >&2 "Git state is not clean, not proceeding.\n\n" "$$(git diff)" ; \ + exit 1 ; \ + fi + +.PHONY: check-go-mod-integrity +check-go-mod-integrity: check-git-state-clean ## check-go-mod-integrity checks wether the source code and the recorded go mod dependencies are in sync. + @ echo "- Checking Go module dependencies integrity" + @ go mod tidy + @ if [ "$$(git status --porcelain)" != "" ]; then \ + printf >&2 '\n`go mod tidy` results in a dirty state, Go mod files are not in sync with the source code files, differences:\n\n%s\n\n' "$$(git diff)" ; \ + git reset --hard ; \ + exit 1 ; \ + fi + +.PHONY: get-go-latest-3-minor-versions-json +get-go-latest-3-minor-versions-json: ## get-go-latest-3-minor-versions-json retrieves the latest 3 minor versions of the configured Go major version as a JSON array. + @ echo $(GO_VERSIONS) | jq --compact-output '[ .[].version | capture("(?$(GO_MAJOR_VERSION))\\.(?[0-9]+)\\.(?[0-9]+)?"; "gins") ] | [ .[] | map_values(. | tonumber) ] | group_by(.minor) | [ ( .[] | max_by(.patch) ) ][-3:] | [ .[] | [ .major, .minor, .patch ] | join(".") ] | reverse' + +.PHONY: get-go-latest-version-json +get-go-latest-version-json: ## get-go-latest-version-json retrieves the latest version of the configured Go major version as a JSON array. + @ echo $(GO_VERSIONS) | jq --compact-output '[ .[].version | capture("(?$(GO_MAJOR_VERSION))\\.(?[0-9]+)\\.(?[0-9]+)?"; "gins") ] | [ .[] | map_values(. | tonumber) ] | group_by(.minor) | [ ( .[] | max_by(.patch) ) ][-1:] | [ .[] | [ .major, .minor, .patch ] | join(".") ] | reverse' + +.PHONY: get-helm-latest-version-json +get-helm-latest-version-json: ## get-helm-latest-version retrieves the latest version of Helm as a JSON string. + @echo $(HELM_VERSIONS) | jq 'first(.[])' + .PHONY: help help: ## help displays the help message. @ grep -E '^[0-9a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' @@ -122,6 +172,10 @@ teardown-e2e-test-env-force: ## teardown-e2e-test-env-force tears down the end t .PHONY: test test: test-unit test-e2e ## test runs all tests in the repository. +.PHONY: test-coverage +test-coverage: ## test-coverage generates data about the test coverage percentage of the code. + @ echo "$(GO_TEST_COVERAGES)" | jq + .PHONY: test-unit test-unit: ## test-unit runs the unit tests in the repository. @ echo "- Running unit tests" @@ -134,7 +188,3 @@ test-e2e: reset-e2e-test-env install-plugin-local test-e2e-no-env teardown-e2e-t test-e2e-no-env: ## test-e2e-no-env runs the end to end tests without any modifications to the testing environment. @ echo "- Running end to end tests" @ go test -count 1 -v $(GO_ROOT_MODULE_PKG)/test/e2e - -.PHONY: vendor -vendor: ## vendor downloads the dependencies to a local vendor folder. - @ go mod vendor