From 4b4455b3d4f50aab9630a3e6d2daccb3d1091895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:10:31 +0200 Subject: [PATCH 01/24] disable execution of codeql when code is pushed to main branch, it will only be executed for PR --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 75bb748..3eccf80 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,8 +12,8 @@ name: "CodeQL" on: - push: - branches: [ "main" ] + #push: + # branches: [ "main" ] pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] From 7759a61a6b301fee13f5f61fd1a24f2f76bd9ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:10:56 +0200 Subject: [PATCH 02/24] disable execution of codeql when code is pushed to main branch, it will only be executed for PR --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3eccf80..a11e273 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,8 +17,8 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] - schedule: - - cron: '25 13 * * 2' + #schedule: + # - cron: '25 13 * * 2' jobs: analyze: From 569671ed16c675defc54328571c845113509f34b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:11:21 +0200 Subject: [PATCH 03/24] disable execution of linters when code is pushed to main branch, it will only be executed for PR --- .github/workflows/linter.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index c5f1dc0..408f5b3 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,8 +1,8 @@ name: Static Analysis on: - push: - branches: - - main + #push: + # branches: + # - main pull_request: branches: - main From 28ff2fc480b0bbe27a8aa5d3a9417f413208ffb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:12:51 +0200 Subject: [PATCH 04/24] freebsd is discarded from the gorelease configuration --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 97fa9d1..2daed7a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -44,7 +44,7 @@ archives: - README.md - LICENSE id: nix - builds: [macos, linux, freebsd] + builds: [macos, linux] format: tar.gz - <<: *archives_defaults id: windows From fccaae553bb5e84c096b82f222b5a2ccad7eecf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:26:55 +0200 Subject: [PATCH 05/24] Update goreleaser configuration --- .github/workflows/release.yml | 4 ++-- README.md | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c997124..b8e973d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,5 +32,5 @@ jobs: version: latest args: release --clean env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAP_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_PAT }} + TAP_GITHUB_TOKEN: ${{ secrets.GH_PAT }} diff --git a/README.md b/README.md index 8161db1..b223bbf 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,17 @@ A powerful Kubebuilder plugin to initialize dynamically the structure of your kubebuilder operator repositories. -## Getting started - -## Installation - ### Prerequisites -This is a plugin for the kubebuilder tool. In case of you haven't installed the tool yet, please visit the -[kubebuilder documentation](https://github.com/kubernetes-sigs/kubebuilder) and follow the instructions to get +This is a plugin for the kubebuilder tool. In case of you haven't installed the tool yet, please visit the +[kubebuilder documentation](https://github.com/kubernetes-sigs/kubebuilder) and follow the instructions to get kuberbuilder properly installed in your computer. -### Homebrew +### Installation -### Download the executable files +#### Homebrew + +#### Download the executable files 1. Visit the latest release at [Release page](https://github.com/astrokube/kubebuilder-initializer-plugin/releases) 2. Download the version that works for you @@ -31,7 +29,7 @@ kuberbuilder properly installed in your computer. - OSX: ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer/v1-alpha - Linux: $HOME/.config/kubebuilder/plugins/kubebuilder-initializer/v1-alpha -### Build from the code +#### Build from the code ```bash git clone https://github.com/astrokube/kubebuilder-initializer-plugin.git @@ -43,10 +41,12 @@ to read the plugins - OSX: ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer/v1-alpha - Linux: $HOME/.config/kubebuilder/plugins/kubebuilder-initializer/v1-alpha -## How this plugin work +## Getting started To deep dive into how Kubebuilder deals with external plugins you can visit the following article -[Extensible CLI and Scaffolding Plugins - Phase 2](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md) +[Extensible CLI and Scaffolding Plugins - Phase 2](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md) + +Once you have installed the plugin you can ## Contributing From dc7cf1d4675513b28dd2dc51ad840f467534ea26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:29:14 +0200 Subject: [PATCH 06/24] Update goreleaser configuration --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8e973d..7a436ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,5 +32,5 @@ jobs: version: latest args: release --clean env: - GITHUB_TOKEN: ${{ secrets.GH_PAT }} - TAP_GITHUB_TOKEN: ${{ secrets.GH_PAT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} From 9532d714a2ae9c4851353ebc7430f2613639d630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:52:27 +0200 Subject: [PATCH 07/24] Update documentation --- README.md | 29 ++++++++++++++++++++++++++++- build/install.mk | 13 ++++++------- docs/assets/plugins.png | Bin 0 -> 84325 bytes 3 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 docs/assets/plugins.png diff --git a/README.md b/README.md index b223bbf..4431b9f 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,34 @@ to read the plugins To deep dive into how Kubebuilder deals with external plugins you can visit the following article [Extensible CLI and Scaffolding Plugins - Phase 2](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md) -Once you have installed the plugin you can +Once you have installed the plugin you can use the Kubebuilder cli as usual. + +1. Check that the plugin has been installed correctly + +```bash +kubebuilder help +``` + +And the `kubebuilder-initializer-plugin/v1-alpha` is displayed as part of the list of available plugins. + +![Kubebuilder pLugins](docs/assets/plugins.png) + +2. Decide which template you would like to use for creating the folder structure of your Kubebuilder operator. You can +create your own template as described (here]() or alternatively you could take advantage of some of the well-known templates +that you can find in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md) + +3. Initialize your project. Keep in mind that this plugin is used exclusively to initialize our project structure, so we should +use also a plugin that supports aPI and webhook creation,for instance the `go.kubebuilder.io/v3` that is prpvided out +of the box by Kubbebuilder. + +```bash +kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-layout/v1-alpha \ + --from "github.com/astrokube/kubebuilder-operator-template" \ + --domain testing \ + --repo github.com/astrokube/k8s-testing-operator --owner astrokube; +``` + + ## Contributing diff --git a/build/install.mk b/build/install.mk index d4e17ec..c51e1b2 100644 --- a/build/install.mk +++ b/build/install.mk @@ -1,14 +1,13 @@ -OSX_PLuGIN_DIR = ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer/v1-alpha -LINUX_PLuGIN_DIR = $HOME/.config/kubebuilder/plugins/kubebuilder-initializer/v1-alpha +OSX_PLuGIN_DIR = ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha +LINUX_PLuGIN_DIR = ~/.config/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha .PHONY: install install: - @echo "=== $(PROJECT_NAME) === [ install ]: install localy..." + @echo "=== $(PROJECT_NAME) === [ install ]: install localy... $(UNAME)" ifeq ($(UNAME), Linux) -# do something Linux-y - mkdir -p $(OSX_PLuGIN_DIR) - cp dist/kubebuilder-initializer-plugin $(OSX_PLuGIN_DIR)/kubebuilder-initializer + mkdir -p $(LINUX_PLuGIN_DIR) + cp dist/kubebuilder-initializer-plugin $(LINUX_PLuGIN_DIR)/kubebuilder-initializer-plugin endif ifeq ($(UNAME), Darwin) mkdir -p $(OSX_PLuGIN_DIR) - cp dist/kubebuilder-initializer-plugin $(LINUX_PLuGIN_DIR)/kubebuilder-initializer + cp dist/kubebuilder-initializer-plugin $(OSX_PLuGIN_DIR)/kubebuilder-initializer-plugin endif \ No newline at end of file diff --git a/docs/assets/plugins.png b/docs/assets/plugins.png new file mode 100644 index 0000000000000000000000000000000000000000..5a5d25f48967a02b809dfa5eb164d7c52b265be5 GIT binary patch literal 84325 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 literal 0 HcmV?d00001 From 3d9ad1a3210b3dace9ab85f8d5ac5e05e9222fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 06:55:19 +0200 Subject: [PATCH 08/24] Update documentation --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4431b9f..786df81 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ And the `kubebuilder-initializer-plugin/v1-alpha` is displayed as part of the li ![Kubebuilder pLugins](docs/assets/plugins.png) -2. Decide which template you would like to use for creating the folder structure of your Kubebuilder operator. You can +2. Choose the template for scaffolding the initial structure of our Kubebuilder operator. You can create your own template as described (here]() or alternatively you could take advantage of some of the well-known templates that you can find in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md) @@ -73,6 +73,7 @@ kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-layout/v1-alpha \ --repo github.com/astrokube/k8s-testing-operator --owner astrokube; ``` +*In the above example, the args `domain`, `repo` and `owner` are required by the plugin `go.kubebuilder.io/v3`.* ## Contributing From 2c7852634d25f0a1dc7fab2b50a460a47af616d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 10:42:38 +0200 Subject: [PATCH 09/24] Update documentation --- README.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 786df81..cbcb047 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,36 @@ of the box by Kubbebuilder. ```bash kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-layout/v1-alpha \ --from "github.com/astrokube/kubebuilder-operator-template" \ - --domain testing \ - --repo github.com/astrokube/k8s-testing-operator --owner astrokube; + --domain astrokube \ + --owner astrokube \ + --repo github.com/astrokube/k8s-testing-operator ``` +The only argument that needs to be passed is the `from` + *In the above example, the args `domain`, `repo` and `owner` are required by the plugin `go.kubebuilder.io/v3`.* +**Non default branches** + +By default, the plugin will fetch the code in the default branch, buy we can specify a branch: + +```bash +--from "github.com/astrokube/kubebuilder-operator-template#" +```` + +**With credentials** + +For those repositories that require authentication we can provide the user credentials or a token as It's shown on +the below + +```bash +--from ":@github.com/astrokube/kubebuilder-operator-template" +... + --from "@github.com/astrokube/kubebuilder-operator-template" +``` + + + ## Contributing From 1a956034f704832c033c4c11549ebbb06c8e6476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 11:24:59 +0200 Subject: [PATCH 10/24] Update documentation --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cbcb047..b86e53e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,24 @@ kuberbuilder properly installed in your computer. #### Homebrew +1. Add the Astrokube repo +```bash +brew tap astrokube/tools +``` + +2. Install Kubebyuilder Initializer plugin: +```bash +brew install kubebuilder-initializer-plugin +``` + +3. Please, create a symbolic link (this is required as the Kubebuilder cli will look at that folder to load the external plugins) + +```bash +mkdir -p ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/ +ln -s /usr/local/Cellar/kubebuilder-initializer-plugin/0.1.0/bin/kubebuilder-initializer-plugin \ + ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/kubebuilder-initializer-plugin +``` + #### Download the executable files 1. Visit the latest release at [Release page](https://github.com/astrokube/kubebuilder-initializer-plugin/releases) @@ -62,7 +80,12 @@ And the `kubebuilder-initializer-plugin/v1-alpha` is displayed as part of the li create your own template as described (here]() or alternatively you could take advantage of some of the well-known templates that you can find in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md) -3. Initialize your project. Keep in mind that this plugin is used exclusively to initialize our project structure, so we should +3. Once we have chosen the template that we want to use, we just need to write the yaml file that contains the values that +will make us to customize the template. By default, the plugin will take a file named `.kubebuilder-layout.yaml`, otherwise +you will need to pass an extra argument with the path to the file. + + +4Initialize your project. Keep in mind that this plugin is used exclusively to initialize our project structure, so we should use also a plugin that supports aPI and webhook creation,for instance the `go.kubebuilder.io/v3` that is prpvided out of the box by Kubbebuilder. From d5433944af622fe525cdae22b3ba8885e219e95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= Date: Fri, 7 Apr 2023 17:14:39 +0200 Subject: [PATCH 11/24] ADd templates for creating new issues --- .github/ISSUE_TEMPLATE/bug_report.yml | 51 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/ISSUE_TEMPLATE/feature_request.yaml | 55 +++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..64a9249 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,51 @@ +name: "🐛 Bug Report" +description: Create a new ticket for a bug. +title: "🐛 [BUG] - " +labels: [ + "bug" +] +body: + - type: dropdown + id: os + attributes: + label: "OS" + description: What is your environment ? + multiple: true + options: + - Windows + - Linux + - Mac + validations: + required: true + - type: textarea + id: description + attributes: + label: "Description" + description: Please enter an explicit description of your issue + placeholder: Short and explicit description of your incident... + validations: + required: true + + - type: textarea + id: reprod + attributes: + label: "Reproduction steps" + description: Please enter an explicit description of your issue + value: | + 1. '...' + 2. '....' + 3. '....' + 4. See error + render: bash + validations: + required: true + - type: textarea + id: screenshot + attributes: + label: "Screenshots" + description: If applicable, add screenshots to help explain your problem. + value: | + ![DESCRIPTION](LINK.png) + render: bash + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..1b87aa5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Support Questions + url: https://github.com/astrokube/kubebuilder-initializer-plugin/discussions/new + about: Need support & not sure if this a bug? You can ask questions in Slack or GitHub discussions. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 0000000..ca18281 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,55 @@ +name: "💡 Feature Request" +description: Create a new ticket for a new feature request +title: "💡 [REQUEST] - <title>" +labels: [ + "question" +] +body: + - type: input + id: start_date + attributes: + label: "Start Date" + description: Start of development + placeholder: "month/day/year" + validations: + required: false + - type: textarea + id: implementation_pr + attributes: + label: "Implementation PR" + description: Pull request used + placeholder: "#Pull Request ID" + validations: + required: false + - type: textarea + id: reference_issues + attributes: + label: "Reference Issues" + description: Common issues + placeholder: "#Issues IDs" + validations: + required: false + - type: textarea + id: summary + attributes: + label: "Summary" + description: Provide a brief explanation of the feature + placeholder: Describe in a few lines your feature request + validations: + required: true + - type: textarea + id: drawbacks + attributes: + label: "Drawbacks" + description: What are the drawbacks/impacts of your feature request ? + placeholder: Identify the drawbacks and impacts while being neutral on your feature request + validations: + required: true + - type: textarea + id: unresolved_question + attributes: + label: "Unresolved questions" + description: What questions still remain unresolved ? + placeholder: Identify any unresolved issues. + validations: + required: false \ No newline at end of file From 9721a484449b177773c092d2bd7dcace2188d2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 05:23:37 +0200 Subject: [PATCH 12/24] Fix typos in README --- README.md | 3 +-- pkg/templatizer/internal/source/git.go | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1e25951..fb379ce 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,6 @@ you will need to pass an extra argument with the path to the file. 4. Initialize your project. Keep in mind that this plugin is used exclusively to initialize our project structure, so we should use also a plugin that supports the APIs and webhooks creation,for instance the `go.kubebuilder.io/v3` that is prpvided out ->>>>>>> main of the box by Kubbebuilder. ```bash @@ -103,7 +102,7 @@ The only argument that needs to be passed is the `from` **Non default branches** -By default, the plugin will fetch the code in the default branch, buy we can specify a branch: +By default, the plugin will fetch the code in the default branch, but we can specify another branch: ```bash --from "github.com/astrokube/kubebuilder-operator-template#<branch>" diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index c7f336e..b557a00 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -64,10 +64,8 @@ func (s *GitSource) GetTemplateContent() (map[string]string, error) { func (s *GitSource) loadFileSystem() error { repo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{ - URL: s.url, - Auth: s.auth, - //Progress: os.Stdout, - // InsecureSkipTLS: true, + URL: s.url, + Auth: s.auth, SingleBranch: true, }) if err != nil { From f3eaebbd6b435f0275cf4727d4dc0c2151887c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 05:30:14 +0200 Subject: [PATCH 13/24] Remove unused Makefile targets to run the rest of the commands from a Docker container --- Makefile | 1 - build/docker.mk | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 build/docker.mk diff --git a/Makefile b/Makefile index b00494d..9cf356d 100644 --- a/Makefile +++ b/Makefile @@ -23,5 +23,4 @@ include build/code.mk include build/test.mk include build/compile.mk include build/release.mk -include build/docker.mk include build/install.mk diff --git a/build/docker.mk b/build/docker.mk deleted file mode 100644 index db11aa4..0000000 --- a/build/docker.mk +++ /dev/null @@ -1,4 +0,0 @@ -.PHONY: docker-% -docker-%: ## Run commands inside a docker container - docker run --rm --workdir /app -v $(CURDIR):/app golang:$(GO_VERSION) \ - make $* \ No newline at end of file From a511c69d2cce7761e75f4844b4d0e1d959937355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 05:38:02 +0200 Subject: [PATCH 14/24] Remove unnexesary flags from Go compilation --- .goreleaser.yml | 1 - Makefile | 2 +- build/compile.mk | 6 +----- internal/info/plugin.go | 12 ------------ internal/info/plugin_test.go | 22 ---------------------- 5 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 internal/info/plugin.go delete mode 100644 internal/info/plugin_test.go diff --git a/.goreleaser.yml b/.goreleaser.yml index 2daed7a..71848fd 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,7 +3,6 @@ project_name: kubebuilder-initializer-plugin before: hooks: - go mod download - - builds: - <<: &build_defaults diff --git a/Makefile b/Makefile index 9cf356d..d8e2f04 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ GO_CMD ?= go PROJECT_NAME := kubebuilder-initializer-plugin UNAME := $(shell uname) -all: init fmt test lint compile +all: init fmt test lint build help: ## Display this help screen @echo "Makefile targets:" diff --git a/build/compile.mk b/build/compile.mk index 756de0b..c4fb3c5 100644 --- a/build/compile.mk +++ b/build/compile.mk @@ -5,11 +5,7 @@ BUILD_DATE = $(shell date -u +%Y-%m-%dT%H:%M:%SZ) VERSION ?= $(shell git describe --tags --always --dirty | sed -e '/^v/s/^v\(.*\)$$/\1/g') VERSION_TAG := $(shell git describe --tags --always --abbrev=0 | sed -e '/^v/s/^v\(.*\)$$/\1/g') -LDFLAGS = -s -w \ - -X github.com/astrokube/kubebuilder-initializer-plugin/internal/info.Commit=$(COMMIT) \ - -X github.com/astrokube/kubebuilder-initializer-plugin/internal/info.Version=$(VERSION) \ - -X github.com/astrokube/kubebuilder-initializer-plugin/internal/info.BuildDate=$(BUILD_DATE) \ - -X github.com/astrokube/kubebuilder-initializer-plugin/internal/info.Compiler=$(GOLANG_VERSION) +LDFLAGS = -s -w .PHONY: build build: ## build executable for the current os diff --git a/internal/info/plugin.go b/internal/info/plugin.go deleted file mode 100644 index 79aeef9..0000000 --- a/internal/info/plugin.go +++ /dev/null @@ -1,12 +0,0 @@ -package info - -var ( - Commit string - Version string - BuildDate string - GolangVersion string -) - -func Author() string { - return "The Astrokube Team <engineering@astrokube.com>" -} diff --git a/internal/info/plugin_test.go b/internal/info/plugin_test.go deleted file mode 100644 index eca8837..0000000 --- a/internal/info/plugin_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package info - -import "testing" - -func TestAuthor(t *testing.T) { - tests := []struct { - name string - want string - }{ - { - name: "simple test", - want: "The Astrokube Team <engineering@astrokube.com>", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := Author(); got != tt.want { - t.Errorf("Author() = %v, want %v", got, tt.want) - } - }) - } -} From bda7a8e1695c4701d2240e0bbeb636a8c5761342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 06:11:30 +0200 Subject: [PATCH 15/24] ADd documentation for public packages --- internal/command/command.go | 3 --- pkg/templatizer/internal/source/git.go | 20 ++++++++++++++++++- .../internal/variables/replacer.go | 3 +++ pkg/templatizer/templatizer.go | 8 ++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/command/command.go b/internal/command/command.go index 0e42e1d..a64b678 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -53,7 +53,6 @@ func Run(request *external.PluginRequest) external.PluginResponse { default: err = fmt.Errorf("unknown command '%s'", request.Command) } - if err != nil { response.Error = true response.ErrorMsgs = []string{err.Error()} @@ -69,8 +68,6 @@ func processFlags(request *external.PluginRequest, flags []external.Flag) *pflag flagsSet.String(f.Name, f.Default, f.Usage) case "boolean": flagsSet.Bool(f.Name, f.Default == "true", f.Usage) - case "array": - flagsSet.StringArray(f.Name, []string{}, f.Usage) default: flagsSet.String(f.Name, f.Default, f.Usage) } diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index b557a00..bc2ee1b 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -1,3 +1,6 @@ +/* +Package source provides us with the implementations that can be used to fetch the source code in the templates. +*/ package source import ( @@ -14,8 +17,9 @@ import ( "github.com/go-git/go-git/v5/storage/memory" ) -var repoRegex = regexp.MustCompile(`((?P<auth>(.*))(@))?(?P<path>([a-zA-Z0-9./\-_]+))(#(?P<branch>(.*)))?`) +var repoRegex = regexp.MustCompile(`(?P<protocol>(https|ssh))://((?P<auth>(.*))(@))?(?P<path>([a-zA-Z0-9./\-_]+))(#(?P<branch>(.*)))?`) +// A GitSource struct is used to fetch the template from a Git repository. type GitSource struct { url string branch string @@ -24,6 +28,18 @@ type GitSource struct { refOrigin string } +// NewGitSource returns a pointer of a new instance of GitSource. +// +// The function receives a parameter, named conn, that contains the details of the Git connection. The function +// uses the below regular expression to extract the details in the parameter. +// +// (?P<protocol>(https|ssh))://((?P<auth>(.*))(@))?(?P<path>([a-zA-Z0-9./\-_]+))(#(?P<branch>(.*)))? +// +// The connection url is composed by the following parts +// protocol: It's required a must be https or ssh depending on the chosen mechanism to establish the connection with the remote repository. +// auth: It's an optional param (not required for public repositories) and we can provide both user credentials or a token. If we pass the credentials the format must be username:password +// path: The repository url. For instance, github.com/astrokube/kubebuilder-initializer-plugin if we use the https protocol, or github.com/astrokube/kubebuilder-initializer-plugin.git if we use the ssh protocol +// branch: Optional param, we can use a specific branch by passing the name of the desired branch after symbol `#` func NewGitSource(conn string) *GitSource { match := repoRegex.FindStringSubmatch(conn) @@ -53,6 +69,8 @@ func NewGitSource(conn string) *GitSource { return s } +// GetTemplateContent returns a map with an entry per each file in the repository. The key of this map +// is the absolute path to the file from the root of the repository and the value is the content of the file. func (s *GitSource) GetTemplateContent() (map[string]string, error) { if err := s.loadFileSystem(); err != nil { return nil, err diff --git a/pkg/templatizer/internal/variables/replacer.go b/pkg/templatizer/internal/variables/replacer.go index 83b5c9f..4bfc462 100644 --- a/pkg/templatizer/internal/variables/replacer.go +++ b/pkg/templatizer/internal/variables/replacer.go @@ -1,3 +1,4 @@ +// Package variables is used to deal with variables and the replacement of values in Go templates package variables import ( @@ -6,6 +7,8 @@ import ( "text/template" ) +// ReplaceVariables is a function that create a Go text template from the given content and replace the provided +// variables in the map func ReplaceVariables(name string, content string, variables map[string]interface{}) (string, error) { t, err := template.New(name).Parse(content) if err != nil { diff --git a/pkg/templatizer/templatizer.go b/pkg/templatizer/templatizer.go index 16b1b73..65711d4 100644 --- a/pkg/templatizer/templatizer.go +++ b/pkg/templatizer/templatizer.go @@ -1,3 +1,5 @@ +// Package templatizer is an isolated package that could be used from other repositories as a library. That's the main +// reason this package is under `pkg`. package templatizer import ( @@ -10,6 +12,12 @@ import ( "gopkg.in/yaml.v3" ) +// Templatize fetch the content in a remote source and replace the variables with the provided values in a file. +// This function recieves the below three parameters: +// - sourceType: So far only `git` is supported +// - connString: url used to establish the connection with the remote source +// - varsFile: Path to the file that contains the variables that will be used to replace values + func Templatize(sourceType string, connString string, varsFile string) (map[string]string, error) { var content map[string]string var err error From 2a4133f902f660c532a7acdcb6c99cd5139d04be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 06:30:19 +0200 Subject: [PATCH 16/24] Make the README easiert to be followed --- README.md | 81 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 3f611ca..2d8c57e 100644 --- a/README.md +++ b/README.md @@ -8,74 +8,76 @@ # Kubebuilder Initializer Plugin -A powerful Kubebuilder plugin to initialize dynamically the structure of your kubebuilder operator repositories. +A powerful Kubebuilder plugin to initialize dynamically the structure of your kubebuilder operator project. ### Prerequisites -This is a plugin for the kubebuilder tool. In case of you haven't installed the tool yet, please visit the -[kubebuilder documentation](https://github.com/kubernetes-sigs/kubebuilder) and follow the instructions to get -kuberbuilder properly installed in your computer. +This is a plugin for the kubebuilder cli tool If you don't have the Kubebuilder cli installed in your computer, please +visit the official documentation, [kubebuilder documentation](https://github.com/kubernetes-sigs/kubebuilder). ### Installation -#### Homebrew - -1. Add the Astrokube repo -```bash -brew tap astrokube/tools -``` - -2. Install Kubebyuilder Initializer plugin: -```bash -brew install kubebuilder-initializer-plugin -``` - -3. Please, create a symbolic link (this is required as the Kubebuilder cli will look at that folder to load the external plugins) +We provide you a variety of alternatives to install the plugin in your computer, take the onw that best fits your needs. -```bash -mkdir -p ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/ -ln -s /usr/local/Cellar/kubebuilder-initializer-plugin/0.1.0/bin/kubebuilder-initializer-plugin \ - ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/kubebuilder-initializer-plugin -``` - -#### Download the executable files +### Download the executable 1. Visit the latest release at [Release page](https://github.com/astrokube/kubebuilder-initializer-plugin/releases) 2. Download the version that works for you -3. Extract the files in the tarball that you downloaded in the previous step +3. Extract the tarball 4. Copy the executable file to the path used by Kubebuilder to read the external plugins - OSX: ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer/v1-alpha - Linux: $HOME/.config/kubebuilder/plugins/kubebuilder-initializer/v1-alpha -#### Build from the code +### From the code + +To compile the code from your own computer, you just need to run the following commands ```bash git clone https://github.com/astrokube/kubebuilder-initializer-plugin.git cd kubebuilder-initializer-plugin make build install ``` -To check that installation was success, please check that the executable file was copied to the folder used by Kubebuilder -to read the plugins + +To check that the installation was success, verify that the executable was copied to the below path (depending on the +operating system) + - OSX: ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer/v1-alpha - Linux: $HOME/.config/kubebuilder/plugins/kubebuilder-initializer/v1-alpha -## Getting started -To deep dive into how Kubebuilder deals with external plugins you can visit the following article -[Extensible CLI and Scaffolding Plugins - Phase 2](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md) +#### Homebrew -Once you have installed the plugin you can use the Kubebuilder cli as usual. +Additionally, you could download the executable from our Astrokube Brew repository. -1. Check that the plugin has been installed correctly +1. Add the Astrokube Brew repository +```bash +brew tap astrokube/tools +``` +2. Install Kubebyuilder Initializer plugin: ```bash -kubebuilder help +brew install kubebuilder-initializer-plugin ``` -And the `kubebuilder-initializer-plugin/v1-alpha` is displayed as part of the list of available plugins. +3. You will need to create a symbolic link to the path used by Kubebuilder to read the external plugins. + +```bash +mkdir -p ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/ +ln -s /usr/local/Cellar/kubebuilder-initializer-plugin/0.1.0/bin/kubebuilder-initializer-plugin \ + ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/kubebuilder-initializer-plugin +``` + +## Getting started + +Once the plugin is installed in your computer, a new plugin is available for you to be used when running the Kubebuilder +cli tool, you can run `kubebuilder help` to check it. + +The `kubebuilder-initializer-plugin/v1-alpha` appears in the list of available plugins. ![Kubebuilder pLugins](docs/assets/plugins.png) + + 2. Choose the template for scaffolding the initial structure of our Kubebuilder operator. You can create your own template as described (here]() or alternatively you could take advantage of some of the well-known templates that you can find in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md) @@ -85,9 +87,7 @@ will make us to customize the template. By default, the plugin will take a file you will need to pass an extra argument with the path to the file. 4. Initialize your project. Keep in mind that this plugin is used exclusively to initialize our project structure, so we should -use also a plugin that supports the APIs and webhooks creation,for instance the `go.kubebuilder.io/v3` that is prpvided out ->>>>>>> main -of the box by Kubbebuilder. +use also a plugin that supports the APIs and webhooks creation,for instance the `go.kubebuilder.io/v3` that is prpvided out of the box by Kubbebuilder. ```bash kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-layout/v1-alpha \ @@ -121,6 +121,11 @@ the below ``` +## Kubebuilder + +To deep dive into how Kubebuilder deals with external plugins you can visit the following article +[Extensible CLI and Scaffolding Plugins - Phase 2](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-2.md) + ## Contributing From c2fad1850086a3bdf5f3bb1058df82eb9215d1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 06:43:23 +0200 Subject: [PATCH 17/24] Make the README easiert to be followed --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d8c57e..3901d04 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,33 @@ mkdir -p ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initiali ln -s /usr/local/Cellar/kubebuilder-initializer-plugin/0.1.0/bin/kubebuilder-initializer-plugin \ ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/kubebuilder-initializer-plugin ``` +## Define your own template + +The Kubebuilder Initializer plugin understand a template like a Git repository in which the name of the elements in the repository +(both folders and files) and the content of the files can contain variables. + +The plugin takes advantage of Go templates to process the templates; that provides us with a very flexible way to define templates. +That implies that we could not only to define variables + +[go.mod](https://github.com/astrokube/kubebuilder-operator-template/blob/main/go.mod#L1) +```text +module {{.repository.server}}/{{.repository.owner}}/{{.repository.name}} +``` + +but also add some logic to our own templates + +[OWNERS_ALIASES](https://github.com/astrokube/kubebuilder-operator-template/blob/main/OWNERS_ALIASES#L4-L9) +```text +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md + +aliases: +{{- range .owners}} + {{.alias}}: +{{- range .members}} + - [{{.}}](https://github.com/{{.}}) +{{- end}} +{{- end}} +``` ## Getting started @@ -76,7 +103,7 @@ The `kubebuilder-initializer-plugin/v1-alpha` appears in the list of available p ![Kubebuilder pLugins](docs/assets/plugins.png) - +This plu 2. Choose the template for scaffolding the initial structure of our Kubebuilder operator. You can create your own template as described (here]() or alternatively you could take advantage of some of the well-known templates From 3a9d0400f0b0cfcb59a7376a0a5ea24eae918770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 07:12:03 +0200 Subject: [PATCH 18/24] Update documentation --- README.md | 58 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 3901d04..223e306 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ A powerful Kubebuilder plugin to initialize dynamically the structure of your kubebuilder operator project. -### Prerequisites +## Prerequisites This is a plugin for the kubebuilder cli tool If you don't have the Kubebuilder cli installed in your computer, please visit the official documentation, [kubebuilder documentation](https://github.com/kubernetes-sigs/kubebuilder). -### Installation +## Installation We provide you a variety of alternatives to install the plugin in your computer, take the onw that best fits your needs. @@ -45,7 +45,7 @@ operating system) - Linux: $HOME/.config/kubebuilder/plugins/kubebuilder-initializer/v1-alpha -#### Homebrew +### Homebrew Additionally, you could download the executable from our Astrokube Brew repository. @@ -66,6 +66,16 @@ mkdir -p ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initiali ln -s /usr/local/Cellar/kubebuilder-initializer-plugin/0.1.0/bin/kubebuilder-initializer-plugin \ ~/Library/Application\ Support/kubebuilder/plugins/kubebuilder-initializer-plugin/v1-alpha/kubebuilder-initializer-plugin ``` + +### Verify the installation + +Once the plugin is installed in your computer, a new plugin is available for you to be used when running the Kubebuilder +cli tool, you can run `kubebuilder help` to check it. + +The `kubebuilder-initializer-plugin/v1-alpha` appears in the list of available plugins. + +![Kubebuilder pLugins](docs/assets/plugins.png) + ## Define your own template The Kubebuilder Initializer plugin understand a template like a Git repository in which the name of the elements in the repository @@ -94,39 +104,43 @@ aliases: {{- end}} ``` -## Getting started +You can find some examples of templates in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md), and we encourage you to contribute +with your own templates, so please, feel free to open a pull request with an entry in this file if you want to share a template with others. -Once the plugin is installed in your computer, a new plugin is available for you to be used when running the Kubebuilder -cli tool, you can run `kubebuilder help` to check it. +**TODO** +We ask you to share an example files with the variables that need to be passed in order to customize the templates, +See an example [here](). Only yaml files are supported (JSON could be supported for futures release If this was required by the community ) +*For upcoming releases, the plugin will inspect the files in the templates and It will be able to generate the yaml file for you* -The `kubebuilder-initializer-plugin/v1-alpha` appears in the list of available plugins. -![Kubebuilder pLugins](docs/assets/plugins.png) +## Getting started -This plu +This plugin is used exclusively in the initial scaffolding (`kubebuilder init`) and It's compatible with any other plugin. +When we run the `init` command, the Kubebuilder cli creates the PROJECT file, this is the main piece for Kubebuilder +to create consistency and being able to inject code when we run other commands such as `kubebuildfer create api` or +`kubebuilder create webhook` +On the other hand, the `Kubebuilder Initializer plugin` must be used in conjunction with other plugins that will +take the control once we need to create a Webhook or an API. -2. Choose the template for scaffolding the initial structure of our Kubebuilder operator. You can -create your own template as described (here]() or alternatively you could take advantage of some of the well-known templates -that you can find in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md) +To take advantage of the Initializer plugin, we just need a repository, that will be used as a template, and the variables file +that will allow us to customize the template. By the default, the plugin read the variables from a named file `.kubebuilder-layout.yaml`, +but this can be customized If required. -3. Once we have chosen the template that we want to use, we just need to write the yaml file that contains the values that -will make us to customize the template. By default, the plugin will take a file named `.kubebuilder-layout.yaml`, otherwise -you will need to pass an extra argument with the path to the file. +To sum up, to initialize our project we just need to pass the argument `--from` and we could additionally pass the argument +`--vars` in case of we don't want to use the default `.kubebuilder-layout.yaml`. -4. Initialize your project. Keep in mind that this plugin is used exclusively to initialize our project structure, so we should -use also a plugin that supports the APIs and webhooks creation,for instance the `go.kubebuilder.io/v3` that is prpvided out of the box by Kubbebuilder. +In the below example, we would use our plugin in conjunction with the `go.kubebuilder.io/v3` that help us to work with +implementation of operators in Go. ```bash -kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-layout/v1-alpha \ +kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-initializer-plugin/v1-alpha \ --from "github.com/astrokube/kubebuilder-operator-template" \ --domain astrokube \ --owner astrokube \ - --repo github.com/astrokube/k8s-testing-operator + --repo https://github.com/astrokube/k8s-testing-operator ``` -The only argument that needs to be passed is the `from` - -*In the above example, the args `domain`, `repo` and `owner` are required by the plugin `go.kubebuilder.io/v3`.* +Be aware that, in the above example, the arguments `domain`, `repo` and `owner` are required by the plugin `go.kubebuilder.io/v3`. **Non default branches** From fdb32f97c272eb9c1f6fa0a3aaff17d7da97f8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 09:09:20 +0200 Subject: [PATCH 19/24] Update documentation --- README.md | 6 ++---- pkg/templatizer/internal/source/git.go | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 223e306..017a079 100644 --- a/README.md +++ b/README.md @@ -134,10 +134,8 @@ implementation of operators in Go. ```bash kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-initializer-plugin/v1-alpha \ - --from "github.com/astrokube/kubebuilder-operator-template" \ - --domain astrokube \ - --owner astrokube \ - --repo https://github.com/astrokube/k8s-testing-operator + --domain astrokube --owner astrokube --repo github.com/astrokube/k8s-testing-operator \ + --from "https://github.com/astrokube/kubebuilder-operator-template" ``` Be aware that, in the above example, the arguments `domain`, `repo` and `owner` are required by the plugin `go.kubebuilder.io/v3`. diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index bc2ee1b..6f30c04 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -47,6 +47,7 @@ func NewGitSource(conn string) *GitSource { for i, name := range repoRegex.SubexpNames() { if i > 0 && i <= len(match) { paramsMap[name] = match[i] + fmt.Printf("%s -> %s", name, match[i]) } } s := &GitSource{} @@ -65,7 +66,7 @@ func NewGitSource(conn string) *GitSource { } s.refOrigin = "origin" s.branch = paramsMap["branch"] - s.url = fmt.Sprintf("https://%s", paramsMap["path"]) + s.url = paramsMap["path"] return s } From ad11afe359beddc771dba62cbf1c9faa97547b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 10:21:34 +0200 Subject: [PATCH 20/24] Fix linter issues --- README.md | 17 ++++++++++------- pkg/templatizer/internal/source/git.go | 10 +++++++--- pkg/templatizer/internal/variables/replacer.go | 2 +- pkg/templatizer/templatizer.go | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 017a079..efb9687 100644 --- a/README.md +++ b/README.md @@ -105,13 +105,15 @@ aliases: ``` You can find some examples of templates in [AWESOME_TEMPLATES.md](AWESOME_TEMPLATES.md), and we encourage you to contribute -with your own templates, so please, feel free to open a pull request with an entry in this file if you want to share a template with others. +with your own templates, so please, feel free to open a pull request with an entry in this file if you want to share a +template with others. **TODO** We ask you to share an example files with the variables that need to be passed in order to customize the templates, -See an example [here](). Only yaml files are supported (JSON could be supported for futures release If this was required by the community ) -*For upcoming releases, the plugin will inspect the files in the templates and It will be able to generate the yaml file for you* +See an example [here](). Only yaml files are supported (JSON could be supported for futures release If this was required +by the community ) +*For upcoming releases, the plugin will inspect the files in the templates and It will be able to generate the yaml file for you* ## Getting started @@ -122,9 +124,9 @@ to create consistency and being able to inject code when we run other commands On the other hand, the `Kubebuilder Initializer plugin` must be used in conjunction with other plugins that will take the control once we need to create a Webhook or an API. -To take advantage of the Initializer plugin, we just need a repository, that will be used as a template, and the variables file -that will allow us to customize the template. By the default, the plugin read the variables from a named file `.kubebuilder-layout.yaml`, -but this can be customized If required. +To take advantage of the Initializer plugin, we just need a repository, that will be used as a template, and the +variables file that will allow us to customize the template. By the default, the plugin read the variables from a +named file `.kubebuilder-layout.yaml`, but this can be customized If required. To sum up, to initialize our project we just need to pass the argument `--from` and we could additionally pass the argument `--vars` in case of we don't want to use the default `.kubebuilder-layout.yaml`. @@ -138,7 +140,8 @@ kubebuilder init --plugins go.kubebuilder.io/v3,kubebuilder-initializer-plugin/ --from "https://github.com/astrokube/kubebuilder-operator-template" ``` -Be aware that, in the above example, the arguments `domain`, `repo` and `owner` are required by the plugin `go.kubebuilder.io/v3`. +Be aware that, in the above example, the arguments `domain`, `repo` and `owner` are required by the plugin +`go.kubebuilder.io/v3`. **Non default branches** diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index 6f30c04..a696f52 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -17,6 +17,7 @@ import ( "github.com/go-git/go-git/v5/storage/memory" ) +//nolint:lll var repoRegex = regexp.MustCompile(`(?P<protocol>(https|ssh))://((?P<auth>(.*))(@))?(?P<path>([a-zA-Z0-9./\-_]+))(#(?P<branch>(.*)))?`) // A GitSource struct is used to fetch the template from a Git repository. @@ -36,9 +37,12 @@ type GitSource struct { // (?P<protocol>(https|ssh))://((?P<auth>(.*))(@))?(?P<path>([a-zA-Z0-9./\-_]+))(#(?P<branch>(.*)))? // // The connection url is composed by the following parts -// protocol: It's required a must be https or ssh depending on the chosen mechanism to establish the connection with the remote repository. -// auth: It's an optional param (not required for public repositories) and we can provide both user credentials or a token. If we pass the credentials the format must be username:password -// path: The repository url. For instance, github.com/astrokube/kubebuilder-initializer-plugin if we use the https protocol, or github.com/astrokube/kubebuilder-initializer-plugin.git if we use the ssh protocol +// protocol: It's required a must be https or ssh depending on the chosen mechanism to establish the connection with the +// remote repository. +// auth: It's an optional param (not required for public repositories) and we can provide both user credentials or +// a token. If we pass the credentials the format must be username:password +// path: The repository url. For instance, github.com/astrokube/kubebuilder-initializer-plugin if we use the https +// protocol, or github.com/astrokube/kubebuilder-initializer-plugin.git if we use the ssh protocol // branch: Optional param, we can use a specific branch by passing the name of the desired branch after symbol `#` func NewGitSource(conn string) *GitSource { match := repoRegex.FindStringSubmatch(conn) diff --git a/pkg/templatizer/internal/variables/replacer.go b/pkg/templatizer/internal/variables/replacer.go index 4bfc462..e544652 100644 --- a/pkg/templatizer/internal/variables/replacer.go +++ b/pkg/templatizer/internal/variables/replacer.go @@ -8,7 +8,7 @@ import ( ) // ReplaceVariables is a function that create a Go text template from the given content and replace the provided -// variables in the map +// variables in the map. func ReplaceVariables(name string, content string, variables map[string]interface{}) (string, error) { t, err := template.New(name).Parse(content) if err != nil { diff --git a/pkg/templatizer/templatizer.go b/pkg/templatizer/templatizer.go index 65711d4..c558fab 100644 --- a/pkg/templatizer/templatizer.go +++ b/pkg/templatizer/templatizer.go @@ -13,7 +13,7 @@ import ( ) // Templatize fetch the content in a remote source and replace the variables with the provided values in a file. -// This function recieves the below three parameters: +// This function receives the below three parameters: // - sourceType: So far only `git` is supported // - connString: url used to establish the connection with the remote source // - varsFile: Path to the file that contains the variables that will be used to replace values From 36301ca2c6795432c5c01fe5ca1f8a33d1f008ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 10:27:37 +0200 Subject: [PATCH 21/24] Fix detected with git source protocol --- pkg/templatizer/internal/source/git.go | 3 +- pkg/templatizer/internal/source/git_test.go | 33 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 pkg/templatizer/internal/source/git_test.go diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index a696f52..411333f 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -51,7 +51,6 @@ func NewGitSource(conn string) *GitSource { for i, name := range repoRegex.SubexpNames() { if i > 0 && i <= len(match) { paramsMap[name] = match[i] - fmt.Printf("%s -> %s", name, match[i]) } } s := &GitSource{} @@ -70,7 +69,7 @@ func NewGitSource(conn string) *GitSource { } s.refOrigin = "origin" s.branch = paramsMap["branch"] - s.url = paramsMap["path"] + s.url = fmt.Sprintf("%s://%s", paramsMap["protocol"], paramsMap["path"]) return s } diff --git a/pkg/templatizer/internal/source/git_test.go b/pkg/templatizer/internal/source/git_test.go new file mode 100644 index 0000000..2cc0d33 --- /dev/null +++ b/pkg/templatizer/internal/source/git_test.go @@ -0,0 +1,33 @@ +package source + +import ( + "reflect" + "testing" +) + +func TestNewGitSource(t *testing.T) { + type args struct { + conn string + } + tests := []struct { + name string + args args + want *GitSource + }{ + { + name: "Basic test", + args: args{conn: "https://github.com/astrokube/kubebuilder-operator-template"}, + want: &GitSource{ + url: "https://github.com/astrokube/kubebuilder-operator-template", + refOrigin: "origin", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewGitSource(tt.args.conn); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewGitSource() = %v, want %v", got, tt.want) + } + }) + } +} From 6be14fd29ea59b561ac37ffce9933280243532f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 10:31:24 +0200 Subject: [PATCH 22/24] Remove commented code --- internal/command/init.go | 21 --------------------- pkg/templatizer/internal/source/git.go | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 0fa4111..6870c16 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -26,27 +26,6 @@ var initFlags = []external.Flag{ "after the repository path (e.g., github.com/my-organization/my-repo#develop) or the credentials If " + "required (e.g., username:password@github.com/my-organization/my-repo#develop)", }, - /** - { - Name: flagDomain, - Type: "string", - Default: "", - Usage: "store the domain of the project. ", - }, - { - Name: flagRepo, - Type: "string", - Default: "", - Usage: "name to use for go module (e.g., github.com/user/repo), defaults to the go package of the current " + - "working directory.", - }, - { - Name: flagProjectName, - Type: "string", - Default: "", - Usage: "the name of the project. This will be used to scaffold the manager data", - }, - */ } var initMetadata = plugin.SubcommandMetadata{ diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index 411333f..a2f611e 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -91,7 +91,7 @@ func (s *GitSource) loadFileSystem() error { SingleBranch: true, }) if err != nil { - return fmt.Errorf("error cloning the repository '%w'", err) + return fmt.Errorf("error cloning the repository from %s '%w'", s.url, err) } w, err := repo.Worktree() if err != nil { From 80d1583b0ea181196d7303421785465ac4e03b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sat, 8 Apr 2023 10:42:35 +0200 Subject: [PATCH 23/24] Update usage of plugin --- internal/command/init.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 6870c16..4ae8b9c 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -33,11 +33,14 @@ var initMetadata = plugin.SubcommandMetadata{ "It scaffolds a repository with a previously created layout", Examples: ` Scaffold with the defaults: - $ kubebuilder init --plugins astrokube-layout/v1 + $ kubebuilder init --plugins kubebuilder-initializer-plugin/v1-alpha \ + --from https://github.com/astronetes/operator-template - Scaffold with a specific layout: - $ kubebuilder init --plugins astrokube-layout/v1 --layout github.com/astronetes/operator-template - --vars custom-vars.yml + Scaffold overwriting the default file for the variables: + $ kubebuilder init --plugins kubebuilder-initializer-plugin/v1-alpha \ + --from https://github.com/astronetes/operator-template \ + --vars path-to-my-custom-variables.yml + `, } From 370a6ca330f6f7adcd495f34b13c089c23d99021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iva=CC=81n=20Corrales?= <ivan.corrales.solera@gmail.com> Date: Sun, 9 Apr 2023 10:17:09 +0200 Subject: [PATCH 24/24] remove unnecesary flags from the plugin --- README.md | 4 +- internal/command/command.go | 56 +++++++++++++++----- internal/command/init.go | 71 +++++++++++++++++++++----- pkg/templatizer/internal/source/git.go | 1 + 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index efb9687..1188c77 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,10 @@ take the control once we need to create a Webhook or an API. To take advantage of the Initializer plugin, we just need a repository, that will be used as a template, and the variables file that will allow us to customize the template. By the default, the plugin read the variables from a -named file `.kubebuilder-layout.yaml`, but this can be customized If required. +named file `.kubebuilder-initializer.yaml`, but this can be customized If required. To sum up, to initialize our project we just need to pass the argument `--from` and we could additionally pass the argument -`--vars` in case of we don't want to use the default `.kubebuilder-layout.yaml`. +`--vars` in case of we don't want to use the default `.kubebuilder-initializer.yaml`. In the below example, we would use our plugin in conjunction with the `go.kubebuilder.io/v3` that help us to work with implementation of operators in Go. diff --git a/internal/command/command.go b/internal/command/command.go index a64b678..aa3dd21 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -2,20 +2,17 @@ package command import ( "fmt" + "strings" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" ) const ( - flagVars = "vars" - - flagDomain = "domain" - flagRepo = "repo" - flagProjectName = "project-name" - flagFrom = "from" - flagSource = "source" - flagInit = "init" + flagVars = "vars" + flagFrom = "from" + flagSource = "source" + flagInit = "init" ) const ( @@ -33,16 +30,26 @@ func Run(request *external.PluginRequest) external.PluginResponse { } var err error + var flagSet *pflag.FlagSet switch request.Command { case ActionInit: - flagSet := processFlags(request, initFlags) + flagSet, err = processFlags(request, initFlags) + if err != nil { + break + } response.Universe, err = runInit(flagSet) case ActionFlags: - flagSet := processFlags(request, flagsFlags) + flagSet, err = processFlags(request, flagsFlags) + if err != nil { + break + } response.Flags, err = runFlags(flagSet) case ActionMetadata: - flagSet := processFlags(request, metadataFlags) + flagSet, err = processFlags(request, metadataFlags) + if err != nil { + break + } response.Metadata, err = runMetadata(flagSet) case ActionCreateAPI: break @@ -60,7 +67,7 @@ func Run(request *external.PluginRequest) external.PluginResponse { return response } -func processFlags(request *external.PluginRequest, flags []external.Flag) *pflag.FlagSet { +func processFlags(request *external.PluginRequest, flags []external.Flag) (*pflag.FlagSet, error) { flagsSet := pflag.NewFlagSet(fmt.Sprintf("%sFlags", request.Command), pflag.ContinueOnError) for _, f := range flags { switch f.Type { @@ -72,7 +79,28 @@ func processFlags(request *external.PluginRequest, flags []external.Flag) *pflag flagsSet.String(f.Name, f.Default, f.Usage) } } - _ = flagsSet.Parse(request.Args) + args := filterProvidedArgs(request.Args, flags) + if err := flagsSet.Parse(args); err != nil { + return nil, err + } + return flagsSet, nil +} - return flagsSet +func filterProvidedArgs(args []string, supportedFlags []external.Flag) (out []string) { + supportedFlagsMap := make(map[string]struct{}, len(supportedFlags)) + for i := range supportedFlags { + key := fmt.Sprintf("--%s", supportedFlags[i].Name) + supportedFlagsMap[key] = struct{}{} + } + expectArgValue := false + for i := range args { + arg := args[i] + if expectArgValue { + out = append(out, arg) + } else if _, ok := supportedFlagsMap[args[i]]; ok { + out = append(out, arg) + } + expectArgValue = strings.HasPrefix(arg, "--") + } + return } diff --git a/internal/command/init.go b/internal/command/init.go index 4ae8b9c..d29d1a7 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -12,7 +12,13 @@ import ( var initFlags = []external.Flag{ { - Name: "vars", + Name: flagSource, + Type: "string", + Default: "git", + Usage: "Type of source. The only supported value is 'git'", + }, + { + Name: flagVars, Type: "string", Default: ".kubebuilder-initializer.yaml", Usage: "path to the file that contains the variables to be used. By default the plugin uses the file in path ." + @@ -22,10 +28,47 @@ var initFlags = []external.Flag{ Name: flagFrom, Type: "string", Default: "", - Usage: "repository path (e.g., github.com/my-organization/my-repo). We can pass a specific branch " + - "after the repository path (e.g., github.com/my-organization/my-repo#develop) or the credentials If " + - "required (e.g., username:password@github.com/my-organization/my-repo#develop)", + Usage: "repository path (e.g., github.com/my-organization/my-repo).", + }, + /** + { + Name: "component-config", + Type: "boolean", + Default: "false", + Usage: "create a versioned ComponentConfig file, may be 'true' or 'false'", + }, + { + Name: "domain", + Type: "string", + Default: "my.domain", + Usage: "domain for groups (default \"my.domain\")", + }, + { + Name: "fetch-deps", + Type: "boolean", + Default: "true", + Usage: "ensure dependencies are downloaded (default true)", + }, + { + Name: "license", + Type: "string", + Default: "", + Usage: "license to use to boilerplate, may be one of 'apache2', 'none' (default \"apache2\")", }, + { + Name: "owner", + Type: "string", + Default: "", + Usage: "owner to add to the copyright", + }, + **/ + + /** + --project-name string name of this project + --project-version string project version (default "3") + --repo string name to use for go module (e.g., github.com/user/repo), defaults to the go package of the current working directory. + --skip-go-version-check if specified, skip checking the Go versio + */ } var initMetadata = plugin.SubcommandMetadata{ @@ -45,14 +88,18 @@ var initMetadata = plugin.SubcommandMetadata{ } func runInit(flags *pflag.FlagSet) (map[string]string, error) { - source, _ := flags.GetString(flagSource) - from, _ := flags.GetString(flagFrom) - vars, _ := flags.GetString(flagVars) - /** - domain, _ := flags.GetString(flagDomain) - projectName, _ := flags.GetString(flagProjectName) - repo, _ := flags.GetString(flagRepo) - **/ + source, err := flags.GetString(flagSource) + if err != nil { + return nil, err + } + from, err := flags.GetString(flagFrom) + if err != nil { + return nil, err + } + vars, err := flags.GetString(flagVars) + if err != nil { + return nil, err + } content, err := templatizer.Templatize(source, from, vars) if err != nil { return content, err diff --git a/pkg/templatizer/internal/source/git.go b/pkg/templatizer/internal/source/git.go index a2f611e..971c061 100644 --- a/pkg/templatizer/internal/source/git.go +++ b/pkg/templatizer/internal/source/git.go @@ -70,6 +70,7 @@ func NewGitSource(conn string) *GitSource { s.refOrigin = "origin" s.branch = paramsMap["branch"] s.url = fmt.Sprintf("%s://%s", paramsMap["protocol"], paramsMap["path"]) + return s }