diff --git a/Makefile b/Makefile index f4b47ee9..da05e7dd 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ endif .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./api/..." .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. diff --git a/examples/ref-implementation/README.md b/examples/ref-implementation/README.md new file mode 100644 index 00000000..9179dcfc --- /dev/null +++ b/examples/ref-implementation/README.md @@ -0,0 +1,156 @@ +# Reference implementation + +This example creates a local version of the CNOE reference implementation. + +## Prerequisites + +Ensure you have the following tools installed on your computer. + +**Required** + +- [idpbuilder](https://github.com/cnoe-io/idpbuilder/releases/latest): version `0.0.2` or later +- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl): version `1.27` or later +- Your computer should have at least 6 GB RAM allocated to Docker. If you are on Docker Desktop, see [this guide](https://docs.docker.com/desktop/settings/mac/). + +**Optional** + +- AWS credentials: Access Key and secret Key. If you want to create AWS resources in one of examples below. + +## Installation + +Run the following command from the root of this repository. + +```bash +idpbuilder create --package-dir examples/ref-implementation +``` + +This will take ~6 minutes for everything to come up. To track the progress, you can go to the [ArgoCD UI](https://argocd.cnoe.localtest.me:8443/applications). + +### What was installed? + +1. **Argo Workflows** to enable workflow orchestrations. +2. **Backstage** as the UI for software catalog and templating. Source is available [here](https://github.com/cnoe-io/backstage-app). +3. **Crossplane**, AWS providers, and basic compositions for deploying cloud related resources (needs your credentials for this to work) +4. **External Secrets** to generate secrets and coordinate secrets between applications. +5. **Keycloak** as the identity provider for applications. +6. **Spark Operator** to demonstrate an example Spark workload through Backstage. + +If you don't want to install a package above, you can remove the ArgoCD Application file corresponding to the package you want to remove. +For example, if you want to remove Spark Operator, you can delete [this file](./spark-operator.yaml). + +```bash +# remove spark operator from this installation. +rm examples/ref-implementation/spark-operator.yaml +``` + +The only package that cannot be removed this way is Keycloak because other packages rely on it. + + +#### Accessing UIs +- Argo CD: https://argocd.cnoe.localtest.me:8443 +- Argo Workflows: https://argo.cnoe.localtest.me:8443 +- Backstage: https://backstage.cnoe.localtest.me:8443 +- Gitea: https://gitea.cnoe.localtest.me:8443 +- Keycloak: https://keycloak.cnoe.localtest.me:8443/admin/master/console/ + +# Using it + +For this example, we will walk through a few demonstrations. Once applications are ready, go to the [backstage URL](https://backstage.cnoe.localtest.me:8443). + +Click on the Sign-In button, you will be asked to log into the Keycloak instance. There are two users set up in this +configuration, and their password can be retrieved with the following command: + +```bash +kubectl -n keycloak get secret keycloak-config \ + -o go-template='{{ range $key, $value := .data }}{{ printf "%s: %s\n" $key ($value | base64decode) }}{{ end }}' +``` + +Use the username **`user1`** and the password value given by `USER_PASSWORD` field to login to the backstage instance. +`user1` is an admin user who has access to everything in the cluster, while `user2` is a regular user with limited access. +Both users use the same password retrieved above. + +If you want to create a new user or change existing users: + +1. Go to the [Keycloak UI](https://keycloak.cnoe.localtest.me:8443/admin/master/console/). +Login with the username `cnoe-admin`. Password is the `KEYCLOAK_ADMIN_PASSWORD` field from the command above. +2. Select `cnoe` from the realms drop down menu. +3. Select users tab. + + +## Basic Deployment + +Let's start by deploying a simple application to the cluster through Backstage. + +Click on the `Create...` button on the left, then select the `Create a Basic Deployment` template. + +![img.png](images/backstage-templates.png) + + +In the next screen, type `demo` for the name field, then click Review, then Create. +Once steps run, click the Open In Catalog button to go to the entity page. + +![img.png](images/basic-template-flow.png) + +In the demo entity page, you will notice a ArgoCD overview card associated with this entity. +You can click on the ArgoCD Application name to see more details. + +![img.png](images/demo-entity.png) + +### What just happened? + +1. Backstage created [a git repository](https://gitea.cnoe.localtest.me:8443/giteaAdmin/demo), then pushed templated contents to it. +2. Backstage created [an ArgoCD Application](https://argocd.cnoe.localtest.me:8443/applications/argocd/demo?) and pointed it to the git repository. +3. Backstage registered the application as [a component](https://gitea.cnoe.localtest.me:8443/giteaAdmin/demo/src/branch/main/catalog-info.yaml) in Backstage. +4. ArgoCD deployed the manifests stored in the repo to the cluster. +5. Backstage retrieved application health from ArgoCD API, then displayed it. + +![image.png](images/basic-deployment.png) + + +## Argo Workflows and Spark Operator + +In this example, we will deploy a simple Apache Spark job through Argo Workflows. + +Click on the `Create...` button on the left, then select the `Basic Argo Workflow witha Spark Job` template. + +![img.png](images/backstage-templates-spark.png) + +Type `demo2` for the name field, then click create. You will notice that the Backstage templating steps are very similar to the basic example above. +Click on the Open In Catalog button to go to the entity page. + +![img.png](images/demo2-entity.png) + +Deployment processes are the same as the first example. Instead of deploying a pod, we deployed a workflow to create a Spark job. + +In the entity page, there is a card for Argo Workflows, and it should say running or succeeded. +You can click the name in the card to go to the Argo Workflows UI to view more details about this workflow run. +When prompted to log in, click the login button under single sign on. Argo Workflows is configured to use SSO with Keycloak allowing you to login with the same credentials as Backstage login. + +Note that Argo Workflows are not usually deployed this way. This is just an example to show you how you can integrate workflows, backstage, and spark. + +Back in the entity page, you can view more details about Spark jobs by navigating to the Spark tab. + +## Application with cloud resources. + +Similar to the above, we can deploy an application with cloud resources using Backstage templates. +In this example, we will create an application with a S3 Bucket. + +Choose a template named `App with S3 bucket`, type `demo3` as the name, then choose a region to create this bucket in. + +Once you click the create button, you will have a very similar setup as the basic example. +The only difference is we now have a resource for a S3 Bucket which is managed by Crossplane. + +Note that Bucket is **not** created because Crossplane doesn't have necessary credentials to do so. +If you'd like it to actually create a bucket, update [the credentials secret file](crossplane-providers/provider-secret.yaml), then run `idpbuilder create --package-dir examples/ref-implementation`. + +In this example, we used Crossplane to provision resources, but you can use other cloud resource management tools such as Terraform instead. +Regardless of your tool choice, concepts are the same. We use Backstage as the templating mechanism and UI for users, then use Kubernetes API with GitOps to deploy resources. + +## Notes + +- In these examples, we have used the pattern of creating a new repository for every app, then having ArgoCD deploy it. +This is done for convenience and demonstration purposes only. There are alternative actions that you can use. +For example, you can create a PR to an existing repository, create a repository but not deploy them yet, etc. + +- If Backstage's pipelining and templating mechanisms is too simple, you can use more advanced workflow engines like Tekton or Argo Workflows. + You can invoke them in Backstage templates, then track progress similar to how it was described above. diff --git a/examples/ref-implementation/argo-workflows.yaml b/examples/ref-implementation/argo-workflows.yaml new file mode 100644 index 00000000..5d588254 --- /dev/null +++ b/examples/ref-implementation/argo-workflows.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: argo-workflows + namespace: argocd + labels: + env: dev + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: cnoe://argo-workflows/manifests + targetRevision: HEAD + path: "dev" + destination: + server: "https://kubernetes.default.svc" + namespace: argo + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true diff --git a/examples/ref-implementation/argo-workflows/manifests/base/install.yaml b/examples/ref-implementation/argo-workflows/manifests/base/install.yaml new file mode 100644 index 00000000..23f8b247 --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/base/install.yaml @@ -0,0 +1,1352 @@ +# This is an auto-generated file. DO NOT EDIT +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterworkflowtemplates.argoproj.io +spec: + group: argoproj.io + names: + kind: ClusterWorkflowTemplate + listKind: ClusterWorkflowTemplateList + plural: clusterworkflowtemplates + shortNames: + - clusterwftmpl + - cwft + singular: clusterworkflowtemplate + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: cronworkflows.argoproj.io +spec: + group: argoproj.io + names: + kind: CronWorkflow + listKind: CronWorkflowList + plural: cronworkflows + shortNames: + - cwf + - cronwf + singular: cronworkflow + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowartifactgctasks.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowArtifactGCTask + listKind: WorkflowArtifactGCTaskList + plural: workflowartifactgctasks + shortNames: + - wfat + singular: workflowartifactgctask + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workfloweventbindings.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowEventBinding + listKind: WorkflowEventBindingList + plural: workfloweventbindings + shortNames: + - wfeb + singular: workfloweventbinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflows.argoproj.io +spec: + group: argoproj.io + names: + kind: Workflow + listKind: WorkflowList + plural: workflows + shortNames: + - wf + singular: workflow + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Status of the workflow + jsonPath: .status.phase + name: Status + type: string + - description: When the workflow was started + format: date-time + jsonPath: .status.startedAt + name: Age + type: date + - description: Human readable message indicating details about why the workflow + is in this condition. + jsonPath: .status.message + name: Message + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowtaskresults.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowTaskResult + listKind: WorkflowTaskResultList + plural: workflowtaskresults + singular: workflowtaskresult + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + message: + type: string + metadata: + type: object + outputs: + properties: + artifacts: + items: + properties: + archive: + properties: + none: + type: object + tar: + properties: + compressionLevel: + format: int32 + type: integer + type: object + zip: + type: object + type: object + archiveLogs: + type: boolean + artifactGC: + properties: + podMetadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + serviceAccountName: + type: string + strategy: + enum: + - "" + - OnWorkflowCompletion + - OnWorkflowDeletion + - Never + type: string + type: object + artifactory: + properties: + passwordSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + url: + type: string + usernameSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + required: + - url + type: object + azure: + properties: + accountKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + blob: + type: string + container: + type: string + endpoint: + type: string + useSDKCreds: + type: boolean + required: + - blob + - container + - endpoint + type: object + deleted: + type: boolean + from: + type: string + fromExpression: + type: string + gcs: + properties: + bucket: + type: string + key: + type: string + serviceAccountKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + required: + - key + type: object + git: + properties: + branch: + type: string + depth: + format: int64 + type: integer + disableSubmodules: + type: boolean + fetch: + items: + type: string + type: array + insecureIgnoreHostKey: + type: boolean + passwordSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + repo: + type: string + revision: + type: string + singleBranch: + type: boolean + sshPrivateKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + usernameSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + required: + - repo + type: object + globalName: + type: string + hdfs: + properties: + addresses: + items: + type: string + type: array + force: + type: boolean + hdfsUser: + type: string + krbCCacheSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + krbConfigConfigMap: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + krbKeytabSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + krbRealm: + type: string + krbServicePrincipalName: + type: string + krbUsername: + type: string + path: + type: string + required: + - path + type: object + http: + properties: + auth: + properties: + basicAuth: + properties: + passwordSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + usernameSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + clientCert: + properties: + clientCertSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + oauth2: + properties: + clientIDSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + clientSecretSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + endpointParams: + items: + properties: + key: + type: string + value: + type: string + required: + - key + type: object + type: array + scopes: + items: + type: string + type: array + tokenURLSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + type: object + headers: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + url: + type: string + required: + - url + type: object + mode: + format: int32 + type: integer + name: + type: string + optional: + type: boolean + oss: + properties: + accessKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + bucket: + type: string + createBucketIfNotPresent: + type: boolean + endpoint: + type: string + key: + type: string + lifecycleRule: + properties: + markDeletionAfterDays: + format: int32 + type: integer + markInfrequentAccessAfterDays: + format: int32 + type: integer + type: object + secretKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + securityToken: + type: string + useSDKCreds: + type: boolean + required: + - key + type: object + path: + type: string + raw: + properties: + data: + type: string + required: + - data + type: object + recurseMode: + type: boolean + s3: + properties: + accessKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + bucket: + type: string + caSecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + createBucketIfNotPresent: + properties: + objectLocking: + type: boolean + type: object + encryptionOptions: + properties: + enableEncryption: + type: boolean + kmsEncryptionContext: + type: string + kmsKeyId: + type: string + serverSideCustomerKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + endpoint: + type: string + insecure: + type: boolean + key: + type: string + region: + type: string + roleARN: + type: string + secretKeySecret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + useSDKCreds: + type: boolean + type: object + subPath: + type: string + required: + - name + type: object + type: array + exitCode: + type: string + parameters: + items: + properties: + default: + type: string + description: + type: string + enum: + items: + type: string + type: array + globalName: + type: string + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + default: + type: string + event: + type: string + expression: + type: string + jqFilter: + type: string + jsonPath: + type: string + parameter: + type: string + path: + type: string + supplied: + type: object + type: object + required: + - name + type: object + type: array + result: + type: string + type: object + phase: + type: string + progress: + type: string + required: + - metadata + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowtasksets.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowTaskSet + listKind: WorkflowTaskSetList + plural: workflowtasksets + shortNames: + - wfts + singular: workflowtaskset + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: workflowtemplates.argoproj.io +spec: + group: argoproj.io + names: + kind: WorkflowTemplate + listKind: WorkflowTemplateList + plural: workflowtemplates + shortNames: + - wftmpl + singular: workflowtemplate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + required: + - metadata + - spec + type: object + served: true + storage: true +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argo + namespace: argo +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: argo-server + namespace: argo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: argo-role + namespace: argo +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update + - apiGroups: + - "" + resources: + - secrets + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: argo-aggregate-to-admin +rules: + - apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workfloweventbindings + - workfloweventbindings/finalizers + - workflowtemplates + - workflowtemplates/finalizers + - cronworkflows + - cronworkflows/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + - workflowtasksets + - workflowtasksets/finalizers + - workflowtaskresults + - workflowtaskresults/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: argo-aggregate-to-edit +rules: + - apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workfloweventbindings + - workfloweventbindings/finalizers + - workflowtemplates + - workflowtemplates/finalizers + - cronworkflows + - cronworkflows/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + - workflowtaskresults + - workflowtaskresults/finalizers + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: argo-aggregate-to-view +rules: + - apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workfloweventbindings + - workfloweventbindings/finalizers + - workflowtemplates + - workflowtemplates/finalizers + - cronworkflows + - cronworkflows/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + - workflowtaskresults + - workflowtaskresults/finalizers + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argo-cluster-role +rules: + - apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - create + - get + - list + - watch + - update + - patch + - delete + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - watch + - list + - apiGroups: + - "" + resources: + - persistentvolumeclaims + - persistentvolumeclaims/finalizers + verbs: + - create + - update + - delete + - get + - apiGroups: + - argoproj.io + resources: + - workflows + - workflows/finalizers + - workflowtasksets + - workflowtasksets/finalizers + - workflowartifactgctasks + verbs: + - get + - list + - watch + - update + - patch + - delete + - create + - apiGroups: + - argoproj.io + resources: + - workflowtemplates + - workflowtemplates/finalizers + - clusterworkflowtemplates + - clusterworkflowtemplates/finalizers + verbs: + - get + - list + - watch + - apiGroups: + - argoproj.io + resources: + - workflowtaskresults + verbs: + - list + - watch + - deletecollection + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - apiGroups: + - argoproj.io + resources: + - cronworkflows + - cronworkflows/finalizers + verbs: + - get + - list + - watch + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: argo-server-cluster-role +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - watch + - list + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create + - apiGroups: + - "" + resources: + - pods + - pods/exec + - pods/log + verbs: + - get + - list + - watch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - watch + - create + - patch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - watch + - apiGroups: + - argoproj.io + resources: + - eventsources + - sensors + - workflows + - workfloweventbindings + - workflowtemplates + - cronworkflows + - clusterworkflowtemplates + verbs: + - create + - get + - list + - watch + - update + - patch + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: argo-binding + namespace: argo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: argo-role +subjects: + - kind: ServiceAccount + name: argo + namespace: argo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argo-cluster-role +subjects: + - kind: ServiceAccount + name: argo + namespace: argo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-server-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: argo-server-cluster-role +subjects: + - kind: ServiceAccount + name: argo-server + namespace: argo +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: workflow-controller-configmap + namespace: argo +--- +apiVersion: v1 +kind: Service +metadata: + name: argo-server + namespace: argo +spec: + ports: + - name: web + port: 2746 + targetPort: 2746 + selector: + app: argo-server +--- +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: workflow-controller +value: 1000000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argo-server + namespace: argo +spec: + selector: + matchLabels: + app: argo-server + template: + metadata: + labels: + app: argo-server + spec: + containers: + - args: + - server + env: [] + image: quay.io/argoproj/argocli:v3.5.4 + name: argo-server + ports: + - containerPort: 2746 + name: web + readinessProbe: + httpGet: + path: / + port: 2746 + scheme: HTTPS + initialDelaySeconds: 10 + periodSeconds: 20 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + volumeMounts: + - mountPath: /tmp + name: tmp + nodeSelector: + kubernetes.io/os: linux + securityContext: + runAsNonRoot: true + serviceAccountName: argo-server + volumes: + - emptyDir: {} + name: tmp +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: workflow-controller + namespace: argo +spec: + selector: + matchLabels: + app: workflow-controller + template: + metadata: + labels: + app: workflow-controller + spec: + containers: + - args: [] + command: + - workflow-controller + env: + - name: LEADER_ELECTION_IDENTITY + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + image: quay.io/argoproj/workflow-controller:v3.5.4 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 6060 + initialDelaySeconds: 90 + periodSeconds: 60 + timeoutSeconds: 30 + name: workflow-controller + ports: + - containerPort: 9090 + name: metrics + - containerPort: 6060 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + nodeSelector: + kubernetes.io/os: linux + priorityClassName: workflow-controller + securityContext: + runAsNonRoot: true + serviceAccountName: argo diff --git a/examples/ref-implementation/argo-workflows/manifests/base/kustomization.yaml b/examples/ref-implementation/argo-workflows/manifests/base/kustomization.yaml new file mode 100644 index 00000000..4cfe240e --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/base/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - install.yaml diff --git a/examples/ref-implementation/argo-workflows/manifests/dev/external-secret.yaml b/examples/ref-implementation/argo-workflows/manifests/dev/external-secret.yaml new file mode 100644 index 00000000..7b9117c3 --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/dev/external-secret.yaml @@ -0,0 +1,20 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: keycloak-oidc + namespace: argo +spec: + secretStoreRef: + name: keycloak + kind: ClusterSecretStore + target: + name: keycloak-oidc + data: + - secretKey: client-id + remoteRef: + key: keycloak-clients + property: ARGO_WORKFLOWS_CLIENT_ID + - secretKey: secret-key + remoteRef: + key: keycloak-clients + property: ARGO_WORKFLOWS_CLIENT_SECRET diff --git a/examples/ref-implementation/argo-workflows/manifests/dev/ingress.yaml b/examples/ref-implementation/argo-workflows/manifests/dev/ingress.yaml new file mode 100644 index 00000000..8df5e070 --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/dev/ingress.yaml @@ -0,0 +1,18 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: argo-workflows-ingress + namespace: argo +spec: + ingressClassName: "nginx" + rules: + - host: argo.cnoe.localtest.me + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: argo-server + port: + name: web diff --git a/examples/ref-implementation/argo-workflows/manifests/dev/kustomization.yaml b/examples/ref-implementation/argo-workflows/manifests/dev/kustomization.yaml new file mode 100644 index 00000000..042c3f6b --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/dev/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - ../base + - external-secret.yaml + - ingress.yaml + - sa-admin.yaml +patches: + - path: patches/cm-argo-workflows.yaml + - path: patches/deployment-argo-server.yaml diff --git a/examples/ref-implementation/argo-workflows/manifests/dev/patches/cm-argo-workflows.yaml b/examples/ref-implementation/argo-workflows/manifests/dev/patches/cm-argo-workflows.yaml new file mode 100644 index 00000000..ecf46598 --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/dev/patches/cm-argo-workflows.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: workflow-controller-configmap + namespace: argo +data: + config: | + sso: + insecureSkipVerify: true + issuer: https://keycloak.cnoe.localtest.me:8443/realms/cnoe + clientId: + name: keycloak-oidc + key: client-id + clientSecret: + name: keycloak-oidc + key: secret-key + redirectUrl: https://argo.cnoe.localtest.me:8443/oauth2/callback + rbac: + enabled: true + scopes: + - openid + - profile + - email + - groups + nodeEvents: + enabled: false diff --git a/examples/ref-implementation/argo-workflows/manifests/dev/patches/deployment-argo-server.yaml b/examples/ref-implementation/argo-workflows/manifests/dev/patches/deployment-argo-server.yaml new file mode 100644 index 00000000..2776ac48 --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/dev/patches/deployment-argo-server.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argo-server + namespace: argo +spec: + template: + spec: + containers: + - name: argo-server + readinessProbe: + httpGet: + path: / + port: 2746 + scheme: HTTP + args: + - server + - --configmap=workflow-controller-configmap + - --auth-mode=client + - --auth-mode=sso + - "--secure=false" + - "--loglevel" + - "info" + - "--log-format" + - "text" \ No newline at end of file diff --git a/examples/ref-implementation/argo-workflows/manifests/dev/sa-admin.yaml b/examples/ref-implementation/argo-workflows/manifests/dev/sa-admin.yaml new file mode 100644 index 00000000..988b299b --- /dev/null +++ b/examples/ref-implementation/argo-workflows/manifests/dev/sa-admin.yaml @@ -0,0 +1,32 @@ +# Used by users in the admin group +# TODO Need to tighten up permissions. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: admin + namespace: argo + annotations: + workflows.argoproj.io/rbac-rule: "'admin' in groups" + workflows.argoproj.io/rbac-rule-precedence: "10" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: argo-admin +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: admin + namespace: argo +--- +apiVersion: v1 +kind: Secret +metadata: + name: admin.service-account-token + annotations: + kubernetes.io/service-account.name: admin + namespace: argo +type: kubernetes.io/service-account-token diff --git a/examples/ref-implementation/backstage-templates.yaml b/examples/ref-implementation/backstage-templates.yaml new file mode 100644 index 00000000..e9040583 --- /dev/null +++ b/examples/ref-implementation/backstage-templates.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: backstage-templates + namespace: argocd + labels: + env: dev +spec: + project: default + source: + repoURL: cnoe://backstage-templates/entities + targetRevision: HEAD + path: "." + directory: + exclude: 'catalog-info.yaml' + destination: + server: "https://kubernetes.default.svc" + namespace: backstage + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true + diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/catalog-info.yaml b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/catalog-info.yaml new file mode 100644 index 00000000..1074ac85 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/catalog-info.yaml @@ -0,0 +1,30 @@ +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: ${{values.name}}-bucket + description: Stores things + annotations: + argocd/app-name: ${{values.name | dump}} +spec: + type: s3-bucket + owner: guest +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: ${{values.name | dump}} + description: This is for testing purposes + annotations: + backstage.io/kubernetes-label-selector: 'entity-id=${{values.name}}' + backstage.io/kubernetes-namespace: default + argocd/app-name: ${{values.name | dump}} + links: + - url: https://gitea.cnoe.localtest.me:8443 + title: Repo URL + icon: github +spec: + owner: guest + lifecycle: experimental + type: service + dependsOn: + - resource:default/${{values.name}}-bucket diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/go.mod b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/go.mod new file mode 100644 index 00000000..cc90c209 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/go.mod @@ -0,0 +1,3 @@ +module ${{ values.name }} + +go 1.19 diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/base/kustomization.yaml b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/base/kustomization.yaml new file mode 100644 index 00000000..46391ffe --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/base/kustomization.yaml @@ -0,0 +1,3 @@ +resources: + - nginx.yaml + - ${{ values.name }}.yaml diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/base/nginx.yaml b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/base/nginx.yaml new file mode 100644 index 00000000..5b5aa603 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/base/nginx.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + labels: + app: nginx +spec: + ports: + - port: 80 + targetPort: 80 + selector: + app: nginx diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/dev/kustomization.yaml b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/dev/kustomization.yaml new file mode 100644 index 00000000..6f69d81f --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/dev/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - ../base diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/prod/kustomization.yaml b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/prod/kustomization.yaml new file mode 100644 index 00000000..8df05cf5 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/kustomize/prod/kustomization.yaml @@ -0,0 +1,35 @@ +{%- if values.awsResources %} +resources: +{%- if 'Bucket' in values.awsResources %} + - ../base/ +{%- endif %} +{%- if 'Table' in values.awsResources %} + - ../base/table.yaml +{%- endif %} +{%- endif %} +namespace: default + +patches: + - target: + kind: Deployment + patch: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: not-used + labels: + backstage.io/kubernetes-id: ${{values.name}} + spec: + template: + metadata: + labels: + backstage.io/kubernetes-id: ${{values.name}} + - target: + kind: Service + patch: | + apiVersion: apps/v1 + kind: Service + metadata: + name: not-used + labels: + backstage.io/kubernetes-id: ${{values.name}} diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/main.go b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/main.go new file mode 100644 index 00000000..d3103f97 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/skeleton/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} \ No newline at end of file diff --git a/examples/ref-implementation/backstage-templates/entities/app-with-bucket/template.yaml b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/template.yaml new file mode 100644 index 00000000..0a209a6d --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/app-with-bucket/template.yaml @@ -0,0 +1,126 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + description: Adds a Go application with AWS resources + name: app-with-aws-resources + title: Add a Go App with AWS resources +spec: + owner: guest + type: service + parameters: + - properties: + name: + title: Application Name + type: string + description: Unique name of the component + ui:autofocus: true + labels: + title: Labels + type: object + additionalProperties: + type: string + description: Labels to apply to the application + ui:autofocus: true + required: + - name + title: Choose your repository location + - description: Configure your bucket + properties: + apiVersion: + default: awsblueprints.io/v1alpha1 + description: APIVersion for the resource + type: string + kind: + default: ObjectStorage + description: Kind for the resource + type: string + config: + description: ObjectStorageSpec defines the desired state of ObjectStorage + properties: + resourceConfig: + description: ResourceConfig defines general properties of this AWS resource. + properties: + deletionPolicy: + description: Defaults to Delete + enum: + - Delete + - Orphan + type: string + region: + type: string + providerConfigName: + type: string + default: default + tags: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + required: + - region + type: object + required: + - resourceConfig + title: Bucket configuration options + type: object + steps: + - id: template + name: Generating component + action: fetch:template + input: + url: ./skeleton + values: + name: ${{parameters.name}} + - action: roadiehq:utils:serialize:yaml + id: serialize + input: + data: + apiVersion: awsblueprints.io/v1alpha1 + kind: ${{ parameters.kind }} + metadata: + name: ${{ parameters.name }} + spec: ${{ parameters.config }} + name: serialize + - action: roadiehq:utils:fs:write + id: write + input: + content: ${{ steps['serialize'].output.serialized }} + path: kustomize/base/${{ parameters.name }}.yaml + name: write-to-file + - id: publish + name: Publishing to a gitea git repository + action: publish:gitea + input: + description: This is an example app + # Hard coded value for this demo purposes only. + repoUrl: gitea.cnoe.localtest.me:8443?repo=${{parameters.name}} + defaultBranch: main + - id: create-argocd-app + name: Create ArgoCD App + action: cnoe:create-argocd-app + input: + appName: ${{parameters.name}} + appNamespace: default + argoInstance: in-cluster + projectName: default + # necessary until we generate our own cert + repoUrl: http://my-gitea-http.gitea.svc.cluster.local:3000/giteaAdmin/${{parameters.name}} + path: "kustomize/base" + - id: register + name: Register + action: catalog:register + input: + repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: 'catalog-info.yaml' + output: + links: + - title: Open in catalog + icon: catalog + entityRef: ${{ steps['register'].output.entityRef }} diff --git a/examples/ref-implementation/backstage-templates/entities/argo-workflows/skeleton/catalog-info.yaml b/examples/ref-implementation/backstage-templates/entities/argo-workflows/skeleton/catalog-info.yaml new file mode 100644 index 00000000..23bc22d0 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/argo-workflows/skeleton/catalog-info.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: ${{values.name | dump}} + description: This is for testing purposes + annotations: + backstage.io/kubernetes-label-selector: 'entity-id=${{values.name}}' + backstage.io/kubernetes-namespace: argo + argocd/app-name: ${{values.name | dump}} + argo-workflows.cnoe.io/label-selector: env=dev,entity-id=${{values.name}} + argo-workflows.cnoe.io/cluster-name: local + apache-spark.cnoe.io/label-selector: env=dev,entity-id=${{values.name}} + apache-spark.cnoe.io/cluster-name: local + links: + - url: https://gitea.cnoe.localtest.me:8443 + title: Repo URL + icon: github +spec: + owner: guest + lifecycle: experimental + type: service diff --git a/examples/ref-implementation/backstage-templates/entities/argo-workflows/skeleton/manifests/deployment.yaml b/examples/ref-implementation/backstage-templates/entities/argo-workflows/skeleton/manifests/deployment.yaml new file mode 100644 index 00000000..962bc6a9 --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/argo-workflows/skeleton/manifests/deployment.yaml @@ -0,0 +1,109 @@ +# apiVersion: argoproj.io/v1alpha1 +# kind: Workflow +# metadata: +# name: ${{values.name}} +# namespace: argo +# labels: +# env: dev +# entity-id: ${{values.name}} +# spec: +# serviceAccountName: admin +# entrypoint: whalesay +# templates: +# - name: whalesay +# container: +# image: docker/whalesay:latest +# command: [cowsay] +# args: ["hello world"] +--- +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: ${{values.name}} + namespace: argo + labels: + env: dev + entity-id: ${{values.name}} +spec: + serviceAccountName: admin + entrypoint: main + action: create + templates: + - name: main + steps: + - - name: spark-job + template: spark-job + - - name: wait + template: wait + arguments: + parameters: + - name: spark-job-name + value: '{{steps.spark-job.outputs.parameters.spark-job-name}}' + + - name: wait + inputs: + parameters: + - name: spark-job-name + resource: + action: get + successCondition: status.applicationState.state == COMPLETED + failureCondition: status.applicationState.state == FAILED + manifest: | + apiVersion: "sparkoperator.k8s.io/v1beta2" + kind: SparkApplication + metadata: + name: {{inputs.parameters.spark-job-name}} + namespace: argo + + - name: spark-job + outputs: + parameters: + - name: spark-job-name + valueFrom: + jsonPath: '{.metadata.name}' + resource: + action: create + setOwnerReference: true + manifest: | + apiVersion: "sparkoperator.k8s.io/v1beta2" + kind: SparkApplication + metadata: + name: spark-pi-${{values.name}} + namespace: argo + labels: + env: dev + entity-id: ${{values.name}} + spec: + type: Scala + mode: cluster + image: "docker.io/apache/spark:v3.1.3" + imagePullPolicy: IfNotPresent + mainClass: org.apache.spark.examples.SparkPi + mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.12-3.1.3.jar" + sparkVersion: "3.1.1" + restartPolicy: + type: Never + volumes: + - name: "test-volume" + hostPath: + path: "/tmp" + type: Directory + driver: + cores: 1 + coreLimit: "1200m" + memory: "512m" + labels: + version: 3.1.1 + serviceAccount: admin + volumeMounts: + - name: "test-volume" + mountPath: "/tmp" + executor: + cores: 1 + instances: 1 + memory: "512m" + labels: + version: 3.1.1 + volumeMounts: + - name: "test-volume" + mountPath: "/tmp" diff --git a/examples/ref-implementation/backstage-templates/entities/argo-workflows/template.yaml b/examples/ref-implementation/backstage-templates/entities/argo-workflows/template.yaml new file mode 100644 index 00000000..7bbc0a8d --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/argo-workflows/template.yaml @@ -0,0 +1,62 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + description: Creates a Basic Kubernetes Deployment + name: argo-workflows-basic + title: Basic Argo Workflow with a Spark Job +spec: + owner: guest + type: service + parameters: + - title: Configuration Options + required: + - name + properties: + name: + type: string + description: name of this application + mainApplicationFile: + type: string + default: 'local:///opt/spark/examples/jars/spark-examples_2.12-3.1.3.jar' + description: Path to the main application file + + steps: + - id: template + name: Generating component + action: fetch:template + input: + url: ./skeleton + values: + name: ${{parameters.name}} + + - id: publish + name: Publishing to a gitea git repository + action: publish:gitea + input: + description: This is an example app + # Hard coded value for this demo purposes only. + repoUrl: gitea.cnoe.localtest.me:8443?repo=${{parameters.name}} + defaultBranch: main + - id: create-argocd-app + name: Create ArgoCD App + action: cnoe:create-argocd-app + input: + appName: ${{parameters.name}} + appNamespace: ${{parameters.name}} + argoInstance: in-cluster + projectName: default + # necessary until we generate our own cert + repoUrl: http://my-gitea-http.gitea.svc.cluster.local:3000/giteaAdmin/${{parameters.name}} + path: "manifests" + - id: register + name: Register + action: catalog:register + input: + repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: 'catalog-info.yaml' + + output: + links: + - title: Open in catalog + icon: catalog + entityRef: ${{ steps['register'].output.entityRef }} diff --git a/examples/ref-implementation/backstage-templates/entities/basic/skeleton/catalog-info.yaml b/examples/ref-implementation/backstage-templates/entities/basic/skeleton/catalog-info.yaml new file mode 100644 index 00000000..1394ee2a --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/basic/skeleton/catalog-info.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: ${{values.name | dump}} + description: This is for testing purposes + annotations: + backstage.io/kubernetes-label-selector: 'entity-id=${{values.name}}' + backstage.io/kubernetes-namespace: default + argocd/app-name: ${{values.name | dump}} + links: + - url: https://gitea.cnoe.localtest.me:8443 + title: Repo URL + icon: github +spec: + owner: guest + lifecycle: experimental + type: service diff --git a/examples/ref-implementation/backstage-templates/entities/basic/skeleton/manifests/deployment.yaml b/examples/ref-implementation/backstage-templates/entities/basic/skeleton/manifests/deployment.yaml new file mode 100644 index 00000000..fc52cffc --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/basic/skeleton/manifests/deployment.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ${{values.name | dump}} + namespace: default + labels: + entity-id: ${{values.name}} + app: nginx +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/examples/ref-implementation/backstage-templates/entities/basic/template.yaml b/examples/ref-implementation/backstage-templates/entities/basic/template.yaml new file mode 100644 index 00000000..81f1737a --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/basic/template.yaml @@ -0,0 +1,58 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + description: Creates a Basic Kubernetes Deployment + name: basic + title: Create a Basic Deployment +spec: + owner: guest + type: service + parameters: + - title: Configuration Options + required: + - name + properties: + name: + type: string + description: name of this application + + steps: + - id: template + name: Generating component + action: fetch:template + input: + url: ./skeleton + values: + name: ${{parameters.name}} + + - id: publish + name: Publishing to a gitea git repository + action: publish:gitea + input: + description: This is an example app + # Hard coded value for this demo purposes only. + repoUrl: gitea.cnoe.localtest.me:8443?repo=${{parameters.name}} + defaultBranch: main + - id: create-argocd-app + name: Create ArgoCD App + action: cnoe:create-argocd-app + input: + appName: ${{parameters.name}} + appNamespace: ${{parameters.name}} + argoInstance: in-cluster + projectName: default + # necessary until we generate our own cert + repoUrl: http://my-gitea-http.gitea.svc.cluster.local:3000/giteaAdmin/${{parameters.name}} + path: "manifests" + - id: register + name: Register + action: catalog:register + input: + repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: 'catalog-info.yaml' + + output: + links: + - title: Open in catalog + icon: catalog + entityRef: ${{ steps['register'].output.entityRef }} diff --git a/examples/ref-implementation/backstage-templates/entities/catalog-info.yaml b/examples/ref-implementation/backstage-templates/entities/catalog-info.yaml new file mode 100644 index 00000000..5dd49ebf --- /dev/null +++ b/examples/ref-implementation/backstage-templates/entities/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Location +metadata: + name: basic-example-templates + description: A collection of example templates +spec: + targets: + - ./basic/template.yaml + - ./argo-workflows/template.yaml + - ./app-with-bucket/template.yaml diff --git a/examples/ref-implementation/backstage.yaml b/examples/ref-implementation/backstage.yaml new file mode 100644 index 00000000..53bcc12e --- /dev/null +++ b/examples/ref-implementation/backstage.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: backstage + namespace: argocd + labels: + env: dev +spec: + project: default + source: + repoURL: cnoe://backstage/manifests + targetRevision: HEAD + path: "." + destination: + server: "https://kubernetes.default.svc" + namespace: backstage + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true diff --git a/examples/ref-implementation/backstage/manifests/argocd-secrets.yaml b/examples/ref-implementation/backstage/manifests/argocd-secrets.yaml new file mode 100644 index 00000000..28ab8558 --- /dev/null +++ b/examples/ref-implementation/backstage/manifests/argocd-secrets.yaml @@ -0,0 +1,77 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: eso-store + namespace: argocd +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: eso-store + namespace: argocd +rules: + - apiGroups: [""] + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - authorization.k8s.io + resources: + - selfsubjectrulesreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: eso-store + namespace: argocd +subjects: + - kind: ServiceAccount + name: eso-store + namespace: argocd +roleRef: + kind: Role + name: eso-store + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: external-secrets.io/v1beta1 +kind: ClusterSecretStore +metadata: + name: argocd +spec: + provider: + kubernetes: + remoteNamespace: argocd + server: + caProvider: + type: ConfigMap + name: kube-root-ca.crt + namespace: argocd + key: ca.crt + auth: + serviceAccount: + name: eso-store + namespace: argocd +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: argocd-credentials + namespace: backstage +spec: + secretStoreRef: + name: argocd + kind: ClusterSecretStore + refreshInterval: "0" + target: + name: argocd-credentials + data: + - secretKey: ARGOCD_ADMIN_PASSWORD + remoteRef: + key: argocd-initial-admin-secret + property: password diff --git a/examples/ref-implementation/backstage/manifests/install.yaml b/examples/ref-implementation/backstage/manifests/install.yaml new file mode 100644 index 00000000..20b1dd09 --- /dev/null +++ b/examples/ref-implementation/backstage/manifests/install.yaml @@ -0,0 +1,434 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: backstage +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: backstage + namespace: backstage +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: backstage-argo-worfklows +rules: + - apiGroups: + - argoproj.io + resources: + - workflows + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: read-all +rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: backstage-argo-worfklows +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backstage-argo-worfklows +subjects: + - kind: ServiceAccount + name: backstage + namespace: backstage +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: backstage-read-all +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: read-all +subjects: + - kind: ServiceAccount + name: backstage + namespace: backstage +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: backstage-config + namespace: backstage +data: + app-config.yaml: | + app: + title: CNOE Backstage + baseUrl: ${BACKSTAGE_FRONTEND_URL} + organization: + name: CNOE + backend: + # Used for enabling authentication, secret is shared by all backend plugins + # See https://backstage.io/docs/tutorials/backend-to-backend-auth for + # information on the format + # auth: + # keys: + # - secret: ${BACKEND_SECRET} + baseUrl: ${BACKSTAGE_FRONTEND_URL} + listen: + port: 7007 + # Uncomment the following host directive to bind to specific interfaces + # host: 127.0.0.1 + csp: + connect-src: ["'self'", 'http:', 'https:'] + # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference + # Default Helmet Content-Security-Policy values can be removed by setting the key to false + cors: + origin: ${BACKSTAGE_FRONTEND_URL} + methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true + database: + client: pg + connection: + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + cache: + store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir + + integrations: + gitea: + - baseUrl: https://gitea.cnoe.localtest.me:8443 + host: gitea.cnoe.localtest.me:8443 + username: ${GITEA_USERNAME} + password: ${GITEA_PASSWORD} + - baseUrl: https://gitea.cnoe.localtest.me + host: gitea.cnoe.localtest.me + username: ${GITEA_USERNAME} + password: ${GITEA_PASSWORD} + # github: + # - host: github.com + # apps: + # - $include: github-integration.yaml + # - host: github.com + # # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information + # # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + # token: ${GITHUB_TOKEN} + ### Example for how to add your GitHub Enterprise instance using the API: + # - host: ghe.example.net + # apiBaseUrl: https://ghe.example.net/api/v3 + # token: ${GHE_TOKEN} + + # Reference documentation http://backstage.io/docs/features/techdocs/configuration + # Note: After experimenting with basic setup, use CI/CD to generate docs + # and an external cloud storage when deploying TechDocs for production use-case. + # https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach + techdocs: + builder: 'local' # Alternatives - 'external' + generator: + runIn: 'docker' # Alternatives - 'local' + publisher: + type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives. + + auth: + environment: development + session: + secret: MW2sV-sIPngEl26vAzatV-6VqfsgAx4bPIz7PuE_2Lk= + providers: + keycloak-oidc: + development: + metadataUrl: ${KEYCLOAK_NAME_METADATA} + clientId: backstage + clientSecret: ${KEYCLOAK_CLIENT_SECRET} + scope: 'openid profile email groups' + prompt: auto + + scaffolder: + # see https://backstage.io/docs/features/software-templates/configuration for software template options + defaultAuthor: + name: backstage-scaffolder + email: noreply + defaultCommitMessage: "backstage scaffolder" + catalog: + import: + entityFilename: catalog-info.yaml + pullRequestBranchName: backstage-integration + rules: + - allow: [Component, System, API, Resource, Location, Template] + locations: + # Examples from a public GitHub repository. + - type: url + target: https://gitea.cnoe.localtest.me:8443/giteaAdmin/idpbuilder-localdev-backstage-entities/src/branch/main/catalog-info.yaml + ## Uncomment these lines to add an example org + # - type: url + # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml + # rules: + # - allow: [User, Group] + kubernetes: + serviceLocatorMethod: + type: 'multiTenant' + clusterLocatorMethods: + - $include: k8s-config.yaml + argocd: + username: admin + password: ${ARGOCD_ADMIN_PASSWORD} + appLocatorMethods: + - type: 'config' + instances: + - name: in-cluster + url: https://argocd.cnoe.localtest.me:8443 + username: admin + password: ${ARGOCD_ADMIN_PASSWORD} + argoWorkflows: + baseUrl: ${ARGO_WORKFLOWS_URL} +--- +apiVersion: v1 +kind: Secret +metadata: + name: k8s-config + namespace: backstage +stringData: + k8s-config.yaml: "type: 'config'\nclusters:\n - url: https://kubernetes.default.svc.cluster.local\n + \ name: local\n authProvider: 'serviceAccount'\n skipTLSVerify: true\n + \ skipMetricsLookup: true\n serviceAccountToken: \n $file: /var/run/secrets/kubernetes.io/serviceaccount/token\n + \ caData: \n $file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n" +--- +apiVersion: v1 +kind: Service +metadata: + name: backstage + namespace: backstage +spec: + ports: + - name: http + port: 7007 + targetPort: http + selector: + app: backstage +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: postgresql + name: postgresql + namespace: backstage +spec: + clusterIP: None + ports: + - name: postgres + port: 5432 + selector: + app: postgresql +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backstage + namespace: backstage +spec: + replicas: 1 + selector: + matchLabels: + app: backstage + template: + metadata: + labels: + app: backstage + spec: + containers: + - command: + - node + - packages/backend + - --config + - config/app-config.yaml + env: + - name: LOG_LEVEL + value: debug + - name: NODE_TLS_REJECT_UNAUTHORIZED + value: "0" + envFrom: + - secretRef: + name: backstage-env-vars + - secretRef: + name: gitea-credentials + - secretRef: + name: argocd-credentials + image: public.ecr.aws/cnoe-io/backstage:rc1 + name: backstage + ports: + - containerPort: 7007 + name: http + volumeMounts: + - mountPath: /app/config + name: backstage-config + readOnly: true + serviceAccountName: backstage + volumes: + - name: backstage-config + projected: + sources: + - configMap: + items: + - key: app-config.yaml + path: app-config.yaml + name: backstage-config + - secret: + items: + - key: k8s-config.yaml + path: k8s-config.yaml + name: k8s-config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app: postgresql + name: postgresql + namespace: backstage +spec: + replicas: 1 + selector: + matchLabels: + app: postgresql + serviceName: service-postgresql + template: + metadata: + labels: + app: postgresql + spec: + containers: + - env: + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: backstage-env-vars + key: POSTGRES_DB + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: backstage-env-vars + key: POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: backstage-env-vars + key: POSTGRES_PASSWORD + image: docker.io/library/postgres:15.3-alpine3.18 + name: postgres + ports: + - containerPort: 5432 + name: postgresdb + resources: + limits: + memory: 500Mi + requests: + cpu: 100m + memory: 300Mi +--- +apiVersion: generators.external-secrets.io/v1alpha1 +kind: Password +metadata: + name: backstage + namespace: backstage +spec: + length: 36 + digits: 5 + symbols: 5 + symbolCharacters: "/-+" + noUpper: false + allowRepeat: true +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: backstage-oidc + namespace: backstage +spec: + secretStoreRef: + name: keycloak + kind: ClusterSecretStore + refreshInterval: "0" + target: + name: backstage-env-vars + template: + engineVersion: v2 + data: + BACKSTAGE_FRONTEND_URL: https://backstage.cnoe.localtest.me:8443 + POSTGRES_HOST: postgresql.backstage.svc.cluster.local + POSTGRES_PORT: '5432' + POSTGRES_DB: backstage + POSTGRES_USER: backstage + POSTGRES_PASSWORD: "{{.POSTGRES_PASSWORD}}" + ARGO_WORKFLOWS_URL: https://argo.cnoe.localtest.me:8443 + KEYCLOAK_NAME_METADATA: https://keycloak.cnoe.localtest.me:8443/realms/cnoe/.well-known/openid-configuration + KEYCLOAK_CLIENT_SECRET: "{{.BACKSTAGE_CLIENT_SECRET}}" + ARGOCD_AUTH_TOKEN: "argocd.token={{.ARGOCD_SESSION_TOKEN}}" + ARGO_CD_URL: 'https://argocd-server.argocd.svc.cluster.local/api/v1/' + data: + - secretKey: ARGOCD_SESSION_TOKEN + remoteRef: + key: keycloak-clients + property: ARGOCD_SESSION_TOKEN + - secretKey: BACKSTAGE_CLIENT_SECRET + remoteRef: + key: keycloak-clients + property: BACKSTAGE_CLIENT_SECRET + dataFrom: + - sourceRef: + generatorRef: + apiVersion: generators.external-secrets.io/v1alpha1 + kind: Password + name: backstage + rewrite: + - transform: + template: "POSTGRES_PASSWORD" +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gitea-credentials + namespace: backstage +spec: + secretStoreRef: + name: gitea + kind: ClusterSecretStore + refreshInterval: "0" + target: + name: gitea-credentials + data: + - secretKey: GITEA_USERNAME + remoteRef: + key: gitea-admin-secret + property: username + - secretKey: GITEA_PASSWORD + remoteRef: + key: gitea-admin-secret + property: password +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: backstage + namespace: backstage +spec: + ingressClassName: "nginx" + rules: + - host: backstage.cnoe.localtest.me + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: backstage + port: + name: http diff --git a/examples/ref-implementation/coredns.yaml b/examples/ref-implementation/coredns.yaml new file mode 100644 index 00000000..ca46cd95 --- /dev/null +++ b/examples/ref-implementation/coredns.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: coredns + namespace: argocd + labels: + env: dev +spec: + project: default + source: + repoURL: cnoe://coredns/manifests + targetRevision: HEAD + path: "." + destination: + server: "https://kubernetes.default.svc" + namespace: kube-system + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true diff --git a/examples/ref-implementation/coredns/manifests/cm-coredns.yaml b/examples/ref-implementation/coredns/manifests/cm-coredns.yaml new file mode 100644 index 00000000..1038f8a0 --- /dev/null +++ b/examples/ref-implementation/coredns/manifests/cm-coredns.yaml @@ -0,0 +1,33 @@ +# the only purpose of this is to resolve `keycloak.cnoe.localtest.me` to a cluster IP +# normally, `keycloak.cnoe.localtest.me` resolves to 127.0.0.1 and thus oidc endpoint configurations cannot be obtained. +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system +data: + Corefile: | + .:53 { + errors + health { + lameduck 5s + } + ready + rewrite name keycloak.cnoe.localtest.me ingress-nginx-controller.ingress-nginx.svc.cluster.local + rewrite name gitea.cnoe.localtest.me ingress-nginx-controller.ingress-nginx.svc.cluster.local + rewrite name argocd.cnoe.localtest.me ingress-nginx-controller.ingress-nginx.svc.cluster.local + rewrite name argo.cnoe.localtest.me ingress-nginx-controller.ingress-nginx.svc.cluster.local + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + ttl 30 + } + prometheus :9153 + forward . /etc/resolv.conf { + max_concurrent 1000 + } + cache 30 + loop + reload + loadbalance + } diff --git a/examples/ref-implementation/crossplane-compositions.yaml b/examples/ref-implementation/crossplane-compositions.yaml new file mode 100644 index 00000000..f46fc7ab --- /dev/null +++ b/examples/ref-implementation/crossplane-compositions.yaml @@ -0,0 +1,22 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: crossplane-compositions + namespace: argocd + labels: + env: dev + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: cnoe://crossplane-compositions/manifests + targetRevision: HEAD + path: "." + directory: + recurse: true + destination: + server: "https://kubernetes.default.svc" + namespace: crossplane-system + syncPolicy: + automated: {} diff --git a/examples/ref-implementation/crossplane-compositions/manifests/s3/definition.yaml b/examples/ref-implementation/crossplane-compositions/manifests/s3/definition.yaml new file mode 100644 index 00000000..b812896b --- /dev/null +++ b/examples/ref-implementation/crossplane-compositions/manifests/s3/definition.yaml @@ -0,0 +1,76 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xobjectstorages.awsblueprints.io +spec: + claimNames: + kind: ObjectStorage + plural: objectstorages + group: awsblueprints.io + names: + kind: XObjectStorage + plural: xobjectstorages + connectionSecretKeys: + - region + - bucket-name + - s3-put-policy + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + properties: + spec: + description: ObjectStorageSpec defines the desired state of ObjectStorage + properties: + resourceConfig: + description: ResourceConfig defines general properties of this AWS + resource. + properties: + deletionPolicy: + description: Defaults to Delete + enum: + - Delete + - Orphan + type: string + name: + description: Set the name of this resource in AWS to the value + provided by this field. + type: string + providerConfigName: + type: string + region: + type: string + tags: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + required: + - providerConfigName + - region + - tags + type: object + required: + - resourceConfig + type: object + status: + description: ObjectStorageStatus defines the observed state of ObjectStorage + properties: + bucketName: + type: string + bucketArn: + type: string + type: object + type: object diff --git a/examples/ref-implementation/crossplane-compositions/manifests/s3/general-purpose.yaml b/examples/ref-implementation/crossplane-compositions/manifests/s3/general-purpose.yaml new file mode 100644 index 00000000..9b3be056 --- /dev/null +++ b/examples/ref-implementation/crossplane-compositions/manifests/s3/general-purpose.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: s3bucket.awsblueprints.io + labels: + awsblueprints.io/provider: aws + awsblueprints.io/environment: dev + s3.awsblueprints.io/configuration: standard +spec: + writeConnectionSecretsToNamespace: crossplane-system + compositeTypeRef: + apiVersion: awsblueprints.io/v1alpha1 + kind: XObjectStorage + patchSets: + - name: common-fields + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceConfig.providerConfigName + toFieldPath: spec.providerConfigRef.name + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceConfig.deletionPolicy + toFieldPath: spec.deletionPolicy + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceConfig.region + toFieldPath: spec.forProvider.region + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceConfig.name + toFieldPath: metadata.annotations[crossplane.io/external-name] + resources: + - name: s3-bucket + connectionDetails: + - name: bucket-name + fromConnectionSecretKey: endpoint + - name: region + fromConnectionSecretKey: region + base: + apiVersion: s3.aws.crossplane.io/v1beta1 + kind: Bucket + spec: + deletionPolicy: Delete + forProvider: + objectOwnership: BucketOwnerEnforced + publicAccessBlockConfiguration: + blockPublicPolicy: true + restrictPublicBuckets: true + serverSideEncryptionConfiguration: + rules: + - applyServerSideEncryptionByDefault: + sseAlgorithm: AES256 + patches: + - type: PatchSet + patchSetName: common-fields + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceConfig.tags + toFieldPath: spec.forProvider.tagging.tagSet + policy: + mergeOptions: + appendSlice: true + keepMapValues: true + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceConfig.region + toFieldPath: spec.forProvider.locationConstraint + - fromFieldPath: spec.writeConnectionSecretToRef.namespace + toFieldPath: spec.writeConnectionSecretToRef.namespace + - type: ToCompositeFieldPath + fromFieldPath: metadata.annotations[crossplane.io/external-name] + toFieldPath: status.bucketName + - type: ToCompositeFieldPath + fromFieldPath: status.atProvider.arn + toFieldPath: status.bucketArn + - fromFieldPath: metadata.uid + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + fmt: "%s-bucket" diff --git a/examples/ref-implementation/crossplane-providers.yaml b/examples/ref-implementation/crossplane-providers.yaml new file mode 100644 index 00000000..4ddc7dec --- /dev/null +++ b/examples/ref-implementation/crossplane-providers.yaml @@ -0,0 +1,22 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: crossplane-providers + namespace: argocd + labels: + env: dev + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: cnoe://crossplane-providers + targetRevision: HEAD + path: "." + destination: + server: "https://kubernetes.default.svc" + namespace: crossplane-system + syncPolicy: + automated: {} + syncOptions: + - CreateNamespace=true diff --git a/examples/ref-implementation/crossplane-providers/provider-aws.yaml b/examples/ref-implementation/crossplane-providers/provider-aws.yaml new file mode 100644 index 00000000..08d06d87 --- /dev/null +++ b/examples/ref-implementation/crossplane-providers/provider-aws.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws +spec: + package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.46.0 diff --git a/examples/ref-implementation/crossplane-providers/provider-config.yaml b/examples/ref-implementation/crossplane-providers/provider-config.yaml new file mode 100644 index 00000000..21924c6e --- /dev/null +++ b/examples/ref-implementation/crossplane-providers/provider-config.yaml @@ -0,0 +1,14 @@ +apiVersion: aws.crossplane.io/v1beta1 +kind: ProviderConfig +metadata: + name: default + annotations: + argocd.argoproj.io/sync-wave: "20" + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: local-secret + key: creds diff --git a/examples/ref-implementation/crossplane-providers/provider-secret.yaml b/examples/ref-implementation/crossplane-providers/provider-secret.yaml new file mode 100644 index 00000000..a78addb5 --- /dev/null +++ b/examples/ref-implementation/crossplane-providers/provider-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: local-secret + namespace: crossplane-system +stringData: + creds: | + [default] + aws_access_key_id = replaceme + aws_secret_access_key = replaceme + aws_session_token = replacemeifneeded diff --git a/examples/ref-implementation/crossplane.yaml b/examples/ref-implementation/crossplane.yaml new file mode 100644 index 00000000..79c7aba7 --- /dev/null +++ b/examples/ref-implementation/crossplane.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: crossplane + namespace: argocd + labels: + env: dev + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: 'https://charts.crossplane.io/stable' + targetRevision: 1.14.5 + helm: + releaseName: crossplane + chart: crossplane + destination: + server: 'https://kubernetes.default.svc' + namespace: crossplane-system + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/examples/ref-implementation/external-secrets.yaml b/examples/ref-implementation/external-secrets.yaml new file mode 100644 index 00000000..df2cb5c3 --- /dev/null +++ b/examples/ref-implementation/external-secrets.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: external-secrets + namespace: argocd + labels: + env: dev + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: 'https://charts.external-secrets.io' + targetRevision: 0.9.11 + helm: + releaseName: external-secrets + chart: external-secrets + destination: + server: "https://kubernetes.default.svc" + namespace: external-secrets + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true diff --git a/examples/ref-implementation/external-secrets/manifests/secret-store.yaml b/examples/ref-implementation/external-secrets/manifests/secret-store.yaml new file mode 100644 index 00000000..88a0cbf0 --- /dev/null +++ b/examples/ref-implementation/external-secrets/manifests/secret-store.yaml @@ -0,0 +1,2 @@ + +--- diff --git a/examples/ref-implementation/images/backstage-templates-spark.png b/examples/ref-implementation/images/backstage-templates-spark.png new file mode 100644 index 00000000..2b0b4112 Binary files /dev/null and b/examples/ref-implementation/images/backstage-templates-spark.png differ diff --git a/examples/ref-implementation/images/backstage-templates.png b/examples/ref-implementation/images/backstage-templates.png new file mode 100644 index 00000000..a53f9854 Binary files /dev/null and b/examples/ref-implementation/images/backstage-templates.png differ diff --git a/examples/ref-implementation/images/basic-deployment.png b/examples/ref-implementation/images/basic-deployment.png new file mode 100644 index 00000000..0f351af5 Binary files /dev/null and b/examples/ref-implementation/images/basic-deployment.png differ diff --git a/examples/ref-implementation/images/basic-template-flow.png b/examples/ref-implementation/images/basic-template-flow.png new file mode 100644 index 00000000..b9762024 Binary files /dev/null and b/examples/ref-implementation/images/basic-template-flow.png differ diff --git a/examples/ref-implementation/images/demo-entity.png b/examples/ref-implementation/images/demo-entity.png new file mode 100644 index 00000000..828e3d71 Binary files /dev/null and b/examples/ref-implementation/images/demo-entity.png differ diff --git a/examples/ref-implementation/images/demo2-entity.png b/examples/ref-implementation/images/demo2-entity.png new file mode 100644 index 00000000..607cb238 Binary files /dev/null and b/examples/ref-implementation/images/demo2-entity.png differ diff --git a/examples/ref-implementation/images/src/basic-deployment.excalidraw b/examples/ref-implementation/images/src/basic-deployment.excalidraw new file mode 100644 index 00000000..0c49ba4e --- /dev/null +++ b/examples/ref-implementation/images/src/basic-deployment.excalidraw @@ -0,0 +1,688 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "yozZorioSE1OUkpHktzVP", + "type": "rectangle", + "x": 727, + "y": 454, + "width": 138, + "height": 68.00000000000001, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1193746031, + "version": 164, + "versionNonce": 917424207, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Qn0U1j1w19_hNzfHMrEHQ" + }, + { + "id": "Um8DNgdEeXUjERYx_0rtv", + "type": "arrow" + }, + { + "id": "qJj5wVYIiRzV91y3h6Xbi", + "type": "arrow" + }, + { + "id": "cE_ucOKJBcWQXtcgaSoPF", + "type": "arrow" + } + ], + "updated": 1707246661988, + "link": null, + "locked": false + }, + { + "id": "Qn0U1j1w19_hNzfHMrEHQ", + "type": "text", + "x": 760.3499984741211, + "y": 475.5, + "width": 71.30000305175781, + "height": 25, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1937750799, + "version": 157, + "versionNonce": 397238721, + "isDeleted": false, + "boundElements": null, + "updated": 1707246500158, + "link": null, + "locked": false, + "text": "ArgoCD", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "yozZorioSE1OUkpHktzVP", + "originalText": "ArgoCD", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 183, + "versionNonce": 512282671, + "isDeleted": false, + "id": "z1vPsJxFaPRhe0i1Ck0Je", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 418, + "y": 351, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 138, + "height": 68.00000000000001, + "seed": 1492127791, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "DyaGAvMwuxh_cnuhL8d3P" + }, + { + "id": "ahkUXt0AQa8URVqUCdwu5", + "type": "arrow" + }, + { + "id": "Um8DNgdEeXUjERYx_0rtv", + "type": "arrow" + } + ], + "updated": 1707246694929, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 186, + "versionNonce": 1954345551, + "isDeleted": false, + "id": "DyaGAvMwuxh_cnuhL8d3P", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 434.9583320617676, + "y": 372.5, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 104.08333587646484, + "height": 25, + "seed": 1610363471, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707246694929, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Backstage", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "z1vPsJxFaPRhe0i1Ck0Je", + "originalText": "Backstage", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 205, + "versionNonce": 1736977089, + "isDeleted": false, + "id": "hKolk3HE8f7p7kku0fuAR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 722, + "y": 251, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 138, + "height": 68.00000000000001, + "seed": 1171434639, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "DpYp_SU3PTt5pGMJEYXeQ" + }, + { + "id": "ahkUXt0AQa8URVqUCdwu5", + "type": "arrow" + }, + { + "id": "cE_ucOKJBcWQXtcgaSoPF", + "type": "arrow" + } + ], + "updated": 1707246657028, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 212, + "versionNonce": 420957761, + "isDeleted": false, + "id": "DpYp_SU3PTt5pGMJEYXeQ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 763.1333332061768, + "y": 272.5, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 55.733333587646484, + "height": 25, + "seed": 1661747887, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707246497718, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Gitea", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "hKolk3HE8f7p7kku0fuAR", + "originalText": "Gitea", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 192, + "versionNonce": 567119311, + "isDeleted": false, + "id": "A_LZS0mn561UWD01SaaNw", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 932, + "y": 353, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 138, + "height": 68.00000000000001, + "seed": 639538113, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "pFG3mG67d8W-gP9a7l27j" + }, + { + "id": "qJj5wVYIiRzV91y3h6Xbi", + "type": "arrow" + } + ], + "updated": 1707246620246, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 210, + "versionNonce": 1183409057, + "isDeleted": false, + "id": "pFG3mG67d8W-gP9a7l27j", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 947.6500015258789, + "y": 374.5, + "strokeColor": "#9c36b5", + "backgroundColor": "transparent", + "width": 106.69999694824219, + "height": 25, + "seed": 1601729441, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1707246498719, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Kubernetes", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "A_LZS0mn561UWD01SaaNw", + "originalText": "Kubernetes", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "ahkUXt0AQa8URVqUCdwu5", + "type": "arrow", + "x": 561, + "y": 389.03022718221666, + "width": 154, + "height": 102.11103654737104, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1646169281, + "version": 238, + "versionNonce": 1284654255, + "isDeleted": false, + "boundElements": null, + "updated": 1707246701910, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 81, + -32.03022718221666 + ], + [ + 154, + -102.11103654737104 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "z1vPsJxFaPRhe0i1Ck0Je", + "focus": 0.5432390553840177, + "gap": 5 + }, + "endBinding": { + "elementId": "hKolk3HE8f7p7kku0fuAR", + "focus": 0.7087101937049524, + "gap": 7 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "version": 337, + "versionNonce": 2107204335, + "isDeleted": false, + "id": "Um8DNgdEeXUjERYx_0rtv", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 565.0411501895638, + "y": 389.1698221307173, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 153.9999999999999, + "height": 103.34207184944046, + "seed": 1365398817, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1707246699212, + "link": null, + "locked": false, + "startBinding": { + "elementId": "z1vPsJxFaPRhe0i1Ck0Je", + "focus": -0.45382037830581345, + "gap": 9.041150189563837 + }, + "endBinding": { + "elementId": "yozZorioSE1OUkpHktzVP", + "focus": -0.8066378321183331, + "gap": 7.958849810436277 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 94.95884981043616, + 39.83017786928269 + ], + [ + 153.9999999999999, + 103.34207184944046 + ] + ] + }, + { + "id": "XAiE7TdBFNjm7rN5XwJO2", + "type": "text", + "x": 508, + "y": 297, + "width": 164.89999389648438, + "height": 25, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1475743759, + "version": 54, + "versionNonce": 201561807, + "isDeleted": false, + "boundElements": null, + "updated": 1707246643630, + "link": null, + "locked": false, + "text": "Create Git Repo", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Create Git Repo", + "lineHeight": 1.25 + }, + { + "id": "Wtfg9wiBcJ8qgM5sJ1Rgy", + "type": "text", + "x": 522, + "y": 444, + "width": 159.28334045410156, + "height": 50, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1002133263, + "version": 60, + "versionNonce": 1766483329, + "isDeleted": false, + "boundElements": null, + "updated": 1707246645667, + "link": null, + "locked": false, + "text": "Create ArgoCD \nApplication", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 43, + "containerId": null, + "originalText": "Create ArgoCD \nApplication", + "lineHeight": 1.25 + }, + { + "id": "qJj5wVYIiRzV91y3h6Xbi", + "type": "arrow", + "x": 873, + "y": 489, + "width": 114, + "height": 66, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 630215073, + "version": 118, + "versionNonce": 1585297729, + "isDeleted": false, + "boundElements": null, + "updated": 1707246649748, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 78, + -6 + ], + [ + 114, + -66 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "yozZorioSE1OUkpHktzVP", + "focus": 0.17612524461839527, + "gap": 8 + }, + "endBinding": { + "elementId": "A_LZS0mn561UWD01SaaNw", + "focus": -0.08501118568232663, + "gap": 2 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "cE_ucOKJBcWQXtcgaSoPF", + "type": "arrow", + "x": 794, + "y": 449, + "width": 2, + "height": 127, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1514085633, + "version": 138, + "versionNonce": 842839791, + "isDeleted": false, + "boundElements": null, + "updated": 1707246662294, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 2, + -127 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "yozZorioSE1OUkpHktzVP", + "focus": -0.037594836371871804, + "gap": 5 + }, + "endBinding": { + "elementId": "hKolk3HE8f7p7kku0fuAR", + "focus": -0.08028535839655757, + "gap": 3 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "8nULB38EPuEIAjdNdyYp0", + "type": "text", + "x": 991, + "y": 479, + "width": 62.13333511352539, + "height": 25, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1148815169, + "version": 7, + "versionNonce": 1706607119, + "isDeleted": false, + "boundElements": null, + "updated": 1707246674659, + "link": null, + "locked": false, + "text": "Deploy", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Deploy", + "lineHeight": 1.25 + }, + { + "id": "dxtUjQKSuIlFaCv7spFWr", + "type": "text", + "x": 809, + "y": 377, + "width": 35.11666488647461, + "height": 25, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 393005647, + "version": 29, + "versionNonce": 1356449295, + "isDeleted": false, + "boundElements": null, + "updated": 1707246685968, + "link": null, + "locked": false, + "text": "Pull", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18, + "containerId": null, + "originalText": "Pull", + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/examples/ref-implementation/keycloak.yaml b/examples/ref-implementation/keycloak.yaml new file mode 100644 index 00000000..d279bc52 --- /dev/null +++ b/examples/ref-implementation/keycloak.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: keycloak + namespace: argocd + labels: + example: ref-implementation +spec: + destination: + namespace: keycloak + server: "https://kubernetes.default.svc" + source: + repoURL: cnoe://keycloak/manifests + targetRevision: HEAD + path: "." + project: default + syncPolicy: + automated: + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/examples/ref-implementation/keycloak/manifests/ingress.yaml b/examples/ref-implementation/keycloak/manifests/ingress.yaml new file mode 100644 index 00000000..c3ba1476 --- /dev/null +++ b/examples/ref-implementation/keycloak/manifests/ingress.yaml @@ -0,0 +1,20 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak-ingress + namespace: keycloak + annotations: + argocd.argoproj.io/sync-wave: "100" +spec: + ingressClassName: "nginx" + rules: + - host: keycloak.cnoe.localtest.me + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: keycloak + port: + name: http diff --git a/examples/ref-implementation/keycloak/manifests/install.yaml b/examples/ref-implementation/keycloak/manifests/install.yaml new file mode 100644 index 00000000..6601d968 --- /dev/null +++ b/examples/ref-implementation/keycloak/manifests/install.yaml @@ -0,0 +1,142 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: keycloak +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + labels: + app: keycloak +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: keycloak + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: keycloak + name: keycloak + namespace: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + containers: + - args: + - start-dev + env: + - name: KEYCLOAK_ADMIN + value: cnoe-admin + envFrom: + - secretRef: + name: keycloak-config + image: quay.io/keycloak/keycloak:22.0.3 + name: keycloak + ports: + - containerPort: 8080 + name: http + readinessProbe: + httpGet: + path: /realms/master + port: 8080 + volumeMounts: + - mountPath: /opt/keycloak/conf + name: keycloak-config + readOnly: true + volumes: + - configMap: + name: keycloak-config + name: keycloak-config +--- +apiVersion: v1 +data: + keycloak.conf: | + # Database + # The database vendor. + db=postgres + + # The username of the database user. + db-username=keycloak + db-url-host=postgresql.keycloak.svc.cluster.local + + # The proxy address forwarding mode if the server is behind a reverse proxy. + proxy=edge + + # hostname configuration + hostname=keycloak.cnoe.localtest.me + hostname-port=8443 + # the admin url requires its own configuration to reflect correct url + hostname-admin=keycloak.cnoe.localtest.me:8443 + # this should only be allowed in development. NEVER in production. + hostname-strict=false + hostname-strict-backchannel=false + + +kind: ConfigMap +metadata: + name: keycloak-config + namespace: keycloak +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: postgresql + name: postgresql + namespace: keycloak +spec: + clusterIP: None + ports: + - name: postgres + port: 5432 + selector: + app: postgresql +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app: postgresql + name: postgresql + namespace: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: postgresql + serviceName: service-postgresql + template: + metadata: + labels: + app: postgresql + spec: + containers: + - envFrom: + - secretRef: + name: keycloak-config + image: docker.io/library/postgres:15.3-alpine3.18 + name: postgres + ports: + - containerPort: 5432 + name: postgresdb + resources: + limits: + memory: 500Mi + requests: + cpu: 100m + memory: 300Mi diff --git a/examples/ref-implementation/keycloak/manifests/keycloak-config.yaml b/examples/ref-implementation/keycloak/manifests/keycloak-config.yaml new file mode 100644 index 00000000..4b8fc2da --- /dev/null +++ b/examples/ref-implementation/keycloak/manifests/keycloak-config.yaml @@ -0,0 +1,364 @@ +# resources here are used to configure keycloak instance for SSO +apiVersion: v1 +kind: ServiceAccount +metadata: + name: keycloak-config + namespace: keycloak +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: keycloak-config + namespace: keycloak +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keycloak-config + namespace: keycloak +subjects: + - kind: ServiceAccount + name: keycloak-config + namespace: keycloak +roleRef: + kind: Role + name: keycloak-config + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: keycloak-config + namespace: argocd +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keycloak-config + namespace: argocd +subjects: + - kind: ServiceAccount + name: keycloak-config + namespace: keycloak +roleRef: + kind: Role + name: keycloak-config + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-job + namespace: keycloak +data: + client-scope-groups-payload.json: | + { + "name": "groups", + "description": "groups a user belongs to", + "attributes": { + "consent.screen.text": "Access to groups a user belongs to.", + "display.on.consent.screen": "true", + "include.in.token.scope": "true", + "gui.order": "" + }, + "type": "default", + "protocol": "openid-connect" + } + group-admin-payload.json: | + {"name":"admin"} + group-base-user-payload.json: | + {"name":"base-user"} + group-mapper-payload.json: | + { + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "name": "groups", + "config": { + "claim.name": "groups", + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + realm-payload.json: | + {"realm":"cnoe","enabled":true} + user-password.json: | + { + "temporary": false, + "type": "password", + "value": "${USER1_PASSWORD}" + } + user-user1.json: | + { + "username": "user1", + "email": "", + "firstName": "user", + "lastName": "one", + "requiredActions": [], + "emailVerified": false, + "groups": [ + "/admin" + ], + "enabled": true + } + user-user2.json: | + { + "username": "user2", + "email": "", + "firstName": "user", + "lastName": "two", + "requiredActions": [], + "emailVerified": false, + "groups": [ + "/base-user" + ], + "enabled": true + } + argo-client-payload.json: | + { + "protocol": "openid-connect", + "clientId": "argo-workflows", + "name": "Argo Workflows Client", + "description": "Used for Argo Workflows SSO", + "publicClient": false, + "authorizationServicesEnabled": false, + "serviceAccountsEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "standardFlowEnabled": true, + "frontchannelLogout": true, + "attributes": { + "saml_idp_initiated_sso_url_name": "", + "oauth2.device.authorization.grant.enabled": false, + "oidc.ciba.grant.enabled": false + }, + "alwaysDisplayInConsole": false, + "rootUrl": "", + "baseUrl": "", + "redirectUris": [ + "https://argo.cnoe.localtest.me:8443/oauth2/callback" + ], + "webOrigins": [ + "/*" + ] + } + + backstage-client-payload.json: | + { + "protocol": "openid-connect", + "clientId": "backstage", + "name": "Backstage Client", + "description": "Used for Backstage SSO", + "publicClient": false, + "authorizationServicesEnabled": false, + "serviceAccountsEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "standardFlowEnabled": true, + "frontchannelLogout": true, + "attributes": { + "saml_idp_initiated_sso_url_name": "", + "oauth2.device.authorization.grant.enabled": false, + "oidc.ciba.grant.enabled": false + }, + "alwaysDisplayInConsole": false, + "rootUrl": "", + "baseUrl": "", + "redirectUris": [ + "https://backstage.cnoe.localtest.me:8443/api/auth/keycloak-oidc/handler/frame" + ], + "webOrigins": [ + "/*" + ] + } + +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: config + namespace: keycloak + annotations: + argocd.argoproj.io/hook: PostSync +spec: + template: + metadata: + generateName: config + spec: + serviceAccountName: keycloak-config + restartPolicy: Never + volumes: + - name: keycloak-config + secret: + secretName: keycloak-config + - name: config-payloads + configMap: + name: config-job + containers: + - name: kubectl + image: docker.io/library/ubuntu:22.04 + volumeMounts: + - name: keycloak-config + readOnly: true + mountPath: "/var/secrets/" + - name: config-payloads + readOnly: true + mountPath: "/var/config/" + command: ["/bin/bash", "-c"] + args: + - | + #! /bin/bash + + set -e -o pipefail + + apt update && apt install curl jq -y + + ADMIN_PASSWORD=$(cat /var/secrets/KEYCLOAK_ADMIN_PASSWORD) + USER1_PASSWORD=$(cat /var/secrets/USER_PASSWORD) + + KEYCLOAK_TOKEN=$(curl -sS --fail-with-body -X POST -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "username=cnoe-admin" \ + --data-urlencode "password=${ADMIN_PASSWORD}" \ + --data-urlencode "grant_type=password" \ + --data-urlencode "client_id=admin-cli" \ + http://keycloak.keycloak.svc.cluster.local:8080/realms/master/protocol/openid-connect/token | jq -e -r '.access_token') + + set +e + + curl --fail-with-body -H "Authorization: bearer ${KEYCLOAK_TOKEN}" 'http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe' &> /dev/null + if [ $? -eq 0 ]; then + exit 0 + fi + set -e + + curl -sS -LO "https://dl.k8s.io/release/v1.28.3//bin/linux/amd64/kubectl" + chmod +x kubectl + + echo "creating cnoe realm and groups" + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/realm-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/client-scope-groups-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/client-scopes + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/group-admin-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/groups + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/group-base-user-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/groups + + # Create scope mapper + echo 'adding group claim to tokens' + CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id') + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/group-mapper-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/client-scopes/${CLIENT_SCOPE_GROUPS_ID}/protocol-mappers/models + + echo "creating test users" + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/user-user1.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/users + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/user-user2.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/users + + USER1ID=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" 'http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/users?lastName=one' | jq -r '.[0].id') + USER2ID=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" 'http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/users?lastName=two' | jq -r '.[0].id') + + echo "setting user passwords" + jq -r --arg pass ${USER1_PASSWORD} '.value = $pass' /var/config/user-password.json > /tmp/user-password-to-be-applied.json + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X PUT --data @/tmp/user-password-to-be-applied.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/users/${USER1ID}/reset-password + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X PUT --data @/tmp/user-password-to-be-applied.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/users/${USER2ID}/reset-password + + echo "creating Argo Workflows client" + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/argo-client-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients + + CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients | jq -e -r '.[] | select(.clientId == "argo-workflows") | .id') + CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id') + + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X PUT http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients/${CLIENT_ID}/default-client-scopes/${CLIENT_SCOPE_GROUPS_ID} + + ARGO_WORKFLOWS_CLIENT_SECRET=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients/${CLIENT_ID} | jq -e -r '.secret') + + echo "creating Backstage client" + curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X POST --data @/var/config/backstage-client-payload.json \ + http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients + + CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients | jq -e -r '.[] | select(.clientId == "backstage") | .id') + + CLIENT_SCOPE_GROUPS_ID=$(curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/client-scopes | jq -e -r '.[] | select(.name == "groups") | .id') + curl -sS -H "Content-Type: application/json" -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -X PUT http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients/${CLIENT_ID}/default-client-scopes/${CLIENT_SCOPE_GROUPS_ID} + + BACKSTAGE_CLIENT_SECRET=$(curl -sS -H "Content-Type: application/json" \ + -H "Authorization: bearer ${KEYCLOAK_TOKEN}" \ + -X GET http://keycloak.keycloak.svc.cluster.local:8080/admin/realms/cnoe/clients/${CLIENT_ID} | jq -e -r '.secret') + + ARGOCD_PASSWORD=$(./kubectl -n argocd get secret argocd-initial-admin-secret -o go-template='{{.data.password | base64decode }}') + + ARGOCD_SESSION_TOKEN=$(curl -k -sS https://argocd-server.argocd.svc.cluster.local:443/api/v1/session -d "{\"username\":\"admin\",\"password\":\"${ARGOCD_PASSWORD}\"}" | jq -r .token) + + echo \ + "apiVersion: v1 + kind: Secret + metadata: + name: keycloak-clients + namespace: keycloak + type: Opaque + stringData: + ARGO_WORKFLOWS_CLIENT_SECRET: ${ARGO_WORKFLOWS_CLIENT_SECRET} + ARGO_WORKFLOWS_CLIENT_ID: argo-workflows + ARGOCD_SESSION_TOKEN: ${ARGOCD_SESSION_TOKEN} + BACKSTAGE_CLIENT_SECRET: ${BACKSTAGE_CLIENT_SECRET} + BACKSTAGE_CLIENT_ID: backstage + " > /tmp/secret.yaml + + ./kubectl apply -f /tmp/secret.yaml + diff --git a/examples/ref-implementation/keycloak/manifests/secret-gen.yaml b/examples/ref-implementation/keycloak/manifests/secret-gen.yaml new file mode 100644 index 00000000..efa73ad7 --- /dev/null +++ b/examples/ref-implementation/keycloak/manifests/secret-gen.yaml @@ -0,0 +1,175 @@ +apiVersion: generators.external-secrets.io/v1alpha1 +kind: Password +metadata: + name: keycloak + namespace: keycloak +spec: + length: 36 + digits: 5 + symbols: 5 + symbolCharacters: "/-+" + noUpper: false + allowRepeat: true +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: keycloak-config + namespace: keycloak +spec: + refreshInterval: "0" + target: + name: keycloak-config + template: + engineVersion: v2 + data: + KEYCLOAK_ADMIN_PASSWORD: "{{.KEYCLOAK_ADMIN_PASSWORD}}" + KC_DB_PASSWORD: "{{.KC_DB_PASSWORD}}" + KC_DB_USERNAME: keycloak + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: "{{.KC_DB_PASSWORD}}" + USER_PASSWORD: "{{.USER_PASSWORD}}" + dataFrom: + - sourceRef: + generatorRef: + apiVersion: generators.external-secrets.io/v1alpha1 + kind: Password + name: keycloak + rewrite: + - transform: + template: "KEYCLOAK_ADMIN_PASSWORD" + - sourceRef: + generatorRef: + apiVersion: generators.external-secrets.io/v1alpha1 + kind: Password + name: keycloak + rewrite: + - transform: + template: "KC_DB_PASSWORD" + - sourceRef: + generatorRef: + apiVersion: generators.external-secrets.io/v1alpha1 + kind: Password + name: keycloak + rewrite: + - transform: + template: "USER_PASSWORD" +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: eso-store + namespace: keycloak +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: keycloak + name: eso-store +rules: + - apiGroups: [""] + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - authorization.k8s.io + resources: + - selfsubjectrulesreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: eso-store + namespace: keycloak +subjects: + - kind: ServiceAccount + name: eso-store + namespace: keycloak +roleRef: + kind: Role + name: eso-store + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: external-secrets.io/v1beta1 +kind: ClusterSecretStore +metadata: + name: keycloak +spec: + provider: + kubernetes: + remoteNamespace: keycloak + server: + caProvider: + type: ConfigMap + name: kube-root-ca.crt + namespace: keycloak + key: ca.crt + auth: + serviceAccount: + name: eso-store + namespace: keycloak +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: eso-store + namespace: gitea +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: eso-store + namespace: gitea +rules: + - apiGroups: [""] + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - authorization.k8s.io + resources: + - selfsubjectrulesreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: eso-store + namespace: gitea +subjects: + - kind: ServiceAccount + name: eso-store + namespace: gitea +roleRef: + kind: Role + name: eso-store + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: external-secrets.io/v1beta1 +kind: ClusterSecretStore +metadata: + name: gitea +spec: + provider: + kubernetes: + remoteNamespace: gitea + server: + caProvider: + type: ConfigMap + name: kube-root-ca.crt + namespace: gitea + key: ca.crt + auth: + serviceAccount: + name: eso-store + namespace: gitea diff --git a/examples/ref-implementation/spark-operator.yaml b/examples/ref-implementation/spark-operator.yaml new file mode 100644 index 00000000..c494ee18 --- /dev/null +++ b/examples/ref-implementation/spark-operator.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spark-operator + namespace: argocd + labels: + env: dev + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + sources: + - repoURL: https://googlecloudplatform.github.io/spark-on-k8s-operator + targetRevision: 1.1.27 + helm: + releaseName: spark-operator + chart: spark-operator + destination: + server: "https://kubernetes.default.svc" + namespace: spark-operator + syncPolicy: + syncOptions: + - CreateNamespace=true + automated: + selfHeal: true