diff --git a/cmd/operator-sdk/cli/cli.go b/cmd/operator-sdk/cli/cli.go index ce7f98b360..b38c5ffbb2 100644 --- a/cmd/operator-sdk/cli/cli.go +++ b/cmd/operator-sdk/cli/cli.go @@ -26,6 +26,7 @@ import ( "github.com/operator-framework/operator-sdk/cmd/operator-sdk/scorecard" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/version" "github.com/operator-framework/operator-sdk/internal/flags" + ansiblev1 "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1" golangv2 "github.com/operator-framework/operator-sdk/internal/plugins/golang/v2" helmv1 "github.com/operator-framework/operator-sdk/internal/plugins/helm/v1" "github.com/operator-framework/operator-sdk/internal/util/projutil" @@ -65,6 +66,7 @@ func GetPluginsCLIAndRoot() (cli.CLI, *cobra.Command) { cli.WithPlugins( &golangv2.Plugin{}, &helmv1.Plugin{}, + &ansiblev1.Plugin{}, ), cli.WithDefaultPlugins( &golangv2.Plugin{}, diff --git a/hack/tests/e2e-ansible-molecule.sh b/hack/tests/e2e-ansible-molecule.sh index a4037dfa9a..78109ff8b6 100755 --- a/hack/tests/e2e-ansible-molecule.sh +++ b/hack/tests/e2e-ansible-molecule.sh @@ -19,45 +19,46 @@ ansible-galaxy collection install community.kubernetes setup_envs $tmp_sdk_root -remove_prereqs() { - header_text "Deleting resources" - kubectl delete --wait=true --ignore-not-found=true --timeout=60s -f "$OPERATORDIR/deploy/crds/ansible.example.com_memcacheds_crd.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/service_account.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/role.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/role_binding.yaml" -} - pushd "$TMPDIR" header_text "Creating memcached-operator" -operator-sdk new memcached-operator \ - --api-version=ansible.example.com/v1alpha1 \ - --kind=Memcached \ - --type=ansible \ - --generate-playbook +mkdir memcached-operator +pushd memcached-operator +operator-sdk init --plugins ansible.sdk.operatorframework.io/v1 \ + --domain example.com \ + --group ansible \ + --version v1alpha1 \ + --kind Memcached \ + --generate-playbook \ + --generate-role header_text "Replacing operator contents" -cp "$ROOTDIR/test/ansible-memcached/tasks.yml" memcached-operator/roles/memcached/tasks/main.yml -cp "$ROOTDIR/test/ansible-memcached/defaults.yml" memcached-operator/roles/memcached/defaults/main.yml -cp "$ROOTDIR/test/ansible-memcached/verify.yml" memcached-operator/molecule/default/verify.yml -cp "$ROOTDIR/test/ansible-memcached/molecule.yml" memcached-operator/molecule/test-local/molecule.yml -cp -a "$ROOTDIR/test/ansible-memcached/memfin" memcached-operator/roles/ -cp -a "$ROOTDIR/test/ansible-memcached/secret" memcached-operator/roles/ -cat "$ROOTDIR/test/ansible-memcached/watches-finalizer.yaml" >> memcached-operator/watches.yaml -cat "$ROOTDIR/test/ansible-memcached/prepare-test-image.yml" >> memcached-operator/molecule/test-local/prepare.yml +cp "$ROOTDIR/test/ansible-memcached/tasks.yml" roles/memcached/tasks/main.yml +cp "$ROOTDIR/test/ansible-memcached/defaults.yml" roles/memcached/defaults/main.yml +cp "$ROOTDIR/test/ansible-memcached/memcached_test.yml" molecule/default/tasks/memcached_test.yml +cp -a "$ROOTDIR/test/ansible-memcached/memfin" roles/ +cp -a "$ROOTDIR/test/ansible-memcached/secret" roles/ +marker=$(tail -n1 watches.yaml) +sed -i'.bak' -e '$ d' watches.yaml;rm -f watches.yaml.bak +cat "$ROOTDIR/test/ansible-memcached/watches-finalizer.yaml" >> watches.yaml header_text "Append v1 kind to watches to test watching already registered GVK" -cat "$ROOTDIR/test/ansible-memcached/watches-v1-kind.yaml" >> memcached-operator/watches.yaml +cat "$ROOTDIR/test/ansible-memcached/watches-v1-kind.yaml" >> watches.yaml +echo $marker >> watches.yaml +sed -i'.bak' -e '/- secrets/a \ \ - services' config/rbac/role.yaml; rm -f config/rbac/role.yaml.bak -header_text "Test local" -pushd memcached-operator -sed -i".bak" -E -e 's/(FROM quay.io\/operator-framework\/ansible-operator)(:.*)?/\1:dev/g' build/Dockerfile; rm -f build/Dockerfile.bak +header_text "Test in kind" +sed -i".bak" -E -e 's/(FROM quay.io\/operator-framework\/ansible-operator)(:.*)?/\1:dev/g' Dockerfile; rm -f Dockerfile.bak OPERATORDIR="$(pwd)" -TEST_CLUSTER_PORT=24443 TEST_OPERATOR_NAMESPACE=default molecule test -s test-local - -remove_prereqs +make kustomize +if [ -f ./bin/kustomize ] ; then + KUSTOMIZE="$(realpath ./bin/kustomize)" +else + KUSTOMIZE="$(which kustomize)" +fi +KUSTOMIZE_PATH=${KUSTOMIZE} TEST_OPERATOR_NAMESPACE=default molecule test -s kind popd popd - +KUSTOMIZE_PATH=${KUSTOMIZE} header_text "Test Ansible Molecule scenarios" pushd "${ROOTDIR}/test/ansible" DEST_IMAGE="quay.io/example/ansible-test-operator:v0.0.1" diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index 617f86b57c..e8bb70e3b6 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -18,22 +18,16 @@ setup_envs $tmp_sdk_root deploy_operator() { header_text "Running deploy operator" - kubectl create -f "$OPERATORDIR/deploy/service_account.yaml" - kubectl create -f "$OPERATORDIR/deploy/role.yaml" - kubectl create -f "$OPERATORDIR/deploy/role_binding.yaml" - kubectl create -f "$OPERATORDIR/deploy/crds/ansible.example.com_memcacheds_crd.yaml" - kubectl create -f "$OPERATORDIR/deploy/crds/ansible.example.com_foos_crd.yaml" - kubectl create -f "$OPERATORDIR/deploy/operator.yaml" + IMG=$DEST_IMAGE make deploy + kubectl create clusterrolebinding memcached-operator-system-metrics-reader --clusterrole=memcached-operator-metrics-reader --serviceaccount=default:default } remove_operator() { + pushd $TMPDIR/memcached-operator header_text "Running remove operator" - kubectl delete --wait=true --ignore-not-found=true --timeout=2m -f "$OPERATORDIR/deploy/crds/ansible.example.com_memcacheds_crd.yaml" - kubectl delete --wait=true --ignore-not-found=true --timeout=2m -f "$OPERATORDIR/deploy/crds/ansible.example.com_foos_crd.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/operator.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/service_account.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/role.yaml" - kubectl delete --wait=true --ignore-not-found=true -f "$OPERATORDIR/deploy/role_binding.yaml" + kubectl delete --ignore-not-found clusterrolebinding memcached-operator-system-metrics-reader + make undeploy + popd } operator_logs() { @@ -42,46 +36,41 @@ operator_logs() { header_text "Getting events" kubectl get events header_text "Getting operator logs" - kubectl logs deployment/memcached-operator + kubectl logs deployment/memcached-operator-controller-manager -c manager } test_operator() { header_text "Testing operator metrics" - # kind has an issue with certain image registries (ex. redhat's), so use a - # different test pod image. - local metrics_test_image="fedora:latest" - header_text "wait for operator pod to run" - if ! timeout 1m kubectl rollout status deployment/memcached-operator; + if ! timeout 1m kubectl rollout status deployment/memcached-operator-controller-manager; then error_text "FAIL: Failed to run" operator_logs exit 1 fi - # TODO @asmacdo to uncomment once new kb layout is merged. - - # header_text "verify that metrics service was created" - # if ! timeout 60s bash -c -- "until kubectl get service/memcached-operator-metrics > /dev/null 2>&1; do sleep 1; done"; - # then - # error_text "FAIL: Failed to get metrics service" - # operator_logs - # exit 1 - # fi - - # TODO Add --metrics-addr flag to the ansible operator and default it to 8080. - - # header_text "verify that the metrics endpoint exists (Port 8383)" - # if ! timeout 1m bash -c -- "until kubectl run --attach --rm --restart=Never test-metrics --image=$metrics_test_image -- curl -sfo /dev/null http://memcached-operator-metrics:8383/metrics; do sleep 1; done"; - # then - # error_text "FAIL: Failed to verify that metrics endpoint exists" - # operator_logs - # exit 1 - # fi - + header_text "verify that metrics service was created" + if ! timeout 60s bash -c -- "until kubectl get service/memcached-operator-controller-manager-metrics-service > /dev/null 2>&1; do sleep 1; done"; + then + error_text "FAIL: Failed to get metrics service" + operator_logs + exit 1 + fi + + header_text "verify that the metrics endpoint exists" + serviceaccount_secret=$(kubectl get serviceaccounts default -n default -o jsonpath='{.secrets[0].name}') + token=$(kubectl get secret ${serviceaccount_secret} -n default -o jsonpath={.data.token} | base64 -d) + + # verify that the metrics endpoint exists + if ! timeout 60s bash -c -- "until kubectl run --attach --rm --restart=Never --namespace=default test-metrics --image=${METRICS_TEST_IMAGE} -- -sfkH \"Authorization: Bearer ${token}\" https://memcached-operator-controller-manager-metrics-service:8443/metrics; do sleep 1; done"; + then + error_text "Failed to verify that metrics endpoint exists" + operator_logs + exit 1 + fi header_text "create custom resource (Memcached CR)" - kubectl create -f deploy/crds/ansible.example.com_v1alpha1_memcached_cr.yaml + kubectl create -f config/samples/ansible_v1alpha1_memcached.yaml if ! timeout 60s bash -c -- 'until kubectl get deployment -l app=memcached | grep memcached; do sleep 1; done'; then error_text "FAIL: Failed to verify to create memcached Deployment" @@ -90,7 +79,7 @@ test_operator() { fi header_text "Wait for Operator Pod" - if ! timeout 60s bash -c -- "until kubectl get pod -l name=memcached-operator; do sleep 1; done" + if ! timeout 60s bash -c -- "until kubectl get pod -l control-plane=controller-manager; do sleep 1; done" then error_text "FAIL: Operator pod does not exist." operator_logs @@ -99,7 +88,7 @@ test_operator() { header_text "Ensure no liveness probe fail events" # We can't directly hit the endpoint, which is not publicly exposed. If k8s sees a failing endpoint, it will create a "Killing" event. - live_pod=$(kubectl get pod -l name=memcached-operator -o jsonpath="{..metadata.name}") + live_pod=$(kubectl get pod -l control-plane=controller-manager -o jsonpath="{..metadata.name}") if kubectl get events --field-selector involvedObject.name=$live_pod | grep Killing then error_text "FAIL: Operator pod killed due to failed liveness probe." @@ -117,7 +106,7 @@ test_operator() { fi header_text "Verify that config map requests skip the cache." - if ! kubectl logs deployment/memcached-operator | grep -e "Skipping cache lookup\".*"Path\":\"\/api\/v1\/namespaces\/default\/configmaps\/test-blacklist-watches\"; + if ! kubectl logs deployment/memcached-operator-controller-manager -c manager | grep -e "Skipping cache lookup\".*"Path\":\"\/api\/v1\/namespaces\/default\/configmaps\/test-blacklist-watches\"; then error_text "FAIL: test-blacklist-watches should not be accessible with the cache." operator_logs @@ -125,13 +114,13 @@ test_operator() { fi - # header_text "verify that metrics reflect cr creation" - # if ! timeout 60s bash -c -- "until kubectl run --attach --rm --restart=Never test-metrics --image=$metrics_test_image -- curl -sf http://memcached-operator-metrics:8383/metrics | grep example-memcached; do sleep 1; done"; - # then - # error_text "FAIL: Failed to verify custom resource metrics" - # operator_logs - # exit 1 - # fi + header_text "verify that metrics reflect cr creation" + if ! timeout 60s bash -c -- "until kubectl run --attach --rm --restart=Never --namespace=default test-metrics --image=${METRICS_TEST_IMAGE} -- -sfkH \"Authorization: Bearer ${token}\" https://memcached-operator-controller-manager-metrics-service:8443/metrics | grep memcached-sample; do sleep 1; done"; + then + error_text "Failed to verify that metrics reflect cr creation" + operator_logs + exit 1 + fi header_text "get memcached deploy by labels" memcached_deployment=$(kubectl get deployment -l app=memcached -o jsonpath="{..metadata.name}") @@ -147,7 +136,7 @@ test_operator() { trap_add 'kubectl delete --ignore-not-found configmap deleteme' EXIT header_text "delete custom resource (Memcached CR)" - kubectl delete -f ${OPERATORDIR}/deploy/crds/ansible.example.com_v1alpha1_memcached_cr.yaml --wait=true + kubectl delete -f ${OPERATORDIR}/config/samples/ansible_v1alpha1_memcached.yaml --wait=true header_text "if the finalizer did not delete the configmap..." if kubectl get configmap deleteme 2> /dev/null; then @@ -165,7 +154,7 @@ test_operator() { fi header_text "Ensure that no errors appear in the log" - if kubectl logs deployment/memcached-operator| grep -i error; + if kubectl logs deployment/memcached-operator-controller-manager -c manager| grep -i error; then error_text "FAIL: the operator log includes errors" operator_logs @@ -175,31 +164,43 @@ test_operator() { header_text "Creating and building the operator" pushd "$TMPDIR" -operator-sdk new memcached-operator \ - --api-version=ansible.example.com/v1alpha1 \ - --kind=Memcached \ - --type=ansible -cp "$ROOTDIR/test/ansible-memcached/tasks.yml" memcached-operator/roles/memcached/tasks/main.yml -cp "$ROOTDIR/test/ansible-memcached/defaults.yml" memcached-operator/roles/memcached/defaults/main.yml -cp -a "$ROOTDIR/test/ansible-memcached/memfin" memcached-operator/roles/ -cat "$ROOTDIR/test/ansible-memcached/watches-finalizer.yaml" >> memcached-operator/watches.yaml -# Append Foo kind to watches to test watching multiple Kinds -cat "$ROOTDIR/test/ansible-memcached/watches-foo-kind.yaml" >> memcached-operator/watches.yaml - - +mkdir memcached-operator pushd memcached-operator - +operator-sdk init --plugins ansible.sdk.operatorframework.io/v1 \ + --domain example.com \ + --group ansible \ + --version v1alpha1 \ + --kind Memcached \ + --generate-playbook \ + --generate-role +cp "$ROOTDIR/test/ansible-memcached/tasks.yml" roles/memcached/tasks/main.yml +cp "$ROOTDIR/test/ansible-memcached/defaults.yml" roles/memcached/defaults/main.yml +cp -a "$ROOTDIR/test/ansible-memcached/memfin" roles/ +marker=$(tail -n1 watches.yaml) +sed -i'.bak' -e '$ d' watches.yaml;rm -f watches.yaml.bak +cat "$ROOTDIR/test/ansible-memcached/watches-finalizer.yaml" >> watches.yaml +echo $marker >> watches.yaml header_text "Adding a second Kind to test watching multiple GVKs" -operator-sdk add crd --kind=Foo --api-version=ansible.example.com/v1alpha1 -sed -i".bak" -E -e 's/(FROM quay.io\/operator-framework\/ansible-operator)(:.*)?/\1:dev/g' build/Dockerfile; rm -f build/Dockerfile.bak -operator-sdk build "$DEST_IMAGE" +operator-sdk create api --kind=Foo --group ansible --version=v1alpha1 +sed -i".bak" -e 's/# FIXME.*/role: \/dev\/null/g' watches.yaml;rm -f watches.yaml.bak + +sed -i".bak" -E -e 's/(FROM quay.io\/operator-framework\/ansible-operator)(:.*)?/\1:dev/g' Dockerfile; rm -f Dockerfile.bak +IMG=$DEST_IMAGE make docker-build # If using a kind cluster, load the image into all nodes. load_image_if_kind "$DEST_IMAGE" -sed -i".bak" -E -e "s|REPLACE_IMAGE|$DEST_IMAGE|g" deploy/operator.yaml; rm -f deploy/operator.yaml.bak -sed -i".bak" -E -e 's|Always|Never|g' deploy/operator.yaml; rm -f deploy/operator.yaml.bak +make kustomize +if [ -f ./bin/kustomize ] ; then + KUSTOMIZE="$(realpath ./bin/kustomize)" +else + KUSTOMIZE="$(which kustomize)" +fi +pushd config/default +${KUSTOMIZE} edit set namespace default +popd + # kind has an issue with certain image registries (ex. redhat's), so use a # different test pod image. -METRICS_TEST_IMAGE="fedora:latest" +METRICS_TEST_IMAGE="curlimages/curl:latest" docker pull "$METRICS_TEST_IMAGE" # If using a kind cluster, load the metrics test image into all nodes. load_image_if_kind "$METRICS_TEST_IMAGE" @@ -209,7 +210,6 @@ OPERATORDIR="$(pwd)" trap_add 'remove_operator' EXIT deploy_operator test_operator -remove_operator popd popd diff --git a/internal/plugins/ansible/v1/api.go b/internal/plugins/ansible/v1/api.go new file mode 100644 index 0000000000..0f33b68464 --- /dev/null +++ b/internal/plugins/ansible/v1/api.go @@ -0,0 +1,146 @@ +/* +Copyright 2020 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ansible + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/pkg/model/config" + "sigs.k8s.io/kubebuilder/pkg/model/resource" + "sigs.k8s.io/kubebuilder/pkg/plugin" + "sigs.k8s.io/kubebuilder/pkg/plugin/scaffold" + + "github.com/operator-framework/operator-sdk/internal/kubebuilder/cmdutil" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds" +) + +const ( + groupFlag = "group" + versionFlag = "version" + kindFlag = "kind" + crdVersionFlag = "crd-version" + + crdVersionV1 = "v1" + crdVersionV1beta1 = "v1beta1" +) + +type createAPIPlugin struct { + config *config.Config + createOptions scaffolds.CreateOptions +} + +var ( + _ plugin.CreateAPI = &createAPIPlugin{} + _ cmdutil.RunOptions = &createAPIPlugin{} +) + +// UpdateContext injects documentation for the command +func (p *createAPIPlugin) UpdateContext(ctx *plugin.Context) { + ctx.Description = `Scaffold a Kubernetes API in which the controller is an Ansible role or playbook. + + - generates a Custom Resource Definition and sample + - Updates watches.yaml + - optionally generates Ansible Role tree + - optionally generates Ansible playbook + +` + ctx.Examples = fmt.Sprintf(`# Create a new API, without Ansible roles or playbooks + $ %s create api \ + --group=apps --version=v1alpha1 \ + --kind=AppService + + $ %s create api \ + --group=apps --version=v1alpha1 \ + --kind=AppService \ + --generate-role + + $ %s create api \ + --group=apps --version=v1alpha1 \ + --kind=AppService \ + --generate-playbook + + $ %s create api \ + --group=apps --version=v1alpha1 \ + --kind=AppService + --generate-playbook + --generate-role +`, + ctx.CommandName, + ctx.CommandName, + ctx.CommandName, + ctx.CommandName, + ) +} + +func (p *createAPIPlugin) BindFlags(fs *pflag.FlagSet) { + fs.SortFlags = false + + fs.StringVar(&p.createOptions.GVK.Group, groupFlag, "", "resource group") + fs.StringVar(&p.createOptions.GVK.Version, versionFlag, "", "resource version") + fs.StringVar(&p.createOptions.GVK.Kind, kindFlag, "", "resource kind") + fs.StringVar(&p.createOptions.CRDVersion, crdVersionFlag, crdVersionV1, "crd version to generate") + fs.BoolVarP(&p.createOptions.GeneratePlaybook, "generate-playbook", "", false, "Generate an Ansible playbook. If passed with --generate-role, the playbook will invoke the role.") + fs.BoolVarP(&p.createOptions.GenerateRole, "generate-role", "", false, "Generate an Ansible role skeleton.") +} + +func (p *createAPIPlugin) InjectConfig(c *config.Config) { + p.config = c +} + +func (p *createAPIPlugin) Run() error { + return cmdutil.Run(p) +} + +func (p *createAPIPlugin) Validate() error { + if p.createOptions.CRDVersion != crdVersionV1 && p.createOptions.CRDVersion != crdVersionV1beta1 { + return fmt.Errorf("value of --%s must be either %q or %q", crdVersionFlag, crdVersionV1, crdVersionV1beta1) + } + + if len(strings.TrimSpace(p.createOptions.GVK.Group)) == 0 { + return fmt.Errorf("value of --%s must not have empty value", groupFlag) + } + if len(strings.TrimSpace(p.createOptions.GVK.Version)) == 0 { + return fmt.Errorf("value of --%s must not have empty value", versionFlag) + } + if len(strings.TrimSpace(p.createOptions.GVK.Kind)) == 0 { + return fmt.Errorf("value of --%s must not have empty value", kindFlag) + } + + // Validate the resource. + r := resource.Options{ + Namespaced: true, + Group: p.createOptions.GVK.Group, + Version: p.createOptions.GVK.Version, + Kind: p.createOptions.GVK.Kind, + } + if err := r.Validate(); err != nil { + return err + } + + return nil +} + +func (p *createAPIPlugin) GetScaffolder() (scaffold.Scaffolder, error) { + return scaffolds.NewCreateAPIScaffolder(p.config, p.createOptions), nil +} + +func (p *createAPIPlugin) PostScaffold() error { + return nil +} diff --git a/internal/plugins/ansible/v1/constants/constants.go b/internal/plugins/ansible/v1/constants/constants.go new file mode 100644 index 0000000000..6a4bd2575e --- /dev/null +++ b/internal/plugins/ansible/v1/constants/constants.go @@ -0,0 +1,30 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package constants + +import ( + "path/filepath" +) + +const ( + FilePathSep = string(filepath.Separator) + RolesDir = "roles" + PlaybooksDir = "playbooks" + MoleculeDir = "molecule" + MoleculeDefaultDir = MoleculeDir + FilePathSep + "default" + MoleculeTestLocalDir = MoleculeDir + FilePathSep + "test-local" + MoleculeClusterDir = MoleculeDir + FilePathSep + "cluster" + MoleculeTemplatesDir = MoleculeDir + FilePathSep + "templates" +) diff --git a/internal/plugins/ansible/v1/init.go b/internal/plugins/ansible/v1/init.go new file mode 100644 index 0000000000..988f650fc7 --- /dev/null +++ b/internal/plugins/ansible/v1/init.go @@ -0,0 +1,153 @@ +/* +Copyright 2020 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ansible + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/kubebuilder/pkg/model/config" + "sigs.k8s.io/kubebuilder/pkg/plugin" + "sigs.k8s.io/kubebuilder/pkg/plugin/scaffold" + + "github.com/operator-framework/operator-sdk/internal/kubebuilder/cmdutil" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds" +) + +type initPlugin struct { + config *config.Config + apiPlugin createAPIPlugin + + doAPIScaffold bool + + // For help text. + commandName string +} + +var ( + _ plugin.Init = &initPlugin{} + _ cmdutil.RunOptions = &initPlugin{} +) + +// UpdateContext injects documentation for the command +func (p *initPlugin) UpdateContext(ctx *plugin.Context) { + ctx.Description = ` +Initialize a new Ansible-based operator project. + +Writes the following files +- a kubebuilder PROJECT file with the domain and project layout configuration +- a Makefile that provides an interface for building and managing the operator +- Kubernetes manifests and kustomize configuration +- a watches.yaml file that defines the mapping between APIs and Roles/Playbooks + +Optionally creates a new API, using the same flags as "create api" +` + ctx.Examples = fmt.Sprintf(` + # Scaffold a project with no API + $ %s init --plugins=%s --domain=my.domain \ + + # Invokes "create api" + $ %s init --plugins=%s \ + --domain=my.domain \ + --group=apps --version=v1alpha1 --kind=AppService + + $ %s init --plugins=%s \ + --domain=my.domain \ + --group=apps --version=v1alpha1 --kind=AppService \ + --generate-role + + $ %s init --plugins=%s \ + --domain=my.domain \ + --group=apps --version=v1alpha1 --kind=AppService \ + --generate-playbook + + $ %s init --plugins=%s \ + --domain=my.domain \ + --group=apps --version=v1alpha1 --kind=AppService \ + --generate-playbook \ + --generate-role +`, + ctx.CommandName, plugin.KeyFor(Plugin{}), + ctx.CommandName, plugin.KeyFor(Plugin{}), + ctx.CommandName, plugin.KeyFor(Plugin{}), + ctx.CommandName, plugin.KeyFor(Plugin{}), + ctx.CommandName, plugin.KeyFor(Plugin{}), + ) + p.commandName = ctx.CommandName +} + +func (p *initPlugin) BindFlags(fs *pflag.FlagSet) { + fs.SortFlags = false + fs.StringVar(&p.config.Domain, "domain", "my.domain", "domain for groups") + p.apiPlugin.BindFlags(fs) +} + +func (p *initPlugin) InjectConfig(c *config.Config) { + c.Layout = plugin.KeyFor(Plugin{}) + p.config = c + p.apiPlugin.config = p.config +} + +func (p *initPlugin) Run() error { + return cmdutil.Run(p) +} + +func (p *initPlugin) Validate() error { + // Check if the project name is a valid namespace according to k8s + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error to get the current path: %v", err) + } + projectName := filepath.Base(dir) + if err := validation.IsDNS1123Label(strings.ToLower(projectName)); err != nil { + return fmt.Errorf("project name (%s) is invalid: %v", projectName, err) + } + + defaultOpts := scaffolds.CreateOptions{CRDVersion: "v1"} + if !p.apiPlugin.createOptions.GVK.Empty() || p.apiPlugin.createOptions != defaultOpts { + p.doAPIScaffold = true + return p.apiPlugin.Validate() + } + return nil +} + +func (p *initPlugin) GetScaffolder() (scaffold.Scaffolder, error) { + var ( + apiScaffolder scaffold.Scaffolder + err error + ) + if p.doAPIScaffold { + apiScaffolder, err = p.apiPlugin.GetScaffolder() + if err != nil { + return nil, err + } + } + return scaffolds.NewInitScaffolder(p.config, apiScaffolder), nil +} + +func (p *initPlugin) PostScaffold() error { + if !p.doAPIScaffold { + fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) + } + + return nil +} diff --git a/internal/plugins/ansible/v1/plugin.go b/internal/plugins/ansible/v1/plugin.go new file mode 100644 index 0000000000..810d76423b --- /dev/null +++ b/internal/plugins/ansible/v1/plugin.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ansible + +import ( + "sigs.k8s.io/kubebuilder/pkg/model/config" + "sigs.k8s.io/kubebuilder/pkg/plugin" + + "github.com/operator-framework/operator-sdk/internal/plugins" +) + +const pluginName = "ansible" + plugins.DefaultNameQualifier + +var ( + supportedProjectVersions = []string{config.Version3Alpha} + pluginVersion = plugin.Version{Number: 1} +) + +var ( + _ plugin.Base = Plugin{} + _ plugin.InitPluginGetter = Plugin{} +) + +type Plugin struct { + initPlugin + createAPIPlugin +} + +func (Plugin) Name() string { return pluginName } +func (Plugin) Version() plugin.Version { return pluginVersion } +func (Plugin) SupportedProjectVersions() []string { return supportedProjectVersions } +func (p Plugin) GetInitPlugin() plugin.Init { return &p.initPlugin } +func (p Plugin) GetCreateAPIPlugin() plugin.CreateAPI { return &p.createAPIPlugin } diff --git a/internal/plugins/ansible/v1/scaffolds/api.go b/internal/plugins/ansible/v1/scaffolds/api.go new file mode 100644 index 0000000000..44f377125f --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/api.go @@ -0,0 +1,130 @@ +/* +Copyright 2019 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scaffolds + +import ( + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/kubebuilder/pkg/model" + "sigs.k8s.io/kubebuilder/pkg/model/config" + "sigs.k8s.io/kubebuilder/pkg/model/file" + "sigs.k8s.io/kubebuilder/pkg/model/resource" + "sigs.k8s.io/kubebuilder/pkg/plugin/scaffold" + + "github.com/operator-framework/operator-sdk/internal/kubebuilder/machinery" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/samples" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks" + ansibleroles "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/roles" +) + +var _ scaffold.Scaffolder = &apiScaffolder{} + +type CreateOptions struct { + GVK schema.GroupVersionKind + // CRDVersion is the version of the `apiextensions.k8s.io` API which will be used to generate the CRD. + CRDVersion string + GeneratePlaybook bool + GenerateRole bool +} + +type apiScaffolder struct { + config *config.Config + opts CreateOptions +} + +// NewCreateAPIScaffolder returns a new Scaffolder for project initialization operations +func NewCreateAPIScaffolder(config *config.Config, opts CreateOptions) scaffold.Scaffolder { + return &apiScaffolder{ + config: config, + opts: opts, + } +} + +func (s *apiScaffolder) newUniverse(r *resource.Resource) *model.Universe { + return model.NewUniverse( + model.WithConfig(s.config), + model.WithResource(r), + ) +} + +// Scaffold implements Scaffolder +func (s *apiScaffolder) Scaffold() error { + return s.scaffold() +} + +func (s *apiScaffolder) scaffold() error { + + resourceOptions := resource.Options{ + Group: s.opts.GVK.Group, + Version: s.opts.GVK.Version, + Kind: s.opts.GVK.Kind, + } + + if s.config.HasResource(resourceOptions.GVK()) { + return errors.New("the API resource already exists") + } + + // Check that the provided group can be added to the project + if !s.config.MultiGroup && len(s.config.Resources) != 0 && !s.config.HasGroup(resourceOptions.Group) { + return fmt.Errorf("multiple groups are not allowed by default, to enable multi-group visit %s", + "kubebuilder.io/migration/multi-group.html") + } + + resource := resourceOptions.NewResource(s.config, true) + s.config.AddResource(resource.GVK()) + + var createAPITemplates []file.Builder + createAPITemplates = append(createAPITemplates, + &rbac.CRDEditorRole{}, + &rbac.KustomizeUpdater{}, + + &crd.CRD{CRDVersion: s.opts.CRDVersion}, + &crd.Kustomization{}, + &samples.CR{}, + &templates.WatchesUpdater{GeneratePlaybook: s.opts.GeneratePlaybook, GenerateRole: s.opts.GenerateRole, PlaybooksDir: constants.PlaybooksDir}, + &mdefault.ResourceTest{}, + ) + if s.opts.GenerateRole { + createAPITemplates = append(createAPITemplates, + &ansibleroles.TasksMain{}, + &ansibleroles.DefaultsMain{}, + &ansibleroles.RoleFiles{}, + &ansibleroles.HandlersMain{}, + &ansibleroles.MetaMain{}, + &ansibleroles.RoleTemplates{}, + &ansibleroles.VarsMain{}, + &ansibleroles.Readme{}, + ) + } + + if s.opts.GeneratePlaybook { + createAPITemplates = append(createAPITemplates, + &playbooks.Playbook{GenerateRole: s.opts.GenerateRole}) + } + return machinery.NewScaffold().Execute( + s.newUniverse(resource), + createAPITemplates..., + ) +} diff --git a/internal/plugins/ansible/v1/scaffolds/init.go b/internal/plugins/ansible/v1/scaffolds/init.go new file mode 100644 index 0000000000..718d659023 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/init.go @@ -0,0 +1,127 @@ +/* +Copyright 2019 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scaffolds + +import ( + "sigs.k8s.io/kubebuilder/pkg/model" + "sigs.k8s.io/kubebuilder/pkg/model/config" + "sigs.k8s.io/kubebuilder/pkg/plugin/scaffold" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind" + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks" + ansibleroles "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/scaffolds/internal/templates/roles" + + "github.com/operator-framework/operator-sdk/internal/kubebuilder/machinery" +) + +const ( + // KustomizeVersion is the kubernetes-sigs/kustomize version to be used in the project + KustomizeVersion = "v3.5.4" + + imageName = "controller:latest" +) + +var _ scaffold.Scaffolder = &initScaffolder{} + +type initScaffolder struct { + config *config.Config + apiScaffolder scaffold.Scaffolder +} + +// NewInitScaffolder returns a new Scaffolder for project initialization operations +func NewInitScaffolder(config *config.Config, apiScaffolder scaffold.Scaffolder) scaffold.Scaffolder { + return &initScaffolder{ + config: config, + apiScaffolder: apiScaffolder, + } +} + +func (s *initScaffolder) newUniverse() *model.Universe { + return model.NewUniverse( + model.WithConfig(s.config), + ) +} + +// Scaffold implements Scaffolder +func (s *initScaffolder) Scaffold() error { + if err := s.scaffold(); err != nil { + return err + } + if s.apiScaffolder != nil { + return s.apiScaffolder.Scaffold() + } + return nil +} + +func (s *initScaffolder) scaffold() error { + return machinery.NewScaffold().Execute( + s.newUniverse(), + &templates.Dockerfile{}, + &templates.RequirementsYml{}, + &templates.Watches{}, + + &rbac.Kustomization{}, + &rbac.ClientClusterRole{}, + &rbac.AuthProxyRole{}, + &rbac.AuthProxyRoleBinding{}, + &rbac.AuthProxyService{}, + &rbac.LeaderElectionRole{}, + &rbac.LeaderElectionRoleBinding{}, + &rbac.Role{}, + &rbac.RoleBinding{}, + + &prometheus.Kustomization{}, + &prometheus.ServiceMonitor{}, + + &manager.Manager{Image: imageName}, + &manager.Kustomization{}, + + &kdefault.Kustomize{}, + &kdefault.AuthProxyPatch{}, + + &templates.Makefile{}, + &ansibleroles.Placeholder{}, + &playbooks.Placeholder{}, + + &mdefault.Converge{}, + &mdefault.Create{}, + &mdefault.Destroy{}, + &mdefault.Kustomize{}, + &mdefault.Molecule{}, + &mdefault.Prepare{}, + &mdefault.Verify{}, + &mkind.Converge{}, + &mkind.Create{}, + &mkind.Destroy{}, + &mkind.Molecule{}, + &pullpolicy.AlwaysPullPatch{}, + &pullpolicy.IfNotPresentPullPatch{}, + &pullpolicy.NeverPullPatch{}, + &testing.DebugLogsPatch{}, + &testing.Kustomization{}, + &testing.ManagerImage{}, + ) +} diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd/crd.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd/crd.go new file mode 100644 index 0000000000..ec7d5a6e63 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd/crd.go @@ -0,0 +1,117 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crd + +import ( + "errors" + "fmt" + "path/filepath" + + "github.com/kr/text" + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &CRD{} + +// CRD scaffolds a manifest for CRD sample. +type CRD struct { + file.TemplateMixin + file.ResourceMixin + + CRDVersion string +} + +// SetTemplateDefaults implements input.Template +func (f *CRD) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "crd", "bases", fmt.Sprintf("%s_%%[plural].yaml", f.Resource.Domain)) + } + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.IfExistsAction = file.Error + + if f.CRDVersion == "" { + f.CRDVersion = "v1" + } else if f.CRDVersion != "v1" && f.CRDVersion != "v1beta1" { + return errors.New("the CRD version value must be either 'v1' or 'v1beta1'") + } + f.TemplateBody = fmt.Sprintf(crdTemplate, + text.Indent(openAPIV3SchemaTemplate, " "), + text.Indent(openAPIV3SchemaTemplate, " "), + ) + return nil +} + +const crdTemplate = `--- +apiVersion: apiextensions.k8s.io/{{ .CRDVersion }} +kind: CustomResourceDefinition +metadata: + name: {{ .Resource.Plural }}.{{ .Resource.Domain }} +spec: + group: {{ .Resource.Domain }} + names: + kind: {{ .Resource.Kind }} + listKind: {{ .Resource.Kind }}List + plural: {{ .Resource.Plural }} + singular: {{ .Resource.Kind | lower }} + scope: Namespaced +{{- if eq .CRDVersion "v1beta1" }} + subresources: + status: {} + validation: +%s +{{- end }} + versions: + - name: {{ .Resource.Version }} +{{- if eq .CRDVersion "v1" }} + schema: +%s +{{- end }} + served: true + storage: true +{{- if eq .CRDVersion "v1" }} + subresources: + status: {} +{{- end }} +` + +const openAPIV3SchemaTemplate = `openAPIV3Schema: + description: {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of {{ .Resource.Kind }} + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: Status defines the observed state of {{ .Resource.Kind }} + type: object + x-kubernetes-preserve-unknown-fields: true + type: object +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd/kustomization.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd/kustomization.go new file mode 100644 index 0000000000..917cadddb0 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/crd/kustomization.go @@ -0,0 +1,87 @@ +/* +Copyright 2019 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package crd + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomization{} +var _ file.Inserter = &Kustomization{} + +// Kustomization scaffolds the kustomization file in manager folder. +type Kustomization struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "crd", "kustomization.yaml") + } + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.TemplateBody = fmt.Sprintf(kustomizationTemplate, + file.NewMarkerFor(f.Path, resourceMarker), + ) + + return nil +} + +const ( + resourceMarker = "crdkustomizeresource" +) + +// GetMarkers implements file.Inserter +func (f *Kustomization) GetMarkers() []file.Marker { + return []file.Marker{ + file.NewMarkerFor(f.Path, resourceMarker), + } +} + +const ( + resourceCodeFragment = `- bases/%s_%s.yaml +` +) + +// GetCodeFragments implements file.Inserter +func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { + fragments := make(file.CodeFragmentsMap, 3) + + // Generate resource code fragments + res := make([]string, 0) + res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.Domain, f.Resource.Plural)) + + // Only store code fragments in the map if the slices are non-empty + if len(res) != 0 { + fragments[file.NewMarkerFor(f.Path, resourceMarker)] = res + } + + return fragments +} + +var kustomizationTemplate = `# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +%s +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go new file mode 100644 index 0000000000..9491a79991 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go @@ -0,0 +1,68 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &AuthProxyPatch{} + +// AuthProxyPatch scaffolds the patch file for enabling +// prometheus metrics for manager Pod. +type AuthProxyPatch struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *AuthProxyPatch) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "default", "manager_auth_proxy_patch.yaml") + } + + f.TemplateBody = kustomizeAuthProxyPatchTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const kustomizeAuthProxyPatchTemplate = `# This patch inject a sidecar container which is a HTTP proxy for the +# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=10" + ports: + - containerPort: 8443 + name: https +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault/kustomization.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault/kustomization.go new file mode 100644 index 0000000000..1bfcdbc10a --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/kdefault/kustomization.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kdefault + +import ( + "os" + "path/filepath" + "strings" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomize{} + +// Kustomize scaffolds the Kustomization file for the default overlay +type Kustomize struct { + file.TemplateMixin + + // Prefix to use for name prefix customization + Prefix string +} + +// SetTemplateDefaults implements input.Template +func (f *Kustomize) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "default", "kustomization.yaml") + } + + f.TemplateBody = kustomizeTemplate + + f.IfExistsAction = file.Error + + if f.Prefix == "" { + // use directory name as prefix + dir, err := os.Getwd() + if err != nil { + return err + } + f.Prefix = strings.ToLower(filepath.Base(dir)) + } + + return nil +} + +const kustomizeTemplate = `# Adds namespace to all resources. +namespace: {{ .Prefix }}-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: {{ .Prefix }}- + +# Labels to add to all resources and selectors. +#commonLabels: +# someName: someValue + +bases: +- ../crd +- ../rbac +- ../manager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus + +patchesStrategicMerge: + # Protect the /metrics endpoint by putting it behind auth. + # If you want your controller-manager to expose the /metrics + # endpoint w/o any authn/z, please comment the following line. +- manager_auth_proxy_patch.yaml +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager/kustomization.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager/kustomization.go new file mode 100644 index 0000000000..84af806d0e --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager/kustomization.go @@ -0,0 +1,47 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomization{} + +// Kustomization scaffolds the Kustomization file in manager folder. +type Kustomization struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "manager", "kustomization.yaml") + } + + f.TemplateBody = kustomizeManagerTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const kustomizeManagerTemplate = `resources: +- manager.yaml +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager/manager.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager/manager.go new file mode 100644 index 0000000000..2a91d24f38 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/manager/manager.go @@ -0,0 +1,98 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manager + +import ( + "fmt" + "os" + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Manager{} + +// Manager scaffolds yaml config for the manager. +type Manager struct { + file.TemplateMixin + + // Image is controller manager image name + Image string + + // OperatorName will be used to create the pods + OperatorName string +} + +// SetTemplateDefaults implements input.Template +func (f *Manager) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "manager", "manager.yaml") + } + + f.TemplateBody = configTemplate + + if f.OperatorName == "" { + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error to get the current path: %v", err) + } + f.OperatorName = filepath.Base(dir) + } + return nil +} + +const configTemplate = `apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + labels: + control-plane: controller-manager + spec: + containers: + - name: manager + args: + - "--enable-leader-election" + - "--leader-election-id={{ .OperatorName }}" + - "--metrics-addr=127.0.0.1:8080" + image: {{ .Image }} + env: + - name: WATCH_NAMESPACE + value: "" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + terminationGracePeriodSeconds: 10 +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus/kustomization.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus/kustomization.go new file mode 100644 index 0000000000..a630cddcf2 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus/kustomization.go @@ -0,0 +1,45 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package prometheus + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomization{} + +// Kustomization scaffolds the kustomizaiton in the prometheus folder +type Kustomization struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "prometheus", "kustomization.yaml") + } + + f.TemplateBody = kustomizationTemplate + + return nil +} + +const kustomizationTemplate = `resources: +- monitor.yaml +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus/monitor.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus/monitor.go new file mode 100644 index 0000000000..03fe1f3a4f --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/prometheus/monitor.go @@ -0,0 +1,59 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package prometheus + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &ServiceMonitor{} + +// ServiceMonitor scaffolds an issuer CR and a certificate CR +type ServiceMonitor struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *ServiceMonitor) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "prometheus", "monitor.yaml") + } + + f.TemplateBody = serviceMonitorTemplate + + return nil +} + +const serviceMonitorTemplate = `--- +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https + selector: + matchLabels: + control-plane: controller-manager +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_client_clusterrole.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_client_clusterrole.go new file mode 100644 index 0000000000..c4a32ab496 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_client_clusterrole.go @@ -0,0 +1,50 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &ClientClusterRole{} + +// ClientClusterRole scaffolds the config/rbac/client_clusterrole.yaml file +type ClientClusterRole struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *ClientClusterRole) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "auth_proxy_client_clusterrole.yaml") + } + + f.TemplateBody = clientClusterRoleTemplate + + return nil +} + +const clientClusterRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: ["/metrics"] + verbs: ["get"] +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go new file mode 100644 index 0000000000..f05e776702 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &AuthProxyRole{} + +// AuthProxyRole scaffolds the config/rbac/auth_proxy_role.yaml file +type AuthProxyRole struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *AuthProxyRole) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "auth_proxy_role.yaml") + } + + f.TemplateBody = proxyRoleTemplate + + return nil +} + +const proxyRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: proxy-role +rules: +- apiGroups: ["authentication.k8s.io"] + resources: + - tokenreviews + verbs: ["create"] +- apiGroups: ["authorization.k8s.io"] + resources: + - subjectaccessreviews + verbs: ["create"] +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go new file mode 100644 index 0000000000..d0d43cf071 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &AuthProxyRoleBinding{} + +// AuthProxyRoleBinding scaffolds the config/rbac/auth_proxy_role_binding_rbac.yaml file +type AuthProxyRoleBinding struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *AuthProxyRoleBinding) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "auth_proxy_role_binding.yaml") + } + + f.TemplateBody = proxyRoleBindinggTemplate + + return nil +} + +const proxyRoleBindinggTemplate = `apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: proxy-role +subjects: +- kind: ServiceAccount + name: default + namespace: system +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go new file mode 100644 index 0000000000..31a66347de --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &AuthProxyService{} + +// AuthProxyService scaffolds the config/rbac/auth_proxy_service.yaml file +type AuthProxyService struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *AuthProxyService) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "auth_proxy_service.yaml") + } + + f.TemplateBody = authProxyServiceTemplate + + return nil +} + +const authProxyServiceTemplate = `apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/kustomization.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/kustomization.go new file mode 100644 index 0000000000..ae19ccfa54 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/kustomization.go @@ -0,0 +1,110 @@ +/* +Copyright 2019 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomization{} + +var rbacKustomizePath = filepath.Join("config", "rbac", "kustomization.yaml") + +const patch6902Marker = "patch6902" + +// Kustomization scaffolds the Kustomization file in rbac folder. +type Kustomization struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = rbacKustomizePath + } + + f.TemplateBody = fmt.Sprintf(kustomizeTemplate, + file.NewMarkerFor(f.Path, patch6902Marker), + ) + f.IfExistsAction = file.Error + + return nil +} + +type KustomizeUpdater struct { + file.TemplateMixin + file.ResourceMixin +} + +func (*KustomizeUpdater) GetIfExistsAction() file.IfExistsAction { + return file.Overwrite +} + +func (*KustomizeUpdater) GetPath() string { + return rbacKustomizePath +} + +func (f *KustomizeUpdater) GetMarkers() []file.Marker { + return []file.Marker{ + file.NewMarkerFor(rbacKustomizePath, patch6902Marker), + } +} + +func (f *KustomizeUpdater) GetCodeFragments() file.CodeFragmentsMap { + fragments := make(file.CodeFragmentsMap, 1) + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // Generate patch6902 fragments + patches := make([]string, 0) + patches = append(patches, f.Resource.Replacer().Replace(patch6902Fragment)) + + if len(patches) != 0 { + fragments[file.NewMarkerFor(rbacKustomizePath, patch6902Marker)] = patches + } + return fragments +} + +const kustomizeTemplate = `resources: + - role.yaml + - role_binding.yaml + - leader_election_role.yaml + - leader_election_role_binding.yaml + # Comment the following 4 lines if you want to disable + # the auth proxy (https://github.com/brancz/kube-rbac-proxy) + # which protects your /metrics endpoint. + - auth_proxy_service.yaml + - auth_proxy_role.yaml + - auth_proxy_role_binding.yaml + - auth_proxy_client_clusterrole.yaml +patchesJson6902: +%s +` +const patch6902Fragment = ` - target: + group: rbac.authorization.k8s.io + version: v1 + kind: ClusterRole + name: manager-role + path: patches/%[kind]_editor_role.yaml +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go new file mode 100644 index 0000000000..da9d8878f9 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go @@ -0,0 +1,69 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &LeaderElectionRole{} + +// LeaderElectionRole scaffolds the config/rbac/leader_election_role.yaml file +type LeaderElectionRole struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *LeaderElectionRole) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "leader_election_role.yaml") + } + + f.TemplateBody = leaderElectionRoleTemplate + + return nil +} + +const leaderElectionRoleTemplate = `# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go new file mode 100644 index 0000000000..875fff93fb --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &LeaderElectionRoleBinding{} + +// LeaderElectionRoleBinding scaffolds the config/rbac/leader_election_role_binding.yaml file +type LeaderElectionRoleBinding struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *LeaderElectionRoleBinding) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "leader_election_role_binding.yaml") + } + + f.TemplateBody = leaderElectionRoleBindingTemplate + + return nil +} + +const leaderElectionRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: default + namespace: system +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/patch_crd_editor.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/patch_crd_editor.go new file mode 100644 index 0000000000..8e5f5e2930 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/patch_crd_editor.go @@ -0,0 +1,72 @@ +/* +Copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &CRDEditorRole{} + +// CRDEditorRole scaffolds the config/rbac/_editor_role.yaml +type CRDEditorRole struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *CRDEditorRole) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "patches", "%[kind]_editor_role.yaml") + } + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.TemplateBody = crdRoleEditorTemplate + + return nil +} + +const crdRoleEditorTemplate = `--- +- op: add + path: /rules/- + value: + apiGroups: + - {{ .Resource.Domain }} + resources: + - {{ .Resource.Plural }} + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- op: add + path: /rules/- + value: + apiGroups: + - {{ .Resource.Domain }} + resources: + - {{ .Resource.Plural }}/status + verbs: + - get + - patch + - update +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/role.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/role.go new file mode 100644 index 0000000000..8dd8fe700a --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/role.go @@ -0,0 +1,77 @@ +// Copyright 2019 The Operator-SDK Authors +// Modifications copyright 2020 The Operator-SDK Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Role{} + +// Role scaffolds the config/rbac/auth_proxy_role.yaml file +type Role struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Role) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "role.yaml") + } + + f.TemplateBody = roleTemplate + + return nil +} + +const roleTemplate = `--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: manager-role +rules: +- apiGroups: + - "" + resources: + - secrets + - pods + - pods/exec + - pods/log + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/role_binding.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/role_binding.go new file mode 100644 index 0000000000..7afd1a317d --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/rbac/role_binding.go @@ -0,0 +1,57 @@ +/* +Copyright 2019 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &RoleBinding{} + +// RoleBinding scaffolds the config/rbac/role_binding.yaml file +type RoleBinding struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *RoleBinding) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "rbac", "role_binding.yaml") + } + + f.TemplateBody = roleBindingTemplate + + return nil +} + +const roleBindingTemplate = `--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: default + namespace: system +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/samples/cr_sample.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/samples/cr_sample.go new file mode 100644 index 0000000000..2dc60284e6 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/samples/cr_sample.go @@ -0,0 +1,76 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package samples + +import ( + "path/filepath" + "strings" + "text/template" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &CR{} +var _ file.UseCustomFuncMap = &CR{} + +// CR scaffolds a sample manifest for a CRD. +type CR struct { + file.TemplateMixin + file.ResourceMixin + + Spec string +} + +// SetTemplateDefaults implements input.Template +func (f *CR) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "samples", "%[group]_%[version]_%[kind].yaml") + } + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.IfExistsAction = file.Error + + if len(f.Spec) == 0 { + f.Spec = defaultSpecTemplate + } + + f.TemplateBody = crSampleTemplate + return nil +} + +func indent(spaces int, v string) string { + pad := strings.Repeat(" ", spaces) + return pad + strings.Replace(v, "\n", "\n"+pad, -1) +} + +// GetFuncMap implements file.UseCustomFuncMap +func (f *CR) GetFuncMap() template.FuncMap { + fm := file.DefaultFuncMap() + fm["indent"] = indent + return fm +} + +const defaultSpecTemplate = `foo: bar` + +const crSampleTemplate = `apiVersion: {{ .Resource.Domain }}/{{ .Resource.Version }} +kind: {{ .Resource.Kind }} +metadata: + name: {{ lower .Resource.Kind }}-sample +spec: +{{ .Spec | indent 2 }} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/debug_logs_patch.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/debug_logs_patch.go new file mode 100644 index 0000000000..ba74464c1d --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/debug_logs_patch.go @@ -0,0 +1,61 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testing + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &DebugLogsPatch{} + +// DebugLogsPatch scaffolds the patch file for enabling +// verbose logs during Ansible testing +type DebugLogsPatch struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *DebugLogsPatch) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "testing", "debug_logs_patch.yaml") + } + + f.TemplateBody = debugLogsPatchTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const debugLogsPatchTemplate = `--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + env: + - name: ANSIBLE_DEBUG_LOGS + value: "TRUE" +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/kustomization.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/kustomization.go new file mode 100644 index 0000000000..9d6175299a --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/kustomization.go @@ -0,0 +1,71 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testing + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomization{} + +// Kustomization scaffolds the kustomization file for use +// during Ansible testing +type Kustomization struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "testing", "kustomization.yaml") + } + + f.TemplateBody = KustomizationTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const KustomizationTemplate = `# Adds namespace to all resources. +namespace: osdk-test + +namePrefix: osdk- + +# Labels to add to all resources and selectors. +#commonLabels: +# someName: someValue + +patchesStrategicMerge: +- manager_image.yaml +- pull_policy/Never.yaml +- debug_logs_patch.yaml +- ../default/manager_auth_proxy_patch.yaml + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../crd +- ../rbac +- ../manager +images: +- name: testing + newName: testing-operator +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/manager_image.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/manager_image.go new file mode 100644 index 0000000000..7ae50f5ead --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/manager_image.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testing + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &ManagerImage{} + +// ManagerImage scaffolds the patch file for overriding the +// default image during Ansible testing +type ManagerImage struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *ManagerImage) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "testing", "manager_image.yaml") + } + + f.TemplateBody = managerImageTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const managerImageTemplate = `--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + image: testing +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/always_pull_patch.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/always_pull_patch.go new file mode 100644 index 0000000000..27612476f2 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/always_pull_patch.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pullpolicy + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &AlwaysPullPatch{} + +// AlwaysPullPatch scaffolds the patch file for overriding the +// default image pull policy during Ansible testing +type AlwaysPullPatch struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *AlwaysPullPatch) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "testing", "pull_policy", "Always.yaml") + } + + f.TemplateBody = alwaysPullPatchTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const alwaysPullPatchTemplate = `--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + imagePullPolicy: Always +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/ifnotpresent_pull_patch.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/ifnotpresent_pull_patch.go new file mode 100644 index 0000000000..f1a7543f1d --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/ifnotpresent_pull_patch.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pullpolicy + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &IfNotPresentPullPatch{} + +// IfNotPresentPullPatch scaffolds the patch file for overriding the +// default image pull policy during Ansible testing +type IfNotPresentPullPatch struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *IfNotPresentPullPatch) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "testing", "pull_policy", "IfNotPresent.yaml") + } + + f.TemplateBody = ifNotPresentPullPatchTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const ifNotPresentPullPatchTemplate = `--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + imagePullPolicy: IfNotPresent +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/never_pull_patch.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/never_pull_patch.go new file mode 100644 index 0000000000..f5000af87e --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/config/testing/pullpolicy/never_pull_patch.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pullpolicy + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &NeverPullPatch{} + +// NeverPullPatch scaffolds the patch file for overriding the +// default image pull policy during Ansible testing +type NeverPullPatch struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *NeverPullPatch) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "testing", "pull_policy", "Never.yaml") + } + + f.TemplateBody = neverPullPatchTemplate + + f.IfExistsAction = file.Error + + return nil +} + +const neverPullPatchTemplate = `--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + imagePullPolicy: Never +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/dockerfile.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/dockerfile.go new file mode 100644 index 0000000000..85737baf26 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/dockerfile.go @@ -0,0 +1,59 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package templates + +import ( + "strings" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" + "github.com/operator-framework/operator-sdk/version" +) + +var _ file.Template = &Dockerfile{} + +// Dockerfile scaffolds a Dockerfile for building a main +type Dockerfile struct { + file.TemplateMixin + ImageTag string + + RolesDir string + PlaybooksDir string +} + +// SetTemplateDefaults implements input.Template +func (f *Dockerfile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Dockerfile" + } + + f.TemplateBody = dockerfileTemplate + f.RolesDir = constants.RolesDir + f.PlaybooksDir = constants.PlaybooksDir + f.ImageTag = strings.TrimSuffix(version.Version, "+git") + return nil +} + +const dockerfileTemplate = `FROM quay.io/operator-framework/ansible-operator:{{.ImageTag}} + +COPY requirements.yml ${HOME}/requirements.yml +RUN ansible-galaxy collection install -r ${HOME}/requirements.yml \ + && chmod -R ug+rwx ${HOME}/.ansible + +COPY watches.yaml ${HOME}/watches.yaml +COPY {{ .RolesDir }}/ ${HOME}/{{ .RolesDir }}/ +COPY {{ .PlaybooksDir }}/ ${HOME}/{{ .PlaybooksDir }}/ +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/makefile.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/makefile.go new file mode 100644 index 0000000000..f7a333b0c6 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/makefile.go @@ -0,0 +1,136 @@ +/* +Copyright 2019 The Kubernetes Authors. +Modifications copyright 2020 The Operator-SDK Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "strings" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/version" +) + +var _ file.Template = &Makefile{} + +// Makefile scaffolds the Makefile +type Makefile struct { + file.TemplateMixin + + // Image is controller manager image name + Image string + + // Kustomize version to use in the project + KustomizeVersion string + + // AnsibleOperatorVersion is the version of the base image and operator binary used in the project + AnsibleOperatorVersion string +} + +// SetTemplateDefaults implements input.Template +func (f *Makefile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Makefile" + } + + f.TemplateBody = makefileTemplate + + f.IfExistsAction = file.Error + + if f.Image == "" { + f.Image = "controller:latest" + } + + if f.KustomizeVersion == "" { + f.KustomizeVersion = "v3.5.4" + } + + if f.AnsibleOperatorVersion == "" { + f.AnsibleOperatorVersion = strings.TrimSuffix(version.Version, "+git") + } + + return nil +} + +const makefileTemplate = ` +# Image URL to use all building/pushing image targets +IMG ?= {{ .Image }} + +all: docker-build + +# Run against the configured Kubernetes cluster in ~/.kube/config +run: ansible-operator + $(ANSIBLE_OPERATOR) + +# Install CRDs into a cluster +install: kustomize + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +# Uninstall CRDs from a cluster +uninstall: kustomize + $(KUSTOMIZE) build config/crd | kubectl delete -f - + +# Deploy controller in the configured Kubernetes cluster in ~/.kube/config +deploy: kustomize + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +# Undeploy controller in the configured Kubernetes cluster in ~/.kube/config +undeploy: kustomize + $(KUSTOMIZE) build config/default | kubectl delete -f - + +# Build the docker image +docker-build: + docker build . -t ${IMG} + +# Push the docker image +docker-push: + docker push ${IMG} + +PATH := $(PATH):$(PWD)/bin +SHELL := env PATH=$(PATH) /bin/sh +OS = $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH = $(shell uname -m | sed 's/x86_64/amd64/') +OSOPER = $(shell uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/apple-darwin/' | sed 's/linux/linux-gnu/') +ARCHOPER = $(shell uname -m ) + +kustomize: +ifeq (, $(shell which kustomize 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p bin ;\ + curl -sSLo - https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/{{ .KustomizeVersion }}/kustomize_{{ .KustomizeVersion }}_$(OS)_$(ARCH).tar.gz | tar xzf - -C bin/ ;\ + } +KUSTOMIZE=./bin/kustomize +else +KUSTOMIZE=$(shell which kustomize) +endif + +ansible-operator: +ifeq (, $(shell which ansible-operator 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p bin ;\ + curl -LO https://github.com/operator-framework/operator-sdk/releases/download/{{ .AnsibleOperatorVersion}}/ansible-operator-{{ .AnsibleOperatorVersion}}-$(ARCHOPER)-$(OSOPER) ;\ + mv ansible-operator-{{ .AnsibleOperatorVersion}}-$(ARCHOPER)-$(OSOPER) ./bin/ansible-operator ;\ + chmod +x ./bin/ansible-operator ;\ + } +ANSIBLE_OPERATOR=$(realpath ./bin/ansible-operator) +else +ANSIBLE_OPERATOR=$(shell which ansible-operator) +endif +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/converge.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/converge.go new file mode 100644 index 0000000000..33449b2fc5 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/converge.go @@ -0,0 +1,57 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Converge{} + +// Converge scaffolds a Converge for building a main +type Converge struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Converge) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "converge.yml") + } + f.TemplateBody = convergeTemplate + return nil +} + +const convergeTemplate = `--- +- name: Converge + hosts: localhost + connection: local + gather_facts: no + collections: + - community.kubernetes + + tasks: + - name: Create Namespace + k8s: + api_version: v1 + kind: Namespace + name: '{{ "{{ namespace }}" }}' + + - import_tasks: kustomize.yml + vars: + state: present +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/create.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/create.go new file mode 100644 index 0000000000..861d43201c --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/create.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Create{} + +// Create scaffolds a Create for building a main +type Create struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Create) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "create.yml") + } + f.TemplateBody = createTemplate + return nil +} + +const createTemplate = `--- +- name: Create + hosts: localhost + connection: local + gather_facts: false + tasks: [] +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/destroy.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/destroy.go new file mode 100644 index 0000000000..c4359143a9 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/destroy.go @@ -0,0 +1,63 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Destroy{} + +// Destroy scaffolds a Destroy for building a main +type Destroy struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Destroy) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "destroy.yml") + } + f.TemplateBody = destroyTemplate + return nil +} + +const destroyTemplate = `--- +- name: Destroy + hosts: localhost + connection: local + gather_facts: false + collections: + - community.kubernetes + + tasks: + - import_tasks: kustomize.yml + vars: + state: absent + + - name: Destroy Namespace + k8s: + api_version: v1 + kind: Namespace + name: '{{ "{{ namespace }}" }}' + state: absent + + - name: Unset pull policy + command: '{{ "{{ kustomize }}" }} edit remove patch pull_policy/{{ "{{ pull_policy }}" }}.yaml' + args: + chdir: '{{ "{{ config_dir }}" }}/testing' +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/kustomize.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/kustomize.go new file mode 100644 index 0000000000..c66d86b4c2 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/kustomize.go @@ -0,0 +1,54 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Kustomize{} + +// Kustomize scaffolds a Kustomize for building a main +type Kustomize struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Kustomize) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "kustomize.yml") + } + f.TemplateBody = kustomizeTemplate + return nil +} + +const kustomizeTemplate = `--- +- name: Build kustomize testing overlay + # load_restrictor must be set to none so we can load patch files from the default overlay + command: '{{ "{{ kustomize }}" }} build --load_restrictor none .' + args: + chdir: '{{ "{{ config_dir }}" }}/testing' + register: resources + changed_when: false + +- name: Set resources to {{ "{{ state }}" }} + k8s: + definition: '{{ "{{ item }}" }}' + state: '{{ "{{ state }}" }}' + wait: yes + loop: '{{ "{{ resources.stdout | from_yaml_all | list }}" }}' +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/molecule.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/molecule.go new file mode 100644 index 0000000000..3108d2bc9a --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/molecule.go @@ -0,0 +1,75 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Molecule{} + +// Molecule scaffolds a Molecule for building a main +type Molecule struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Molecule) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "molecule.yml") + } + f.TemplateBody = moleculeTemplate + return nil +} + +const moleculeTemplate = `--- +dependency: + name: galaxy +driver: + name: delegated +lint: | + set -e + yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" . +platforms: + - name: cluster + groups: + - k8s +provisioner: + name: ansible + lint: | + set -e + ansible-lint + inventory: + group_vars: + all: + namespace: ${TEST_OPERATOR_NAMESPACE:-osdk-test} + host_vars: + localhost: + ansible_python_interpreter: '{{ "{{ ansible_playbook_python }}" }}' + config_dir: ${MOLECULE_PROJECT_DIRECTORY}/config + samples_dir: ${MOLECULE_PROJECT_DIRECTORY}/config/samples + operator_image: ${OPERATOR_IMAGE:-""} + operator_pull_policy: ${OPERATOR_PULL_POLICY:-"Always"} + kustomize: ${KUSTOMIZE_PATH:-kustomize} + env: + K8S_AUTH_KUBECONFIG: ${KUBECONFIG:-"~/.kube/config"} +verifier: + name: ansible + lint: | + set -e + ansible-lint +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/prepare.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/prepare.go new file mode 100644 index 0000000000..4638e83685 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/prepare.go @@ -0,0 +1,67 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Prepare{} + +// Prepare scaffolds a Prepare for building a main +type Prepare struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Prepare) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "prepare.yml") + } + f.TemplateBody = prepareTemplate + return nil +} + +const prepareTemplate = `--- +- name: Prepare + hosts: localhost + connection: local + gather_facts: false + + tasks: + - name: Ensure operator image is set + fail: + msg: | + You must specify the OPERATOR_IMAGE environment variable in order to run the + 'default' scenario + when: not operator_image + + - name: Set testing image + command: '{{ "{{ kustomize }}" }} edit set image testing={{ "{{ operator_image }}" }}' + args: + chdir: '{{ "{{ config_dir }}" }}/testing' + + - name: Set pull policy + command: '{{ "{{ kustomize }}" }} edit add patch pull_policy/{{ "{{ pull_policy }}" }}.yaml' + args: + chdir: '{{ "{{ config_dir }}" }}/testing' + + - name: Set testing namespace + command: '{{ "{{ kustomize }}" }} edit set namespace {{ "{{ namespace }}" }}' + args: + chdir: '{{ "{{ config_dir }}" }}/testing' +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/tasks_test_resource.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/tasks_test_resource.go new file mode 100644 index 0000000000..a69511576f --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/tasks_test_resource.go @@ -0,0 +1,63 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &ResourceTest{} + +// ResourceTest scaffolds a ResourceTest for building a main +type ResourceTest struct { + file.TemplateMixin + file.ResourceMixin + SampleFile string +} + +// SetTemplateDefaults implements input.Template +func (f *ResourceTest) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "tasks", "%[kind]_test.yml") + f.Path = f.Resource.Replacer().Replace(f.Path) + } + f.SampleFile = f.Resource.Replacer().Replace("%[group]_%[version]_%[kind].yaml") + + f.TemplateBody = resourceTestTemplate + return nil +} + +const resourceTestTemplate = `--- +- name: Create the {{.Resource.Domain}}/{{.Resource.Version}}.{{.Resource.Kind}} + k8s: + state: present + namespace: '{{ "{{ namespace }}" }}' + definition: "{{ "{{ lookup('template', '/'.join([samples_dir, cr_file])) | from_yaml }}" }}" + wait: yes + wait_timeout: 300 + wait_condition: + type: Running + reason: Successful + status: "True" + vars: + cr_file: '{{ .SampleFile }}' + +- name: Add assertions here + assert: + that: false + fail_msg: FIXME Add real assertions for your operator +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/verify.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/verify.go new file mode 100644 index 0000000000..0e9eb3a41c --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mdefault/verify.go @@ -0,0 +1,92 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mdefault + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Verify{} + +// Verify scaffolds a Verify for building a main +type Verify struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Verify) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "default", "verify.yml") + } + f.TemplateBody = verifyTemplate + return nil +} + +const verifyTemplate = `--- +- name: Verify + hosts: localhost + connection: local + gather_facts: no + collections: + - community.kubernetes + + tasks: + - block: + - name: Import all test files from tasks/ + include_tasks: '{{ "{{ item }}" }}' + with_fileglob: + - tasks/*_test.yml + rescue: + - name: Retrieve relevant resources + k8s_info: + api_version: '{{ "{{ item.api_version }}" }}' + kind: '{{ "{{ item.kind }}" }}' + namespace: '{{ "{{ namespace }}" }}' + loop: + - api_version: v1 + kind: Pod + - api_version: apps/v1 + kind: Deployment + - api_version: v1 + kind: Secret + - api_version: v1 + kind: ConfigMap + register: debug_resources + + - name: Retrieve Pod logs + k8s_log: + name: '{{ "{{ item.metadata.name }}" }}' + namespace: '{{ "{{ namespace }}" }}' + loop: "{{ "{{ q('k8s', api_version='v1', kind='Pod', namespace=namespace) }}" }}" + register: debug_logs + + - name: Output gathered resources + debug: + var: debug_resources + + - name: Output gathered logs + debug: + var: item.log_lines + loop: '{{ "{{ debug_logs.results }}" }}' + + - name: Re-emit failure + vars: + failed_task: + result: '{{ "{{ ansible_failed_result }}" }}' + fail: + msg: '{{ "{{ failed_task }}" }}' +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/converge.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/converge.go new file mode 100644 index 0000000000..b3adfad88f --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/converge.go @@ -0,0 +1,63 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mkind + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Converge{} + +// Converge scaffolds a Converge for building a main +type Converge struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Converge) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "kind", "converge.yml") + } + f.TemplateBody = convergeTemplate + return nil +} + +const convergeTemplate = `--- +- name: Converge + hosts: localhost + connection: local + gather_facts: no + + tasks: + - name: Build operator image + docker_image: + build: + path: '{{ "{{ project_dir }}" }}' + pull: no + name: '{{ "{{ operator_image }}" }}' + tag: latest + push: no + source: build + force_source: yes + + - name: Load image into kind cluster + command: kind load docker-image --name osdk-test '{{ "{{ operator_image }}" }}' + register: result + changed_when: '"not yet present" in result.stdout' + +- import_playbook: ../default/converge.yml +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/create.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/create.go new file mode 100644 index 0000000000..78b4721aea --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/create.go @@ -0,0 +1,47 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mkind + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Create{} + +// Create scaffolds a Create for building a main +type Create struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Create) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "kind", "create.yml") + } + f.TemplateBody = createTemplate + return nil +} + +const createTemplate = `--- +- name: Create + hosts: localhost + connection: local + gather_facts: false + tasks: + - name: Create test kind cluster + command: kind create cluster --name osdk-test --kubeconfig {{ "{{ kubeconfig }}" }} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/destroy.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/destroy.go new file mode 100644 index 0000000000..2474be5d15 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/destroy.go @@ -0,0 +1,50 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mkind + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Destroy{} + +// Destroy scaffolds a Destroy for building a main +type Destroy struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Destroy) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "kind", "destroy.yml") + } + f.TemplateBody = destroyTemplate + return nil +} + +const destroyTemplate = `--- +- name: Destroy + hosts: localhost + connection: local + gather_facts: false + collections: + - community.kubernetes + + tasks: + - name: Destroy test kind cluster + command: kind delete cluster --name osdk-test --kubeconfig {{ "{{ kubeconfig }}" }} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/molecule.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/molecule.go new file mode 100644 index 0000000000..f952d5e3a0 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/molecule/mkind/molecule.go @@ -0,0 +1,81 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mkind + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Molecule{} + +// Molecule scaffolds a Molecule for building a main +type Molecule struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Molecule) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("molecule", "kind", "molecule.yml") + } + f.TemplateBody = moleculeTemplate + return nil +} + +const moleculeTemplate = `--- +dependency: + name: galaxy +driver: + name: delegated +lint: | + set -e + yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" . +platforms: + - name: cluster + groups: + - k8s +provisioner: + name: ansible + playbooks: + prepare: ../default/prepare.yml + verify: ../default/verify.yml + lint: | + set -e + ansible-lint + inventory: + group_vars: + all: + namespace: ${TEST_OPERATOR_NAMESPACE:-osdk-test} + host_vars: + localhost: + ansible_python_interpreter: '{{ "{{ ansible_playbook_python }}" }}' + config_dir: ${MOLECULE_PROJECT_DIRECTORY}/config + samples_dir: ${MOLECULE_PROJECT_DIRECTORY}/config/samples + project_dir: ${MOLECULE_PROJECT_DIRECTORY} + operator_image: testing-operator + pull_policy: "Never" + kubeconfig: "{{ "{{ lookup('env', 'KUBECONFIG') }}" }}" + kustomize: ${KUSTOMIZE_PATH:-kustomize} + env: + K8S_AUTH_KUBECONFIG: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig + KUBECONFIG: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig +verifier: + name: ansible + lint: | + set -e + ansible-lint +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks/placeholder.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks/placeholder.go new file mode 100644 index 0000000000..b2923d6741 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks/placeholder.go @@ -0,0 +1,38 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package playbooks + +import ( + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const placeholderPath = "playbooks" + constants.FilePathSep + ".placeholder" + +type Placeholder struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Placeholder) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = placeholderPath + } + f.TemplateBody = placeholderTemplate + return nil +} + +const placeholderTemplate = `` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks/playbook.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks/playbook.go new file mode 100644 index 0000000000..b7e038a3db --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/playbooks/playbook.go @@ -0,0 +1,56 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package playbooks + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +type Playbook struct { + file.TemplateMixin + file.ResourceMixin + + GenerateRole bool +} + +func (f *Playbook) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("playbooks", "%[kind].yml") + f.Path = f.Resource.Replacer().Replace(f.Path) + } + if f.Path == "" { + f.Path = "playbook.yml" + } + f.TemplateBody = playbookTmpl + return nil +} + +const playbookTmpl = `--- +- hosts: localhost + gather_facts: no + collections: + - community.kubernetes + - operator_sdk.util + + {{- if .GenerateRole }} + tasks: + - import_role: + name: "{{.Resource.Kind | lower }}" + {{- else }} + tasks: [] + {{- end }} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/requirements.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/requirements.go new file mode 100644 index 0000000000..a7ff6a89ce --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/requirements.go @@ -0,0 +1,39 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package templates + +import ( + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +// RequirementsYml - A requirements file for Ansible collection dependencies +type RequirementsYml struct { + file.TemplateMixin +} + +func (f *RequirementsYml) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "requirements.yml" + } + f.TemplateBody = requirementsYmlTmpl + return nil +} + +const requirementsYmlTmpl = `--- +collections: + - name: community.kubernetes + version: "<1.0.0" + - operator_sdk.util +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/defaults_main.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/defaults_main.go new file mode 100644 index 0000000000..1d85cf0104 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/defaults_main.go @@ -0,0 +1,44 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const defaultsMainPath = "defaults" + constants.FilePathSep + "main.yml" + +type DefaultsMain struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *DefaultsMain) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", defaultsMainPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + f.TemplateBody = defaultsMainAnsibleTmpl + return nil +} + +const defaultsMainAnsibleTmpl = `--- +# defaults file for {{ .Resource.Kind}} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/files_dir.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/files_dir.go new file mode 100644 index 0000000000..5685f904f7 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/files_dir.go @@ -0,0 +1,43 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const filesPath = "files" + constants.FilePathSep + ".placeholder" + +type RoleFiles struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *RoleFiles) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", filesPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = rolesFilesDirPlaceholder + return nil +} + +const rolesFilesDirPlaceholder = `` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/handlers_main.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/handlers_main.go new file mode 100644 index 0000000000..8a3a80195e --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/handlers_main.go @@ -0,0 +1,45 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const handlersMainPath = "handlers" + constants.FilePathSep + "main.yml" + +type HandlersMain struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *HandlersMain) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", handlersMainPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = handlersMainAnsibleTmpl + return nil +} + +const handlersMainAnsibleTmpl = `--- +# handlers file for {{ .Resource.Kind}} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/meta_main.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/meta_main.go new file mode 100644 index 0000000000..6986c8b2d1 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/meta_main.go @@ -0,0 +1,107 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const metaMainPath = "meta" + constants.FilePathSep + "main.yml" + +type MetaMain struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *MetaMain) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", metaMainPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = metaMainAnsibleTmpl + return nil +} + +const metaMainAnsibleTmpl = `--- +galaxy_info: + author: your name + description: your description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Some suggested licenses: + # - BSD (default) + # - MIT + # - GPLv2 + # - GPLv3 + # - Apache + # - CC-BY + license: license (GPLv2, CC-BY, etc) + + min_ansible_version: 2.9 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # Optionally specify the branch Galaxy will use when accessing the GitHub + # repo for this role. During role install, if no tags are available, + # Galaxy will use this branch. During import Galaxy will access files on + # this branch. If Travis integration is configured, only notifications for this + # branch will be accepted. Otherwise, in all cases, the repo's default branch + # (usually master) will be used. + #github_branch: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. +collections: +- operator_sdk.util +- community.kubernetes +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/placeholder.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/placeholder.go new file mode 100644 index 0000000000..cc957e61d5 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/placeholder.go @@ -0,0 +1,38 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const placeholderPath = "roles" + constants.FilePathSep + ".placeholder" + +type Placeholder struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Placeholder) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = placeholderPath + } + f.TemplateBody = placeholderTemplate + return nil +} + +const placeholderTemplate = `` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/readme.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/readme.go new file mode 100644 index 0000000000..7f33746683 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/readme.go @@ -0,0 +1,85 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const ReadmePath = "README.md" + +type Readme struct { + file.TemplateMixin + file.ResourceMixin +} + +func (f *Readme) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", ReadmePath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = readmeAnsibleTmpl + return nil +} + +const readmeAnsibleTmpl = `Role Name +========= + +A brief description of the role goes here. + +Requirements +------------ + +Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, +if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. + +Role Variables +-------------- + +A description of the settable variables for this role should go here, including any variables that are in +defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables +that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well + +Dependencies +------------ + +A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set +for other roles, or variables that are used from other roles. + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for +users too: + + - hosts: servers + roles: + - { role: username.rolename, x: 42 } + +License +------- + +BSD + +Author Information +------------------ + +An optional section for the role authors to include contact information, or a website (HTML is not allowed). +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/tasks_main.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/tasks_main.go new file mode 100644 index 0000000000..1130177c6a --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/tasks_main.go @@ -0,0 +1,45 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const tasksMainPath = "tasks" + constants.FilePathSep + "main.yml" + +type TasksMain struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *TasksMain) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", tasksMainPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = tasksMainAnsibleTmpl + return nil +} + +const tasksMainAnsibleTmpl = `--- +# tasks file for {{ .Resource.Kind}} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/templates_dir.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/templates_dir.go new file mode 100644 index 0000000000..bd67873e22 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/templates_dir.go @@ -0,0 +1,43 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const templatesPath = "templates" + constants.FilePathSep + ".placeholder" + +type RoleTemplates struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *RoleTemplates) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", templatesPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = templatesDirPlaceholder + return nil +} + +const templatesDirPlaceholder = `` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/vars_main.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/vars_main.go new file mode 100644 index 0000000000..f77ea8ec08 --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/roles/vars_main.go @@ -0,0 +1,45 @@ +// Copyright 2018 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roles + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" + + "github.com/operator-framework/operator-sdk/internal/plugins/ansible/v1/constants" +) + +const varsMainPath = "vars" + constants.FilePathSep + "main.yml" + +type VarsMain struct { + file.TemplateMixin + file.ResourceMixin +} + +// SetTemplateDefaults implements input.Template +func (f *VarsMain) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(constants.RolesDir, "%[kind]", varsMainPath) + f.Path = f.Resource.Replacer().Replace(f.Path) + } + + f.TemplateBody = varsMainAnsibleTmpl + return nil +} + +const varsMainAnsibleTmpl = `--- +# vars file for {{ .Resource.Kind}} +` diff --git a/internal/plugins/ansible/v1/scaffolds/internal/templates/watches.go b/internal/plugins/ansible/v1/scaffolds/internal/templates/watches.go new file mode 100644 index 0000000000..9274567c7a --- /dev/null +++ b/internal/plugins/ansible/v1/scaffolds/internal/templates/watches.go @@ -0,0 +1,116 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package templates + +import ( + "bytes" + "fmt" + "text/template" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &Watches{} + +const ( + defaultWatchesFile = "watches.yaml" + watchMarker = "watch" +) + +// Watches scaffolds the watches.yaml file +type Watches struct { + file.TemplateMixin +} + +// SetTemplateDefaults implements input.Template +func (f *Watches) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = defaultWatchesFile + } + + f.TemplateBody = fmt.Sprintf(watchesTemplate, + file.NewMarkerFor(f.Path, watchMarker), + ) + return nil +} + +var _ file.Inserter = &WatchesUpdater{} + +type WatchesUpdater struct { + file.TemplateMixin + file.ResourceMixin + + GeneratePlaybook bool + GenerateRole bool + PlaybooksDir string +} + +func (*WatchesUpdater) GetPath() string { + return defaultWatchesFile +} + +func (*WatchesUpdater) GetIfExistsAction() file.IfExistsAction { + return file.Overwrite +} + +func (f *WatchesUpdater) GetMarkers() []file.Marker { + return []file.Marker{ + file.NewMarkerFor(defaultWatchesFile, watchMarker), + } +} + +func (f *WatchesUpdater) GetCodeFragments() file.CodeFragmentsMap { + fragments := make(file.CodeFragmentsMap, 1) + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // Generate watch fragments + watches := make([]string, 0) + buf := &bytes.Buffer{} + + // TODO(asmacdo) Move template execution into a function, executed by the apiScaffolder.scaffold() + // DefaultFuncMap used provide the function "lower", used in the watch fragment. + tmpl := template.Must(template.New("rules").Funcs(file.DefaultFuncMap()).Parse(watchFragment)) + err := tmpl.Execute(buf, f) + if err != nil { + panic(err) + } + watches = append(watches, buf.String()) + + if len(watches) != 0 { + fragments[file.NewMarkerFor(defaultWatchesFile, watchMarker)] = watches + } + return fragments +} + +const watchesTemplate = `--- +# Use the 'create api' subcommand to add watches to this file. +%s +` + +const watchFragment = `- version: {{.Resource.Version}} + group: {{.Resource.Domain}} + kind: {{.Resource.Kind}} + {{- if .GeneratePlaybook }} + playbook: {{ .PlaybooksDir }}/{{ .Resource.Kind | lower }}.yml + {{- else if .GenerateRole}} + role: {{ .Resource.Kind | lower }} + {{- else }} + # FIXME: Specify the role or playbook for this resource. + {{- end }} +` diff --git a/test/ansible-memcached/memcached_test.yml b/test/ansible-memcached/memcached_test.yml new file mode 100644 index 0000000000..6ab38a9988 --- /dev/null +++ b/test/ansible-memcached/memcached_test.yml @@ -0,0 +1,150 @@ +--- +- name: Load CR + set_fact: + custom_resource: "{{ lookup('template', '/'.join([samples_dir, cr_file])) | from_yaml }}" + vars: + cr_file: 'ansible_v1alpha1_memcached.yaml' + +- name: Create the ansible.example.com/v1alpha1.Memcached + k8s: + state: present + namespace: '{{ namespace }}' + definition: '{{ custom_resource }}' + wait: yes + wait_timeout: 300 + wait_condition: + type: Running + reason: Successful + status: "True" + +- name: Wait 2 minutes for memcached deployment + debug: + var: deploy + until: + - deploy is defined + - deploy.status is defined + - deploy.status.replicas is defined + - deploy.status.replicas == deploy.status.get("availableReplicas", 0) + retries: 12 + delay: 10 + vars: + deploy: '{{ lookup("k8s", + kind="Deployment", + api_version="apps/v1", + namespace=namespace, + label_selector="app=memcached" + )}}' + +- name: Create ConfigMap that the Operator should delete + k8s: + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + name: deleteme + namespace: '{{ namespace }}' + data: + delete: me + +- name: Verify custom status exists + assert: + that: debug_cr.status.get("test") == "hello world" + vars: + debug_cr: '{{ lookup("k8s", + kind=custom_resource.kind, + api_version=custom_resource.apiVersion, + namespace=namespace, + resource_name=custom_resource.metadata.name + )}}' + +# This will verify that the `secret` role was executed +- name: Verify that test-service was created + assert: + that: lookup('k8s', kind='Service', api_version='v1', namespace=namespace, resource_name='test-service') + +- name: Verify that project testing-foo was created + assert: + that: lookup('k8s', kind='Namespace', api_version='v1', resource_name='testing-foo') + when: "'project.openshift.io' in lookup('k8s', cluster_info='api_groups')" + +- when: molecule_yml.scenario.name == "test-local" + block: + - name: Restart the operator by killing the pod + k8s: + state: absent + definition: + api_version: v1 + kind: Pod + metadata: + namespace: '{{ namespace }}' + name: '{{ pod.metadata.name }}' + vars: + pod: '{{ q("k8s", api_version="v1", kind="Pod", namespace=namespace, label_selector="name=memcached-operator").0 }}' + + - name: Wait 2 minutes for operator deployment + debug: + var: deploy + until: + - deploy is defined + - deploy.status is defined + - deploy.status.replicas is defined + - deploy.status.replicas == deploy.status.get("availableReplicas", 0) + retries: 12 + delay: 10 + vars: + deploy: '{{ lookup("k8s", + kind="Deployment", + api_version="apps/v1", + namespace=namespace, + resource_name="memcached-operator" + )}}' + + - name: Wait for reconciliation to have a chance at finishing + pause: + seconds: 15 + + - name: Delete the service that is created. + k8s: + kind: Service + api_version: v1 + namespace: '{{ namespace }}' + name: test-service + state: absent + + - name: Verify that test-service was re-created + debug: + var: service + until: service + retries: 12 + delay: 10 + vars: + service: '{{ lookup("k8s", + kind="Service", + api_version="v1", + namespace=namespace, + resource_name="test-service", + )}}' + +- name: Delete the custom resource + k8s: + state: absent + namespace: '{{ namespace }}' + definition: '{{ custom_resource }}' + +- name: Wait for the custom resource to be deleted + k8s_info: + api_version: '{{ custom_resource.apiVersion }}' + kind: '{{ custom_resource.kind }}' + namespace: '{{ namespace }}' + name: '{{ custom_resource.metadata.name }}' + register: cr + retries: 10 + delay: 6 + until: not cr.resources + failed_when: cr.resources + +- name: Verify the Deployment was deleted (wait 30s) + assert: + that: not lookup('k8s', kind='Deployment', api_version='apps/v1', namespace=namespace, label_selector='app=memcached') + retries: 10 + delay: 3 diff --git a/test/ansible-memcached/molecule.yml b/test/ansible-memcached/molecule.yml deleted file mode 100644 index 3cf844facc..0000000000 --- a/test/ansible-memcached/molecule.yml +++ /dev/null @@ -1,55 +0,0 @@ ---- -dependency: - name: galaxy -driver: - name: docker -platforms: -- name: kind-test-local - groups: - - k8s - image: bsycorp/kind:latest-1.17 - privileged: True - override_command: no - exposed_ports: - - 8443/tcp - - 10080/tcp - published_ports: - - 0.0.0.0:${TEST_CLUSTER_PORT:-10443}:8443/tcp - pre_build_image: yes - volumes: - - ${MOLECULE_PROJECT_DIRECTORY}:/build:Z -provisioner: - name: ansible - inventory: - group_vars: - all: - namespace: ${TEST_OPERATOR_NAMESPACE:-osdk-test} - kubeconfig_file: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig - host_vars: - localhost: - ansible_python_interpreter: '{{ ansible_playbook_python }}' - template_dir: ${MOLECULE_PROJECT_DIRECTORY}/molecule/templates - deploy_dir: ${MOLECULE_PROJECT_DIRECTORY}/deploy - env: - K8S_AUTH_KUBECONFIG: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig - KUBECONFIG: ${MOLECULE_EPHEMERAL_DIRECTORY}/kubeconfig - ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles - KIND_PORT: '${TEST_CLUSTER_PORT:-10443}' - log: True -scenario: - test_sequence: - - lint - - destroy - - dependency - - syntax - - create - - prepare - - converge - - side_effect - - verify - - destroy -verifier: - name: ansible - lint: | - set -e - ansible-lint diff --git a/test/ansible-memcached/verify.yml b/test/ansible-memcached/verify.yml deleted file mode 100644 index c643a0efe1..0000000000 --- a/test/ansible-memcached/verify.yml +++ /dev/null @@ -1,216 +0,0 @@ ---- - -- name: Verify - hosts: localhost - connection: local - collections: - - community.kubernetes - - vars: - ansible_python_interpreter: '{{ ansible_playbook_python }}' - deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy" - custom_resource: "{{ lookup('file', '/'.join([deploy_dir, 'crds/ansible.example.com_v1alpha1_memcached_cr.yaml'])) | from_yaml }}" - - tasks: - - block: - - name: debug memcached lookup - debug: - var: deploy - vars: - deploy: '{{ lookup("k8s", - kind="Deployment", - api_version="apps/v1", - namespace=namespace, - label_selector="app=memcached" - )}}' - - - name: Wait 2 minutes for memcached deployment - debug: - var: deploy - until: - - deploy is defined - - deploy.status is defined - - deploy.status.replicas is defined - - deploy.status.replicas == deploy.status.get("availableReplicas", 0) - retries: 12 - delay: 10 - vars: - deploy: '{{ lookup("k8s", - kind="Deployment", - api_version="apps/v1", - namespace=namespace, - label_selector="app=memcached" - )}}' - - - name: Create ConfigMap that the Operator should delete - k8s: - definition: - apiVersion: v1 - kind: ConfigMap - metadata: - name: deleteme - namespace: '{{ namespace }}' - data: - delete: me - - - name: Verify custom status exists - assert: - that: debug_cr.status.get("test") == "hello world" - vars: - debug_cr: '{{ lookup("k8s", - kind=custom_resource.kind, - api_version=custom_resource.apiVersion, - namespace=namespace, - resource_name=custom_resource.metadata.name - )}}' - - # This will verify that the `secret` role was executed - - name: Verify that test-service was created - assert: - that: lookup('k8s', kind='Service', api_version='v1', namespace=namespace, resource_name='test-service') - - - name: Verify that project testing-foo was created - assert: - that: lookup('k8s', kind='Namespace', api_version='v1', resource_name='testing-foo') - when: "'project.openshift.io' in lookup('k8s', cluster_info='api_groups')" - - - when: molecule_yml.scenario.name == "test-local" - block: - - name: Restart the operator by killing the pod - k8s: - state: absent - definition: - api_version: v1 - kind: Pod - metadata: - namespace: '{{ namespace }}' - name: '{{ pod.metadata.name }}' - vars: - pod: '{{ q("k8s", api_version="v1", kind="Pod", namespace=namespace, label_selector="name=memcached-operator").0 }}' - - - name: Wait 2 minutes for operator deployment - debug: - var: deploy - until: - - deploy is defined - - deploy.status is defined - - deploy.status.replicas is defined - - deploy.status.replicas == deploy.status.get("availableReplicas", 0) - retries: 12 - delay: 10 - vars: - deploy: '{{ lookup("k8s", - kind="Deployment", - api_version="apps/v1", - namespace=namespace, - resource_name="memcached-operator" - )}}' - - - name: Wait for reconciliation to have a chance at finishing - pause: - seconds: 15 - - - name: Delete the service that is created. - k8s: - kind: Service - api_version: v1 - namespace: '{{ namespace }}' - name: test-service - state: absent - - - name: Verify that test-service was re-created - debug: - var: service - until: service - retries: 12 - delay: 10 - vars: - service: '{{ lookup("k8s", - kind="Service", - api_version="v1", - namespace=namespace, - resource_name="test-service", - )}}' - - - name: Delete the custom resource - k8s: - state: absent - namespace: '{{ namespace }}' - definition: '{{ custom_resource }}' - - - name: Wait for the custom resource to be deleted - k8s_info: - api_version: '{{ custom_resource.apiVersion }}' - kind: '{{ custom_resource.kind }}' - namespace: '{{ namespace }}' - name: '{{ custom_resource.metadata.name }}' - register: cr - retries: 10 - delay: 6 - until: not cr.resources - failed_when: cr.resources - - - name: Verify the Deployment was deleted (wait 30s) - assert: - that: not lookup('k8s', kind='Deployment', api_version='apps/v1', namespace=namespace, label_selector='app=memcached') - retries: 10 - delay: 3 - - - name: Add operator pod to inventory - add_host: - name: '{{ pod.metadata.name }}' - groups: operator - ansible_connection: kubectl - ansible_remote_tmp: /tmp/ansible - kubectl_namespace: '{{ namespace }}' - when: molecule_yml.scenario.name == "test-local" - vars: - pod: '{{ q("k8s", api_version="v1", kind="Pod", namespace=namespace, label_selector="name=memcached-operator").0 }}' - rescue: - - name: debug cr - ignore_errors: yes - failed_when: false - debug: - var: debug_cr - vars: - debug_cr: '{{ lookup("k8s", - kind=custom_resource.kind, - api_version=custom_resource.apiVersion, - namespace=namespace, - resource_name=custom_resource.metadata.name - )}}' - - - name: debug memcached lookup - ignore_errors: yes - failed_when: false - debug: - var: deploy - vars: - deploy: '{{ lookup("k8s", - kind="Deployment", - api_version="apps/v1", - namespace=namespace, - label_selector="app=memcached" - )}}' - - - name: get operator logs - ignore_errors: yes - failed_when: false - command: kubectl logs deployment/{{ definition.metadata.name }} -n {{ namespace }} - vars: - definition: "{{ lookup('file', '/'.join([deploy_dir, 'operator.yaml'])) | from_yaml }}" - register: log - - - debug: var=log.stdout_lines - - - fail: - msg: "Failed in verify.yml" - -- hosts: operator - gather_facts: no - become: false - tasks: - - name: Output latest log - command: cat /tmp/ansible-operator/runner/ansible.example.com/v1alpha1/Memcached/{{ namespace }}/example-memcached/artifacts/latest/stdout - register: ansible_log - - debug: var=ansible_log.stdout_lines diff --git a/test/ansible/molecule/cluster/tasks/selector_test.yml b/test/ansible/molecule/cluster/tasks/selector_test.yml index b51b98bfe5..5084a1d601 100644 --- a/test/ansible/molecule/cluster/tasks/selector_test.yml +++ b/test/ansible/molecule/cluster/tasks/selector_test.yml @@ -42,7 +42,7 @@ - name: Wait for 30 seconds wait_for: - timeout: 300 + timeout: 30 - name: Assert sentinel ConfigMap has not been created for Molecule Test assert: diff --git a/website/content/en/docs/cli/operator-sdk_init.md b/website/content/en/docs/cli/operator-sdk_init.md index 81b7d3d61a..db7a9c7167 100644 --- a/website/content/en/docs/cli/operator-sdk_init.md +++ b/website/content/en/docs/cli/operator-sdk_init.md @@ -42,7 +42,7 @@ operator-sdk init [flags] -h, --help help for init --license string license to use to boilerplate, may be one of 'apache2', 'none' (default "apache2") --owner string owner to add to the copyright - --plugins strings Name and optionally version of the plugin to initialize the project with. Available plugins: ("go.kubebuilder.io/v2", "helm.sdk.operatorframework.io/v1") + --plugins strings Name and optionally version of the plugin to initialize the project with. Available plugins: ("ansible.sdk.operatorframework.io/v1", "go.kubebuilder.io/v2", "helm.sdk.operatorframework.io/v1") --project-version string project version, possible values: ("2", "3-alpha") (default "3-alpha") --repo string name to use for go module (e.g., github.com/user/repo), defaults to the go package of the current working directory. --skip-go-version-check if specified, skip checking the Go version