diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml new file mode 100644 index 0000000..bf506b1 --- /dev/null +++ b/.github/workflows/e2e-tests.yaml @@ -0,0 +1,77 @@ +name: E2E Tests + +# Trigger the workflow on pull requests and direct pushes to any branch +on: + push: + pull_request: + +jobs: + test: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + # Pull requests from the same repository won't trigger this checks as they were already triggered by the push + if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository) + steps: + - name: Clone the code + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '~1.22' + - name: Install Helm and Kubectl + if: matrix.os == 'macos-latest' + run: | + brew install helm + brew install kubectl + - name: Setup Minikube cluster + if: matrix.os != 'macos-latest' + uses: medyagh/setup-minikube@latest + # This step is needed as the following one tries to remove + # kustomize for each test but has no permission to do so + - name: Remove pre-installed kustomize + if: matrix.os != 'macos-latest' + run: sudo rm -f /usr/local/bin/kustomize + - name: Perform the E2E test + if: matrix.os != 'macos-latest' + run: | + chmod -R +x scripts + + export "GITHUB_PRIVATE_KEY=${{ secrets.GH_TEST_APP_PK }}" + export "GH_APP_ID=${{ secrets.GH_APP_ID }}" + export "GH_INSTALL_ID=${{ secrets.GH_INSTALL_ID }}" + export "VAULT_ADDR=http://vault.default:8200" + export "VAULT_ROLE_AUDIENCE=githubapp" + export "VAULT_ROLE=githubapp" + + eval $(minikube docker-env) + + # Run tests + make test-e2e || true + + # debug + #docker images + #kubectl -n github-app-operator-system describe po + #kubectl -n github-app-operator-system describe deploy + #echo 'kubectl get mutatingwebhookconfiguration cert-manager-webhook -o jsonpath={.webhooks[*].clientConfig.caBundle}' + #kubectl get mutatingwebhookconfiguration cert-manager-webhook -o jsonpath={.webhooks[*].clientConfig.caBundle} + #kubectl -n cert-manager describe deploy,po + + #echo "######### gh operator logs ##########" + #kubectl -n github-app-operator-system logs deploy/github-app-operator-controller-manager + + #echo "######### cert-manager-webhook logs ##########" + #kubectl -n cert-manager logs deploy/cert-manager-webhook + - name: Report failure + uses: nashmaniac/create-issue-action@v1.2 + # Only report failures of pushes (PRs have are visible through the Checks section) to the default branch + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + title: 🐛 Unit tests failed on ${{ matrix.os }} for ${{ github.sha }} + token: ${{ secrets.GITHUB_TOKEN }} + labels: kind/bug + body: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6acec69..1e5617e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -36,6 +36,14 @@ jobs: - name: Remove pre-installed kustomize if: matrix.os != 'macos-latest' run: sudo rm -f /usr/local/bin/kustomize + - name: Perform the webhook tests + if: matrix.os != 'macos-latest' + run: | + export "GH_APP_ID=${{ secrets.GH_APP_ID }}" + export "GH_INSTALL_ID=${{ secrets.GH_INSTALL_ID }}" + + # Run webhook tests + make test-webhooks # Install vault to minikube cluster to test vault case with kubernetes auth - name: Install and configure Vault if: matrix.os != 'macos-latest' @@ -45,7 +53,7 @@ jobs: cd scripts chmod +x install_and_setup_vault_k8s.sh ./install_and_setup_vault_k8s.sh - - name: Perform the test + - name: Perform the controller integration tests if: matrix.os != 'macos-latest' run: | export "GITHUB_PRIVATE_KEY=${{ secrets.GH_TEST_APP_PK }}" @@ -54,6 +62,7 @@ jobs: export "VAULT_ADDR=http://localhost:8200" export "VAULT_ROLE_AUDIENCE=githubapp" export "VAULT_ROLE=githubapp" + export ENABLE_WEBHOOKS=false # Run vault port forward in background kubectl port-forward vault-0 8200:8200 & diff --git a/.gitignore b/.gitignore index 7a7feec..0cd4962 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ Dockerfile.cross # Test binary, built with `go test -c` *.test +# test data +cluster-keys.json + # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/Makefile b/Makefile index 22dde5e..82f988a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Image URL to use all building/pushing image targets -IMG ?= controller:latest +IMG ?= samirtahir91076/github-app-operator:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.29.0 @@ -62,7 +62,11 @@ vet: ## Run go vet against code. .PHONY: test test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v -E '/e2e|v1|utils|cmd|test_helpers|vault') -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v -E '/e2e|v1|utils|cmd|test_helpers|vault') -v -ginkgo.v -coverprofile cover.out + +.PHONY: test-webhooks +test-webhooks: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./api/v1/ -v -ginkgo.v -coverprofile cover.out # Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. .PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. @@ -194,10 +198,11 @@ HELMIFY ?= $(LOCALBIN)/helmify .PHONY: helmify helmify: $(HELMIFY) ## Download helmify locally if necessary. $(HELMIFY): $(LOCALBIN) - test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@v0.4.5 + test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@v0.4.14 helm: manifests kustomize helmify - $(KUSTOMIZE) build config/default | $(HELMIFY) charts/github-app-operator + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | $(HELMIFY) -cert-manager-as-subchart charts/github-app-operator ################################## # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist diff --git a/PROJECT b/PROJECT index dcf8b6c..4ff62e3 100644 --- a/PROJECT +++ b/PROJECT @@ -17,4 +17,7 @@ resources: kind: GithubApp path: github-app-operator/api/v1 version: v1 + webhooks: + validation: true + webhookVersion: v1 version: "3" diff --git a/README.md b/README.md index 53b4b6a..7c7b6e8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The `github-app-operator` is a Kubernetes operator that generates an access toke ### Private Key Retrieval Options > [!TIP] -> There is a sample constraint template and constraint for Gatekeeper to restrict the type of private key source in the `gatekeeper-policy` folder since we can't restrict it to be unique in the GithubApp CRD. +> There is a sample constraint template and constraint for Gatekeeper to restrict the type of private key source in the `gatekeeper-policy` folder if you dont want to use the validating webhook built-in. #### 1. Using a Kubernetes Secret @@ -216,8 +216,17 @@ EOF ### To deploy with Helm using public Docker image A helm chart is generated using `make helm` when a new tag is pushed, i.e a release. -You can pull the helm chart from this repos packages -- See the [packages page](https://github.com/samirtahir91/github-app-operator/pkgs/container/github-app-operator%2Fhelm-charts%2Fgithub-app-operator) +This chart will have webhooks and cert manager enabled. +If you want to install without webhooks and cert manager required use the local manual chart. +```sh +cd charts/github-app-operator +helm upgrade --install -n github-app-operator-system . --create-namespace \ + --set webhook.enabled=false \ + --set controllerManager.manager.env.enableWebhooks="false" +``` + +You can pull the automatically built helm chart from this repos packages +- See the [packages](https://github.com/samirtahir91/github-app-operator/pkgs/container/github-app-operator%2Fhelm-charts%2Fgithub-app-operator) - Pull with helm: - ```sh helm pull oci://ghcr.io/samirtahir91/github-app-operator/helm-charts/github-app-operator --version @@ -282,6 +291,7 @@ export GH_INSTALL_ID= export "VAULT_ADDR=http://localhost:8200" # this can be local k8s Vault or some other Vault export "VAULT_ROLE_AUDIENCE=githubapp" export "VAULT_ROLE=githubapp" +export "ENABLE_WEBHOOKS=false" ``` - This uses Vault, you can spin up a simple Vault server using this script. - It will use Helm and configure the Vault server with a test private key as per the env var ${GITHUB_PRIVATE_KEY}. @@ -305,6 +315,7 @@ export GITHUB_PRIVATE_KEY= export GH_APP_ID= export GH_INSTALL_ID= USE_EXISTING_CLUSTER=false make test +USE_EXISTING_CLUSTER=false make test-webhooks ``` **Generate coverage html report:** diff --git a/api/v1/githubapp_webhook.go b/api/v1/githubapp_webhook.go new file mode 100644 index 0000000..6a9a516 --- /dev/null +++ b/api/v1/githubapp_webhook.go @@ -0,0 +1,101 @@ +/* +Copyright 2024. + +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 v1 + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var githubapplog = logf.Log.WithName("githubapp-resource") + +// SetupWebhookWithManager will setup the manager to manage the webhooks +func (r *GithubApp) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. +// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. +// +kubebuilder:webhook:path=/validate-githubapp-samir-io-v1-githubapp,mutating=false,failurePolicy=fail,sideEffects=None,groups=githubapp.samir.io,resources=githubapps,verbs=create;update,versions=v1,name=vgithubapp.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &GithubApp{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *GithubApp) ValidateCreate() (admission.Warnings, error) { + githubapplog.Info("validate create", "name", r.Name) + + // Ensure only one of googlePrivateKeySecret, privateKeySecret, or vaultPrivateKey is specified + err := validateGithubAppSpec(r) + if err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *GithubApp) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + githubapplog.Info("validate update", "name", r.Name) + + // Ensure only one of googlePrivateKeySecret, privateKeySecret, or vaultPrivateKey is specified + err := validateGithubAppSpec(r) + if err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *GithubApp) ValidateDelete() (admission.Warnings, error) { + githubapplog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} + +// validateGithubAppSpec validates that only one of googlePrivateKeySecret, privateKeySecret, or vaultPrivateKey is specified +func validateGithubAppSpec(r *GithubApp) error { + count := 0 + + if r.Spec.GcpPrivateKeySecret != "" { + count++ + } + if r.Spec.PrivateKeySecret != "" { + count++ + } + if r.Spec.VaultPrivateKey != nil { + count++ + } + + if count != 1 { + return fmt.Errorf("exactly one of googlePrivateKeySecret, privateKeySecret, or vaultPrivateKey must be specified") + } + + return nil +} diff --git a/api/v1/githubapp_webhook_test.go b/api/v1/githubapp_webhook_test.go new file mode 100644 index 0000000..1c04285 --- /dev/null +++ b/api/v1/githubapp_webhook_test.go @@ -0,0 +1,98 @@ +/* +Copyright 2024. + +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 v1 + +import ( + "fmt" + "os" + "strconv" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // github app private key secret + privateKeySecret = "gh-app-key-test" +) + +var ( + appId int + installId int + acessTokenSecretName string +) + +// Function to initialise vars for github app +func init() { + var err error + appId, err = strconv.Atoi(os.Getenv("GH_APP_ID")) + if err != nil { + panic(err) + } + installId, err = strconv.Atoi(os.Getenv("GH_INSTALL_ID")) + if err != nil { + panic(err) + } + acessTokenSecretName = fmt.Sprintf("github-app-access-token-%s", strconv.Itoa(appId)) +} + +var _ = Describe("GithubApp Webhook", func() { + var ( + obj *GithubApp + validator GithubApp + rolloutDeploymentSpec *RolloutDeploymentSpec + vaultPrivateKeySpec *VaultPrivateKeySpec + gcpPrivateKeySecret string + ) + BeforeEach(func() { + obj = &GithubApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gh-app-webhook-test", + Namespace: "default", + }, + Spec: GithubAppSpec{ + AppId: appId, + InstallId: installId, + PrivateKeySecret: privateKeySecret, + RolloutDeployment: rolloutDeploymentSpec, + VaultPrivateKey: vaultPrivateKeySpec, + AccessTokenSecret: acessTokenSecretName, + GcpPrivateKeySecret: gcpPrivateKeySecret, + }, + } + + validator = GithubApp{} + + Expect(obj).NotTo(BeNil(), "Expected obj to be initialized") + }) + + AfterEach(func() { + // TODO (user): Add any teardown logic common to all tests + }) + + Context("When creating GithubApp under Validating Webhook", func() { + It("Should deny creation if more than one of googlePrivateKeySecret, privateKeySecret, or vaultPrivateKey is specified", func() { + obj.Spec.GcpPrivateKeySecret = "this-should-fail" + Expect(validator.ValidateCreate()).Error().To( + MatchError(ContainSubstring("exactly one of googlePrivateKeySecret, privateKeySecret, or vaultPrivateKey must be specified")), + "Private key source validation to fail for more than one option") + }) + }) + +}) diff --git a/api/v1/webhook_suite_test.go b/api/v1/webhook_suite_test.go new file mode 100644 index 0000000..174030d --- /dev/null +++ b/api/v1/webhook_suite_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2024. + +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 v1 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "runtime" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + admissionv1 "k8s.io/api/admission/v1" + // +kubebuilder:scaffold:imports + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.30.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := apimachineryruntime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), + LeaderElection: false, + Metrics: metricsserver.Options{BindAddress: "0"}, + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&GithubApp{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + return conn.Close() + }).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index bcc8c2b..03e354c 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1 import ( - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/charts/github-app-operator/.helmignore b/charts/github-app-operator/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/github-app-operator/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/github-app-operator/Chart.lock b/charts/github-app-operator/Chart.lock new file mode 100644 index 0000000..4e777df --- /dev/null +++ b/charts/github-app-operator/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: cert-manager + repository: https://charts.jetstack.io + version: v1.12.2 +digest: sha256:0c772741e7db522f947508a4754ab4e34d91f6a0878a496f36ed08fdcb371a9b +generated: "2024-11-14T20:09:04.560710432Z" diff --git a/charts/github-app-operator/Chart.yaml b/charts/github-app-operator/Chart.yaml new file mode 100644 index 0000000..a54c6c3 --- /dev/null +++ b/charts/github-app-operator/Chart.yaml @@ -0,0 +1,28 @@ +apiVersion: v2 +name: github-app-operator +description: A Helm chart for Kubernetes +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" + +dependencies: + - name: cert-manager + repository: https://charts.jetstack.io + condition: certmanager.enabled + alias: certmanager + version: "v1.12.2" diff --git a/charts/github-app-operator/charts/cert-manager-v1.12.2.tgz b/charts/github-app-operator/charts/cert-manager-v1.12.2.tgz new file mode 100644 index 0000000..13d233c Binary files /dev/null and b/charts/github-app-operator/charts/cert-manager-v1.12.2.tgz differ diff --git a/charts/github-app-operator/templates/_helpers.tpl b/charts/github-app-operator/templates/_helpers.tpl new file mode 100644 index 0000000..213e37b --- /dev/null +++ b/charts/github-app-operator/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "github-app-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "github-app-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "github-app-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "github-app-operator.labels" -}} +helm.sh/chart: {{ include "github-app-operator.chart" . }} +{{ include "github-app-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "github-app-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "github-app-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "github-app-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "github-app-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/github-app-operator/templates/deployment.yaml b/charts/github-app-operator/templates/deployment.yaml new file mode 100644 index 0000000..7aa80e0 --- /dev/null +++ b/charts/github-app-operator/templates/deployment.yaml @@ -0,0 +1,111 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "github-app-operator.fullname" . }}-controller-manager + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + control-plane: controller-manager + {{- include "github-app-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.controllerManager.replicas }} + selector: + matchLabels: + control-plane: controller-manager + {{- include "github-app-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + control-plane: controller-manager + {{- include "github-app-operator.selectorLabels" . | nindent 8 }} + annotations: + kubectl.kubernetes.io/default-container: manager + spec: + containers: + - args: {{- toYaml .Values.controllerManager.manager.args | nindent 8 }} + command: + - /manager + env: + - name: CHECK_INTERVAL + value: {{ quote .Values.controllerManager.manager.env.checkInterval }} + - name: EXPIRY_THRESHOLD + value: {{ quote .Values.controllerManager.manager.env.expiryThreshold }} + - name: DEBUG_LOG + value: {{ quote .Values.controllerManager.manager.env.debugLog }} + - name: VAULT_ROLE + value: {{ quote .Values.controllerManager.manager.env.vaultRole }} + - name: VAULT_ROLE_AUDIENCE + value: {{ quote .Values.controllerManager.manager.env.vaultRoleAudience }} + - name: VAULT_ADDR + value: {{ quote .Values.controllerManager.manager.env.vaultAddr }} + - name: GITHUB_PROXY + value: {{ quote .Values.controllerManager.manager.env.githubProxy }} + - name: VAULT_NAMESPACE + value: {{ quote .Values.controllerManager.manager.env.vaultNamespace }} + - name: VAULT_PROXY_ADDR + value: {{ quote .Values.controllerManager.manager.env.vaultProxyAddr }} + - name: ENABLE_WEBHOOKS + value: {{ quote .Values.controllerManager.manager.env.enableWebhooks }} + - name: KUBERNETES_CLUSTER_DOMAIN + value: {{ quote .Values.kubernetesClusterDomain }} + image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag + | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.controllerManager.manager.imagePullPolicy }} + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10 + }} + securityContext: {{- toYaml .Values.controllerManager.manager.containerSecurityContext + | nindent 10 }} + volumeMounts: + {{- if .Values.webhook.enabled -}} + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + {{- end }} + - mountPath: /var/run/github-app-secrets + name: github-app-secrets + - args: {{- toYaml .Values.controllerManager.kubeRbacProxy.args | nindent 8 }} + env: + - name: KUBERNETES_CLUSTER_DOMAIN + value: {{ quote .Values.kubernetesClusterDomain }} + image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag + | default .Chart.AppVersion }} + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + protocol: TCP + resources: {{- toYaml .Values.controllerManager.kubeRbacProxy.resources | nindent + 10 }} + securityContext: {{- toYaml .Values.controllerManager.kubeRbacProxy.containerSecurityContext + | nindent 10 }} + securityContext: {{- toYaml .Values.controllerManager.podSecurityContext | nindent + 8 }} + serviceAccountName: {{ include "github-app-operator.fullname" . }}-controller-manager + terminationGracePeriodSeconds: 10 + volumes: + {{- if .Values.webhook.enabled -}} + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert + {{- end }} + - emptyDir: {} + name: github-app-secrets \ No newline at end of file diff --git a/charts/github-app-operator/templates/githubapp-crd.yaml b/charts/github-app-operator/templates/githubapp-crd.yaml new file mode 100644 index 0000000..dcfd9af --- /dev/null +++ b/charts/github-app-operator/templates/githubapp-crd.yaml @@ -0,0 +1,135 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: githubapps.githubapp.samir.io + annotations: + {{- if .Values.webhook.enabled -}} + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "github-app-operator.fullname" + . }}-serving-cert' + {{- end }} + controller-gen.kubebuilder.io/version: v0.14.0 + labels: + {{- include "github-app-operator.labels" . | nindent 4 }} +spec: + {{- if .Values.webhook.enabled -}} + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: '{{ include "github-app-operator.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /convert + conversionReviewVersions: + - v1 + {{- end }} + group: githubapp.samir.io + names: + kind: GithubApp + listKind: GithubAppList + plural: githubapps + singular: githubapp + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.appId + name: App ID + type: string + - jsonPath: .spec.accessTokenSecret + name: Access Token Secret + type: string + - jsonPath: .spec.installId + name: Install ID + type: string + - jsonPath: .status.expiresAt + name: Expires At + type: string + - jsonPath: .status.error + name: Error + type: string + name: v1 + schema: + openAPIV3Schema: + description: GithubApp is the Schema for the githubapps API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: GithubAppSpec defines the desired state of GithubApp + properties: + accessTokenSecret: + type: string + appId: + type: integer + googlePrivateKeySecret: + type: string + installId: + type: integer + privateKeySecret: + type: string + rolloutDeployment: + description: RolloutDeploymentSpec defines the specification for restarting + pods + properties: + labels: + additionalProperties: + type: string + type: object + type: object + vaultPrivateKey: + description: VaultPrivateKeySpec defines the spec for retrieving the + private key from Vault + properties: + mountPath: + type: string + secretKey: + type: string + secretPath: + type: string + required: + - mountPath + - secretKey + - secretPath + type: object + required: + - accessTokenSecret + - appId + - installId + type: object + status: + description: GithubAppStatus defines the observed state of GithubApp + properties: + error: + description: Error field to store error messages + type: string + expiresAt: + description: Expiry of access token + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/charts/github-app-operator/templates/leader-election-rbac.yaml b/charts/github-app-operator/templates/leader-election-rbac.yaml new file mode 100644 index 0000000..aea639a --- /dev/null +++ b/charts/github-app-operator/templates/leader-election-rbac.yaml @@ -0,0 +1,59 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "github-app-operator.fullname" . }}-leader-election-role + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "github-app-operator.fullname" . }}-leader-election-rolebinding + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: '{{ include "github-app-operator.fullname" . }}-leader-election-role' +subjects: +- kind: ServiceAccount + name: '{{ include "github-app-operator.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/charts/github-app-operator/templates/manager-rbac.yaml b/charts/github-app-operator/templates/manager-rbac.yaml new file mode 100644 index 0000000..c9af6e7 --- /dev/null +++ b/charts/github-app-operator/templates/manager-rbac.yaml @@ -0,0 +1,94 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "github-app-operator.fullname" . }}-manager-role + labels: + {{- include "github-app-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - get +- apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create + - get +- apiGroups: + - githubapp.samir.io + resources: + - githubapps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - githubapp.samir.io + resources: + - githubapps/finalizers + verbs: + - update +- apiGroups: + - githubapp.samir.io + resources: + - githubapps/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "github-app-operator.fullname" . }}-manager-rolebinding + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ include "github-app-operator.fullname" . }}-manager-role' +subjects: +- kind: ServiceAccount + name: '{{ include "github-app-operator.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/charts/github-app-operator/templates/metrics-reader-rbac.yaml b/charts/github-app-operator/templates/metrics-reader-rbac.yaml new file mode 100644 index 0000000..fa82930 --- /dev/null +++ b/charts/github-app-operator/templates/metrics-reader-rbac.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "github-app-operator.fullname" . }}-metrics-reader + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} +rules: +- nonResourceURLs: + - /metrics + verbs: + - get \ No newline at end of file diff --git a/charts/github-app-operator/templates/metrics-service.yaml b/charts/github-app-operator/templates/metrics-service.yaml new file mode 100644 index 0000000..62193b5 --- /dev/null +++ b/charts/github-app-operator/templates/metrics-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "github-app-operator.fullname" . }}-controller-manager-metrics-service + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + control-plane: controller-manager + {{- include "github-app-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.metricsService.type }} + selector: + control-plane: controller-manager + {{- include "github-app-operator.selectorLabels" . | nindent 4 }} + ports: + {{- .Values.metricsService.ports | toYaml | nindent 2 }} \ No newline at end of file diff --git a/charts/github-app-operator/templates/proxy-rbac.yaml b/charts/github-app-operator/templates/proxy-rbac.yaml new file mode 100644 index 0000000..dfeb4b7 --- /dev/null +++ b/charts/github-app-operator/templates/proxy-rbac.yaml @@ -0,0 +1,40 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "github-app-operator.fullname" . }}-proxy-role + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "github-app-operator.fullname" . }}-proxy-rolebinding + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ include "github-app-operator.fullname" . }}-proxy-role' +subjects: +- kind: ServiceAccount + name: '{{ include "github-app-operator.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/charts/github-app-operator/templates/selfsigned-issuer.yaml b/charts/github-app-operator/templates/selfsigned-issuer.yaml new file mode 100644 index 0000000..193aa6d --- /dev/null +++ b/charts/github-app-operator/templates/selfsigned-issuer.yaml @@ -0,0 +1,13 @@ +{{- if .Values.webhook.enabled -}} +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "github-app-operator.fullname" . }}-selfsigned-issuer + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "1" + labels: + {{- include "github-app-operator.labels" . | nindent 4 }} +spec: + selfSigned: {} +{{- end }} \ No newline at end of file diff --git a/charts/github-app-operator/templates/serviceaccount.yaml b/charts/github-app-operator/templates/serviceaccount.yaml new file mode 100644 index 0000000..3c90765 --- /dev/null +++ b/charts/github-app-operator/templates/serviceaccount.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "github-app-operator.fullname" . }}-controller-manager + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + {{- include "github-app-operator.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.controllerManager.serviceAccount.annotations | nindent 4 }} \ No newline at end of file diff --git a/charts/github-app-operator/templates/serving-cert.yaml b/charts/github-app-operator/templates/serving-cert.yaml new file mode 100644 index 0000000..2ba53d2 --- /dev/null +++ b/charts/github-app-operator/templates/serving-cert.yaml @@ -0,0 +1,21 @@ +{{- if .Values.webhook.enabled -}} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "github-app-operator.fullname" . }}-serving-cert + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "2" + labels: + {{- include "github-app-operator.labels" . | nindent 4 }} +spec: + dnsNames: + - '{{ include "github-app-operator.fullname" . }}-webhook-service.{{ .Release.Namespace + }}.svc' + - '{{ include "github-app-operator.fullname" . }}-webhook-service.{{ .Release.Namespace + }}.svc.{{ .Values.kubernetesClusterDomain }}' + issuerRef: + kind: Issuer + name: '{{ include "github-app-operator.fullname" . }}-selfsigned-issuer' + secretName: webhook-server-cert +{{- end }} \ No newline at end of file diff --git a/charts/github-app-operator/templates/validating-webhook-configuration.yaml b/charts/github-app-operator/templates/validating-webhook-configuration.yaml new file mode 100644 index 0000000..e072f08 --- /dev/null +++ b/charts/github-app-operator/templates/validating-webhook-configuration.yaml @@ -0,0 +1,31 @@ +{{- if .Values.webhook.enabled -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ include "github-app-operator.fullname" . }}-validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "github-app-operator.fullname" . }}-serving-cert + labels: + {{- include "github-app-operator.labels" . | nindent 4 }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ include "github-app-operator.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-githubapp-samir-io-v1-githubapp + failurePolicy: Fail + name: vgithubapp.kb.io + rules: + - apiGroups: + - githubapp.samir.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - githubapps + sideEffects: None +{{- end }} \ No newline at end of file diff --git a/charts/github-app-operator/templates/webhook-service.yaml b/charts/github-app-operator/templates/webhook-service.yaml new file mode 100644 index 0000000..33bafe7 --- /dev/null +++ b/charts/github-app-operator/templates/webhook-service.yaml @@ -0,0 +1,15 @@ +{{- if .Values.webhook.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "github-app-operator.fullname" . }}-webhook-service + labels: + {{- include "github-app-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.webhook.webhookService.type }} + selector: + control-plane: controller-manager + {{- include "github-app-operator.selectorLabels" . | nindent 4 }} + ports: + {{- .Values.webhook.webhookService.ports | toYaml | nindent 2 }} +{{- end }} \ No newline at end of file diff --git a/charts/github-app-operator/values.yaml b/charts/github-app-operator/values.yaml new file mode 100644 index 0000000..37b65e3 --- /dev/null +++ b/charts/github-app-operator/values.yaml @@ -0,0 +1,84 @@ +certmanager: + enabled: false + installCRDs: true + namespace: cert-manager +controllerManager: + kubeRbacProxy: + args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.15.0 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + manager: + args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + env: + checkInterval: 5m + debugLog: "false" + enableWebhooks: "false" + expiryThreshold: 15m + githubProxy: "" + vaultAddr: http://vault.default:8200 + vaultNamespace: "" + vaultProxyAddr: "" + vaultRole: githubapp + vaultRoleAudience: githubapp + image: + repository: samirtahir91076/github-app-operator + tag: latest + imagePullPolicy: Never + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + podSecurityContext: + fsGroup: 65532 + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + replicas: 1 + serviceAccount: + annotations: {} +kubernetesClusterDomain: cluster.local +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP +webhook: + enabled: false + webhookService: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + type: ClusterIP diff --git a/cmd/main.go b/cmd/main.go index 8c44c59..d0db1d0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -187,6 +187,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "GithubApp") os.Exit(1) } + if os.Getenv("ENABLE_WEBHOOKS") == "true" { + if err = (&githubappv1.GithubApp{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "GithubApp") + os.Exit(1) + } + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 0000000..eafdd4c --- /dev/null +++ b/config/certmanager/certificate.yaml @@ -0,0 +1,35 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/name: github-app-operator + app.kubernetes.io/managed-by: kustomize + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: certificate + app.kubernetes.io/instance: serving-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + app.kubernetes.io/managed-by: kustomize + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize diff --git a/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml new file mode 100644 index 0000000..bebea5a --- /dev/null +++ b/config/certmanager/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- certificate.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml new file mode 100644 index 0000000..cf6f89e --- /dev/null +++ b/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,8 @@ +# This configuration is for teaching kustomize how to update name ref substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index e06a53f..b78ce05 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -8,16 +8,16 @@ resources: patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD -#- path: patches/webhook_in_githubapps.yaml +- path: patches/webhook_in_githubapps.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD -#- path: patches/cainjection_in_githubapps.yaml +- path: patches/cainjection_in_githubapps.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # [WEBHOOK] To enable webhook, uncomment the following section # the following config is for teaching kustomize how to do kustomization for CRDs. -#configurations: -#- kustomizeconfig.yaml +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/patches/cainjection_in_githubapps.yaml b/config/crd/patches/cainjection_in_githubapps.yaml new file mode 100644 index 0000000..f17d4bb --- /dev/null +++ b/config/crd/patches/cainjection_in_githubapps.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: githubapps.githubapp.samir.io diff --git a/config/crd/patches/webhook_in_githubapps.yaml b/config/crd/patches/webhook_in_githubapps.yaml new file mode 100644 index 0000000..3e089b8 --- /dev/null +++ b/config/crd/patches/webhook_in_githubapps.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: githubapps.githubapp.samir.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index f9fe748..2efae3d 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -20,9 +20,9 @@ resources: - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- ../webhook +- ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus @@ -34,7 +34,7 @@ patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- path: manager_webhook_patch.yaml +- path: manager_webhook_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. @@ -43,100 +43,100 @@ patches: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. # Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: -# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.namespace # namespace of the certificate CR -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - select: -# kind: CustomResourceDefinition -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - select: -# kind: CustomResourceDefinition -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# - source: # Add cert-manager annotation to the webhook Service -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # namespace of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 0 -# create: true -# - source: -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.namespace # namespace of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true +replacements: + - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml + fieldPath: .metadata.namespace # namespace of the certificate CR + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true + - select: + kind: CustomResourceDefinition + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml + fieldPath: .metadata.name + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + - select: + kind: CustomResourceDefinition + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + - source: # Add cert-manager annotation to the webhook Service + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.name # namespace of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 0 + create: true + - source: + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.namespace # namespace of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 1 + create: true diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml new file mode 100644 index 0000000..738de35 --- /dev/null +++ b/config/default/manager_webhook_patch.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml new file mode 100644 index 0000000..ad3838a --- /dev/null +++ b/config/default/webhookcainjection_patch.yaml @@ -0,0 +1,25 @@ +# This patch add annotation to admission webhook config and +# CERTIFICATE_NAMESPACE and CERTIFICATE_NAME will be substituted by kustomize +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/name: github-app-operator + app.kubernetes.io/managed-by: kustomize + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/name: validatingwebhookconfiguration + app.kubernetes.io/instance: validating-webhook-configuration + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: github-app-operator + app.kubernetes.io/part-of: github-app-operator + app.kubernetes.io/managed-by: kustomize + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index ad13e96..213f28b 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -4,5 +4,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: controller + newName: samirtahir91076/github-app-operator newTag: latest diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 5c63178..dc5b019 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -71,7 +71,7 @@ spec: - /manager args: - --leader-elect - image: github-app-operator:latest + image: controller imagePullPolicy: Never name: manager securityContext: @@ -122,6 +122,9 @@ spec: # Optional proxy for Vault - name: VAULT_PROXY_ADDR value: "" + # Optional enable webhook set to "true" + - name: ENABLE_WEBHOOKS + value: "true" # optional vault env vars - https://pkg.go.dev/github.com/hashicorp/vault/api#pkg-constants # volume to cache private keys volumeMounts: diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml new file mode 100644 index 0000000..9cf2613 --- /dev/null +++ b/config/webhook/kustomization.yaml @@ -0,0 +1,6 @@ +resources: +- manifests.yaml +- service.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml new file mode 100644 index 0000000..206316e --- /dev/null +++ b/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,22 @@ +# the following config is for teaching kustomize where to look at when substituting nameReference. +# It requires kustomize v2.1.0 or newer to work properly. +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + - kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + +namespace: +- kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true +- kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml new file mode 100644 index 0000000..8d44ba4 --- /dev/null +++ b/config/webhook/manifests.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-githubapp-samir-io-v1-githubapp + failurePolicy: Fail + name: vgithubapp.kb.io + rules: + - apiGroups: + - githubapp.samir.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - githubapps + sideEffects: None diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml new file mode 100644 index 0000000..c1bb2db --- /dev/null +++ b/config/webhook/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: github-app-operator + app.kubernetes.io/managed-by: kustomize + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager diff --git a/internal/controller/githubapp_controller.go b/internal/controller/githubapp_controller.go index 3a8be6f..898a868 100644 --- a/internal/controller/githubapp_controller.go +++ b/internal/controller/githubapp_controller.go @@ -20,7 +20,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/golang-jwt/jwt/v4" "math/rand" "net/http" "os" @@ -29,7 +28,10 @@ import ( "sync" "time" + "github.com/golang-jwt/jwt/v4" + githubappv1 "github-app-operator/api/v1" + vault "github.com/hashicorp/vault/api" // vault client appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" diff --git a/internal/controller/githubapp_controller_test.go b/internal/controller/githubapp_controller_test.go index 9713436..6dd331f 100644 --- a/internal/controller/githubapp_controller_test.go +++ b/internal/controller/githubapp_controller_test.go @@ -29,6 +29,7 @@ import ( test_helpers "github-app-operator/internal/controller/test_helpers" githubappv1 "github-app-operator/api/v1" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 44de1c9..4f4a898 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" // http client "os" + //"os/exec" "path/filepath" "runtime" @@ -37,6 +38,7 @@ import ( . "github.com/onsi/gomega" test_helpers "github-app-operator/internal/controller/test_helpers" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -155,7 +157,7 @@ var _ = BeforeSuite(func() { // Verify if reconciliation was successful Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Token request failed: %v", err)) } else { - // Set a dummy token just to satisfy the SetupWithManager fucntion + // Set a dummy token just to satisfy the SetupWithManager function // which will read the token and get the service account and and namespace. // This token is for 'default' service account in the 'namespace0' namespace token = `eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5ieTJyVUk2ZzlQZ0k0anNGclRvTkJDM0FsUjJjLUJDVUhzNU9mVG9lcEUifQ. diff --git a/internal/controller/test_helpers/test_helpers.go b/internal/controller/test_helpers/test_helpers.go index 67462f2..51c4203 100644 --- a/internal/controller/test_helpers/test_helpers.go +++ b/internal/controller/test_helpers/test_helpers.go @@ -13,6 +13,7 @@ import ( gomega "github.com/onsi/gomega" githubappv1 "github-app-operator/api/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/internal/controller/vault.go b/internal/controller/vault.go index 2e518bf..61990d4 100644 --- a/internal/controller/vault.go +++ b/internal/controller/vault.go @@ -20,6 +20,7 @@ import ( "context" "encoding/base64" "fmt" + "k8s.io/utils/ptr" auth "github.com/hashicorp/vault/api/auth/kubernetes" // vault k8s auth diff --git a/scripts/delete_vault.sh b/scripts/delete_vault.sh index fc79688..cbf7750 100644 --- a/scripts/delete_vault.sh +++ b/scripts/delete_vault.sh @@ -3,4 +3,4 @@ # Run this script to delete the vault setup in kubernetes helm delete vault -kubectl delete pvc data-vault-0 \ No newline at end of file +#kubectl delete pvc data-vault-0 \ No newline at end of file diff --git a/scripts/install_and_setup_vault_k8s.sh b/scripts/install_and_setup_vault_k8s.sh index 55fad4e..55761de 100644 --- a/scripts/install_and_setup_vault_k8s.sh +++ b/scripts/install_and_setup_vault_k8s.sh @@ -8,7 +8,8 @@ helm repo add hashicorp https://helm.releases.hashicorp.com helm repo update # install vault with single node -helm install vault hashicorp/vault --values helm-vault-raft-values.yml +helm install vault hashicorp/vault \ + --set server.dataStorage.enabled=false kubectl get pods # wait for vault to run diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 982aad5..2023f01 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -34,23 +34,37 @@ var _ = Describe("controller", Ordered, func() { By("installing prometheus operator") Expect(utils.InstallPrometheusOperator()).To(Succeed()) + By("install and setup Vault") + Expect(utils.InstallAndSetupVault()).To(Succeed()) + By("installing the cert-manager") Expect(utils.InstallCertManager()).To(Succeed()) By("creating manager namespace") cmd := exec.Command("kubectl", "create", "ns", namespace) _, _ = utils.Run(cmd) + + By("removing operator crds") + cmd = exec.Command("make", "uninstall") + _, _ = utils.Run(cmd) }) AfterAll(func() { By("uninstalling the Prometheus manager bundle") utils.UninstallPrometheusOperator() + By("removing manager namespace") + cmd := exec.Command("kubectl", "delete", "ns", namespace) + _, _ = utils.Run(cmd) + By("uninstalling the cert-manager bundle") utils.UninstallCertManager() - By("removing manager namespace") - cmd := exec.Command("kubectl", "delete", "ns", namespace) + By("uninstalling Vault") + utils.UninstallVault() + + By("removing operator crds") + cmd = exec.Command("make", "uninstall") _, _ = utils.Run(cmd) }) @@ -60,16 +74,23 @@ var _ = Describe("controller", Ordered, func() { var err error // projectimage stores the name of the image used in the example - var projectimage = "example.com/github-app-operator:v0.0.1" + var projectimage = "samirtahir91076/github-app-operator:e2e-test" + + By("setting the terminal to use the docker daemon inside minikube") + cmd := exec.Command("/bin/sh", "-c", "eval $(minikube docker-env)") + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) By("building the manager(Operator) image") - cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage)) + cmd = exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage)) _, err = utils.Run(cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred()) + /* Using minikube for now By("loading the the manager(Operator) image on Kind") err = utils.LoadImageToKindClusterWithName(projectimage) ExpectWithOffset(1, err).NotTo(HaveOccurred()) + */ By("installing CRDs") cmd = exec.Command("make", "install") diff --git a/test/utils/utils.go b/test/utils/utils.go index 545ab0f..383320b 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -21,6 +21,8 @@ import ( "os" "os/exec" "strings" + "time" + //lint:ignore ST1001 this is boilerplate code from kubebuilder . "github.com/onsi/ginkgo/v2" //nolint:golint,revive ) @@ -30,7 +32,7 @@ const ( prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + "releases/download/%s/bundle.yaml" - certmanagerVersion = "v1.5.3" + certmanagerVersion = "v1.12.2" certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml" ) @@ -99,8 +101,46 @@ func InstallCertManager() error { "--timeout", "5m", ) - _, err := Run(cmd) - return err + if _, err := Run(cmd); err != nil { + return err + } + + // check caBundle + retryInterval := time.Second * 5 + retryTimeout := time.Minute * 2 + start := time.Now() + + for time.Since(start) < retryTimeout { + cmd := exec.Command("kubectl", "get", "mutatingwebhookconfiguration", "cert-manager-webhook", + "-o", "jsonpath={.webhooks[*].clientConfig.caBundle}") + output, err := Run(cmd) + if err == nil && strings.TrimSpace(string(output)) != "" { + return nil + } + + time.Sleep(retryInterval) + } + return fmt.Errorf("failed to get caBundle from MutatingWebhookConfiguration") +} + +// UninstallVault uninstalls the Vault +func UninstallVault() { + installScript := "./scripts/delete_vault.sh" + cmd := exec.Command("/bin/sh", "-c", installScript) + if _, err := Run(cmd); err != nil { + warnError(err) + } +} + +// Install and setup Vault with github app private key +func InstallAndSetupVault() error { + installScript := "./scripts/install_and_setup_vault_k8s.sh" + cmd := exec.Command("/bin/sh", "-c", installScript) + if _, err := Run(cmd); err != nil { + return err + } + + return nil } // LoadImageToKindCluster loads a local docker image to the kind cluster