├── testdata └── bundles │ ├── plain-v0 │ ├── empty │ │ ├── emptyfile │ │ ├── Dockerfile │ │ └── build-push-e2e-bundle.sh │ ├── subdir │ │ ├── .dockerignore │ │ ├── manifests │ │ │ ├── emptydir │ │ │ │ └── .gitignore │ │ │ ├── namespace-a.yaml │ │ │ └── subdir │ │ │ │ └── namespace-b.yaml │ │ ├── Dockerfile │ │ └── build-push-e2e-bundle.sh │ ├── no-manifests │ │ ├── .dockerignore │ │ ├── manifests │ │ │ └── .gitignore │ │ ├── Dockerfile │ │ └── build-push-e2e-bundle.sh │ ├── dependent │ │ ├── Dockerfile │ │ ├── manifests │ │ │ └── cr-no-crd.yaml │ │ └── build-push-e2e-bundle.sh │ ├── provides │ │ ├── Dockerfile │ │ └── build-push-e2e-bundle.sh │ ├── valid │ │ ├── Dockerfile │ │ ├── build-push-e2e-bundle.sh │ │ └── build-push-e2e-bundle-secure.sh │ ├── invalid-crds-and-crs │ │ ├── Dockerfile │ │ └── build-push-e2e-bundle.sh │ └── invalid-missing-crds │ │ ├── Dockerfile │ │ └── build-push-e2e-bundle.sh │ └── registry │ ├── valid │ ├── Dockerfile │ ├── metadata │ │ └── annotations.yaml │ ├── build-push-e2e-bundle.sh │ └── manifests │ │ └── prometheusrules.monitoring.coreos.com.crd.yaml │ └── invalid │ ├── Dockerfile │ ├── manifests │ └── update-service-operator.clusterserviceversion.yaml │ ├── metadata │ └── annotations.yaml │ └── build-push-e2e-bundle.sh ├── CODEOWNERS ├── manifests ├── base │ ├── provisioners │ │ ├── kustomization.yaml │ │ └── helm │ │ │ ├── resources │ │ │ ├── serviceaccount.yaml │ │ │ ├── service.yaml │ │ │ ├── cluster_role_binding.yaml │ │ │ ├── cluster_role.yaml │ │ │ └── deployment.yaml │ │ │ └── kustomization.yaml │ ├── apis │ │ ├── kustomization.yml │ │ ├── webhooks │ │ │ ├── resources │ │ │ │ ├── serviceaccount.yaml │ │ │ │ ├── namespace.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── cluster_role_binding.yaml │ │ │ │ ├── cluster_role.yaml │ │ │ │ ├── webhook.yaml │ │ │ │ └── deployment.yaml │ │ │ ├── patches │ │ │ │ └── namespace_selectors.yaml │ │ │ ├── kustomizeconfig.yaml │ │ │ └── kustomization.yml │ │ └── crds │ │ │ ├── kustomization.yml │ │ │ └── patches │ │ │ └── bundledeployment_validation.yaml │ ├── kustomization.yaml │ ├── crdvalidator │ │ ├── 00_namespace.yaml │ │ ├── 01_service_account.yaml │ │ ├── kustomization.yml │ │ ├── 03_service.yaml │ │ ├── 01_cluster_role.yaml │ │ ├── 02_cluster_role_binding.yaml │ │ ├── 04_webhook.yaml │ │ └── 05_deployment.yaml │ └── core │ │ ├── resources │ │ ├── serviceaccount.yaml │ │ ├── bundle_reader_client_clusterrole.yaml │ │ ├── service.yaml │ │ ├── cluster_role_binding.yaml │ │ ├── cluster_role.yaml │ │ └── deployment.yaml │ │ └── kustomization.yaml ├── overlays │ └── cert-manager │ │ ├── resources │ │ ├── provisioners │ │ │ ├── kustomization.yaml │ │ │ └── helm │ │ │ │ ├── kustomization.yaml │ │ │ │ └── certificate.yaml │ │ ├── crdvalidator │ │ │ ├── kustomization.yaml │ │ │ └── certificate.yaml │ │ ├── core │ │ │ ├── kustomization.yaml │ │ │ ├── certificate.yaml │ │ │ └── rukpak_issuer.yaml │ │ └── apis │ │ │ ├── kustomization.yaml │ │ │ └── webhooks │ │ │ ├── kustomization.yaml │ │ │ ├── kustomizeconfig.yaml │ │ │ └── certificate.yaml │ │ ├── patches │ │ ├── crd_validation_cainjection.yaml │ │ ├── validating_webhook_cainjection.yaml │ │ ├── core_deployment_certs.yaml │ │ └── helm_provisioner_deployment_certs.yaml │ │ └── kustomization.yaml └── README.md ├── api ├── doc.go └── v1alpha2 │ └── groupversion_info.go ├── .markdownlint.yaml ├── .bingo ├── kind.mod ├── go.mod ├── yq.mod ├── bingo.mod ├── ginkgo.mod ├── goreleaser.mod ├── kustomize.mod ├── controller-gen.mod ├── .gitignore ├── setup-envtest.mod ├── golangci-lint.mod ├── README.md ├── variables.env ├── bingo.sum ├── kind.sum └── ginkgo.sum ├── pkg ├── util │ ├── labels.go │ ├── defaults.go │ ├── hash.go │ ├── tar.go │ └── fs.go ├── source │ ├── common.go │ ├── configmaps.go │ └── http.go ├── storage │ ├── storage_suite_test.go │ ├── storage.go │ ├── http.go │ ├── localdir.go │ └── storage_test.go ├── updater │ └── util.go ├── errors │ └── unrecoverable.go ├── finalizer │ ├── deletecachedbundle.go │ └── cleanupunpackcache.go ├── handler │ └── interfaces.go ├── features │ └── features.go ├── provisioner │ ├── registry │ │ └── registry.go │ ├── helm │ │ └── helm.go │ └── plain │ │ └── plain.go ├── helm-operator-plugins │ └── predicate │ │ └── depedent.go └── preflights │ └── crdupgradesafety │ └── crdupgradesafety.go ├── test ├── testutil │ ├── constants.go │ ├── name.go │ └── structs.go ├── tools │ ├── git │ │ ├── service.yaml │ │ ├── git-shell-commands │ │ │ └── no-interactive-login │ │ ├── ssh_knownhosts.txt │ │ ├── git.yaml │ │ ├── start.sh │ │ ├── Dockerfile │ │ └── setup_git.sh │ ├── remotedebug │ │ ├── kind-config.yaml │ │ ├── resources │ │ │ └── service.yaml │ │ ├── kustomization.yaml │ │ └── patch │ │ │ ├── install_dlv_init.yaml │ │ │ └── remote_debug_command.yaml │ └── imageregistry │ │ ├── image-registry.sh │ │ └── image-registry-secure.sh └── e2e │ ├── image_repo.go │ └── e2e_suite_test.go ├── cmd ├── crdvalidator │ ├── annotation │ │ └── annotation.go │ ├── main.go │ └── handlers │ │ └── crd.go └── unpack │ └── main.go ├── .github ├── workflows │ ├── todo.yaml │ ├── e2e.yaml │ ├── go-apidiff.yaml │ ├── add-to-project.yaml │ ├── unit.yaml │ ├── sanity.yaml │ ├── release-e2e.yaml │ ├── tilt.yaml │ ├── stale.yaml │ └── release.yaml └── dependabot.yml ├── .gitignore ├── Dockerfile ├── hack └── boilerplate.go.txt ├── Tiltfile ├── internal ├── unit │ └── unit.go ├── version │ └── version.go ├── operator-registry │ └── registry.go ├── webhook │ └── configmaps.go └── healthchecks │ └── builtin.go ├── docs ├── bundles │ ├── registry.md │ └── helm.md ├── dev │ ├── designs.md │ └── release.md ├── sources │ ├── local.md │ ├── http.md │ └── image.md └── concepts │ └── bundle-immutability.md ├── DCO ├── .golangci.yaml ├── tilt.md └── .goreleaser.yml /testdata/bundles/plain-v0/empty/emptyfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @operator-framework/rukpak-maintainers 2 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/subdir/.dockerignore: -------------------------------------------------------------------------------- 1 | manifests/.gitignore 2 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/no-manifests/.dockerignore: -------------------------------------------------------------------------------- 1 | manifests/.gitignore 2 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/no-manifests/manifests/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/subdir/manifests/emptydir/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /manifests/base/provisioners/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ./helm 3 | 4 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/empty/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY emptyfile emptyfile 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/dependent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/provides/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/subdir/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/valid/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/no-manifests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests manifests 3 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/provisioners/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ./helm 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/invalid-crds-and-crs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/invalid-missing-crds/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/crdvalidator/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/provisioners/helm/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml -------------------------------------------------------------------------------- /api/doc.go: -------------------------------------------------------------------------------- 1 | // Package api contains type definitions for all external versions of rukpak's APIs. 2 | package api 3 | -------------------------------------------------------------------------------- /manifests/base/apis/kustomization.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ./crds 3 | - ./webhooks 4 | 5 | namespace: rukpak-system 6 | -------------------------------------------------------------------------------- /testdata/bundles/registry/valid/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | COPY metadata /metadata 4 | -------------------------------------------------------------------------------- /testdata/bundles/registry/invalid/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY manifests /manifests 3 | COPY metadata /metadata 4 | -------------------------------------------------------------------------------- /manifests/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ./apis 3 | - ./core 4 | - ./crdvalidator 5 | - ./provisioners 6 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/core/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | - rukpak_issuer.yaml 4 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/00_namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: crdvalidator-system 5 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/apis/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ./webhooks 3 | 4 | namespace: rukpak-system 5 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # Rules by id 2 | MD013: 3 | line_length: 300 4 | MD029: false 5 | 6 | # Rules by groups 7 | blank_lines: false 8 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/subdir/manifests/namespace-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: namespace-a 5 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/subdir/manifests/subdir/namespace-b.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: namespace-b 5 | -------------------------------------------------------------------------------- /.bingo/kind.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require sigs.k8s.io/kind v0.23.0 6 | -------------------------------------------------------------------------------- /.bingo/go.mod: -------------------------------------------------------------------------------- 1 | module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files. -------------------------------------------------------------------------------- /.bingo/yq.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require github.com/mikefarah/yq/v4 v4.34.1 6 | -------------------------------------------------------------------------------- /manifests/base/core/resources/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: core-admin 5 | namespace: rukpak-system 6 | -------------------------------------------------------------------------------- /pkg/util/labels.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | const ( 4 | CoreOwnerKindKey = "core.rukpak.io/owner-kind" 5 | CoreOwnerNameKey = "core.rukpak.io/owner-name" 6 | ) 7 | -------------------------------------------------------------------------------- /.bingo/bingo.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require github.com/bwplotka/bingo v0.8.0 6 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: webhooks-admin 5 | namespace: system 6 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/apis/webhooks/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /test/testutil/constants.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | const ( 4 | DefaultCrdName = "samplecrd" 5 | DefaultCrName = "samplecr" 6 | DefaultGroup = "e2e.io" 7 | ) 8 | -------------------------------------------------------------------------------- /.bingo/ginkgo.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require github.com/onsi/ginkgo/v2 v2.17.1 // ginkgo 6 | -------------------------------------------------------------------------------- /.bingo/goreleaser.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require github.com/goreleaser/goreleaser v1.17.2 6 | -------------------------------------------------------------------------------- /.bingo/kustomize.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require sigs.k8s.io/kustomize/kustomize/v5 v5.0.1 6 | -------------------------------------------------------------------------------- /pkg/util/defaults.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | const ( 4 | DefaultSystemNamespace = "rukpak-system" 5 | DefaultUnpackImage = "quay.io/operator-framework/rukpak:main" 6 | ) 7 | -------------------------------------------------------------------------------- /cmd/crdvalidator/annotation/annotation.go: -------------------------------------------------------------------------------- 1 | package annotation 2 | 3 | const ( 4 | ValidationKey = "core.rukpak.io/safe-crd-upgrade-validation" 5 | Disabled = "false" 6 | ) 7 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/01_service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | namespace: crdvalidator-system 5 | name: crd-validation-webhook 6 | -------------------------------------------------------------------------------- /manifests/base/provisioners/helm/resources/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: helm-provisioner-admin 5 | namespace: rukpak-system 6 | -------------------------------------------------------------------------------- /test/testutil/name.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import "k8s.io/apimachinery/pkg/util/rand" 4 | 5 | func GenName(namePrefix string) string { 6 | return namePrefix + rand.String(5) 7 | } 8 | -------------------------------------------------------------------------------- /.bingo/controller-gen.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.22.0 4 | 5 | require sigs.k8s.io/controller-tools v0.15.0 // cmd/controller-gen 6 | -------------------------------------------------------------------------------- /.bingo/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore everything 3 | * 4 | 5 | # But not these files: 6 | !.gitignore 7 | !*.mod 8 | !*.sum 9 | !README.md 10 | !Variables.mk 11 | !variables.env 12 | 13 | *tmp.mod 14 | -------------------------------------------------------------------------------- /test/tools/git/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: local-git 5 | spec: 6 | selector: 7 | app: local-git 8 | ports: 9 | - port: 2222 10 | targetPort: 22 11 | -------------------------------------------------------------------------------- /pkg/source/common.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func generateMessage(bundleName string) string { 8 | return fmt.Sprintf("Successfully unpacked the %s Bundle", bundleName) 9 | } 10 | -------------------------------------------------------------------------------- /.bingo/setup-envtest.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.20 4 | 5 | require sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20220607150856-0c2effbc7eab 6 | -------------------------------------------------------------------------------- /.bingo/golangci-lint.mod: -------------------------------------------------------------------------------- 1 | module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT 2 | 3 | go 1.21 4 | 5 | toolchain go1.22.2 6 | 7 | require github.com/golangci/golangci-lint v1.57.2 // cmd/golangci-lint 8 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | pod-security.kubernetes.io/enforce: baseline 6 | pod-security.kubernetes.io/enforce-version: latest 7 | name: system 8 | -------------------------------------------------------------------------------- /test/tools/git/git-shell-commands/no-interactive-login: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | printf '%s\n' "Welcome to git-server-docker!" 3 | printf '%s\n' "You've successfully authenticated, but I do not" 4 | printf '%s\n' "provide interactive shell access." 5 | exit 128 6 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/kustomization.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | - 00_namespace.yaml 3 | - 01_cluster_role.yaml 4 | - 01_service_account.yaml 5 | - 02_cluster_role_binding.yaml 6 | - 03_service.yaml 7 | - 04_webhook.yaml 8 | - 05_deployment.yaml 9 | -------------------------------------------------------------------------------- /test/e2e/image_repo.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | // ImageRepo is meant to be set via -ldflags during execution of this code 4 | // where it will then be used to configure the testing suite. 5 | var ImageRepo = "docker-registry.rukpak-e2e.svc.cluster.local:5000/bundles" 6 | -------------------------------------------------------------------------------- /manifests/base/core/resources/bundle_reader_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: bundle-reader 5 | rules: 6 | - nonResourceURLs: 7 | - /bundles/* 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /pkg/storage/storage_suite_test.go: -------------------------------------------------------------------------------- 1 | package storage_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestStorage(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Storage Suite") 13 | } 14 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/dependent/manifests/cr-no-crd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: operators 6 | --- 7 | apiVersion: operators.coreos.com/v1 8 | kind: OperatorGroup 9 | metadata: 10 | name: global-operators 11 | namespace: operators 12 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | protocol: TCP 11 | targetPort: 9443 12 | selector: 13 | app: webhooks 14 | -------------------------------------------------------------------------------- /manifests/base/core/resources/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | namespace: rukpak-system 5 | name: core 6 | spec: 7 | ports: 8 | - name: https 9 | port: 443 10 | protocol: TCP 11 | targetPort: 8443 12 | selector: 13 | app: core 14 | -------------------------------------------------------------------------------- /.github/workflows/todo.yaml: -------------------------------------------------------------------------------- 1 | name: todo 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: "ubuntu-latest" 9 | steps: 10 | - uses: "actions/checkout@v4" 11 | - name: "Create issues from TODO comments" 12 | uses: "alstr/todo-to-issue-action@v5" 13 | -------------------------------------------------------------------------------- /manifests/base/provisioners/helm/resources/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | namespace: rukpak-system 5 | name: helm-provisioner 6 | spec: 7 | ports: 8 | - port: 443 9 | protocol: TCP 10 | targetPort: 8443 11 | selector: 12 | app: helm-provisioner 13 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/03_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: crd-validation-webhook 5 | namespace: crdvalidator-system 6 | spec: 7 | ports: 8 | - port: 9443 9 | protocol: TCP 10 | targetPort: 9443 11 | selector: 12 | app: crd-validation-webhook 13 | -------------------------------------------------------------------------------- /manifests/base/apis/crds/kustomization.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | - core.rukpak.io_bundledeployments.yaml 3 | patches: 4 | - path: patches/bundledeployment_validation.yaml 5 | target: 6 | group: apiextensions.k8s.io 7 | version: v1 8 | kind: CustomResourceDefinition 9 | name: bundledeployments.core.rukpak.io 10 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/patches/crd_validation_cainjection.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: ValidatingWebhookConfiguration 4 | metadata: 5 | name: crd-validation-webhook 6 | annotations: 7 | cert-manager.io/inject-ca-from: crdvalidator-system/crd-validation-webhook-certificate 8 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/patches/validating_webhook_cainjection.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: ValidatingWebhookConfiguration 4 | metadata: 5 | name: validating-webhook-configuration 6 | annotations: 7 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 8 | -------------------------------------------------------------------------------- /pkg/updater/util.go: -------------------------------------------------------------------------------- 1 | package updater 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | func ConditionsSemanticallyEqual(a, b metav1.Condition) bool { 6 | return a.Type == b.Type && a.Status == b.Status && a.Reason == b.Reason && a.Message == b.Message && a.ObservedGeneration == b.ObservedGeneration 7 | } 8 | -------------------------------------------------------------------------------- /test/tools/remotedebug/kind-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kind.x-k8s.io/v1alpha4 2 | kind: Cluster 3 | nodes: 4 | - role: control-plane 5 | extraPortMappings: 6 | - containerPort: 31111 7 | hostPort: 40000 8 | listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0" 9 | protocol: tcp # Optional, defaults to tcp 10 | -------------------------------------------------------------------------------- /test/tools/remotedebug/resources/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: core-remote-debug 5 | namespace: rukpak-system 6 | spec: 7 | type: NodePort 8 | ports: 9 | - port: 40000 10 | targetPort: 40000 11 | nodePort: 31111 12 | protocol: TCP 13 | selector: 14 | app: core -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/apis/webhooks/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | nameReference: 2 | - kind: Issuer 3 | group: cert-manager.io 4 | fieldSpecs: 5 | - kind: Certificate 6 | group: cert-manager.io 7 | path: spec/issuerRef/name 8 | 9 | varReference: 10 | - kind: Certificate 11 | group: cert-manager.io 12 | path: spec/dnsNames 13 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/01_cluster_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: crd-validation-webhook 5 | rules: 6 | - apiGroups: ["apiextensions.k8s.io"] 7 | resources: ["customresourcedefinitions"] 8 | verbs: ["get", "watch", "list"] 9 | - apiGroups: ["*"] 10 | resources: ["*"] 11 | verbs: ["list"] 12 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/patches/namespace_selectors.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: ValidatingWebhookConfiguration 4 | metadata: 5 | name: validating-webhook-configuration 6 | webhooks: 7 | - name: vconfigmaps.core.rukpak.io 8 | namespaceSelector: 9 | matchLabels: 10 | kubernetes.io/metadata.name: rukpak-system 11 | -------------------------------------------------------------------------------- /pkg/errors/unrecoverable.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // Unrecoverable represents an error that can not be recovered 4 | // from without user intervention. When this error is returned 5 | // the request should not be requeued. 6 | type Unrecoverable struct { 7 | error 8 | } 9 | 10 | func NewUnrecoverable(err error) *Unrecoverable { 11 | return &Unrecoverable{err} 12 | } 13 | -------------------------------------------------------------------------------- /test/tools/remotedebug/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../../../manifests/overlays/cert-manager 3 | - ./resources/service.yaml 4 | 5 | patchesJson6902: 6 | - target: 7 | group: apps 8 | version: v1 9 | kind: Deployment 10 | name: core 11 | path: patch/remote_debug_command.yaml 12 | 13 | patchesStrategicMerge: 14 | - patch/install_dlv_init.yaml 15 | -------------------------------------------------------------------------------- /test/tools/git/ssh_knownhosts.txt: -------------------------------------------------------------------------------- 1 | |1|o9jbc9csoTXsMn/rVT3HCLZfLVw=|QsVVKQiYlSFWjMnbhwJuBuP92OU= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINx1gFJ7GnSma6U/s2XvHYSPVn/2PRzcJ77neKZZgG8w 2 | |1|yjtqO7OyLpYXaoibacE0bRR6poA=|EOehPrkHiOXNZXZnZh3HmAwP/fw= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOYeDAU1vFddTpWTfi1JMQWTf5WwjiomQVQnUe4HSuU60ajiOvTUf8x7hL9SYsmT4bt5RJatnUc34CRSXVZTWng= -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "gomod" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | groups: 12 | k8s-dependencies: 13 | patterns: 14 | - "k8s.io/*" 15 | - "sigs.k8s.io/*" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories 15 | vendor/ 16 | bin/ 17 | dist/ 18 | 19 | # Release artifacts 20 | rukpak.yaml 21 | 22 | .tiltbuild/ 23 | -------------------------------------------------------------------------------- /manifests/base/core/resources/cluster_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: core-admin 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: core-admin 9 | subjects: 10 | - apiGroup: "" 11 | kind: ServiceAccount 12 | name: core-admin 13 | namespace: rukpak-system 14 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/02_cluster_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: crd-validation-webhook 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: crd-validation-webhook 9 | subjects: 10 | - kind: ServiceAccount 11 | name: crd-validation-webhook 12 | namespace: crdvalidator-system 13 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/cluster_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: webhooks-admin 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: webhooks-admin 9 | subjects: 10 | - apiGroup: "" 11 | kind: ServiceAccount 12 | name: rukpak-webhooks-admin 13 | namespace: system 14 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/cluster_role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: webhooks-admin 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - list 13 | - watch 14 | - apiGroups: 15 | - core.rukpak.io 16 | resources: 17 | - bundledeployments 18 | verbs: 19 | - list 20 | - watch 21 | -------------------------------------------------------------------------------- /manifests/base/provisioners/helm/resources/cluster_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: helm-provisioner-admin 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: helm-provisioner-admin 9 | subjects: 10 | - apiGroup: "" 11 | kind: ServiceAccount 12 | name: helm-provisioner-admin 13 | namespace: rukpak-system 14 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yaml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | merge_group: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | e2e-kind: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | - name: Run e2e tests 20 | run: | 21 | make e2e 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/static:debug-nonroot AS builder 2 | 3 | # Stage 2: 4 | FROM gcr.io/distroless/static:nonroot 5 | 6 | # Grab the cp binary so we can cp the unpack 7 | # binary to a shared volume in the bundle image. 8 | COPY --from=builder /busybox/cp /busybox/ls / 9 | 10 | WORKDIR / 11 | 12 | COPY helm helm 13 | COPY core core 14 | COPY unpack unpack 15 | COPY webhooks webhooks 16 | COPY crdvalidator crdvalidator 17 | 18 | EXPOSE 8080 19 | -------------------------------------------------------------------------------- /testdata/bundles/registry/valid/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | operators.operatorframework.io.bundle.channel.default.v1: beta 3 | operators.operatorframework.io.bundle.channels.v1: beta 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 6 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 7 | operators.operatorframework.io.bundle.package.v1: prometheus 8 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/core/certificate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: core 6 | namespace: rukpak-system 7 | spec: 8 | secretName: core-cert 9 | dnsNames: 10 | - localhost 11 | - $(CORE_SERVICE_NAME).$(CORE_SERVICE_NAMESPACE).svc 12 | - $(CORE_SERVICE_NAME).$(CORE_SERVICE_NAMESPACE).svc.cluster.local 13 | issuerRef: 14 | kind: Issuer 15 | name: rukpak-ca-issuer 16 | -------------------------------------------------------------------------------- /.github/workflows/go-apidiff.yaml: -------------------------------------------------------------------------------- 1 | name: go-apidiff 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | go-apidiff: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out code into the Go module directory 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version-file: "go.mod" 18 | id: go 19 | - name: Run go-apidiff 20 | uses: joelanford/go-apidiff@main 21 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/provisioners/helm/certificate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: helm-provisioner 6 | namespace: rukpak-system 7 | spec: 8 | secretName: helm-provisioner-cert 9 | dnsNames: 10 | - $(HELM_PROVISIONER_SERVICE_NAME).$(HELM_PROVISIONER_SERVICE_NAMESPACE).svc 11 | - $(HELM_PROVISIONER_SERVICE_NAME).$(HELM_PROVISIONER_SERVICE_NAMESPACE).svc.cluster.local 12 | issuerRef: 13 | kind: Issuer 14 | name: rukpak-ca-issuer 15 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yaml: -------------------------------------------------------------------------------- 1 | name: Add epic issues to OLMv1 project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - labeled 8 | 9 | jobs: 10 | add-to-project: 11 | name: Add issue to project 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/add-to-project@v1.0.2 15 | with: 16 | project-url: https://github.com/orgs/operator-framework/projects/8 17 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 18 | labeled: epic 19 | label-operator: OR 20 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | nameReference: 2 | - kind: Service 3 | version: v1 4 | fieldSpecs: 5 | - kind: ValidatingWebhookConfiguration 6 | group: admissionregistration.k8s.io 7 | path: webhooks/clientConfig/service/name 8 | 9 | namespace: 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/namespace 13 | create: true 14 | 15 | varReference: 16 | - kind: ValidatingWebhookConfiguration 17 | group: admissionregistration.k8s.io 18 | path: metadata/annotations 19 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/crdvalidator/certificate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Certificate 4 | metadata: 5 | name: crd-validation-webhook-certificate 6 | namespace: crdvalidator-system 7 | spec: 8 | secretName: crd-validation-webhook-certificate 9 | dnsNames: 10 | - crd-validation-webhook.crdvalidator-system.svc 11 | issuerRef: 12 | name: selfsigned 13 | --- 14 | apiVersion: cert-manager.io/v1 15 | kind: Issuer 16 | metadata: 17 | name: selfsigned 18 | namespace: crdvalidator-system 19 | spec: 20 | selfSigned: {} 21 | -------------------------------------------------------------------------------- /.github/workflows/unit.yaml: -------------------------------------------------------------------------------- 1 | name: unit 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | unit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | - run: make test-unit 20 | 21 | - uses: codecov/codecov-action@v4 22 | with: 23 | disable_search: true 24 | files: cover.out 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | -------------------------------------------------------------------------------- /test/tools/git/git.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: local-git-pod 5 | labels: 6 | app: local-git 7 | spec: 8 | containers: 9 | - name: git 10 | image: localhost/git:latest 11 | imagePullPolicy: IfNotPresent 12 | ports: 13 | - containerPort: 22 14 | protocol: TCP 15 | name: https 16 | terminationMessagePolicy: FallbackToLogsOnError 17 | volumeMounts: 18 | - name: ssh-cert 19 | mountPath: "/git-server/keys" 20 | readOnly: true 21 | volumes: 22 | - name: ssh-cert 23 | secret: 24 | secretName: ssh-secret 25 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /Tiltfile: -------------------------------------------------------------------------------- 1 | if not os.path.exists('../tilt-support'): 2 | fail('Please clone https://github.com/operator-framework/tilt-support to ../tilt-support') 3 | 4 | load('../tilt-support/Tiltfile', 'deploy_repo') 5 | 6 | repo = { 7 | 'image': 'quay.io/operator-framework/rukpak', 8 | 'yaml': 'manifests/overlays/cert-manager', 9 | 'binaries': { 10 | 'core': 'core', 11 | 'crdvalidator': 'crd-validation-webhook', 12 | 'helm': 'helm-provisioner', 13 | 'webhooks': 'rukpak-webhooks', 14 | }, 15 | 'starting_debug_port': 10000, 16 | } 17 | 18 | deploy_repo('rukpak', repo) 19 | -------------------------------------------------------------------------------- /internal/unit/unit.go: -------------------------------------------------------------------------------- 1 | package unit 2 | 3 | import ( 4 | "fmt" 5 | 6 | "sigs.k8s.io/controller-runtime/pkg/client" 7 | "sigs.k8s.io/controller-runtime/pkg/envtest" 8 | ) 9 | 10 | func SetupClient() (client.Client, error) { 11 | testenv := &envtest.Environment{} 12 | 13 | config, err := testenv.Start() 14 | if err != nil { 15 | return nil, fmt.Errorf("failed to start envtest: %v", err) 16 | } 17 | 18 | kubeclient, err := client.New(config, client.Options{}) 19 | if err != nil { 20 | return nil, fmt.Errorf("failed to create kubeclient: %v", err) 21 | } 22 | 23 | return kubeclient, nil 24 | } 25 | -------------------------------------------------------------------------------- /manifests/base/core/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - resources/bundle_reader_client_clusterrole.yaml 3 | - resources/cluster_role.yaml 4 | - resources/cluster_role_binding.yaml 5 | - resources/deployment.yaml 6 | - resources/service.yaml 7 | - resources/serviceaccount.yaml 8 | 9 | vars: 10 | - name: CORE_SERVICE_NAMESPACE # namespace of the service 11 | objref: 12 | kind: Service 13 | version: v1 14 | name: core 15 | fieldref: 16 | fieldpath: metadata.namespace 17 | - name: CORE_SERVICE_NAME 18 | objref: 19 | kind: Service 20 | version: v1 21 | name: core 22 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/apis/webhooks/certificate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: Issuer 4 | metadata: 5 | name: rukpak-selfsigned 6 | namespace: system 7 | spec: 8 | selfSigned: {} 9 | --- 10 | apiVersion: cert-manager.io/v1 11 | kind: Certificate 12 | metadata: 13 | name: rukpak-webhook-certificate 14 | namespace: system 15 | spec: 16 | secretName: rukpak-webhook-certificate 17 | dnsNames: 18 | - SERVICE_NAME.SERVICE_NAMESPACE.svc 19 | - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local 20 | issuerRef: 21 | kind: Issuer 22 | name: rukpak-selfsigned 23 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/kustomization.yml: -------------------------------------------------------------------------------- 1 | namePrefix: rukpak- 2 | 3 | resources: 4 | - resources/namespace.yaml 5 | - resources/serviceaccount.yaml 6 | - resources/service.yaml 7 | - resources/deployment.yaml 8 | - resources/webhook.yaml 9 | - resources/cluster_role.yaml 10 | - resources/cluster_role_binding.yaml 11 | 12 | configurations: 13 | - kustomizeconfig.yaml 14 | 15 | patches: 16 | - target: 17 | group: admissionregistration.k8s.io 18 | version: v1 19 | kind: ValidatingWebhookConfiguration 20 | name: validating-webhook-configuration 21 | path: patches/namespace_selectors.yaml 22 | -------------------------------------------------------------------------------- /manifests/base/provisioners/helm/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - resources/cluster_role.yaml 3 | - resources/cluster_role_binding.yaml 4 | - resources/deployment.yaml 5 | - resources/service.yaml 6 | - resources/serviceaccount.yaml 7 | 8 | vars: 9 | - name: HELM_PROVISIONER_SERVICE_NAMESPACE # namespace of the service 10 | objref: 11 | kind: Service 12 | version: v1 13 | name: helm-provisioner 14 | fieldref: 15 | fieldpath: metadata.namespace 16 | - name: HELM_PROVISIONER_SERVICE_NAME 17 | objref: 18 | kind: Service 19 | version: v1 20 | name: helm-provisioner 21 | -------------------------------------------------------------------------------- /pkg/finalizer/deletecachedbundle.go: -------------------------------------------------------------------------------- 1 | package finalizer 2 | 3 | import ( 4 | "context" 5 | 6 | "sigs.k8s.io/controller-runtime/pkg/client" 7 | "sigs.k8s.io/controller-runtime/pkg/finalizer" 8 | 9 | "github.com/operator-framework/rukpak/pkg/storage" 10 | ) 11 | 12 | var _ finalizer.Finalizer = &DeleteCachedBundle{} 13 | 14 | const DeleteCachedBundleKey = "core.rukpak.io/delete-cached-bundle" 15 | 16 | type DeleteCachedBundle struct { 17 | storage.Storage 18 | } 19 | 20 | func (f DeleteCachedBundle) Finalize(ctx context.Context, obj client.Object) (finalizer.Result, error) { 21 | return finalizer.Result{}, f.Delete(ctx, obj) 22 | } 23 | -------------------------------------------------------------------------------- /docs/bundles/registry.md: -------------------------------------------------------------------------------- 1 | # Registry Bundle Spec 2 | 3 | ## Overview 4 | 5 | This document is meant to define the `registry` bundle format as a reference for those publishing `registry` bundles 6 | for use with RukPak. For more information on the concept of a bundle, click [here](https://github.com/operator-framework/rukpak#bundle). 7 | 8 | The `registry+v1` bundles, or `registry` bundles, contains a set of static Kubernetes YAML 9 | manifests organized in the legacy Operator Lifecycle Manger (OLM) format. For more information on the `registry+v1` format, see 10 | the [OLM packaging doc](https://olm.operatorframework.io/docs/tasks/creating-operator-manifests/). 11 | -------------------------------------------------------------------------------- /testdata/bundles/registry/invalid/manifests/update-service-operator.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | capabilities: Basic Install 6 | description: Creates and maintains an OpenShift Update Service instance 7 | operatorframework.io/suggested-namespace: openshift-update-service 8 | name: update-service-operator.v5.0.0 9 | namespace: placeholder 10 | spec: 11 | installModes: 12 | - supported: false 13 | type: OwnNamespace 14 | - supported: false 15 | type: SingleNamespace 16 | - supported: true 17 | type: MultiNamespace 18 | - supported: false 19 | type: AllNamespaces 20 | -------------------------------------------------------------------------------- /pkg/handler/interfaces.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "io/fs" 6 | 7 | "helm.sh/helm/v3/pkg/chart" 8 | "helm.sh/helm/v3/pkg/chartutil" 9 | 10 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 11 | ) 12 | 13 | type Handler interface { 14 | Handle(context.Context, fs.FS, *rukpakv1alpha2.BundleDeployment) (*chart.Chart, chartutil.Values, error) 15 | } 16 | 17 | type HandlerFunc func(context.Context, fs.FS, *rukpakv1alpha2.BundleDeployment) (*chart.Chart, chartutil.Values, error) 18 | 19 | func (f HandlerFunc) Handle(ctx context.Context, fsys fs.FS, bd *rukpakv1alpha2.BundleDeployment) (*chart.Chart, chartutil.Values, error) { 20 | return f(ctx, fsys, bd) 21 | } 22 | -------------------------------------------------------------------------------- /manifests/README.md: -------------------------------------------------------------------------------- 1 | # Rukpak Manifests 2 | 3 | Manifests for Rukpak are organized into the `base/` and `overlays/` folders. This is to allow developers to develop with rukpak using alternative certificate management solutions by applying a separate overlay on top of the `base/` kustomization folder. 4 | 5 | `base/` includes everything required to install rukpak except for certificate management related items. Those items are contained in a separate overlay, in this case `overlays/cert-manager`. 6 | 7 | At the moment, rukpak contains just one overlay using [cert-manager](https://github.com/cert-manager/cert-manager). To run `kustomize` commands against the entire set of rukpak's manifests, the `manifests/overlays/cert-manager` folder should be targeted. -------------------------------------------------------------------------------- /pkg/finalizer/cleanupunpackcache.go: -------------------------------------------------------------------------------- 1 | package finalizer 2 | 3 | import ( 4 | "context" 5 | 6 | "sigs.k8s.io/controller-runtime/pkg/client" 7 | "sigs.k8s.io/controller-runtime/pkg/finalizer" 8 | 9 | "github.com/operator-framework/rukpak/api/v1alpha2" 10 | "github.com/operator-framework/rukpak/pkg/source" 11 | ) 12 | 13 | var _ finalizer.Finalizer = &CleanupUnpackCache{} 14 | 15 | const CleanupUnpackCacheKey = "core.rukpak.io/cleanup-unpack-cache" 16 | 17 | type CleanupUnpackCache struct { 18 | Unpacker source.Unpacker 19 | } 20 | 21 | func (f CleanupUnpackCache) Finalize(ctx context.Context, obj client.Object) (finalizer.Result, error) { 22 | return finalizer.Result{}, f.Unpacker.Cleanup(ctx, obj.(*v1alpha2.BundleDeployment)) 23 | } 24 | -------------------------------------------------------------------------------- /test/tools/git/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If there is some public key in keys folder 4 | # then it copies its contain in authorized_keys file 5 | if [ "$(ls -A /git-server/keys/)" ]; then 6 | cd /home/git 7 | cat /git-server/keys/*.pub > .ssh/authorized_keys 8 | chown -R git:git .ssh 9 | chmod 700 .ssh 10 | chmod -R 600 .ssh/* 11 | fi 12 | 13 | # Checking permissions and fixing SGID bit in repos folder 14 | # More info: https://github.com/jkarlosb/git-server-docker/issues/1 15 | if [ "$(ls -A /git-server/repos/)" ]; then 16 | cd /git-server/repos 17 | chown -R git:git . 18 | chmod -R ug+rwX . 19 | find . -type d -exec chmod g+s '{}' + 20 | fi 21 | 22 | # -D flag avoids executing sshd as a daemon 23 | /usr/sbin/sshd -D 24 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/04_webhook.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: ValidatingWebhookConfiguration 4 | metadata: 5 | name: crd-validation-webhook 6 | webhooks: 7 | - name: "webhook.crdvalidator.io" 8 | rules: 9 | - apiGroups: ["apiextensions.k8s.io"] 10 | apiVersions: ["v1"] 11 | operations: ["CREATE", "UPDATE"] 12 | resources: ["customresourcedefinitions"] 13 | scope: "*" 14 | clientConfig: 15 | service: 16 | namespace: crdvalidator-system 17 | name: crd-validation-webhook 18 | path: /validate-crd 19 | port: 9443 20 | objectSelector: 21 | matchLabels: 22 | core.rukpak.io/owner-kind: BundleDeployment 23 | admissionReviewVersions: ["v1"] 24 | sideEffects: None 25 | -------------------------------------------------------------------------------- /pkg/features/features.go: -------------------------------------------------------------------------------- 1 | package features 2 | 3 | import ( 4 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 5 | "k8s.io/component-base/featuregate" 6 | ) 7 | 8 | const ( 9 | // Add new feature gates constants (strings) 10 | // Ex: SomeFeature featuregate.Feature = "SomeFeature" 11 | 12 | BundleDeploymentHealth featuregate.Feature = "BundleDeploymentHealth" 13 | ) 14 | 15 | var rukpakFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ 16 | // Add new feature gate definitions 17 | // Ex: SomeFeature: {...} 18 | 19 | BundleDeploymentHealth: {Default: false, PreRelease: featuregate.Alpha}, 20 | } 21 | 22 | var RukpakFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() 23 | 24 | func init() { 25 | utilruntime.Must(RukpakFeatureGate.Add(rukpakFeatureGates)) 26 | } 27 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/patches/core_deployment_certs.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/template/spec/containers/0/args/- 3 | value: "--tls-cert-file=/etc/pki/tls/tls.crt" 4 | - op: add 5 | path: /spec/template/spec/containers/0/args/- 6 | value: "--tls-private-key-file=/etc/pki/tls/tls.key" 7 | - op: add 8 | path: /spec/template/spec/containers/0/volumeMounts/- 9 | value: {"name":"certs", "mountPath":"/etc/pki/tls"} 10 | - op: add 11 | path: /spec/template/spec/containers/1/args/- 12 | value: "--bundle-ca-file=/etc/pki/tls/ca.crt" 13 | - op: add 14 | path: /spec/template/spec/containers/1/volumeMounts/- 15 | value: {"name":"certs", "mountPath":"/etc/pki/tls"} 16 | - op: add 17 | path: /spec/template/spec/volumes/- 18 | value: {"name":"certs", "secret":{"secretName":"core-cert", "optional":false}} 19 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/patches/helm_provisioner_deployment_certs.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/template/spec/containers/0/args/- 3 | value: "--tls-cert-file=/etc/pki/tls/tls.crt" 4 | - op: add 5 | path: /spec/template/spec/containers/0/args/- 6 | value: "--tls-private-key-file=/etc/pki/tls/tls.key" 7 | - op: add 8 | path: /spec/template/spec/containers/0/volumeMounts/- 9 | value: {"name":"certs", "mountPath":"/etc/pki/tls"} 10 | - op: add 11 | path: /spec/template/spec/containers/1/args/- 12 | value: "--bundle-ca-file=/etc/pki/tls/ca.crt" 13 | - op: add 14 | path: /spec/template/spec/containers/1/volumeMounts/- 15 | value: {"name":"certs", "mountPath":"/etc/pki/tls"} 16 | - op: add 17 | path: /spec/template/spec/volumes/- 18 | value: {"name":"certs", "secret":{"secretName":"helm-provisioner-cert", "optional":false}} 19 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "runtime/debug" 6 | ) 7 | 8 | var ( 9 | gitCommit = "unknown" 10 | commitDate = "unknown" 11 | repoState = "unknown" 12 | 13 | stateMap = map[string]string{ 14 | "true": "dirty", 15 | "false": "clean", 16 | } 17 | ) 18 | 19 | func String() string { 20 | return fmt.Sprintf("revision: %q, date: %q, state: %q", gitCommit, commitDate, repoState) 21 | } 22 | 23 | func init() { 24 | info, ok := debug.ReadBuildInfo() 25 | if !ok { 26 | return 27 | } 28 | for _, setting := range info.Settings { 29 | switch setting.Key { 30 | case "vcs.revision": 31 | gitCommit = setting.Value 32 | case "vcs.time": 33 | commitDate = setting.Value 34 | case "vcs.modified": 35 | if v, ok := stateMap[setting.Value]; ok { 36 | repoState = v 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.bingo/README.md: -------------------------------------------------------------------------------- 1 | # Project Development Dependencies. 2 | 3 | This is directory which stores Go modules with pinned buildable package that is used within this repository, managed by https://github.com/bwplotka/bingo. 4 | 5 | * Run `bingo get` to install all tools having each own module file in this directory. 6 | * Run `bingo get ` to install that have own module file in this directory. 7 | * For Makefile: Make sure to put `include .bingo/Variables.mk` in your Makefile, then use $() variable where is the .bingo/.mod. 8 | * For shell: Run `source .bingo/variables.env` to source all environment variable for each tool. 9 | * For go: Import `.bingo/variables.go` to for variable names. 10 | * See https://github.com/bwplotka/bingo or -h on how to add, remove or change binaries dependencies. 11 | 12 | ## Requirements 13 | 14 | * Go 1.14+ 15 | -------------------------------------------------------------------------------- /.bingo/variables.env: -------------------------------------------------------------------------------- 1 | # Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.8. DO NOT EDIT. 2 | # All tools are designed to be build inside $GOBIN. 3 | # Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk. 4 | GOBIN=${GOBIN:=$(go env GOBIN)} 5 | 6 | if [ -z "$GOBIN" ]; then 7 | GOBIN="$(go env GOPATH)/bin" 8 | fi 9 | 10 | 11 | BINGO="${GOBIN}/bingo-v0.8.0" 12 | 13 | CONTROLLER_GEN="${GOBIN}/controller-gen-v0.15.0" 14 | 15 | GINKGO="${GOBIN}/ginkgo-v2.17.1" 16 | 17 | GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.57.2" 18 | 19 | GORELEASER="${GOBIN}/goreleaser-v1.17.2" 20 | 21 | KIND="${GOBIN}/kind-v0.23.0" 22 | 23 | KUSTOMIZE="${GOBIN}/kustomize-v5.0.1" 24 | 25 | SETUP_ENVTEST="${GOBIN}/setup-envtest-v0.0.0-20220607150856-0c2effbc7eab" 26 | 27 | YQ="${GOBIN}/yq-v4.34.1" 28 | 29 | -------------------------------------------------------------------------------- /manifests/overlays/cert-manager/resources/core/rukpak_issuer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cert-manager.io/v1 3 | kind: ClusterIssuer 4 | metadata: 5 | name: rukpak-selfsigned-issuer 6 | spec: 7 | selfSigned: {} 8 | --- 9 | apiVersion: cert-manager.io/v1 10 | kind: Certificate 11 | metadata: 12 | name: rukpak-ca 13 | namespace: rukpak-system 14 | spec: 15 | isCA: true 16 | secretName: rukpak-ca 17 | dnsNames: 18 | - rukpak.io 19 | duration: 2160h # 90d 20 | renewBefore: 360h # 15d 21 | privateKey: 22 | rotationPolicy: Always 23 | algorithm: ECDSA 24 | size: 256 25 | issuerRef: 26 | name: rukpak-selfsigned-issuer 27 | kind: ClusterIssuer 28 | group: cert-manager.io 29 | --- 30 | apiVersion: cert-manager.io/v1 31 | kind: Issuer 32 | metadata: 33 | name: rukpak-ca-issuer 34 | namespace: rukpak-system 35 | spec: 36 | ca: 37 | secretName: rukpak-ca -------------------------------------------------------------------------------- /manifests/base/apis/crds/patches/bundledeployment_validation.yaml: -------------------------------------------------------------------------------- 1 | # the max bundle deployment name up to 52 2 | # it allows extra 11 letters for related resource names 3 | - op: add 4 | path: /spec/versions/0/schema/openAPIV3Schema/properties/metadata/properties 5 | value: 6 | name: 7 | type: string 8 | maxLength: 52 9 | # Union source type 10 | - op: add 11 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/source/oneOf 12 | value: 13 | - required: 14 | - git 15 | - required: 16 | - image 17 | - required: 18 | - configMaps 19 | - required: 20 | - http 21 | 22 | # Union git ref 23 | - op: add 24 | path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/source/properties/git/properties/ref/oneOf 25 | value: 26 | - required: 27 | - branch 28 | - required: 29 | - commit 30 | - required: 31 | - tag 32 | -------------------------------------------------------------------------------- /test/tools/git/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | WORKDIR /git-server/ 4 | 5 | RUN apk add --no-cache openssh git \ 6 | && ssh-keygen -A \ 7 | && mkdir /git-server/keys \ 8 | && adduser -D -s /usr/bin/git-shell git \ 9 | && echo git:12345 | chpasswd \ 10 | && mkdir /home/git/.ssh 11 | 12 | # Key generation on the server 13 | COPY testdata /git-server/repos 14 | 15 | # This is a login shell for SSH accounts to provide restricted Git access. 16 | # It permits execution only of server-side Git commands implementing the 17 | # pull/push functionality, plus custom commands present in a subdirectory 18 | # named git-shell-commands in the user’s home directory. 19 | # More info: https://git-scm.com/docs/git-shell 20 | COPY git-shell-commands /home/git/git-shell-commands 21 | 22 | # sshd_config file is edited for enable access key and disable access password 23 | COPY sshd_config /etc/ssh/sshd_config 24 | COPY start.sh start.sh 25 | 26 | EXPOSE 22 27 | 28 | CMD ["sh", "start.sh"] 29 | -------------------------------------------------------------------------------- /pkg/provisioner/registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/fs" 7 | 8 | "helm.sh/helm/v3/pkg/chart" 9 | "helm.sh/helm/v3/pkg/chartutil" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | 12 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 13 | "github.com/operator-framework/rukpak/pkg/convert" 14 | "github.com/operator-framework/rukpak/pkg/provisioner/plain" 15 | ) 16 | 17 | const ( 18 | // ProvisionerID is the unique registry provisioner ID 19 | ProvisionerID = "core-rukpak-io-registry" 20 | ) 21 | 22 | func HandleBundleDeployment(ctx context.Context, fsys fs.FS, bd *rukpakv1alpha2.BundleDeployment) (*chart.Chart, chartutil.Values, error) { 23 | plainFS, err := convert.RegistryV1ToPlain(fsys, bd.Spec.InstallNamespace, []string{metav1.NamespaceAll}) 24 | if err != nil { 25 | return nil, nil, fmt.Errorf("convert registry+v1 bundle to plain+v0 bundle: %v", err) 26 | } 27 | return plain.HandleBundleDeployment(ctx, plainFS, bd) 28 | } 29 | -------------------------------------------------------------------------------- /manifests/base/crdvalidator/05_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: crd-validation-webhook 5 | namespace: crdvalidator-system 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: crd-validation-webhook 10 | template: 11 | metadata: 12 | labels: 13 | app: crd-validation-webhook 14 | spec: 15 | serviceAccountName: crd-validation-webhook 16 | containers: 17 | - image: quay.io/operator-framework/rukpak:devel 18 | imagePullPolicy: IfNotPresent 19 | command: ["/crdvalidator"] 20 | name: crd-validation-webhook 21 | volumeMounts: 22 | - name: tls 23 | mountPath: "/etc/admission-webhook/tls" 24 | resources: 25 | requests: 26 | cpu: 1m 27 | memory: 15Mi 28 | terminationMessagePolicy: FallbackToLogsOnError 29 | volumes: 30 | - name: tls 31 | secret: 32 | secretName: CRD_VALIDATION_WEBHOOK_SECRET_NAME 33 | -------------------------------------------------------------------------------- /test/tools/remotedebug/patch/install_dlv_init.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: rukpak-system 5 | name: core 6 | spec: 7 | template: 8 | spec: 9 | securityContext: 10 | runAsNonRoot: false 11 | volumes: 12 | - name: gobindir 13 | emptyDir: {} 14 | initContainers: 15 | - name: install-dlv 16 | env: 17 | - name: CGO_ENABLED 18 | value: "0" 19 | image: golang:1.17 20 | command: 21 | - go 22 | args: 23 | - install 24 | - -ldflags 25 | - "-s -w -extldflags '-static'" 26 | - github.com/go-delve/delve/cmd/dlv@latest 27 | terminationMessagePolicy: FallbackToLogsOnError 28 | volumeMounts: 29 | - name: gobindir 30 | mountPath: /go/bin/ 31 | containers: 32 | - name: kube-rbac-proxy 33 | - name: manager 34 | volumeMounts: 35 | - name: gobindir 36 | mountPath: /go/bin/ 37 | -------------------------------------------------------------------------------- /docs/dev/designs.md: -------------------------------------------------------------------------------- 1 | # Designs 2 | 3 | This file contains links to a series of designs that were written during the course of RukPak development. Most design 4 | documents are written using HackMD, and then shared here. These design docs provide further context on the features and 5 | behavior of RukPak. 6 | 7 | The docs themselves may be out slightly out of date, but are still useful in understanding the project. For example, the 8 | BundleDeployment Kind was previously named BundleInstance. 9 | 10 | ## Links 11 | 12 | * [Support Custom Bundle Content Probes](https://hackmd.io/lUnrQHaKTsCLZ6j3Q52hSQ) 13 | * [Supporting Local Bundle Content](https://hackmd.io/pChFoobdQNOW911zRK6L6Q) 14 | * [Plain Bundle Metadata Proposal](https://hackmd.io/Tk6uXGZ2SKqreteOUAF-NA) 15 | * [RukPak client: rukpakctl](https://hackmd.io/AJ8ygfzbTrmXv_CIiUCPtQ) 16 | * [Sourcing Bundle Content From Git Repositories](https://hackmd.io/TUJIB1tXRSqZzqc_tk76lg) 17 | * [Embedded Bundle Support](https://hackmd.io/HNWfNUbqTUGAW5VRMcc40w) 18 | * [Introducing The Pivoter](https://hackmd.io/aEgk-7wfTBKhjHzYryRBGQ) -------------------------------------------------------------------------------- /testdata/bundles/registry/invalid/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: cincinnati-operator 7 | operators.operatorframework.io.bundle.channels.v1: v1 8 | operators.operatorframework.io.bundle.channel.default.v1: v1 9 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.9.0+git 10 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 11 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 12 | 13 | # The following annotation will make your bundle be published on 14 | # 4.8 and carried on to the upper versions 4.9, 4.10, etc. 15 | com.redhat.openshift.versions: "v4.8" 16 | 17 | # Annotations for testing. 18 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 19 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 20 | -------------------------------------------------------------------------------- /pkg/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "io/fs" 6 | "net/http" 7 | 8 | "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | type Storage interface { 12 | Loader 13 | Storer 14 | } 15 | 16 | type Loader interface { 17 | Load(ctx context.Context, owner client.Object) (fs.FS, error) 18 | } 19 | 20 | type Storer interface { 21 | Store(ctx context.Context, owner client.Object, bundle fs.FS) error 22 | Delete(ctx context.Context, owner client.Object) error 23 | 24 | http.Handler 25 | URLFor(ctx context.Context, owner client.Object) (string, error) 26 | } 27 | 28 | type fallbackLoaderStorage struct { 29 | Storage 30 | fallbackLoader Loader 31 | } 32 | 33 | func WithFallbackLoader(s Storage, fallback Loader) Storage { 34 | return &fallbackLoaderStorage{ 35 | Storage: s, 36 | fallbackLoader: fallback, 37 | } 38 | } 39 | 40 | func (s *fallbackLoaderStorage) Load(ctx context.Context, owner client.Object) (fs.FS, error) { 41 | fsys, err := s.Storage.Load(ctx, owner) 42 | if err != nil { 43 | return s.fallbackLoader.Load(ctx, owner) 44 | } 45 | return fsys, nil 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/sanity.yaml: -------------------------------------------------------------------------------- 1 | name: sanity 2 | 3 | on: 4 | workflow_dispatch: 5 | merge_group: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | verify: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: "go.mod" 19 | - name: Run verification checks 20 | run: make verify 21 | lint: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - uses: actions/setup-go@v5 28 | with: 29 | go-version-file: "go.mod" 30 | 31 | - name: Run golangci linting checks 32 | run: make lint GOLANGCI_LINT_ARGS="--out-format github-actions" 33 | markdown: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | 39 | - uses: DavidAnson/markdownlint-cli2-action@v16 40 | with: 41 | config: .markdownlint.yaml 42 | globs: | 43 | **/*.md 44 | !.bingo 45 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/webhook.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: ValidatingWebhookConfiguration 4 | metadata: 5 | name: validating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /validate-core-rukpak-io-v1alpha2-bundledeployment 14 | failurePolicy: Fail 15 | name: vbundles.core.rukpak.io 16 | rules: 17 | - apiGroups: 18 | - core.rukpak.io 19 | apiVersions: 20 | - v1alpha2 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - bundledeployments 26 | sideEffects: None 27 | - admissionReviewVersions: 28 | - v1 29 | clientConfig: 30 | service: 31 | name: webhook-service 32 | namespace: system 33 | path: /validate-core-v1-configmap 34 | failurePolicy: Fail 35 | name: vconfigmaps.core.rukpak.io 36 | rules: 37 | - apiGroups: 38 | - "" 39 | apiVersions: 40 | - v1 41 | operations: 42 | - CREATE 43 | - DELETE 44 | resources: 45 | - configmaps 46 | sideEffects: None 47 | -------------------------------------------------------------------------------- /test/tools/remotedebug/patch/remote_debug_command.yaml: -------------------------------------------------------------------------------- 1 | # Move the original command to the beginning of the args array 2 | - op: copy 3 | from: /spec/template/spec/containers/1/command/0 4 | path: /spec/template/spec/containers/1/args/0 5 | # Set the command to /dlv 6 | - op: add 7 | path: /spec/template/spec/containers/1/command 8 | value: ["/go/bin/dlv"] 9 | # Prepend the args array with the following dlv args 10 | - op: add 11 | path: /spec/template/spec/containers/1/args/0 12 | value: "--listen=:40000" 13 | - op: add 14 | path: /spec/template/spec/containers/1/args/1 15 | value: "--continue" 16 | - op: add 17 | path: /spec/template/spec/containers/1/args/2 18 | value: "--headless=true" 19 | - op: add 20 | path: /spec/template/spec/containers/1/args/3 21 | value: "--api-version=2" 22 | - op: add 23 | path: /spec/template/spec/containers/1/args/4 24 | value: "--accept-multiclient" 25 | - op: add 26 | path: /spec/template/spec/containers/1/args/5 27 | value: "--log" 28 | - op: add 29 | path: /spec/template/spec/containers/1/args/6 30 | value: "exec" 31 | - op: add 32 | path: /spec/template/spec/containers/1/args/7 33 | value: "--" 34 | -------------------------------------------------------------------------------- /.github/workflows/release-e2e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release-e2e 3 | 4 | on: 5 | release: 6 | types: 7 | - published 8 | - edited 9 | 10 | jobs: 11 | release-e2e-kind: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version-file: "go.mod" 18 | 19 | - name: Download kubectl 20 | run: | 21 | curl -LO "https://dl.k8s.io/release/$(go list -m k8s.io/kubectl | cut -d" " -f2 | sed 's/^v0/v1/')/bin/linux/amd64/kubectl" 22 | sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl 23 | 24 | - name: Start a kind cluster 25 | run: make kind-cluster 26 | 27 | - name: Install the cert-manager dependency 28 | run: make cert-mgr 29 | 30 | - name: Install the rukpak release manifests 31 | run: | 32 | kubectl apply -f https://github.com/operator-framework/rukpak/releases/download/${{ github.event.release.tag_name }}/rukpak.yaml 33 | make wait 34 | 35 | - name: Load testdata bundle container images into kind 36 | run: make kind-load-bundles KIND=hack/tools/bin/kind 37 | 38 | - name: Run e2e tests 39 | run: make test-e2e KIND=hack/tools/bin/kind 40 | -------------------------------------------------------------------------------- /.github/workflows/tilt.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths: 4 | - '.bingo/**' 5 | - '.github/workflows/tilt.yaml' 6 | - 'api/**' 7 | - 'cmd/**' 8 | - 'config/**' 9 | - 'internal/**' 10 | - 'pkg/**' 11 | - 'Tiltfile' 12 | merge_group: 13 | 14 | jobs: 15 | tilt: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | repository: operator-framework/tilt-support 21 | path: tilt-support 22 | - uses: actions/checkout@v4 23 | with: 24 | path: rukpak 25 | - name: Install Tilt 26 | run: | 27 | TILT_VERSION="0.33.3" 28 | curl -fsSL https://github.com/tilt-dev/tilt/releases/download/v$TILT_VERSION/tilt.$TILT_VERSION.linux.x86_64.tar.gz | \ 29 | tar -xzv -C /usr/local/bin tilt 30 | - name: Install ctlptl 31 | run: | 32 | CTLPTL_VERSION="0.8.20" 33 | curl -fsSL https://github.com/tilt-dev/ctlptl/releases/download/v$CTLPTL_VERSION/ctlptl.$CTLPTL_VERSION.linux.x86_64.tar.gz | \ 34 | tar -xzv -C /usr/local/bin ctlptl 35 | - name: Set up kind 36 | run: ctlptl create cluster kind --registry=ctlptl-registry 37 | - name: Test Tilt 38 | run: | 39 | cd rukpak 40 | tilt ci 41 | -------------------------------------------------------------------------------- /manifests/base/core/resources/cluster_role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: core-admin 6 | rules: 7 | - nonResourceURLs: 8 | - /bundles/* 9 | verbs: 10 | - get 11 | - apiGroups: 12 | - '*' 13 | resources: 14 | - '*' 15 | verbs: 16 | - '*' 17 | - apiGroups: 18 | - authentication.k8s.io 19 | resources: 20 | - tokenreviews 21 | verbs: 22 | - create 23 | - apiGroups: 24 | - authorization.k8s.io 25 | resources: 26 | - subjectaccessreviews 27 | verbs: 28 | - create 29 | - apiGroups: 30 | - "" 31 | resources: 32 | - configmaps 33 | verbs: 34 | - list 35 | - watch 36 | - apiGroups: 37 | - "" 38 | resources: 39 | - pods 40 | verbs: 41 | - create 42 | - delete 43 | - list 44 | - watch 45 | - apiGroups: 46 | - "" 47 | resources: 48 | - pods/log 49 | verbs: 50 | - get 51 | - apiGroups: 52 | - core.rukpak.io 53 | resources: 54 | - bundledeployments 55 | verbs: 56 | - list 57 | - watch 58 | - apiGroups: 59 | - core.rukpak.io 60 | resources: 61 | - bundledeployments/finalizers 62 | verbs: 63 | - update 64 | - apiGroups: 65 | - core.rukpak.io 66 | resources: 67 | - bundledeployments/status 68 | verbs: 69 | - patch 70 | - update 71 | -------------------------------------------------------------------------------- /manifests/base/provisioners/helm/resources/cluster_role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: helm-provisioner-admin 6 | rules: 7 | - nonResourceURLs: 8 | - /bundles/* 9 | verbs: 10 | - get 11 | - apiGroups: 12 | - '*' 13 | resources: 14 | - '*' 15 | verbs: 16 | - '*' 17 | - apiGroups: 18 | - authentication.k8s.io 19 | resources: 20 | - tokenreviews 21 | verbs: 22 | - create 23 | - apiGroups: 24 | - authorization.k8s.io 25 | resources: 26 | - subjectaccessreviews 27 | verbs: 28 | - create 29 | - apiGroups: 30 | - "" 31 | resources: 32 | - configmaps 33 | verbs: 34 | - list 35 | - watch 36 | - apiGroups: 37 | - "" 38 | resources: 39 | - pods 40 | verbs: 41 | - create 42 | - delete 43 | - list 44 | - watch 45 | - apiGroups: 46 | - "" 47 | resources: 48 | - pods/log 49 | verbs: 50 | - get 51 | - apiGroups: 52 | - core.rukpak.io 53 | resources: 54 | - bundledeployments 55 | verbs: 56 | - list 57 | - watch 58 | - apiGroups: 59 | - core.rukpak.io 60 | resources: 61 | - bundledeployments/finalizers 62 | verbs: 63 | - update 64 | - apiGroups: 65 | - core.rukpak.io 66 | resources: 67 | - bundledeployments/status 68 | verbs: 69 | - patch 70 | - update 71 | -------------------------------------------------------------------------------- /api/v1alpha2/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha2 contains API Schema definitions for the core v1alpha2 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=core.rukpak.io 20 | package v1alpha2 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "core.rukpak.io", Version: "v1alpha2"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /docs/bundles/helm.md: -------------------------------------------------------------------------------- 1 | # Helm Bundle Spec 2 | 3 | ## Overview 4 | 5 | This document is meant to define the `helm` bundle format as a reference for those publishing `helm` bundles 6 | for use with RukPak. For more information on the concept of a bundle, click [here](https://github.com/operator-framework/rukpak#bundle). 7 | 8 | A helm bundle is a [helm chart](https://helm.sh/docs/chart_template_guide/getting_started/#charts). 9 | The helm chart can be created by [helm create](https://helm.sh/docs/helm/helm_create/). 10 | 11 | The current helm bundle format name is the `helm+v3` that 12 | combines the type of bundle (helm) with the current helm format version (v3). 13 | 14 | ## Example 15 | 16 | For example, below is some examples of a file tree in a "helm+v3" bundle. 17 | 18 | ```tree 19 | 20 | └── hello-world 21 | ├── .helmignore 22 | ├── Chart.yaml 23 | ├── README.md 24 | ├── templates 25 | │ ├── NOTES.txt 26 | │ ├── _helpers.tpl 27 | │ ├── deployment.yaml 28 | │ ├── service.yaml 29 | │ └── serviceaccount.yaml 30 | └── values.yaml 31 | ``` 32 | 33 | and 34 | 35 | ```tree 36 | 37 | ├── .helmignore 38 | ├── Chart.yaml 39 | ├── README.md 40 | ├── templates 41 | │ ├── NOTES.txt 42 | │ ├── _helpers.tpl 43 | │ ├── deployment.yaml 44 | │ ├── service.yaml 45 | │ └── serviceaccount.yaml 46 | └── values.yaml 47 | ``` 48 | -------------------------------------------------------------------------------- /internal/operator-registry/registry.go: -------------------------------------------------------------------------------- 1 | // Package registry contains the manually vendored type definitions from 2 | // the operator-framework/operator-registry repository. 3 | package registry 4 | 5 | // AnnotationsFile holds annotation information about a bundle 6 | type AnnotationsFile struct { 7 | // annotations is a list of annotations for a given bundle 8 | Annotations Annotations `json:"annotations" yaml:"annotations"` 9 | } 10 | 11 | // Annotations is a list of annotations for a given bundle 12 | type Annotations struct { 13 | // PackageName is the name of the overall package, ala `etcd`. 14 | PackageName string `json:"operators.operatorframework.io.bundle.package.v1" yaml:"operators.operatorframework.io.bundle.package.v1"` 15 | 16 | // Channels are a comma separated list of the declared channels for the bundle, ala `stable` or `alpha`. 17 | Channels string `json:"operators.operatorframework.io.bundle.channels.v1" yaml:"operators.operatorframework.io.bundle.channels.v1"` 18 | 19 | // DefaultChannelName is, if specified, the name of the default channel for the package. The 20 | // default channel will be installed if no other channel is explicitly given. If the package 21 | // has a single channel, then that channel is implicitly the default. 22 | DefaultChannelName string `json:"operators.operatorframework.io.bundle.channel.default.v1" yaml:"operators.operatorframework.io.bundle.channel.default.v1"` 23 | } 24 | -------------------------------------------------------------------------------- /manifests/base/apis/webhooks/resources/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: system 5 | name: webhooks 6 | labels: 7 | app: webhooks 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: webhooks 13 | template: 14 | metadata: 15 | labels: 16 | app: webhooks 17 | spec: 18 | securityContext: 19 | runAsNonRoot: true 20 | seccompProfile: 21 | type: RuntimeDefault 22 | serviceAccountName: webhooks-admin 23 | containers: 24 | - name: webhooks 25 | securityContext: 26 | allowPrivilegeEscalation: false 27 | capabilities: 28 | drop: [ "ALL" ] 29 | command: ["/webhooks"] 30 | image: quay.io/operator-framework/rukpak:devel 31 | imagePullPolicy: IfNotPresent 32 | ports: 33 | - containerPort: 8080 34 | - containerPort: 9443 35 | name: webhook-server 36 | protocol: TCP 37 | volumeMounts: 38 | - mountPath: /tmp/k8s-webhook-server/serving-certs 39 | name: cert 40 | readOnly: true 41 | resources: 42 | requests: 43 | cpu: 1m 44 | memory: 15Mi 45 | terminationMessagePolicy: FallbackToLogsOnError 46 | volumes: 47 | - name: cert 48 | secret: 49 | defaultMode: 420 50 | secretName: WEBHOOK_SECRET_NAME 51 | -------------------------------------------------------------------------------- /test/tools/git/setup_git.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export GIT_NAME="local-git" 4 | export GIT_NAMESPACE=rukpak-e2e 5 | export DNS_NAME=$GIT_NAME.$GIT_NAMESPACE.svc.cluster.local 6 | export KIND_CLUSTER_NAME=$1 7 | 8 | kubectl create ns $GIT_NAMESPACE || true 9 | 10 | mkdir -p test/tools/git/tmp 11 | kubectl delete secret ssh-secret -n $GIT_NAMESPACE 12 | kubectl delete secret gitsecret -n rukpak-system 13 | ssh-keygen -t rsa -b 4096 -C "akuroda@us.ibm.com" -P "" -f test/tools/git/tmp/id_rsa 14 | kubectl create secret generic ssh-secret --from-file=test/tools/git/tmp/id_rsa --from-file=test/tools/git/tmp/id_rsa.pub -n $GIT_NAMESPACE 15 | kubectl create secret generic gitsecret --type "kubernetes.io/ssh-auth" --from-file=ssh-privatekey=test/tools/git/tmp/id_rsa --from-file=ssh-knownhosts=test/tools/git/ssh_knownhosts.txt -n rukpak-system 16 | rm -rf test/tools/git/tmp 17 | 18 | # 19 | git clone https://github.com/operator-framework/combo.git test/tools/git/testdata/combo 20 | 21 | # create docker image 22 | docker build -t localhost/git:latest test/tools/git/ 23 | kind load docker-image localhost/git:latest --name $KIND_CLUSTER_NAME 24 | 25 | # create image registry service 26 | kubectl apply -f test/tools/git/service.yaml -n $GIT_NAMESPACE 27 | # create image registry pod 28 | kubectl apply -f test/tools/git/git.yaml -n $GIT_NAMESPACE 29 | 30 | # clean up 31 | rm -rf test/tools/git/tmp/certs 32 | kubectl wait --for=condition=ContainersReady --namespace=rukpak-e2e pod/local-git-pod --timeout=60s 33 | rm -rf test/tools/git/testdata/combo 34 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: stale 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | # The start of everyday and at hour 12 6 | - cron: '00 00 * * *' 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v9.0.0 13 | id: stale 14 | with: 15 | # Overall configuration 16 | operations-per-run: 100 17 | 18 | # PR configuration 19 | days-before-pr-stale: 30 20 | stale-pr-message: 'This PR has become stale because it has been open for 30 days with no activity. Please update this PR or remove the `lifecycle/stale` label before it is automatically closed in 30 days. Adding the `lifecycle/frozen` label will cause this PR to ignore lifecycle events.' 21 | days-before-pr-close: 30 22 | close-pr-message: 'This PR has been closed as no updates were detected after 30 days of being stale. Please feel free to reopen this PR if necessary.' 23 | stale-pr-label: lifecycle/stale 24 | exempt-pr-labels: lifecycle/frozen 25 | close-pr-label: lifecycle/rotten 26 | 27 | # Issue configuration 28 | days-before-issue-stale: 60 29 | stale-issue-message: 'This issue has become stale because it has been open 60 days with no activity. The maintainers of this repo will remove this label during issue triage or it will be removed automatically after an update. Adding the `lifecycle/frozen` label will cause this issue to ignore lifecycle events.' 30 | days-before-issue-close: -1 31 | stale-issue-label: lifecycle/stale 32 | exempt-issue-labels: lifecycle/frozen 33 | -------------------------------------------------------------------------------- /pkg/util/hash.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/json" 6 | "fmt" 7 | "math/big" 8 | ) 9 | 10 | // DeepHashObject writes specified object to hash using the spew library 11 | // which follows pointers and prints actual values of the nested objects 12 | // ensuring the hash does not change when a pointer changes. 13 | func DeepHashObject(obj interface{}) (string, error) { 14 | // While the most accurate encoding we could do for Kubernetes objects (runtime.Object) 15 | // would use the API machinery serializers, those operate over entire objects - and 16 | // we often need to operate on snippets. Checking with the experts and the implementation, 17 | // we can see that the serializers are a thin wrapper over json.Marshal for encoding: 18 | // https://github.com/kubernetes/kubernetes/blob/8509ab82b96caa2365552efa08c8ba8baf11c5ec/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go#L216-L247 19 | // Therefore, we can be confident that using json.Marshal() here will: 20 | // 1. be stable & idempotent - the library sorts keys, etc. 21 | // 2. be germane to our needs - only fields that serialize and are sent to the server 22 | // will be encoded 23 | 24 | hasher := sha256.New224() 25 | hasher.Reset() 26 | encoder := json.NewEncoder(hasher) 27 | if err := encoder.Encode(obj); err != nil { 28 | return "", fmt.Errorf("couldn't encode object: %w", err) 29 | } 30 | 31 | // base62(sha224(bytes)) is a useful hash and encoding for adding the contents of this 32 | // to a Kubernetes identifier or other field which has length and character set requirements 33 | var hash []byte 34 | hash = hasher.Sum(hash) 35 | 36 | var i big.Int 37 | i.SetBytes(hash[:]) 38 | return i.Text(36), nil 39 | } 40 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/empty/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/empty" 26 | bundle_name="plain-empty" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | 32 | kubectl apply -f - << EOF 33 | apiVersion: batch/v1 34 | kind: Job 35 | metadata: 36 | name: "kaniko-${bundle_name}" 37 | namespace: "${namespace}" 38 | spec: 39 | template: 40 | spec: 41 | containers: 42 | - name: kaniko 43 | image: gcr.io/kaniko-project/executor:latest 44 | args: ["--dockerfile=/workspace/Dockerfile", 45 | "--context=/workspace/", 46 | "--destination=${tag}", 47 | "--skip-tls-verify"] 48 | volumeMounts: 49 | - name: dockerfile 50 | mountPath: /workspace/ 51 | restartPolicy: Never 52 | volumes: 53 | - name: dockerfile 54 | configMap: 55 | name: rukpak-e2e-${bundle_name}.root 56 | EOF 57 | 58 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 59 | -------------------------------------------------------------------------------- /pkg/util/tar.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | "io/fs" 9 | "os" 10 | ) 11 | 12 | // FSToTarGZ writes the filesystem represented by fsys to w as a gzipped tar archive. 13 | // This function unsets user and group information in the tar archive so that readers 14 | // of archives produced by this function do not need to account for differences in 15 | // permissions between source and destination filesystems. 16 | func FSToTarGZ(w io.Writer, fsys fs.FS) error { 17 | gzw := gzip.NewWriter(w) 18 | tw := tar.NewWriter(gzw) 19 | if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 20 | if err != nil { 21 | return err 22 | } 23 | 24 | if d.Type()&os.ModeSymlink != 0 { 25 | return nil 26 | } 27 | info, err := d.Info() 28 | if err != nil { 29 | return fmt.Errorf("get file info for %q: %v", path, err) 30 | } 31 | 32 | h, err := tar.FileInfoHeader(info, "") 33 | if err != nil { 34 | return fmt.Errorf("build tar file info header for %q: %v", path, err) 35 | } 36 | h.Uid = 0 37 | h.Gid = 0 38 | h.Uname = "" 39 | h.Gname = "" 40 | h.Name = path 41 | 42 | if err := tw.WriteHeader(h); err != nil { 43 | return fmt.Errorf("write tar header for %q: %v", path, err) 44 | } 45 | if d.IsDir() { 46 | return nil 47 | } 48 | f, err := fsys.Open(path) 49 | if err != nil { 50 | return fmt.Errorf("open file %q: %v", path, err) 51 | } 52 | if _, err := io.Copy(tw, f); err != nil { 53 | return fmt.Errorf("write tar data for %q: %v", path, err) 54 | } 55 | return nil 56 | }); err != nil { 57 | return fmt.Errorf("generate tar.gz from FS: %v", err) 58 | } 59 | if err := tw.Close(); err != nil { 60 | return err 61 | } 62 | return gzw.Close() 63 | } 64 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/no-manifests/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/no-manifests" 26 | bundle_name="plain-no-manifests" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | 32 | kubectl apply -f - << EOF 33 | apiVersion: batch/v1 34 | kind: Job 35 | metadata: 36 | name: "kaniko-${bundle_name}" 37 | namespace: "${namespace}" 38 | spec: 39 | template: 40 | spec: 41 | containers: 42 | - name: kaniko 43 | image: gcr.io/kaniko-project/executor:latest 44 | args: ["--dockerfile=/workspace/Dockerfile", 45 | "--context=/workspace/", 46 | "--destination=${tag}", 47 | "--skip-tls-verify"] 48 | volumeMounts: 49 | - name: dockerfile 50 | mountPath: /workspace/ 51 | - name: manifests 52 | mountPath: /workspace/manifests/ 53 | restartPolicy: Never 54 | volumes: 55 | - name: dockerfile 56 | configMap: 57 | name: rukpak-e2e-${bundle_name}.root 58 | - name: manifests 59 | emptyDir: {} 60 | EOF 61 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 62 | -------------------------------------------------------------------------------- /test/e2e/e2e_suite_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | 10 | appsv1 "k8s.io/api/apps/v1" 11 | batchv1 "k8s.io/api/batch/v1" 12 | corev1 "k8s.io/api/core/v1" 13 | rbacv1 "k8s.io/api/rbac/v1" 14 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 15 | "k8s.io/apimachinery/pkg/runtime" 16 | "k8s.io/client-go/kubernetes" 17 | "k8s.io/client-go/rest" 18 | ctrl "sigs.k8s.io/controller-runtime" 19 | "sigs.k8s.io/controller-runtime/pkg/client" 20 | "sigs.k8s.io/controller-runtime/pkg/log" 21 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 22 | 23 | operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 24 | 25 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 26 | ) 27 | 28 | var ( 29 | cfg *rest.Config 30 | c client.Client 31 | kubeClient kubernetes.Interface 32 | ) 33 | 34 | func TestE2E(t *testing.T) { 35 | log.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 36 | 37 | RegisterFailHandler(Fail) 38 | SetDefaultEventuallyTimeout(1 * time.Minute) 39 | SetDefaultEventuallyPollingInterval(1 * time.Second) 40 | RunSpecs(t, "E2E Suite") 41 | } 42 | 43 | var _ = BeforeSuite(func() { 44 | cfg = ctrl.GetConfigOrDie() 45 | 46 | scheme := runtime.NewScheme() 47 | Expect(rukpakv1alpha2.AddToScheme(scheme)).To(Succeed()) 48 | Expect(rbacv1.AddToScheme(scheme)).To(Succeed()) 49 | Expect(batchv1.AddToScheme(scheme)).To(Succeed()) 50 | Expect(operatorsv1.AddToScheme(scheme)).To(Succeed()) 51 | Expect(corev1.AddToScheme(scheme)).To(Succeed()) 52 | Expect(appsv1.AddToScheme(scheme)).To(Succeed()) 53 | Expect(apiextensionsv1.AddToScheme(scheme)).To(Succeed()) 54 | 55 | var err error 56 | c, err = client.New(cfg, client.Options{Scheme: scheme}) 57 | Expect(err).ToNot(HaveOccurred()) 58 | 59 | kubeClient, err = kubernetes.NewForConfig(cfg) 60 | Expect(err).ToNot(HaveOccurred()) 61 | }) 62 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | ######## 2 | # NOTE 3 | # 4 | # This file is duplicated in the following repos: 5 | # - operator-framework/kubectl-operator 6 | # - operator-framework/catalogd 7 | # - operator-framework/operator-controller 8 | # - operator-framework/rukpak 9 | # 10 | # If you are making a change, please make it in ALL 11 | # of the above repositories! 12 | # 13 | # TODO: Find a way to have a shared golangci config. 14 | ######## 15 | 16 | run: 17 | # Default timeout is 1m, up to give more room 18 | timeout: 4m 19 | 20 | linters: 21 | enable: 22 | - asciicheck 23 | - bodyclose 24 | - errorlint 25 | - gci 26 | - gofmt 27 | - gosec 28 | - importas 29 | - misspell 30 | - nestif 31 | - nonamedreturns 32 | - prealloc 33 | - stylecheck 34 | - tparallel 35 | - unconvert 36 | - unparam 37 | - unused 38 | - whitespace 39 | 40 | linters-settings: 41 | gci: 42 | sections: 43 | - standard 44 | - dot 45 | - default 46 | - prefix(github.com/operator-framework) 47 | 48 | # TODO: change this to `localmodule` when golangci-lint 49 | # is updated to 1.58+ 50 | - prefix(github.com/operator-framework/rukpak) 51 | custom-order: true 52 | 53 | errorlint: 54 | errorf: false 55 | 56 | importas: 57 | alias: 58 | - pkg: k8s.io/apimachinery/pkg/apis/meta/v1 59 | alias: metav1 60 | - pkg: k8s.io/apimachinery/pkg/api/errors 61 | alias: apierrors 62 | - pkg: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 63 | alias: apiextensionsv1 64 | - pkg: k8s.io/apimachinery/pkg/util/runtime 65 | alias: utilruntime 66 | - pkg: "^k8s\\.io/api/([^/]+)/(v[^/]+)$" 67 | alias: $1$2 68 | - pkg: sigs.k8s.io/controller-runtime 69 | alias: ctrl 70 | - pkg: github.com/operator-framework/rukpak/api/v1alpha2 71 | alias: rukpakv1alpha2 72 | - pkg: github.com/blang/semver/v4 73 | alias: bsemver 74 | 75 | output: 76 | formats: 77 | - format: tab 78 | -------------------------------------------------------------------------------- /docs/sources/local.md: -------------------------------------------------------------------------------- 1 | # Local Bundles 2 | 3 | ## Summary 4 | 5 | A Bundle can reference content from an in-cluster resource instead of a remote container image or a git repository by 6 | using the`local` source type in the Bundle manifest. This enables one to easily source content locally without external 7 | repositories/registries. Sourcing from a local configmap is currently supported, with support for additional content 8 | sources like a PV is on the roadmap. 9 | 10 | For example, for a plain+v0 bundle, the local configmap backing the bundle, or "bundle configmap", must have a certain 11 | data structure in order to produce a valid Bundle that works with the plain provisioner. It should have a map of 12 | manifest file contents with its filename as the key in the `data` of the configmap. The name and namespace of the 13 | configmap must be specified in `spec.source.local.configmap.name` and `spec.source.local.configmap.namespace` 14 | respectively. 15 | 16 | The configmap can be created with the command: 17 | 18 | ```bash 19 | kubectl create configmap --from-file= 20 | ``` 21 | 22 | > Note: Once a configmap is referenced by a Bundle, the configmap will have an owner reference placed on it that points 23 | > to the Bundle. As a result, when the Bundle is removed, the configmap will also be removed. This ensures that the cluster is 24 | > in the same state before and after installing content. 25 | 26 | ### Example 27 | 28 | 1. Create the configmap 29 | 30 | ``` bash 31 | kubectl create configmap my-configmap --from-file=../testdata/bundles/plain-v0/valid/manifests -n rukpak-system 32 | ``` 33 | 34 | 2. Create a bundle 35 | 36 | ```bash 37 | kubectl apply -f -<> $GITHUB_ENV 43 | echo GORELEASER_ARGS="--clean" >> $GITHUB_ENV 44 | echo ENABLE_RELEASE_PIPELINE=true >> $GITHUB_ENV 45 | elif [[ $GITHUB_REF == refs/heads/* ]]; then 46 | # Branch build. 47 | echo IMAGE_TAG="$(echo "${GITHUB_REF#refs/heads/}" | sed -r 's|/+|-|g')" >> $GITHUB_ENV 48 | echo GORELEASER_ARGS="--clean --skip-validate" >> $GITHUB_ENV 49 | elif [[ $GITHUB_REF == refs/pull/* ]]; then 50 | # PR build. 51 | echo IMAGE_TAG="pr-$(echo "${GITHUB_REF}" | sed -E 's|refs/pull/([^/]+)/?.*|\1|')" >> $GITHUB_ENV 52 | else 53 | echo IMAGE_TAG="$(git describe --tags --always)" >> $GITHUB_ENV 54 | fi 55 | 56 | - name: Generate the rukpak release manifests 57 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} 58 | run: | 59 | echo VERSION="${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 60 | make quickstart 61 | 62 | - name: Run goreleaser 63 | run: make release 64 | env: 65 | GITHUB_TOKEN: ${{ github.token }} 66 | -------------------------------------------------------------------------------- /test/testutil/structs.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "fmt" 5 | 6 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | ) 10 | 11 | // NewTestingCRD takes a name, group and versions to be set for a new CRD. If name is set to "" 12 | // then it will run testutil.GenName() on it with the DefaultCrdName. 13 | func NewTestingCRD(name, group string, versions []apiextensionsv1.CustomResourceDefinitionVersion) *apiextensionsv1.CustomResourceDefinition { 14 | if name == "" { 15 | name = GenName(DefaultCrdName) 16 | } 17 | return &apiextensionsv1.CustomResourceDefinition{ 18 | ObjectMeta: metav1.ObjectMeta{ 19 | Name: fmt.Sprintf("%v.%v", name, group), 20 | }, 21 | Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 22 | Scope: apiextensionsv1.ClusterScoped, 23 | Group: group, 24 | Versions: versions, 25 | Names: apiextensionsv1.CustomResourceDefinitionNames{ 26 | Plural: name, 27 | Singular: name, 28 | Kind: name, 29 | ListKind: name + "List", 30 | }, 31 | }, 32 | } 33 | } 34 | 35 | // NewTestingCR takes a name, group, version and king in order to create a 36 | // new CR of a resource correctly. 37 | func NewTestingCR(name, group, version, kind string) *unstructured.Unstructured { 38 | newTestingCr := &unstructured.Unstructured{} 39 | newTestingCr.SetKind(kind) 40 | newTestingCr.SetAPIVersion(group + "/" + version) 41 | newTestingCr.SetGenerateName(name) 42 | return newTestingCr 43 | } 44 | 45 | // CrdReady takes a CRD's status and determines if it is ready to accept new CRs 46 | func CrdReady(status *apiextensionsv1.CustomResourceDefinitionStatus) bool { 47 | if status == nil { 48 | return false 49 | } 50 | established, namesAccepted := false, false 51 | for _, cdt := range status.Conditions { 52 | switch cdt.Type { 53 | case apiextensionsv1.Established: 54 | if cdt.Status == apiextensionsv1.ConditionTrue { 55 | established = true 56 | } 57 | case apiextensionsv1.NamesAccepted: 58 | if cdt.Status == apiextensionsv1.ConditionTrue { 59 | namesAccepted = true 60 | } 61 | } 62 | } 63 | return established && namesAccepted 64 | } 65 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/valid/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/valid" 26 | bundle_name="plain-valid" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | 33 | kubectl apply -f - << EOF 34 | apiVersion: batch/v1 35 | kind: Job 36 | metadata: 37 | name: "kaniko-${bundle_name}" 38 | namespace: "${namespace}" 39 | spec: 40 | template: 41 | spec: 42 | initContainers: 43 | - name: copy-manifests 44 | image: busybox 45 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 46 | volumeMounts: 47 | - name: manifests 48 | mountPath: /manifests 49 | - name: manifests-data 50 | mountPath: /manifests-data 51 | containers: 52 | - name: kaniko 53 | image: gcr.io/kaniko-project/executor:latest 54 | args: ["--dockerfile=/workspace/Dockerfile", 55 | "--context=/workspace/", 56 | "--destination=${tag}", 57 | "--skip-tls-verify"] 58 | volumeMounts: 59 | - name: dockerfile 60 | mountPath: /workspace/ 61 | - name: manifests 62 | mountPath: /workspace/manifests/ 63 | restartPolicy: Never 64 | volumes: 65 | - name: dockerfile 66 | configMap: 67 | name: rukpak-e2e-${bundle_name}.root 68 | - name: manifests 69 | emptyDir: {} 70 | - name: manifests-data 71 | configMap: 72 | name: rukpak-e2e-${bundle_name}.manifests 73 | EOF 74 | 75 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 76 | -------------------------------------------------------------------------------- /pkg/storage/http.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "compress/gzip" 5 | "context" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "fmt" 9 | "io/fs" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/nlepage/go-tarfs" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | 16 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 17 | ) 18 | 19 | type HTTP struct { 20 | client http.Client 21 | requestOpts []func(*http.Request) 22 | } 23 | 24 | type HTTPOption func(*HTTP) 25 | 26 | func WithInsecureSkipVerify(v bool) HTTPOption { 27 | return func(s *HTTP) { 28 | tr := s.client.Transport.(*http.Transport) 29 | if tr.TLSClientConfig == nil { 30 | tr.TLSClientConfig = &tls.Config{ 31 | MinVersion: tls.VersionTLS12, 32 | } 33 | } 34 | tr.TLSClientConfig.InsecureSkipVerify = v 35 | } 36 | } 37 | 38 | func WithRootCAs(rootCAs *x509.CertPool) HTTPOption { 39 | return func(s *HTTP) { 40 | tr := s.client.Transport.(*http.Transport) 41 | if tr.TLSClientConfig == nil { 42 | tr.TLSClientConfig = &tls.Config{ 43 | MinVersion: tls.VersionTLS12, 44 | } 45 | } 46 | tr.TLSClientConfig.RootCAs = rootCAs 47 | } 48 | } 49 | 50 | func WithBearerToken(token string) HTTPOption { 51 | return func(s *HTTP) { 52 | s.requestOpts = append(s.requestOpts, func(request *http.Request) { 53 | request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 54 | }) 55 | } 56 | } 57 | 58 | type HTTPRequestOption func(*http.Request) 59 | 60 | func NewHTTP(opts ...HTTPOption) *HTTP { 61 | s := &HTTP{client: http.Client{ 62 | Timeout: time.Minute, 63 | Transport: http.DefaultTransport.(*http.Transport).Clone(), 64 | }} 65 | for _, f := range opts { 66 | f(s) 67 | } 68 | return s 69 | } 70 | 71 | func (s *HTTP) Load(ctx context.Context, owner client.Object) (fs.FS, error) { 72 | bundledeployment := owner.(*rukpakv1alpha2.BundleDeployment) 73 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, bundledeployment.Status.ContentURL, nil) 74 | if err != nil { 75 | return nil, err 76 | } 77 | for _, f := range s.requestOpts { 78 | f(req) 79 | } 80 | resp, err := s.client.Do(req) 81 | if err != nil { 82 | return nil, err 83 | } 84 | defer resp.Body.Close() 85 | if resp.StatusCode != http.StatusOK { 86 | return nil, fmt.Errorf("unexpected response status %q", resp.Status) 87 | } 88 | tarReader, err := gzip.NewReader(resp.Body) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return tarfs.New(tarReader) 93 | } 94 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/dependent/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/dependent" 26 | bundle_name="plain-dependent" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | 33 | kubectl apply -f - << EOF 34 | apiVersion: batch/v1 35 | kind: Job 36 | metadata: 37 | name: "kaniko-${bundle_name}" 38 | namespace: "${namespace}" 39 | spec: 40 | template: 41 | spec: 42 | initContainers: 43 | - name: copy-manifests 44 | image: busybox 45 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 46 | volumeMounts: 47 | - name: manifests 48 | mountPath: /manifests 49 | - name: manifests-data 50 | mountPath: /manifests-data 51 | containers: 52 | - name: kaniko 53 | image: gcr.io/kaniko-project/executor:latest 54 | args: ["--dockerfile=/workspace/Dockerfile", 55 | "--context=/workspace/", 56 | "--destination=${tag}", 57 | "--skip-tls-verify"] 58 | volumeMounts: 59 | - name: dockerfile 60 | mountPath: /workspace/ 61 | - name: manifests 62 | mountPath: /workspace/manifests/ 63 | restartPolicy: Never 64 | volumes: 65 | - name: dockerfile 66 | configMap: 67 | name: rukpak-e2e-${bundle_name}.root 68 | - name: manifests 69 | emptyDir: {} 70 | - name: manifests-data 71 | configMap: 72 | name: rukpak-e2e-${bundle_name}.manifests 73 | EOF 74 | 75 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 76 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/provides/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/provides" 26 | bundle_name="plain-provides" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | 33 | kubectl apply -f - << EOF 34 | apiVersion: batch/v1 35 | kind: Job 36 | metadata: 37 | name: "kaniko-${bundle_name}" 38 | namespace: "${namespace}" 39 | spec: 40 | template: 41 | spec: 42 | initContainers: 43 | - name: copy-manifests 44 | image: busybox 45 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 46 | volumeMounts: 47 | - name: manifests 48 | mountPath: /manifests 49 | - name: manifests-data 50 | mountPath: /manifests-data 51 | containers: 52 | - name: kaniko 53 | image: gcr.io/kaniko-project/executor:latest 54 | args: ["--dockerfile=/workspace/Dockerfile", 55 | "--context=/workspace/", 56 | "--destination=${tag}", 57 | "--skip-tls-verify"] 58 | volumeMounts: 59 | - name: dockerfile 60 | mountPath: /workspace/ 61 | - name: manifests 62 | mountPath: /workspace/manifests/ 63 | restartPolicy: Never 64 | volumes: 65 | - name: dockerfile 66 | configMap: 67 | name: rukpak-e2e-${bundle_name}.root 68 | - name: manifests 69 | emptyDir: {} 70 | - name: manifests-data 71 | configMap: 72 | name: rukpak-e2e-${bundle_name}.manifests 73 | EOF 74 | 75 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 76 | -------------------------------------------------------------------------------- /pkg/storage/localdir.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/fs" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "path/filepath" 15 | 16 | "github.com/nlepage/go-tarfs" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | 19 | "github.com/operator-framework/rukpak/pkg/util" 20 | ) 21 | 22 | var _ Storage = &LocalDirectory{} 23 | 24 | const DefaultBundleCacheDir = "/var/cache/bundles" 25 | 26 | type LocalDirectory struct { 27 | RootDirectory string 28 | URL url.URL 29 | } 30 | 31 | func (s *LocalDirectory) Load(_ context.Context, owner client.Object) (fs.FS, error) { 32 | bundleFile, err := os.Open(s.bundlePath(owner.GetName())) 33 | if err != nil { 34 | return nil, err 35 | } 36 | defer bundleFile.Close() 37 | tarReader, err := gzip.NewReader(bundleFile) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return tarfs.New(tarReader) 42 | } 43 | 44 | func (s *LocalDirectory) Store(_ context.Context, owner client.Object, bundle fs.FS) error { 45 | buf := &bytes.Buffer{} 46 | if err := util.FSToTarGZ(buf, bundle); err != nil { 47 | return fmt.Errorf("convert bundle %q to tar.gz: %v", owner.GetName(), err) 48 | } 49 | 50 | bundleFile, err := os.Create(s.bundlePath(owner.GetName())) 51 | if err != nil { 52 | return err 53 | } 54 | defer bundleFile.Close() 55 | 56 | if _, err := io.Copy(bundleFile, buf); err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | func (s *LocalDirectory) Delete(_ context.Context, owner client.Object) error { 63 | return ignoreNotExist(os.Remove(s.bundlePath(owner.GetName()))) 64 | } 65 | 66 | func (s *LocalDirectory) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 67 | fsys := &util.FilesOnlyFilesystem{FS: os.DirFS(s.RootDirectory)} 68 | http.StripPrefix(s.URL.Path, http.FileServer(http.FS(fsys))).ServeHTTP(resp, req) 69 | } 70 | 71 | func (s *LocalDirectory) URLFor(_ context.Context, owner client.Object) (string, error) { 72 | return fmt.Sprintf("%s%s", s.URL.String(), localDirectoryBundleFile(owner.GetName())), nil 73 | } 74 | 75 | func (s *LocalDirectory) bundlePath(bundleName string) string { 76 | return filepath.Join(s.RootDirectory, localDirectoryBundleFile(bundleName)) 77 | } 78 | 79 | func localDirectoryBundleFile(bundleName string) string { 80 | return fmt.Sprintf("%s.tgz", bundleName) 81 | } 82 | 83 | func ignoreNotExist(err error) error { 84 | if errors.Is(err, os.ErrNotExist) { 85 | return nil 86 | } 87 | return err 88 | } 89 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/invalid-crds-and-crs/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/invalid-crds-and-crs" 26 | bundle_name="plain-invalid-crds-and-crs" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | 33 | kubectl apply -f - << EOF 34 | apiVersion: batch/v1 35 | kind: Job 36 | metadata: 37 | name: "kaniko-${bundle_name}" 38 | namespace: "${namespace}" 39 | spec: 40 | template: 41 | spec: 42 | initContainers: 43 | - name: copy-manifests 44 | image: busybox 45 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 46 | volumeMounts: 47 | - name: manifests 48 | mountPath: /manifests 49 | - name: manifests-data 50 | mountPath: /manifests-data 51 | containers: 52 | - name: kaniko 53 | image: gcr.io/kaniko-project/executor:latest 54 | args: ["--dockerfile=/workspace/Dockerfile", 55 | "--context=/workspace/", 56 | "--destination=${tag}", 57 | "--skip-tls-verify"] 58 | volumeMounts: 59 | - name: dockerfile 60 | mountPath: /workspace/ 61 | - name: manifests 62 | mountPath: /workspace/manifests/ 63 | restartPolicy: Never 64 | volumes: 65 | - name: dockerfile 66 | configMap: 67 | name: rukpak-e2e-${bundle_name}.root 68 | - name: manifests 69 | emptyDir: {} 70 | - name: manifests-data 71 | configMap: 72 | name: rukpak-e2e-${bundle_name}.manifests 73 | EOF 74 | 75 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 76 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/invalid-missing-crds/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/invalid-missing-crds" 26 | bundle_name="plain-invalid-missing-crds" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | 33 | kubectl apply -f - << EOF 34 | apiVersion: batch/v1 35 | kind: Job 36 | metadata: 37 | name: "kaniko-${bundle_name}" 38 | namespace: "${namespace}" 39 | spec: 40 | template: 41 | spec: 42 | initContainers: 43 | - name: copy-manifests 44 | image: busybox 45 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 46 | volumeMounts: 47 | - name: manifests 48 | mountPath: /manifests 49 | - name: manifests-data 50 | mountPath: /manifests-data 51 | containers: 52 | - name: kaniko 53 | image: gcr.io/kaniko-project/executor:latest 54 | args: ["--dockerfile=/workspace/Dockerfile", 55 | "--context=/workspace/", 56 | "--destination=${tag}", 57 | "--skip-tls-verify"] 58 | volumeMounts: 59 | - name: dockerfile 60 | mountPath: /workspace/ 61 | - name: manifests 62 | mountPath: /workspace/manifests/ 63 | restartPolicy: Never 64 | volumes: 65 | - name: dockerfile 66 | configMap: 67 | name: rukpak-e2e-${bundle_name}.root 68 | - name: manifests 69 | emptyDir: {} 70 | - name: manifests-data 71 | configMap: 72 | name: rukpak-e2e-${bundle_name}.manifests 73 | EOF 74 | 75 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 76 | -------------------------------------------------------------------------------- /pkg/provisioner/helm/helm.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/fs" 9 | 10 | "golang.org/x/sync/errgroup" 11 | "helm.sh/helm/v3/pkg/chart" 12 | "helm.sh/helm/v3/pkg/chart/loader" 13 | "helm.sh/helm/v3/pkg/chartutil" 14 | 15 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 16 | "github.com/operator-framework/rukpak/pkg/util" 17 | ) 18 | 19 | const ( 20 | // ProvisionerID is the unique helm provisioner ID 21 | ProvisionerID = "core-rukpak-io-helm" 22 | ) 23 | 24 | func HandleBundleDeployment(_ context.Context, fsys fs.FS, bd *rukpakv1alpha2.BundleDeployment) (*chart.Chart, chartutil.Values, error) { 25 | // Helm expects an FS whose root contains a single chart directory. Depending on how 26 | // the bundle is sourced, the FS may or may not contain this single chart directory in 27 | // its root. This FS wrapper adds this base directory unless the FS already has a base 28 | // directory. 29 | chartFS, err := util.EnsureBaseDirFS(fsys, "chart") 30 | if err != nil { 31 | return nil, nil, err 32 | } 33 | 34 | values, err := loadValues(bd) 35 | if err != nil { 36 | return nil, nil, err 37 | } 38 | chart, err := getChart(chartFS) 39 | if err != nil { 40 | return nil, nil, err 41 | } 42 | return chart, values, nil 43 | } 44 | 45 | func loadValues(bd *rukpakv1alpha2.BundleDeployment) (chartutil.Values, error) { 46 | data, err := json.Marshal(bd.Spec.Config) 47 | if err != nil { 48 | return nil, fmt.Errorf("marshal JSON for deployment config: %v", err) 49 | } 50 | var config map[string]string 51 | if err := json.Unmarshal(data, &config); err != nil { 52 | return nil, fmt.Errorf("parse deployment config: %v", err) 53 | } 54 | valuesString := config["values"] 55 | 56 | var values chartutil.Values 57 | if valuesString == "" { 58 | return nil, nil 59 | } 60 | 61 | values, err = chartutil.ReadValues([]byte(valuesString)) 62 | if err != nil { 63 | return nil, fmt.Errorf("read chart values: %v", err) 64 | } 65 | return values, nil 66 | } 67 | 68 | func getChart(chartfs fs.FS) (*chart.Chart, error) { 69 | pr, pw := io.Pipe() 70 | var eg errgroup.Group 71 | eg.Go(func() error { 72 | return pw.CloseWithError(util.FSToTarGZ(pw, chartfs)) 73 | }) 74 | 75 | var chrt *chart.Chart 76 | eg.Go(func() error { 77 | var err error 78 | chrt, err = loader.LoadArchive(pr) 79 | if err != nil { 80 | return err 81 | } 82 | return chrt.Validate() 83 | }) 84 | if err := eg.Wait(); err != nil { 85 | return nil, err 86 | } 87 | return chrt, nil 88 | } 89 | -------------------------------------------------------------------------------- /test/tools/imageregistry/image-registry.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | image-registry.sh is a script to stand up an image registry within a cluster. 9 | Usage: 10 | image-registry.sh [NAMESPACE] [NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace that should be created and is the namespace in which the image registry will be created 14 | - NAME is the name that should be used for the image registry Deployment and Service 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | name=$2 25 | 26 | kubectl apply -f - << EOF 27 | apiVersion: v1 28 | kind: Namespace 29 | metadata: 30 | name: ${namespace} 31 | --- 32 | apiVersion: cert-manager.io/v1 33 | kind: Issuer 34 | metadata: 35 | name: selfsigned-issuer 36 | namespace: ${namespace} 37 | spec: 38 | selfSigned: {} 39 | --- 40 | apiVersion: cert-manager.io/v1 41 | kind: Certificate 42 | metadata: 43 | name: ${namespace}-registry 44 | namespace: ${namespace} 45 | spec: 46 | secretName: ${namespace}-registry 47 | isCA: true 48 | dnsNames: 49 | - ${name}.${namespace}.svc 50 | - ${name}.${namespace}.svc.cluster.local 51 | privateKey: 52 | algorithm: ECDSA 53 | size: 256 54 | issuerRef: 55 | name: selfsigned-issuer 56 | kind: Issuer 57 | group: cert-manager.io 58 | --- 59 | apiVersion: apps/v1 60 | kind: Deployment 61 | metadata: 62 | name: ${name} 63 | namespace: ${namespace} 64 | labels: 65 | app: registry 66 | spec: 67 | replicas: 1 68 | selector: 69 | matchLabels: 70 | app: registry 71 | template: 72 | metadata: 73 | labels: 74 | app: registry 75 | spec: 76 | containers: 77 | - name: registry 78 | image: registry:2 79 | volumeMounts: 80 | - name: certs-vol 81 | mountPath: "/certs" 82 | env: 83 | - name: REGISTRY_HTTP_TLS_CERTIFICATE 84 | value: "/certs/tls.crt" 85 | - name: REGISTRY_HTTP_TLS_KEY 86 | value: "/certs/tls.key" 87 | volumes: 88 | - name: certs-vol 89 | secret: 90 | secretName: ${namespace}-registry 91 | --- 92 | apiVersion: v1 93 | kind: Service 94 | metadata: 95 | name: ${name} 96 | namespace: ${namespace} 97 | spec: 98 | selector: 99 | app: registry 100 | ports: 101 | - port: 5000 102 | targetPort: 5000 103 | EOF 104 | 105 | kubectl wait --for=condition=Available -n "${namespace}" "deploy/${name}" --timeout=60s 106 | -------------------------------------------------------------------------------- /manifests/base/core/resources/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: rukpak-system 5 | name: core 6 | labels: 7 | app: core 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: core 13 | template: 14 | metadata: 15 | labels: 16 | app: core 17 | annotations: 18 | kubectl.kubernetes.io/default-container: manager 19 | spec: 20 | serviceAccountName: core-admin 21 | securityContext: 22 | runAsNonRoot: true 23 | seccompProfile: 24 | type: RuntimeDefault 25 | containers: 26 | - name: kube-rbac-proxy 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | capabilities: 30 | drop: ["ALL"] 31 | image: quay.io/brancz/kube-rbac-proxy:v0.15.0 32 | args: 33 | - "--http2-disable=true" 34 | - "--secure-listen-address=0.0.0.0:8443" 35 | - "--upstream=http://127.0.0.1:8080/" 36 | - "--logtostderr=true" 37 | - "--v=1" 38 | - "--client-ca-file=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" 39 | ports: 40 | - containerPort: 8443 41 | protocol: TCP 42 | name: https 43 | volumeMounts: [] 44 | resources: 45 | requests: 46 | cpu: 1m 47 | memory: 15Mi 48 | terminationMessagePolicy: FallbackToLogsOnError 49 | - name: manager 50 | securityContext: 51 | allowPrivilegeEscalation: false 52 | capabilities: 53 | drop: ["ALL"] 54 | image: quay.io/operator-framework/rukpak:devel 55 | imagePullPolicy: IfNotPresent 56 | command: ["/core"] 57 | args: 58 | - "--unpack-cache-dir=/var/cache/unpack" 59 | - "--provisioner-storage-dir=/var/cache/bundles" 60 | - "--http-bind-address=127.0.0.1:8080" 61 | - "--http-external-address=https://$(CORE_SERVICE_NAME).$(CORE_SERVICE_NAMESPACE).svc" 62 | - "--feature-gates=BundleDeploymentHealth=true" 63 | ports: 64 | - containerPort: 8080 65 | volumeMounts: 66 | - name: bundle-cache 67 | mountPath: /var/cache/bundles 68 | - name: unpack-cache 69 | mountPath: /var/cache/unpack 70 | resources: 71 | requests: 72 | cpu: 10m 73 | memory: 160Mi 74 | terminationMessagePolicy: FallbackToLogsOnError 75 | volumes: 76 | - name: bundle-cache 77 | emptyDir: {} 78 | - name: unpack-cache 79 | emptyDir: {} 80 | -------------------------------------------------------------------------------- /manifests/base/provisioners/helm/resources/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | namespace: rukpak-system 5 | name: helm-provisioner 6 | labels: 7 | app: helm-provisioner 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: helm-provisioner 13 | template: 14 | metadata: 15 | labels: 16 | app: helm-provisioner 17 | annotations: 18 | kubectl.kubernetes.io/default-container: manager 19 | spec: 20 | securityContext: 21 | runAsNonRoot: true 22 | seccompProfile: 23 | type: RuntimeDefault 24 | serviceAccountName: helm-provisioner-admin 25 | containers: 26 | - name: kube-rbac-proxy 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | capabilities: 30 | drop: [ "ALL" ] 31 | image: quay.io/brancz/kube-rbac-proxy:v0.15.0 32 | args: 33 | - "--http2-disable=true" 34 | - "--secure-listen-address=0.0.0.0:8443" 35 | - "--upstream=http://127.0.0.1:8080/" 36 | - "--logtostderr=true" 37 | - "--v=1" 38 | - "--client-ca-file=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" 39 | ports: 40 | - containerPort: 8443 41 | protocol: TCP 42 | name: https 43 | volumeMounts: [] 44 | resources: 45 | requests: 46 | cpu: 1m 47 | memory: 15Mi 48 | terminationMessagePolicy: FallbackToLogsOnError 49 | - name: manager 50 | securityContext: 51 | allowPrivilegeEscalation: false 52 | capabilities: 53 | drop: [ "ALL" ] 54 | image: quay.io/operator-framework/rukpak:devel 55 | imagePullPolicy: IfNotPresent 56 | command: ["/helm"] 57 | args: 58 | - "--unpack-cache-dir=/var/cache/unpack" 59 | - "--storage-dir=/var/cache/bundles" 60 | - "--http-bind-address=127.0.0.1:8080" 61 | - "--http-external-address=https://$(HELM_PROVISIONER_SERVICE_NAME).$(HELM_PROVISIONER_SERVICE_NAMESPACE).svc" 62 | ports: 63 | - containerPort: 8080 64 | volumeMounts: 65 | - name: bundle-cache 66 | mountPath: /var/cache/bundles 67 | - name: unpack-cache 68 | mountPath: /var/cache/unpack 69 | resources: 70 | requests: 71 | cpu: 10m 72 | memory: 160Mi 73 | terminationMessagePolicy: FallbackToLogsOnError 74 | volumes: 75 | - name: bundle-cache 76 | emptyDir: {} 77 | - name: unpack-cache 78 | emptyDir: {} 79 | -------------------------------------------------------------------------------- /docs/sources/http.md: -------------------------------------------------------------------------------- 1 | # Http source 2 | 3 | ## Summary 4 | 5 | The http source provides a compressed archive file (`tgz` format) downloadable by the http protocol as the source of the bundle. 6 | The `source.type` for the http source is `http`. When creating a http source, a URL of the compressed archive file must be specified. 7 | It is expected that a proper format of bundle content is present 8 | in the compressed archive file. 9 | 10 | ## Example 11 | 12 | Referencing a compressed archive file in a github repository release archive: 13 | 14 | ```yaml 15 | apiVersion: core.rukpak.io/v1alpha1 16 | kind: BundleDeployment 17 | metadata: 18 | name: my-ahoy 19 | spec: 20 | provisionerClassName: core-rukpak-io-helm 21 | template: 22 | metadata: 23 | labels: 24 | app: my-ahoy 25 | spec: 26 | provisionerClassName: core-rukpak-io-helm 27 | source: 28 | http: 29 | url: https://github.com/helm/examples/releases/download/hello-world-0.1.0/hello-world-0.1.0.tgz 30 | type: http 31 | ``` 32 | 33 | ## Authorization 34 | 35 | An http source can provide authorization for access to private compressed archives by creating a secret in the namespace that the provisioner is deployed. 36 | The secret used in the http source is based around [Basic authentication secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret) 37 | and is expected to contain `data.username` and `data.password` for the username and password, respectively. 38 | If the `http.auth.secret.insecureSkipVerify` is set true, the download operation will accept any certificate presented by the server and any host name in that 39 | certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is 40 | used. This should be used only for testing. 41 | 42 | ### Example with authorization 43 | 44 | 1. Create the secret 45 | 46 | ```sh 47 | kubectl create secret generic accesssecret --type "kubernetes.io/basic-auth" --from-literal=username=myusername --from-literal=password=mypassword -n rukpak-system 48 | ``` 49 | 50 | 2. Create a bundle deployment referencing a private compressed archive file: 51 | 52 | ```bash 53 | kubectl apply -f -< 51 | [slack]: 52 | [workflow]: 53 | [image]: 54 | [semver]: 55 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/valid/build-push-e2e-bundle-secure.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/valid" 26 | bundle_name="plain-valid" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}-secure.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}-secure.manifests 32 | 33 | kubectl apply -f - << EOF 34 | apiVersion: batch/v1 35 | kind: Job 36 | metadata: 37 | name: "kaniko-${bundle_name}-secure" 38 | namespace: "${namespace}" 39 | spec: 40 | template: 41 | spec: 42 | initContainers: 43 | - name: copy-manifests 44 | image: busybox 45 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 46 | volumeMounts: 47 | - name: manifests 48 | mountPath: /manifests 49 | - name: manifests-data 50 | mountPath: /manifests-data 51 | containers: 52 | - name: kaniko 53 | image: gcr.io/kaniko-project/executor:latest 54 | args: ["--dockerfile=/workspace/Dockerfile", 55 | "--context=/workspace/", 56 | "--destination=${tag}", 57 | "--skip-tls-verify"] 58 | volumeMounts: 59 | - name: dockerfile 60 | mountPath: /workspace/ 61 | - name: manifests 62 | mountPath: /workspace/manifests/ 63 | - name: secure-registry-auth 64 | mountPath: /workspace/.docker 65 | env: 66 | - name: DOCKER_CONFIG 67 | value: /workspace/.docker 68 | restartPolicy: Never 69 | volumes: 70 | - name: dockerfile 71 | configMap: 72 | name: rukpak-e2e-${bundle_name}-secure.root 73 | - name: manifests 74 | emptyDir: {} 75 | - name: manifests-data 76 | configMap: 77 | name: rukpak-e2e-${bundle_name}-secure.manifests 78 | - name: secure-registry-auth 79 | secret: 80 | secretName: secureregistrysecret 81 | items: 82 | - key: .dockerconfigjson 83 | path: config.json 84 | EOF 85 | 86 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name}-secure --timeout=60s 87 | -------------------------------------------------------------------------------- /tilt.md: -------------------------------------------------------------------------------- 1 | # Rapid iterative development with Tilt 2 | 3 | [Tilt](https://tilt.dev) is a tool that enables rapid iterative development of containerized workloads. 4 | 5 | Here is an example workflow without Tilt for modifying some source code and testing those changes in a cluster: 6 | 7 | 1. Modify the source code. 8 | 2. Build the container image. 9 | 3. Either push the image to a registry or load it into your kind cluster. 10 | 4. Deploy all the appropriate Kubernetes manifests for your application. 11 | 1. Or, if this is an update, you'd instead scale the Deployment to 0 replicas, scale back to 1, and wait for the 12 | new pod to be running. 13 | 14 | This process can take minutes, depending on how long each step takes. 15 | 16 | Here is the same workflow with Tilt: 17 | 18 | 1. Run `tilt up` 19 | 2. Modify the source code 20 | 3. Wait for Tilt to update the container with your changes 21 | 22 | This ends up taking a fraction of the time, sometimes on the order of a few seconds! 23 | 24 | ## Installing Tilt 25 | 26 | Follow Tilt's [instructions](https://docs.tilt.dev/install.html) for installation. 27 | 28 | ## Installing cert-manager 29 | 30 | [cert-manager](https://cert-manager.io) is a prerequisite for running rukpak. Please follow the 31 | [cert-manager installation documentation](https://cert-manager.io/docs/installation/). 32 | 33 | ## Starting Tilt 34 | 35 | This is typically as short as: 36 | 37 | ```shell 38 | tilt up 39 | ``` 40 | 41 | **NOTE:** if you are using Podman, at least as of v4.5.1, you need to do this: 42 | 43 | ```shell 44 | DOCKER_BUILDKIT=0 tilt up 45 | ``` 46 | 47 | Otherwise, you'll see an error when Tilt tries to build your image that looks similar to: 48 | 49 | ```text 50 | Build Failed: ImageBuild: stat /var/tmp/libpod_builder2384046170/build/Dockerfile: no such file or directory 51 | ``` 52 | 53 | When Tilt starts, you'll see something like this in your terminal: 54 | 55 | ```text 56 | Tilt started on http://localhost:10350/ 57 | v0.33.1, built 2023-06-28 58 | 59 | (space) to open the browser 60 | (s) to stream logs (--stream=true) 61 | (t) to open legacy terminal mode (--legacy=true) 62 | (ctrl-c) to exit 63 | ``` 64 | 65 | Typically, you'll want to press the space bar to have it open the UI in your web browser. 66 | 67 | Shortly after starting, Tilt processes the `Tiltfile`, resulting in: 68 | 69 | - Building the go binaries 70 | - Building the images 71 | - Loading the images into kind 72 | - Running kustomize and applying everything except the Deployments that reference the images above 73 | - Modifying the Deployments to use the just-built images 74 | - Creating the Deployments 75 | 76 | ## Making code changes 77 | 78 | Any time you change any of the files listed in the `deps` section in the `_binary` `local_resource`, 79 | Tilt automatically rebuilds the go binary. As soon as the binary is rebuilt, Tilt pushes it (and only it) into the 80 | appropriate running container, and then restarts the process. 81 | -------------------------------------------------------------------------------- /pkg/storage/storage_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io/fs" 8 | "os" 9 | "path/filepath" 10 | 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/util/rand" 16 | 17 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 18 | "github.com/operator-framework/rukpak/pkg/util" 19 | ) 20 | 21 | var _ = Describe("WithFallbackLoader", func() { 22 | var ( 23 | ctx context.Context 24 | primaryBundleDeployment *rukpakv1alpha2.BundleDeployment 25 | fallbackBundleDeployment *rukpakv1alpha2.BundleDeployment 26 | primaryStore *LocalDirectory 27 | fallbackStore *LocalDirectory 28 | primaryFS fs.FS 29 | fallbackFS fs.FS 30 | 31 | store Storage 32 | ) 33 | 34 | BeforeEach(func() { 35 | ctx = context.Background() 36 | primaryBundleDeployment = &rukpakv1alpha2.BundleDeployment{ 37 | ObjectMeta: metav1.ObjectMeta{ 38 | Name: util.GenerateBundleName("primary", rand.String(8)), 39 | }, 40 | } 41 | fallbackBundleDeployment = &rukpakv1alpha2.BundleDeployment{ 42 | ObjectMeta: metav1.ObjectMeta{ 43 | Name: util.GenerateBundleName("fallback", rand.String(8)), 44 | }, 45 | } 46 | primaryDir := filepath.Join(GinkgoT().TempDir(), fmt.Sprintf("primary-%s", rand.String(8))) 47 | Expect(os.MkdirAll(primaryDir, 0700)).To(Succeed()) 48 | fallbackDir := filepath.Join(GinkgoT().TempDir(), fmt.Sprintf("fallback-%s", rand.String(8))) 49 | Expect(os.MkdirAll(fallbackDir, 0700)).To(Succeed()) 50 | 51 | primaryStore = &LocalDirectory{RootDirectory: primaryDir} 52 | primaryFS = generateFS() 53 | Expect(primaryStore.Store(ctx, primaryBundleDeployment, primaryFS)).To(Succeed()) 54 | 55 | fallbackStore = &LocalDirectory{RootDirectory: fallbackDir} 56 | fallbackFS = generateFS() 57 | Expect(fallbackStore.Store(ctx, fallbackBundleDeployment, fallbackFS)).To(Succeed()) 58 | 59 | store = WithFallbackLoader(primaryStore, fallbackStore) 60 | }) 61 | 62 | It("should find primary bundle", func() { 63 | loadedTestFS, err := store.Load(ctx, primaryBundleDeployment) 64 | Expect(err).ToNot(HaveOccurred()) 65 | Expect(fsEqual(primaryFS, loadedTestFS)).To(BeTrue()) 66 | }) 67 | It("should find fallback bundle", func() { 68 | loadedTestFS, err := store.Load(ctx, fallbackBundleDeployment) 69 | Expect(err).ToNot(HaveOccurred()) 70 | Expect(fsEqual(fallbackFS, loadedTestFS)).To(BeTrue()) 71 | }) 72 | It("should fail to find unknown bundle", func() { 73 | unknownBundle := &rukpakv1alpha2.BundleDeployment{ 74 | ObjectMeta: metav1.ObjectMeta{ 75 | Name: util.GenerateBundleName("unknown", rand.String(8)), 76 | }, 77 | } 78 | loadedTestFS, err := store.Load(ctx, unknownBundle) 79 | Expect(err).To(WithTransform(func(err error) bool { return errors.Is(err, os.ErrNotExist) }, BeTrue())) 80 | Expect(loadedTestFS).To(BeNil()) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /pkg/source/configmaps.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "path/filepath" 7 | "testing/fstest" 8 | 9 | corev1 "k8s.io/api/core/v1" 10 | utilerrors "k8s.io/apimachinery/pkg/util/errors" 11 | "k8s.io/apimachinery/pkg/util/sets" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 15 | ) 16 | 17 | type ConfigMaps struct { 18 | Reader client.Reader 19 | ConfigMapNamespace string 20 | } 21 | 22 | func (o *ConfigMaps) Unpack(ctx context.Context, bundle *rukpakv1alpha2.BundleDeployment) (*Result, error) { 23 | if bundle.Spec.Source.Type != rukpakv1alpha2.SourceTypeConfigMaps { 24 | return nil, fmt.Errorf("bundle source type %q not supported", bundle.Spec.Source.Type) 25 | } 26 | if bundle.Spec.Source.ConfigMaps == nil { 27 | return nil, fmt.Errorf("bundle source configmaps configuration is unset") 28 | } 29 | 30 | configMapSources := bundle.Spec.Source.ConfigMaps 31 | 32 | bundleFS := fstest.MapFS{} 33 | seenFilepaths := map[string]sets.Set[string]{} 34 | 35 | for _, cmSource := range configMapSources { 36 | cmName := cmSource.ConfigMap.Name 37 | dir := filepath.Clean(cmSource.Path) 38 | 39 | // Validating admission webhook handles validation for: 40 | // - paths outside the bundle root 41 | // - configmaps referenced by bundles must be immutable 42 | 43 | var cm corev1.ConfigMap 44 | if err := o.Reader.Get(ctx, client.ObjectKey{Name: cmName, Namespace: o.ConfigMapNamespace}, &cm); err != nil { 45 | return nil, fmt.Errorf("get configmap %s/%s: %v", o.ConfigMapNamespace, cmName, err) 46 | } 47 | 48 | addToBundle := func(configMapName, filename string, data []byte) { 49 | filepath := filepath.Join(dir, filename) 50 | if _, ok := seenFilepaths[filepath]; !ok { 51 | seenFilepaths[filepath] = sets.New[string]() 52 | } 53 | seenFilepaths[filepath].Insert(configMapName) 54 | bundleFS[filepath] = &fstest.MapFile{ 55 | Data: data, 56 | } 57 | } 58 | for filename, data := range cm.Data { 59 | addToBundle(cmName, filename, []byte(data)) 60 | } 61 | for filename, data := range cm.BinaryData { 62 | addToBundle(cmName, filename, data) 63 | } 64 | } 65 | 66 | errs := []error{} 67 | for filepath, cmNames := range seenFilepaths { 68 | if len(cmNames) > 1 { 69 | errs = append(errs, fmt.Errorf("duplicate path %q found in configmaps %v", filepath, sets.List(cmNames))) 70 | continue 71 | } 72 | } 73 | if len(errs) > 0 { 74 | return nil, utilerrors.NewAggregate(errs) 75 | } 76 | 77 | resolvedSource := &rukpakv1alpha2.BundleSource{ 78 | Type: rukpakv1alpha2.SourceTypeConfigMaps, 79 | ConfigMaps: bundle.Spec.Source.DeepCopy().ConfigMaps, 80 | } 81 | 82 | message := generateMessage("configMaps") 83 | return &Result{Bundle: bundleFS, ResolvedSource: resolvedSource, State: StateUnpacked, Message: message}, nil 84 | } 85 | 86 | func (o *ConfigMaps) Cleanup(_ context.Context, _ *rukpakv1alpha2.BundleDeployment) error { 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /pkg/util/fs.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "testing/fstest" 10 | ) 11 | 12 | // FilesOnlyFilesystem is an fs.FS implementation that treats non-regular files 13 | // (e.g. directories, symlinks, devices, etc.) as non-existent. The reason for 14 | // this is so that we only serve bundle files. 15 | // 16 | // This treats directories as not found so that the http server does not serve 17 | // HTML directory index responses. 18 | // 19 | // This treats other symlink files as not found so that we prevent HTTP requests 20 | // from escaping the filesystem root. 21 | // 22 | // Lastly, this treats other non-regular files as not found because they are 23 | // out of scope for serving bundle contents. 24 | type FilesOnlyFilesystem struct { 25 | FS fs.FS 26 | } 27 | 28 | func (f *FilesOnlyFilesystem) Open(name string) (fs.File, error) { 29 | file, err := f.FS.Open(name) 30 | if err != nil { 31 | return nil, err 32 | } 33 | stat, err := file.Stat() 34 | if err != nil { 35 | return nil, err 36 | } 37 | if !stat.Mode().IsRegular() { 38 | return nil, os.ErrNotExist 39 | } 40 | return file, nil 41 | } 42 | 43 | // EnsureBaseDirFS ensures that an fs.FS contains a single directory in its root 44 | // This is useful for bundle formats that require a base directory in the root of 45 | // the bundle. 46 | // 47 | // For example, an unpacked Helm chart might have /Chart.yaml, and we'd 48 | // typically assume as the bundle root. However, when helm archives 49 | // contain at the root of the archive: //Chart.yaml. 50 | // 51 | // If the fs.FS already has this structure, EnsureBaseDirFS just returns fsys 52 | // directly. Otherwise, it returns a new fs.FS where the defaultBaseDir is inserted 53 | // at the root, such that fsys appears within defaultBaseDir. 54 | func EnsureBaseDirFS(fsys fs.FS, defaultBaseDir string) (fs.FS, error) { 55 | cleanDefaultBaseDir := filepath.Clean(defaultBaseDir) 56 | if dir, _ := filepath.Split(cleanDefaultBaseDir); dir != "" { 57 | return nil, fmt.Errorf("default base directory %q contains multiple path segments: must be exactly one", defaultBaseDir) 58 | } 59 | rootFSEntries, err := fs.ReadDir(fsys, ".") 60 | if err != nil { 61 | return nil, err 62 | } 63 | if len(rootFSEntries) == 1 && rootFSEntries[0].IsDir() { 64 | return fsys, nil 65 | } 66 | return &baseDirFS{fsys, defaultBaseDir}, nil 67 | } 68 | 69 | type baseDirFS struct { 70 | fsys fs.FS 71 | baseDir string 72 | } 73 | 74 | func (f baseDirFS) Open(name string) (fs.File, error) { 75 | if name == "." { 76 | return fstest.MapFS{f.baseDir: &fstest.MapFile{Mode: fs.ModeDir}}.Open(name) 77 | } 78 | if name == f.baseDir { 79 | return f.fsys.Open(".") 80 | } 81 | basePrefix := f.baseDir + string(os.PathSeparator) 82 | if strings.HasPrefix(name, basePrefix) { 83 | subName := strings.TrimPrefix(name, basePrefix) 84 | if subName == "" { 85 | subName = "." 86 | } 87 | return f.fsys.Open(subName) 88 | } 89 | return nil, fs.ErrNotExist 90 | } 91 | -------------------------------------------------------------------------------- /internal/webhook/configmaps.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | ctrl "sigs.k8s.io/controller-runtime" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/webhook" 12 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 13 | 14 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 15 | ) 16 | 17 | //+kubebuilder:rbac:groups=core.rukpak.io,resources=bundledeployments,verbs=list;watch 18 | //+kubebuilder:webhook:path=/validate-core-v1-configmap,mutating=false,failurePolicy=fail,sideEffects=None,groups="",resources=configmaps,verbs=create;delete,versions=v1,name=vconfigmaps.core.rukpak.io,admissionReviewVersions=v1 19 | 20 | type ConfigMap struct { 21 | Client client.Client 22 | } 23 | 24 | func (w *ConfigMap) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 25 | // Only allow configmap to be created if either of the following is true: 26 | // 1. The configmap is immutable. 27 | // 2. The configmap is not referenced by a bundle. 28 | 29 | cm := obj.(*corev1.ConfigMap) 30 | if cm.Immutable != nil && *cm.Immutable { 31 | return nil, nil 32 | } 33 | 34 | bundleDeploymentList := &rukpakv1alpha2.BundleDeploymentList{} 35 | if err := w.Client.List(ctx, bundleDeploymentList); err != nil { 36 | return nil, err 37 | } 38 | bundleReferrers := []string{} 39 | for _, bundle := range bundleDeploymentList.Items { 40 | if bundle.Spec.Source.Type == rukpakv1alpha2.SourceTypeConfigMaps { 41 | for _, bundleConfigMapRef := range bundle.Spec.Source.ConfigMaps { 42 | if bundleConfigMapRef.ConfigMap.Name == cm.Name { 43 | bundleReferrers = append(bundleReferrers, bundle.Name) 44 | } 45 | } 46 | } 47 | } 48 | if len(bundleReferrers) > 0 { 49 | return nil, fmt.Errorf("configmap %q is referenced in .spec.source.configMaps[].configMap.name by bundles %v; referenced configmaps must have .immutable == true", cm.Name, bundleReferrers) 50 | } 51 | return nil, nil 52 | } 53 | 54 | func (w *ConfigMap) ValidateUpdate(_ context.Context, _, _ runtime.Object) (admission.Warnings, error) { 55 | return nil, nil 56 | } 57 | 58 | func (w *ConfigMap) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { 59 | cm := obj.(*corev1.ConfigMap) 60 | 61 | bundleList := &rukpakv1alpha2.BundleDeploymentList{} 62 | if err := w.Client.List(ctx, bundleList); err != nil { 63 | return nil, err 64 | } 65 | for _, b := range bundleList.Items { 66 | for _, cmSource := range b.Spec.Source.ConfigMaps { 67 | if cmSource.ConfigMap.Name == cm.Name { 68 | return nil, fmt.Errorf("configmap %q is in-use by bundle %q", cm.Name, b.Name) 69 | } 70 | } 71 | } 72 | return nil, nil 73 | } 74 | 75 | func (w *ConfigMap) SetupWebhookWithManager(mgr ctrl.Manager) error { 76 | mgr.GetWebhookServer().Register("/validate-core-v1-configmap", admission.WithCustomValidator(mgr.GetScheme(), &corev1.ConfigMap{}, w).WithRecoverPanic(true)) 77 | return nil 78 | } 79 | 80 | var _ webhook.CustomValidator = &ConfigMap{} 81 | -------------------------------------------------------------------------------- /pkg/provisioner/plain/plain.go: -------------------------------------------------------------------------------- 1 | package plain 2 | 3 | import ( 4 | "context" 5 | "crypto/sha256" 6 | "errors" 7 | "fmt" 8 | "io/fs" 9 | "path/filepath" 10 | 11 | "helm.sh/helm/v3/pkg/chart" 12 | "helm.sh/helm/v3/pkg/chartutil" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | "sigs.k8s.io/yaml" 15 | 16 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 17 | "github.com/operator-framework/rukpak/pkg/util" 18 | ) 19 | 20 | const ( 21 | // ProvisionerID is the unique plain provisioner ID 22 | ProvisionerID = "core-rukpak-io-plain" 23 | 24 | manifestsDir = "manifests" 25 | ) 26 | 27 | func HandleBundleDeployment(_ context.Context, fsys fs.FS, _ *rukpakv1alpha2.BundleDeployment) (*chart.Chart, chartutil.Values, error) { 28 | if err := ValidateBundle(fsys); err != nil { 29 | return nil, nil, err 30 | } 31 | 32 | chrt, err := chartFromBundle(fsys) 33 | if err != nil { 34 | return nil, nil, err 35 | } 36 | return chrt, nil, nil 37 | } 38 | 39 | func ValidateBundle(fsys fs.FS) error { 40 | objects, err := getBundleObjects(fsys) 41 | if err != nil { 42 | return fmt.Errorf("get objects from bundle manifests: %v", err) 43 | } 44 | if len(objects) == 0 { 45 | return errors.New("invalid bundle: found zero objects: plain+v0 bundles are required to contain at least one object") 46 | } 47 | return nil 48 | } 49 | 50 | func getBundleObjects(bundleFS fs.FS) ([]client.Object, error) { 51 | entries, err := fs.ReadDir(bundleFS, manifestsDir) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | var bundleObjects []client.Object 57 | for _, e := range entries { 58 | if e.IsDir() { 59 | return nil, fmt.Errorf("subdirectories are not allowed within the %q directory of the bundle image filesystem: found %q", manifestsDir, filepath.Join(manifestsDir, e.Name())) 60 | } 61 | 62 | manifestObjects, err := getObjects(bundleFS, e) 63 | if err != nil { 64 | return nil, err 65 | } 66 | bundleObjects = append(bundleObjects, manifestObjects...) 67 | } 68 | return bundleObjects, nil 69 | } 70 | 71 | func getObjects(bundle fs.FS, manifest fs.DirEntry) ([]client.Object, error) { 72 | manifestPath := filepath.Join(manifestsDir, manifest.Name()) 73 | manifestReader, err := bundle.Open(manifestPath) 74 | if err != nil { 75 | return nil, err 76 | } 77 | defer manifestReader.Close() 78 | return util.ManifestObjects(manifestReader, manifestPath) 79 | } 80 | 81 | func chartFromBundle(fsys fs.FS) (*chart.Chart, error) { 82 | objects, err := getBundleObjects(fsys) 83 | if err != nil { 84 | return nil, fmt.Errorf("read bundle objects from bundle: %v", err) 85 | } 86 | 87 | chrt := &chart.Chart{ 88 | Metadata: &chart.Metadata{}, 89 | } 90 | for _, obj := range objects { 91 | yamlData, err := yaml.Marshal(obj) 92 | if err != nil { 93 | return nil, err 94 | } 95 | hash := sha256.Sum256(yamlData) 96 | chrt.Templates = append(chrt.Templates, &chart.File{ 97 | Name: fmt.Sprintf("object-%x.yaml", hash[0:8]), 98 | Data: yamlData, 99 | }) 100 | } 101 | return chrt, nil 102 | } 103 | -------------------------------------------------------------------------------- /cmd/crdvalidator/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/tls" 21 | "flag" 22 | "os" 23 | 24 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client/config" 28 | "sigs.k8s.io/controller-runtime/pkg/log" 29 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 30 | "sigs.k8s.io/controller-runtime/pkg/manager" 31 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 32 | "sigs.k8s.io/controller-runtime/pkg/webhook" 33 | 34 | "github.com/operator-framework/rukpak/cmd/crdvalidator/handlers" 35 | ) 36 | 37 | var ( 38 | scheme = runtime.NewScheme() 39 | entryLog = log.Log.WithName("crdvalidator") 40 | ) 41 | 42 | const defaultCertDir = "/etc/admission-webhook/tls" 43 | 44 | func init() { 45 | if err := apiextensionsv1.AddToScheme(scheme); err != nil { 46 | entryLog.Error(err, "unable to set up crd scheme") 47 | os.Exit(1) 48 | } 49 | } 50 | 51 | func main() { 52 | var enableHTTP2 bool 53 | flag.BoolVar(&enableHTTP2, "enable-http2", enableHTTP2, "If HTTP/2 should be enabled for the webhook servers.") 54 | 55 | opts := zap.Options{ 56 | Development: true, 57 | } 58 | opts.BindFlags(flag.CommandLine) 59 | 60 | flag.Parse() 61 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 62 | 63 | // Setup webhook options 64 | disableHTTP2 := func(c *tls.Config) { 65 | if enableHTTP2 { 66 | return 67 | } 68 | c.NextProtos = []string{"http/1.1"} 69 | } 70 | 71 | webhookServer := webhook.NewServer(webhook.Options{ 72 | TLSOpts: []func(config *tls.Config){disableHTTP2}, 73 | CertDir: defaultCertDir, 74 | }) 75 | 76 | entryLog.Info("setting up manager") 77 | mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{Scheme: scheme, WebhookServer: webhookServer}) 78 | if err != nil { 79 | entryLog.Error(err, "unable to set up overall controller manager") 80 | os.Exit(1) 81 | } 82 | 83 | entryLog.Info("setting up webhook server") 84 | hookServer := mgr.GetWebhookServer() 85 | 86 | // Register CRD validation handler 87 | entryLog.Info("registering webhooks to the webhook server") 88 | crdValidatorHandler := handlers.NewCrdValidator(entryLog, mgr.GetClient()) 89 | hookServer.Register("/validate-crd", &webhook.Admission{ 90 | Handler: &crdValidatorHandler, 91 | }) 92 | 93 | entryLog.Info("starting manager") 94 | if err := mgr.Start(signals.SetupSignalHandler()); err != nil { 95 | entryLog.Error(err, "unable to run manager") 96 | os.Exit(1) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /testdata/bundles/registry/valid/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/registry/valid" 26 | bundle_name="registry-valid" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/metadata" rukpak-e2e-${bundle_name}.metadata 33 | 34 | kubectl apply -f - << EOF 35 | apiVersion: batch/v1 36 | kind: Job 37 | metadata: 38 | name: "kaniko-${bundle_name}" 39 | namespace: "${namespace}" 40 | spec: 41 | template: 42 | spec: 43 | initContainers: 44 | - name: copy-manifests 45 | image: busybox 46 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 47 | volumeMounts: 48 | - name: manifests 49 | mountPath: /manifests 50 | - name: manifests-data 51 | mountPath: /manifests-data 52 | - name: copy-metadata 53 | image: busybox 54 | command: ['sh', '-c', 'cp /metadata-data/* /metadata'] 55 | volumeMounts: 56 | - name: metadata 57 | mountPath: /metadata 58 | - name: metadata-data 59 | mountPath: /metadata-data 60 | containers: 61 | - name: kaniko 62 | image: gcr.io/kaniko-project/executor:latest 63 | args: ["--dockerfile=/workspace/Dockerfile", 64 | "--context=/workspace/", 65 | "--destination=${tag}", 66 | "--skip-tls-verify"] 67 | volumeMounts: 68 | - name: dockerfile 69 | mountPath: /workspace/ 70 | - name: manifests 71 | mountPath: /workspace/manifests/ 72 | - name: metadata 73 | mountPath: /workspace/metadata/ 74 | restartPolicy: Never 75 | volumes: 76 | - name: dockerfile 77 | configMap: 78 | name: rukpak-e2e-${bundle_name}.root 79 | - name: manifests 80 | emptyDir: {} 81 | - name: metadata 82 | emptyDir: {} 83 | - name: manifests-data 84 | configMap: 85 | name: rukpak-e2e-${bundle_name}.manifests 86 | - name: metadata-data 87 | configMap: 88 | name: rukpak-e2e-${bundle_name}.metadata 89 | EOF 90 | 91 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 92 | -------------------------------------------------------------------------------- /testdata/bundles/registry/invalid/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/registry/invalid" 26 | bundle_name="registry-invalid" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/metadata" rukpak-e2e-${bundle_name}.metadata 33 | 34 | kubectl apply -f - << EOF 35 | apiVersion: batch/v1 36 | kind: Job 37 | metadata: 38 | name: "kaniko-${bundle_name}" 39 | namespace: "${namespace}" 40 | spec: 41 | template: 42 | spec: 43 | initContainers: 44 | - name: copy-manifests 45 | image: busybox 46 | command: ['sh', '-c', 'cp /manifests-data/* /manifests'] 47 | volumeMounts: 48 | - name: manifests 49 | mountPath: /manifests 50 | - name: manifests-data 51 | mountPath: /manifests-data 52 | - name: copy-metadata 53 | image: busybox 54 | command: ['sh', '-c', 'cp /metadata-data/* /metadata'] 55 | volumeMounts: 56 | - name: metadata 57 | mountPath: /metadata 58 | - name: metadata-data 59 | mountPath: /metadata-data 60 | containers: 61 | - name: kaniko 62 | image: gcr.io/kaniko-project/executor:latest 63 | args: ["--dockerfile=/workspace/Dockerfile", 64 | "--context=/workspace/", 65 | "--destination=${tag}", 66 | "--skip-tls-verify"] 67 | volumeMounts: 68 | - name: dockerfile 69 | mountPath: /workspace/ 70 | - name: manifests 71 | mountPath: /workspace/manifests/ 72 | - name: metadata 73 | mountPath: /workspace/metadata/ 74 | restartPolicy: Never 75 | volumes: 76 | - name: dockerfile 77 | configMap: 78 | name: rukpak-e2e-${bundle_name}.root 79 | - name: manifests 80 | emptyDir: {} 81 | - name: metadata 82 | emptyDir: {} 83 | - name: manifests-data 84 | configMap: 85 | name: rukpak-e2e-${bundle_name}.manifests 86 | - name: metadata-data 87 | configMap: 88 | name: rukpak-e2e-${bundle_name}.metadata 89 | EOF 90 | 91 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 92 | -------------------------------------------------------------------------------- /testdata/bundles/plain-v0/subdir/build-push-e2e-bundle.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | help=" 8 | build-push-e2e-bundle.sh is a script to build and push the e2e bundle image using kaniko. 9 | Usage: 10 | build-push-e2e-bundle.sh [NAMESPACE] [TAG] [BUNDLE_DIR] [BUNDLE_NAME] 11 | 12 | Argument Descriptions: 13 | - NAMESPACE is the namespace the kaniko Job should be created in 14 | - TAG is the full tag used to build and push the catalog image 15 | " 16 | 17 | if [[ "$#" -ne 2 ]]; then 18 | echo "Illegal number of arguments passed" 19 | echo "${help}" 20 | exit 1 21 | fi 22 | 23 | namespace=$1 24 | tag=$2 25 | bundle_dir="testdata/bundles/plain-v0/subdir" 26 | bundle_name="plain-subdir" 27 | 28 | echo "${namespace}" "${tag}" 29 | 30 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/" rukpak-e2e-${bundle_name}.root 31 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests" rukpak-e2e-${bundle_name}.manifests 32 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests/emptydir" rukpak-e2e-${bundle_name}.manifests.emptydir 33 | kubectl create configmap -n "${namespace}" --from-file="${bundle_dir}/manifests/subdir" rukpak-e2e-${bundle_name}.manifests.subdir 34 | 35 | kubectl apply -f - << EOF 36 | apiVersion: batch/v1 37 | kind: Job 38 | metadata: 39 | name: "kaniko-${bundle_name}" 40 | namespace: "${namespace}" 41 | spec: 42 | template: 43 | spec: 44 | initContainers: 45 | - name: copy-manifests 46 | image: busybox 47 | command: ['sh', '-c', 'cp -r /manifests-data/* /manifests'] 48 | volumeMounts: 49 | - name: manifests 50 | mountPath: /manifests 51 | - name: manifests-data 52 | mountPath: /manifests-data 53 | - name: emptydir-data 54 | mountPath: /manifests-data/emptydir 55 | - name: subdir-data 56 | mountPath: /manifests-data/subdir 57 | containers: 58 | - name: kaniko 59 | image: gcr.io/kaniko-project/executor:latest 60 | args: ["--dockerfile=/workspace/Dockerfile", 61 | "--context=/workspace/", 62 | "--destination=${tag}", 63 | "--skip-tls-verify"] 64 | volumeMounts: 65 | - name: dockerfile 66 | mountPath: /workspace/ 67 | - name: manifests 68 | mountPath: /workspace/manifests/ 69 | restartPolicy: Never 70 | volumes: 71 | - name: dockerfile 72 | configMap: 73 | name: rukpak-e2e-${bundle_name}.root 74 | - name: manifests 75 | emptyDir: {} 76 | - name: manifests-data 77 | configMap: 78 | name: rukpak-e2e-${bundle_name}.manifests 79 | - name: emptydir-data 80 | configMap: 81 | name: rukpak-e2e-${bundle_name}.manifests.emptydir 82 | - name: subdir-data 83 | configMap: 84 | name: rukpak-e2e-${bundle_name}.manifests.subdir 85 | EOF 86 | 87 | kubectl wait --for=condition=Complete -n "${namespace}" jobs/kaniko-${bundle_name} --timeout=60s 88 | -------------------------------------------------------------------------------- /pkg/source/http.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "compress/gzip" 5 | "context" 6 | "crypto/tls" 7 | "fmt" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/nlepage/go-tarfs" 12 | corev1 "k8s.io/api/core/v1" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2" 16 | ) 17 | 18 | // http is a bundle source that sources bundles from the specified url. 19 | type HTTP struct { 20 | client.Reader 21 | SecretNamespace string 22 | } 23 | 24 | // Unpack unpacks a bundle by requesting the bundle contents from a specified URL 25 | func (b *HTTP) Unpack(ctx context.Context, bundle *rukpakv1alpha2.BundleDeployment) (*Result, error) { 26 | if bundle.Spec.Source.Type != rukpakv1alpha2.SourceTypeHTTP { 27 | return nil, fmt.Errorf("cannot unpack source type %q with %q unpacker", bundle.Spec.Source.Type, rukpakv1alpha2.SourceTypeHTTP) 28 | } 29 | 30 | url := bundle.Spec.Source.HTTP.URL 31 | action := fmt.Sprintf("%s %s", http.MethodGet, url) 32 | 33 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 34 | if err != nil { 35 | return nil, fmt.Errorf("create http request %q for bundle content: %v", action, err) 36 | } 37 | var userName, password string 38 | if bundle.Spec.Source.HTTP.Auth.Secret.Name != "" { 39 | userName, password, err = b.getCredentials(ctx, bundle) 40 | if err != nil { 41 | return nil, err 42 | } 43 | req.SetBasicAuth(userName, password) 44 | } 45 | 46 | httpClient := http.Client{Timeout: 10 * time.Second} 47 | if bundle.Spec.Source.HTTP.Auth.InsecureSkipVerify { 48 | tr := http.DefaultTransport.(*http.Transport).Clone() 49 | tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // nolint:gosec 50 | httpClient.Transport = tr 51 | } 52 | 53 | resp, err := httpClient.Do(req) 54 | if err != nil { 55 | return nil, fmt.Errorf("%s: http request for bundle content failed: %v", action, err) 56 | } 57 | defer resp.Body.Close() 58 | if resp.StatusCode != http.StatusOK { 59 | return nil, fmt.Errorf("%s: unexpected status %q", action, resp.Status) 60 | } 61 | 62 | tarReader, err := gzip.NewReader(resp.Body) 63 | if err != nil { 64 | return nil, err 65 | } 66 | fs, err := tarfs.New(tarReader) 67 | if err != nil { 68 | return nil, fmt.Errorf("error creating FS: %s", err) 69 | } 70 | 71 | message := generateMessage("http") 72 | 73 | return &Result{Bundle: fs, ResolvedSource: bundle.Spec.Source.DeepCopy(), State: StateUnpacked, Message: message}, nil 74 | } 75 | 76 | func (b *HTTP) Cleanup(_ context.Context, _ *rukpakv1alpha2.BundleDeployment) error { 77 | return nil 78 | } 79 | 80 | // getCredentials reads credentials from the secret specified in the bundle 81 | // It returns the username ane password when they are in the secret 82 | func (b *HTTP) getCredentials(ctx context.Context, bundle *rukpakv1alpha2.BundleDeployment) (string, string, error) { 83 | secret := &corev1.Secret{} 84 | err := b.Get(ctx, client.ObjectKey{Namespace: b.SecretNamespace, Name: bundle.Spec.Source.HTTP.Auth.Secret.Name}, secret) 85 | if err != nil { 86 | return "", "", err 87 | } 88 | userName := string(secret.Data["username"]) 89 | password := string(secret.Data["password"]) 90 | 91 | return userName, password, nil 92 | } 93 | -------------------------------------------------------------------------------- /cmd/crdvalidator/handlers/crd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package handlers 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | 24 | "github.com/go-logr/logr" 25 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 28 | 29 | "github.com/operator-framework/rukpak/cmd/crdvalidator/annotation" 30 | "github.com/operator-framework/rukpak/internal/crd" 31 | ) 32 | 33 | // +kubebuilder:webhook:path=/validate-crd,mutating=false,failurePolicy=fail,groups="",resources=customresourcedefinitions,verbs=create;update,versions=v1,name=crd-validation-webhook.io 34 | 35 | // CrdValidator houses a client, decoder and Handle function for ensuring 36 | // that a CRD create/update request is safe 37 | type CrdValidator struct { 38 | log logr.Logger 39 | client client.Client 40 | decoder admission.Decoder 41 | } 42 | 43 | func NewCrdValidator(log logr.Logger, client client.Client) CrdValidator { 44 | return CrdValidator{ 45 | log: log.V(1).WithName("crdhandler"), // Default to non-verbose logs 46 | client: client, 47 | decoder: admission.NewDecoder(client.Scheme()), 48 | } 49 | } 50 | 51 | // Handle takes an incoming CRD create/update request and confirms that it is 52 | // a safe upgrade based on the crd.Validate() function call 53 | func (cv *CrdValidator) Handle(ctx context.Context, req admission.Request) admission.Response { 54 | incomingCrd := &apiextensionsv1.CustomResourceDefinition{} 55 | if err := cv.decoder.Decode(req, incomingCrd); err != nil { 56 | message := fmt.Sprintf("failed to decode CRD %q", req.Name) 57 | cv.log.V(0).Error(err, message) 58 | return admission.Errored(http.StatusBadRequest, fmt.Errorf("%s: %v", message, err)) 59 | } 60 | 61 | // Check if the request should get validated 62 | if disabled(incomingCrd) { 63 | return admission.Allowed("") 64 | } 65 | 66 | if err := crd.Validate(ctx, cv.client, incomingCrd); err != nil { 67 | message := fmt.Sprintf( 68 | "failed to validate safety of %s for CRD %q (NOTE: to disable this validation, set the %q annotation to %q): %s", 69 | req.Operation, req.Name, annotation.ValidationKey, annotation.Disabled, err) 70 | cv.log.V(0).Info(message) 71 | return admission.Denied(message) 72 | } 73 | 74 | cv.log.Info("admission allowed for %s of CRD %q", req.Name, req.Operation) 75 | return admission.Allowed("") 76 | } 77 | 78 | // disabled takes a CRD and checks its content to see crdvalidator 79 | // is disabled explicitly 80 | func disabled(crd *apiextensionsv1.CustomResourceDefinition) bool { 81 | // Check if the annotation to disable validation is set 82 | return crd.GetAnnotations()[annotation.ValidationKey] == annotation.Disabled 83 | } 84 | -------------------------------------------------------------------------------- /cmd/unpack/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "compress/gzip" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/fs" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | 15 | "github.com/spf13/cobra" 16 | "k8s.io/apimachinery/pkg/util/sets" 17 | 18 | "github.com/operator-framework/rukpak/internal/version" 19 | ) 20 | 21 | func main() { 22 | var bundleDir string 23 | var rukpakVersion bool 24 | 25 | skipRootPaths := sets.NewString( 26 | "/dev", 27 | "/etc", 28 | "/proc", 29 | "/product_name", 30 | "/product_uuid", 31 | "/sys", 32 | "/bin", 33 | ) 34 | cmd := &cobra.Command{ 35 | Use: "unpack", 36 | Args: cobra.ExactArgs(0), 37 | RunE: func(cmd *cobra.Command, _ []string) error { 38 | if rukpakVersion { 39 | fmt.Println(version.String()) 40 | os.Exit(0) 41 | } 42 | var err error 43 | bundleDir, err = filepath.Abs(bundleDir) 44 | if err != nil { 45 | log.Fatalf("get absolute path of bundle directory %q: %v", bundleDir, err) 46 | } 47 | 48 | bundleFS := os.DirFS(bundleDir) 49 | buf := &bytes.Buffer{} 50 | gzw := gzip.NewWriter(buf) 51 | tw := tar.NewWriter(gzw) 52 | if err := fs.WalkDir(bundleFS, ".", func(path string, d fs.DirEntry, err error) error { 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if d.Type()&os.ModeSymlink != 0 { 58 | return nil 59 | } 60 | if bundleDir == "/" { 61 | // If bundleDir is the filesystem root, skip some known unrelated directories 62 | fullPath := filepath.Join(bundleDir, path) 63 | if skipRootPaths.Has(fullPath) { 64 | return filepath.SkipDir 65 | } 66 | } 67 | info, err := d.Info() 68 | if err != nil { 69 | return fmt.Errorf("get file info for %q: %v", path, err) 70 | } 71 | 72 | h, err := tar.FileInfoHeader(info, "") 73 | if err != nil { 74 | return fmt.Errorf("build tar file info header for %q: %v", path, err) 75 | } 76 | h.Uid = 0 77 | h.Gid = 0 78 | h.Uname = "" 79 | h.Gname = "" 80 | h.Name = path 81 | 82 | if err := tw.WriteHeader(h); err != nil { 83 | return fmt.Errorf("write tar header for %q: %v", path, err) 84 | } 85 | if d.IsDir() { 86 | return nil 87 | } 88 | f, err := bundleFS.Open(path) 89 | if err != nil { 90 | return fmt.Errorf("open file %q: %v", path, err) 91 | } 92 | if _, err := io.Copy(tw, f); err != nil { 93 | return fmt.Errorf("write tar data for %q: %v", path, err) 94 | } 95 | return nil 96 | }); err != nil { 97 | log.Fatalf("generate tar.gz for bundle dir %q: %v", bundleDir, err) 98 | } 99 | if err := tw.Close(); err != nil { 100 | log.Fatal(err) 101 | } 102 | if err := gzw.Close(); err != nil { 103 | log.Fatal(err) 104 | } 105 | 106 | bundleMap := map[string]interface{}{ 107 | "content": buf.Bytes(), 108 | } 109 | enc := json.NewEncoder(os.Stdout) 110 | if err := enc.Encode(bundleMap); err != nil { 111 | log.Fatalf("encode bundle map as JSON: %v", err) 112 | } 113 | return nil 114 | }, 115 | } 116 | cmd.Flags().StringVar(&bundleDir, "bundle-dir", "", "directory in which the bundle can be found") 117 | cmd.Flags().BoolVar(&rukpakVersion, "version", false, "displays rukpak version information") 118 | 119 | if err := cmd.Execute(); err != nil { 120 | log.Fatal(err) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pkg/helm-operator-plugins/predicate/depedent.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Operator-SDK Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package predicate 18 | 19 | import ( 20 | "reflect" 21 | 22 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | logf "sigs.k8s.io/controller-runtime/pkg/log" 26 | crtpredicate "sigs.k8s.io/controller-runtime/pkg/predicate" 27 | ) 28 | 29 | var log = logf.Log.WithName("predicate") 30 | 31 | type GenerationChangedPredicate = crtpredicate.GenerationChangedPredicate 32 | 33 | // DependentPredicateFuncs returns functions defined for filtering events 34 | func DependentPredicateFuncs[T client.Object]() crtpredicate.TypedFuncs[T] { 35 | dependentPredicate := crtpredicate.TypedFuncs[T]{ 36 | // We don't need to reconcile dependent resource creation events 37 | // because dependent resources are only ever created during 38 | // reconciliation. Another reconcile would be redundant. 39 | CreateFunc: func(e event.TypedCreateEvent[T]) bool { 40 | o := e.Object 41 | log.V(1).Info("Skipping reconciliation for dependent resource creation", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GetObjectKind().GroupVersionKind().GroupVersion(), "kind", o.GetObjectKind().GroupVersionKind().Kind) 42 | return false 43 | }, 44 | // Reconcile when a dependent resource is deleted so that it can be 45 | // recreated. 46 | DeleteFunc: func(e event.TypedDeleteEvent[T]) bool { 47 | o := e.Object 48 | log.V(1).Info("Reconciling due to dependent resource deletion", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GetObjectKind().GroupVersionKind().GroupVersion(), "kind", o.GetObjectKind().GroupVersionKind().Kind) 49 | return true 50 | }, 51 | 52 | // Don't reconcile when a generic event is received for a dependent 53 | GenericFunc: func(e event.TypedGenericEvent[T]) bool { 54 | o := e.Object 55 | log.V(1).Info("Skipping reconcile due to generic event", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GetObjectKind().GroupVersionKind().GroupVersion(), "kind", o.GetObjectKind().GroupVersionKind().Kind) 56 | return false 57 | }, 58 | 59 | // Reconcile when a dependent resource is updated, so that it can 60 | // be patched back to the resource managed by the CR, if 61 | // necessary. Ignore updates that only change the status and 62 | // resourceVersion. 63 | UpdateFunc: func(e event.TypedUpdateEvent[T]) bool { 64 | oldObj := e.ObjectOld.DeepCopyObject().(*unstructured.Unstructured) 65 | newObj := e.ObjectNew.DeepCopyObject().(*unstructured.Unstructured) 66 | 67 | delete(oldObj.Object, "status") 68 | delete(newObj.Object, "status") 69 | oldObj.SetResourceVersion("") 70 | newObj.SetResourceVersion("") 71 | 72 | if reflect.DeepEqual(oldObj.Object, newObj.Object) { 73 | return false 74 | } 75 | log.V(1).Info("Reconciling due to dependent resource update", "name", newObj.GetName(), "namespace", newObj.GetNamespace(), "apiVersion", newObj.GroupVersionKind().GroupVersion(), "kind", newObj.GroupVersionKind().Kind) 76 | return true 77 | }, 78 | } 79 | 80 | return dependentPredicate 81 | } 82 | -------------------------------------------------------------------------------- /.bingo/bingo.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 2 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 3 | github.com/bwplotka/bingo v0.8.0 h1:Cx9eQb+ed9aU7sbrmZagomKx+wYor9y5z5HM91bvp1U= 4 | github.com/bwplotka/bingo v0.8.0/go.mod h1:eXPFwhZ92mmOUBk6F7aKcAJoq8HX88Ju3wLZKwtNKEw= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 7 | github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs= 8 | github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI= 9 | github.com/frankban/quicktest v1.13.1/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= 10 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 11 | github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= 12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 13 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 17 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 18 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 19 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 20 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 21 | github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 22 | golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= 23 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 24 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 25 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 27 | golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY= 29 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 31 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 32 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 33 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 34 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 35 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 38 | mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= 39 | mvdan.cc/sh/v3 v3.4.3 h1:zbuKH7YH9cqU6PGajhFFXZY7dhPXcDr55iN/cUAqpuw= 40 | mvdan.cc/sh/v3 v3.4.3/go.mod h1:p/tqPPI4Epfk2rICAe2RoaNd8HBSJ8t9Y2DA9yQlbzY= 41 | -------------------------------------------------------------------------------- /docs/concepts/bundle-immutability.md: -------------------------------------------------------------------------------- 1 | # Bundle Immutability 2 | 3 | ## Overview 4 | 5 | A Bundle object, once accepted by the api-server, is considered an immutable artifact by the rest of the RukPak system. 6 | This behavior is meant to enforce the notion that a Bundle represents some unique, static piece of content that should 7 | be sourced onto the cluster. A user can have confidence, therefore, that a particular Bundle is pointing to a specific 8 | set of manifests, and cannot be updated without creating a new Bundle. This property is true for both standalone Bundles 9 | and dynamic Bundles created via an embedded BundleTemplate. 10 | 11 | Bundle immutability is enforced via the core RukPak webhook. This webhook watches Bundle events, and for any update to a 12 | Bundle, checks whether the spec of the existing Bundle is semantically equal to that in the proposed updated Bundle. If 13 | they are not equal, the update is rejected by the webhook. Note that other Bundle fields, such as metadata or status, 14 | can and will be updated during the Bundle's lifecycle -- it's only the spec that is considered immutable. 15 | 16 | ## Example 17 | 18 | Applying a Bundle and then attempting to update its spec should fail. Let's see this in action by first creating a 19 | Bundle: 20 | 21 | ```console 22 | kubectl apply -f -<> /auth/htpasswd" 83 | terminationMessagePolicy: FallbackToLogsOnError 84 | volumeMounts: 85 | - name: auth-vol 86 | mountPath: "/auth" 87 | containers: 88 | - name: registry 89 | image: registry:2 90 | volumeMounts: 91 | - name: certs-vol 92 | mountPath: "/certs" 93 | - name: auth-vol 94 | mountPath: "/auth" 95 | readOnly: true 96 | env: 97 | - name: REGISTRY_HTTP_TLS_CERTIFICATE 98 | value: "/certs/tls.crt" 99 | - name: REGISTRY_HTTP_TLS_KEY 100 | value: "/certs/tls.key" 101 | - name: REGISTRY_AUTH 102 | value: "htpasswd" 103 | - name: REGISTRY_AUTH_HTPASSWD_REALM 104 | value: "Registry Realm" 105 | - name: REGISTRY_AUTH_HTPASSWD_PATH 106 | value: "/auth/htpasswd" 107 | volumes: 108 | - name: certs-vol 109 | secret: 110 | secretName: ${namespace}-registry-secure 111 | - name: auth-vol 112 | emptyDir: {} 113 | --- 114 | apiVersion: v1 115 | kind: Service 116 | metadata: 117 | name: ${name}-secure 118 | namespace: ${namespace} 119 | spec: 120 | selector: 121 | app: registry-secure 122 | ports: 123 | - port: 5000 124 | targetPort: 5000 125 | EOF 126 | 127 | kubectl create secret docker-registry "secureregistrysecret" --docker-server=${name}-secure.${namespace}.svc.cluster.local:5000 --docker-username="myuser" --docker-password="mypasswd" --docker-email="email@foo.com" -n rukpak-system 128 | kubectl create secret docker-registry "secureregistrysecret" --docker-server=${name}-secure.${namespace}.svc.cluster.local:5000 --docker-username="myuser" --docker-password="mypasswd" --docker-email="email@foo.com" -n rukpak-e2e 129 | kubectl wait --for=condition=Available -n "${namespace}" "deploy/${name}-secure" --timeout=60s 130 | -------------------------------------------------------------------------------- /.bingo/kind.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= 2 | github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= 4 | github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= 8 | github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 9 | github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= 10 | github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= 11 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 12 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 13 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 17 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 18 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 19 | github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= 20 | github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 21 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 22 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 23 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 24 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 25 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= 26 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= 27 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 28 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 29 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 30 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 34 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 35 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 36 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg= 38 | sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk= 39 | sigs.k8s.io/kind v0.17.0 h1:CScmGz/wX66puA06Gj8OZb76Wmk7JIjgWf5JDvY7msM= 40 | sigs.k8s.io/kind v0.17.0/go.mod h1:Qqp8AiwOlMZmJWs37Hgs31xcbiYXjtXlRBSftcnZXQk= 41 | sigs.k8s.io/kind v0.22.0 h1:z/+yr/azoOfzsfooqRsPw1wjJlqT/ukXP0ShkHwNlsI= 42 | sigs.k8s.io/kind v0.22.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs= 43 | sigs.k8s.io/kind v0.23.0 h1:8fyDGWbWTeCcCTwA04v4Nfr45KKxbSPH1WO9K+jVrBg= 44 | sigs.k8s.io/kind v0.23.0/go.mod h1:ZQ1iZuJLh3T+O8fzhdi3VWcFTzsdXtNv2ppsHc8JQ7s= 45 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 46 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 47 | -------------------------------------------------------------------------------- /.bingo/ginkgo.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 3 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= 7 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 8 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 9 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 10 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 11 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 12 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 13 | github.com/onsi/ginkgo/v2 v2.8.3 h1:RpbK1G8nWPNaCVFBWsOGnEQQGgASi6b8fxcWBvDYjxQ= 14 | github.com/onsi/ginkgo/v2 v2.8.3/go.mod h1:6OaUA8BCi0aZfmzYT/q9AacwTzDpNbxILUT+TlBq6MY= 15 | github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= 16 | github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= 17 | github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= 18 | github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= 19 | github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= 20 | github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= 21 | github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= 22 | github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= 23 | github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= 24 | github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 25 | github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= 26 | github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 29 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 30 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 31 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 33 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 34 | golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= 35 | golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 36 | golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= 37 | golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 38 | golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= 39 | golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 40 | golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= 41 | golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 45 | -------------------------------------------------------------------------------- /docs/sources/image.md: -------------------------------------------------------------------------------- 1 | # Image source 2 | 3 | ## Summary 4 | 5 | The image source provides the contents in a container image as the source of the bundle. The `source.type` for the image source is `image`. 6 | When creating a image source, a reference to an URL of the image is specified. It is expected that a proper format of bundle contents is present 7 | in the image. 8 | 9 | ## Example 10 | 11 | For example, below is a minimal example of a Dockerfile that builds a bundle for image source from a directory 12 | containing bundle contents. 13 | 14 | ```dockerfile 15 | FROM scratch 16 | COPY "Bundle contents directory" "File location for the bundle format" 17 | ``` 18 | 19 | Build the image using a container tool like docker or podman. Use an image tag that references a repository that you 20 | have push access to. For example, 21 | 22 | ```bash 23 | docker build -f Dockerfile.example -t quay.io/operator-framework/rukpak:example . 24 | ``` 25 | 26 | Push the image to the remote registry 27 | 28 | ```bash 29 | docker push quay.io/operator-framework/rukpak:example 30 | ``` 31 | 32 | The bundle source is referred in the following Bundle Deployment example. 33 | ```yaml 34 | apiVersion: core.rukpak.io/v1alpha1 35 | kind: BundleDeployment 36 | metadata: 37 | name: my-bundle 38 | spec: 39 | provisionerClassName: core-rukpak-io-plain 40 | template: 41 | metadata: 42 | labels: 43 | app: my-bundle 44 | spec: 45 | source: 46 | type: image 47 | image: 48 | ref: quay.io/operator-framework/rukpak:example 49 | provisionerClassName: core-rukpak-io-plain 50 | ``` 51 | 52 | ## Private image registries 53 | 54 | A Bundle can reference content in a private image registry by creating an `pullSecret` in the namespace that the provisioner is deployed. 55 | 56 | ### Methods 57 | 58 | Create the secret for quay.io registry 59 | 60 | ```bash 61 | kubectl create secret docker-registry mysecret --docker-server=quay.io --docker-username="your user name" --docker-password="your password" --docker-email="your e-mail adress" -n rukpak-system 62 | ``` 63 | 64 | Use the secret to pull the private image 65 | 66 | #### Method 1: Create a Bundle referencing a private image registry 67 | 68 | ```yaml 69 | apiVersion: core.rukpak.io/v1alpha1 70 | kind: BundleDeployment 71 | metadata: 72 | name: my-bundle 73 | spec: 74 | provisionerClassName: core-rukpak-io-plain 75 | template: 76 | metadata: 77 | labels: 78 | app: my-bundle 79 | spec: 80 | source: 81 | type: image 82 | image: 83 | ref: quay.io/my-registry/rukpak:example 84 | pullSecret: mysecret 85 | provisionerClassName: core-rukpak-io-plain 86 | ``` 87 | 88 | #### Method 2: Add the secret to the `imagePullSecrets` in the `default` service account in the provisioner deployed namespace 89 | 90 | ```bash 91 | kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "mysecret"}]}' -n rukpak-system 92 | ``` 93 | * This command replaces the secrets already in the `imagePullSecrets`. To add the secret to the existing secrets, add the secret in the imagePullSecrets array of the existing secrets like `imagePullSecrets": [{"name": "mysecret"}, {"name": "existing_secret1"}, {"name": "existing_secret2"}]` 94 | 95 | ## Technical Details 96 | 97 | * The root-level / directory in the container image is a bundle root directory of the bundle. 98 | 99 | * The image source launches a pod to ensure that the bundle image is pulled using the CRI, which is beneficial because it means bundle 100 | images are pulled with the exact same set of credentials, proxies, and other configurations that would be accessible to kubelet for any other workload images. 101 | 102 | * The pod must be schedulable in the namespace in which the provisioner using the image source is running. There are implications with PSA which can cause 103 | bundle images to fail to unpack. To avoid unpack failures and ensure widest compatibility with various provisioners, bundle image authors should ensure that 104 | bundle images can be scheduled in a namespace with the restricted mode enforced. Bundle directory hierarchies in images should be traversable/readable by arbitrary users. --------------------------------------------------------------------------------