diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ec50cd286e7b47974349d1a56c5007a19d99c58f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.tgz +*.xpkg +local +argopw +argowftoken \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f3b2bfe14be500df86d195ea674951905791ac54 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Infrastructure Crossplane Provisioner +This repository contains the resources required to setup the Infrastructure Provisioner. The required components are the following: + + * [Crossplane](https://docs.crossplane.io/latest/) + * [ArgoCD](https://argo-cd.readthedocs.io/en/stable/) + * [Gitea](https://about.gitea.com/products/gitea/) + * [ArgoEvents](https://argoproj.github.io/argo-events/) + * [ArgoWorkflows](https://argoproj.github.io/workflows/) + +## Installation + +The installation uses [Helm](https://helm.sh/docs/intro/install/) and requires a running Kubernetes cluster. + +The two charts which need to be installed in the cluster: + * `dependencies` installs and configures the required k8s controllers and CRDs + * `resources` installs the required resources that implement provisioner logic + +In the dependencies chart, ensure the appropriate `storageClassName` is set for your cluster. +In the resources chart, ensure the correct `kafkaEndpoint` is set. + +<b>Note: Currently, the charts have to be installed as-is with all their listed dependencies. Do not try to replace a dependency with one that you already have running, such as using an already existing gitea instance. Decoupling of dependencies is still a work in progress!</b> + +Before running helm install ensure that the following `secrets` are present: + * `ec-pull-secret` allows the installer or the argo workflows to pull the necessary images from code.europa.eu (EC_USERNAME: gitlab username, EC_PASSWORD: gitlab password or token) + * `ionos-provider` access token for the Ionos cloud API + * `gitea-secret` internal to the provisioner, used in the gitops process (will be removed in the future) +```cmd +NS=infrastructure +kubectl create -n $NS secret docker-registry "ec-pull-secret" --docker-server="code.europa.eu:4567" --docker-username="$EC_USERNAME" --docker-password="$EC_PASSWORD" +kubectl create -n $NS secret generic ionos-provider --from-literal=credentials="{\"token\":\"${IONOS_TOKEN}\"}" +kubectl create -n $NS secret generic gitea-secret --from-literal=username=gitops_test --from-literal=password=test1234 +``` + +## Local setup + +#### To setup locally, install [KinD](https://kind.sigs.k8s.io/) to setup a local K8s cluster. Then run the `local-setup.sh` script to setup your environment. +#### Accessing the UIs of the components: +Retrieve initial admin password for ArgoCD and Auth Token for ArgoWorkflows +```cmd +NS=infrastructure +echo "Bearer $(kubectl get -n $NS secret cli.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)" > argowftoken +kubectl get -n $NS secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d > argopw +``` + +Port forward for each service +```cmd +NS=infrastructure +kubectl port-forward -n $NS svc/argocd-server 8888:443 +kubectl port-forward -n $NS svc/argowf-argo-workflows-server 8777:2746 +kubectl port-forward -n $NS svc/gitea-http 8333:3000 +``` diff --git a/build_package.sh b/build_package.sh new file mode 100644 index 0000000000000000000000000000000000000000..b1cc1a304273a8b821a9386ed7243bafc27170ca --- /dev/null +++ b/build_package.sh @@ -0,0 +1,4 @@ +# Builds the crossplane configuration package image and pushes it to EC gitlab +# VERSION=v0.3.7 +crossplane xpkg build -f package/ -e package/examples/ -o configuration-${VERSION}.xpkg --verbose +crossplane xpkg push code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/configuration:${VERSION} -f configuration-${VERSION}.xpkg --domain https://code.europa.eu \ No newline at end of file diff --git a/charts/dependencies/Chart.lock b/charts/dependencies/Chart.lock new file mode 100644 index 0000000000000000000000000000000000000000..6a940d5f7df074ac417fdfebc331480f0ace7eba --- /dev/null +++ b/charts/dependencies/Chart.lock @@ -0,0 +1,18 @@ +dependencies: +- name: crossplane + repository: https://charts.crossplane.io/stable + version: 1.16.2 +- name: argo-cd + repository: https://argoproj.github.io/argo-helm + version: 6.7.13 +- name: argo-events + repository: https://argoproj.github.io/argo-helm + version: 2.4.8 +- name: argo-workflows + repository: https://argoproj.github.io/argo-helm + version: 0.42.5 +- name: gitea + repository: https://dl.gitea.com/charts + version: 10.4.1 +digest: sha256:45897c92d11fd234df71398dc2b5775f46a36c53bd4772f6d6ef9e54367013f6 +generated: "2024-12-10T23:20:09.55863639+02:00" diff --git a/charts/dependencies/Chart.yaml b/charts/dependencies/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..55a22eb4f8949d42cd3b56c47ba5a7223636c07c --- /dev/null +++ b/charts/dependencies/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v2 +name: provisioner-dependencies +description: Chart which installs dependencies for the Provisioner +version: 0.1.0 + +dependencies: + - name: crossplane + version: 1.16.2 + repository: https://charts.crossplane.io/stable + - name: argo-cd + version: 6.7.13 + repository: https://argoproj.github.io/argo-helm + - name: argo-events + version: 2.4.8 + repository: https://argoproj.github.io/argo-helm + - name: argo-workflows + version: 0.42.5 + repository: https://argoproj.github.io/argo-helm + - name: gitea + version: 10.4.1 + repository: https://dl.gitea.com/charts diff --git a/charts/dependencies/templates/post-install-cm.yaml b/charts/dependencies/templates/post-install-cm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a74435860f4c97d4d0eda9b287ef687f307fa736 --- /dev/null +++ b/charts/dependencies/templates/post-install-cm.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-postinstall-configmap +data: + setup-gitea.sh: | + set -euo pipefail + apk add --no-cache curl + GITEA_HEALTH="http://{{ .Release.Name }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/api/healthz" + curl -v "$GITEA_HEALTH" + + git config --global user.email "setup@helm-hook.kube"; + git config --global user.name "${GIT_USER}"; + + DIR1="management-repo" + MGMT_REPO="http://${GIT_USER}:${GIT_PASSWORD}@{{ .Release.Name }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/management-repo.git" + echo "Creating directory: $DIR1" + mkdir -p "$DIR1" + cd "$DIR1" + git init + cp /postconfig/data-application-template.yaml . + mkdir applications + touch applications/.empty + git add data-application-template.yaml + git add applications + git commit -m "initialized $DIR1" + git remote add origin "$MGMT_REPO" + git branch -M master + git push -u origin master + cd .. + + DIR2="data-repo" + DATA_REPO="http://${GIT_USER}:${GIT_PASSWORD}@{{ .Release.Name }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/data-repo.git" + echo "Creating directory: $DIR2" + mkdir -p "$DIR2" + cd "$DIR2" + git init + mkdir claims + touch claims/.empty + git add claims + git commit -m "initialized $DIR2" + git remote add origin "$DATA_REPO" + git branch -M master + git push -u origin master + data-application-template.yaml: | + apiVersion: argoproj.io/v1alpha1 + kind: Application + metadata: + name: crossplane-claim-{UUID} + namespace: {{ .Release.Namespace }} + finalizers: + - resources-finalizer.argocd.argoproj.io + labels: + track-events: claim-application + claim-uuid: "{UUID}" + claim-kind: "{KIND}" + spec: + project: default + source: + repoURL: http://{{ .Release.Name }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/data-repo.git + path: claims/claim_{UUID} + targetRevision: master + destination: + server: https://kubernetes.default.svc + syncPolicy: + automated: + selfHeal: true + prune: true + allowEmpty: true \ No newline at end of file diff --git a/charts/dependencies/templates/post-install.yaml b/charts/dependencies/templates/post-install.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b36b294e4d39057f9c3e7e8dc92d7003ed157473 --- /dev/null +++ b/charts/dependencies/templates/post-install.yaml @@ -0,0 +1,36 @@ +# templates/post-install-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: post-install-job + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-weight": "0" +spec: + backoffLimit: 12 + ttlSecondsAfterFinished: 600 + template: + spec: + restartPolicy: Never + containers: + - name: script-runner + image: alpine/git:latest + command: ["/bin/sh", "/postconfig/setup-gitea.sh"] + env: + - name: GIT_USER + valueFrom: + secretKeyRef: + name: gitea-secret + key: username + - name: GIT_PASSWORD + valueFrom: + secretKeyRef: + name: gitea-secret + key: password + volumeMounts: + - name: postconfig-volume + mountPath: /postconfig + volumes: + - name: postconfig-volume + configMap: + name: {{ .Release.Name }}-postinstall-configmap diff --git a/charts/dependencies/templates/pvc.yaml b/charts/dependencies/templates/pvc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..50014ef6914aad419603f018768f0ef4cbdbcc44 --- /dev/null +++ b/charts/dependencies/templates/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gitea-pvc +spec: + storageClassName: {{ .Values.gitea.storageClassName }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.gitea.volumeSize }} \ No newline at end of file diff --git a/charts/dependencies/values.yaml b/charts/dependencies/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f9e8b9c00a5b55ce984b40f6b9f37031d1939b9a --- /dev/null +++ b/charts/dependencies/values.yaml @@ -0,0 +1,230 @@ +crossplane: + imagePullSecrets: + - ec-pull-secret + configuration: + packages: + - code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/configuration:v0.3.6 + +gitea: + # storageClassName: standard + storageClassName: csi-cinder-high-speed + volumeSize: 100G + service: + http: + type: ClusterIP + port: 3000 + redis-cluster: + enabled: false + redis: + enabled: false + postgresql: + enabled: false + postgresql-ha: + enabled: false + persistence: + enabled: true + mount: true + create: false + claimName: gitea-pvc + gitea: + config: + APP_NAME: "Provisioner GIT Server" + repository: + ENABLE_PUSH_CREATE_USER: true + DEFAULT_PUSH_CREATE_PRIVATE: false + database: + DB_TYPE: sqlite3 + session: + PROVIDER: memory + cache: + ADAPTER: db + queue: + TYPE: level + admin: + username: "gitops_test" + password: "test1234" + email: "test@gitops.email.com" + +argo-cd: + server: + extraArgs: + - --insecure + service: + type: ClusterIP + dex: + enabled: false + notifications: + enabled: false + applicationSet: + enabled: false + # Health check for crossplane MRs: + # https://docs.crossplane.io/latest/guides/crossplane-with-argo-cd/#set-health-status + configs: + cm: + timeout.reconciliation: 180s + application.resourceTrackingMethod: annotation + resource.exclusions: | + - apiGroups: + - "*" + kinds: + - ProviderConfigUsage + - XServerInstance + resource.customizations: | + "*.upbound.io/*": + health.lua: | + health_status = { + status = "Progressing", + message = "Provisioning ..." + } + + local function contains (table, val) + for i, v in ipairs(table) do + if v == val then + return true + end + end + return false + end + + local has_no_status = { + "ProviderConfig", + "ProviderConfigUsage" + } + + if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then + health_status.status = "Healthy" + health_status.message = "Resource is up-to-date." + return health_status + end + + if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then + if obj.kind == "ProviderConfig" and obj.status.users ~= nil then + health_status.status = "Healthy" + health_status.message = "Resource is in use." + return health_status + end + return health_status + end + + for i, condition in ipairs(obj.status.conditions) do + if condition.type == "LastAsyncOperation" then + if condition.status == "False" then + health_status.status = "Degraded" + health_status.message = condition.message + return health_status + end + end + + if condition.type == "Synced" then + if condition.status == "False" then + health_status.status = "Degraded" + health_status.message = condition.message + return health_status + end + end + + if condition.type == "Ready" then + if condition.status == "True" then + health_status.status = "Healthy" + health_status.message = "Resource is up-to-date." + return health_status + end + health_status.message = condition.reason + end + end + + return health_status + + "*.crossplane.io/*": + health.lua: | + health_status = { + status = "Progressing", + message = "Provisioning ..." + } + + local function contains (table, val) + for i, v in ipairs(table) do + if v == val then + return true + end + end + return false + end + + local has_no_status = { + "Composition", + "CompositionRevision", + "DeploymentRuntimeConfig", + "ControllerConfig", + "ProviderConfig", + "ProviderConfigUsage" + } + if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then + health_status.status = "Healthy" + health_status.message = "Resource is up-to-date." + return health_status + end + + if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then + if obj.kind == "ProviderConfig" and obj.status.users ~= nil then + health_status.status = "Healthy" + health_status.message = "Resource is in use." + return health_status + end + return health_status + end + + for i, condition in ipairs(obj.status.conditions) do + if condition.type == "LastAsyncOperation" then + if condition.status == "False" then + health_status.status = "Degraded" + health_status.message = condition.message + return health_status + end + end + + if condition.type == "Synced" then + if condition.status == "False" then + health_status.status = "Degraded" + health_status.message = condition.message + return health_status + end + end + + if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then + if condition.status == "True" then + health_status.status = "Healthy" + health_status.message = "Resource is up-to-date." + return health_status + end + end + end + + return health_status + + "*.example.org/*": + health.lua: | + health_status = { + status = "Progressing", + message = "Provisioning of resources in progress." + } + + if obj.status == nil or next(obj.status) == nil then + return health_status + end + + for i, condition in ipairs(obj.status.conditions) do + if condition.type == "Ready" then + if condition.status == "True" then + health_status.status = "Healthy" + health_status.message = "Provisioning finalized successfully." + return health_status + end + if condition.message ~= nil then + health_status.message = condition.message + end + return health_status + end + end + + return health_status \ No newline at end of file diff --git a/charts/resources/Chart.yaml b/charts/resources/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c64cb7aa1befc6c88f134206835b9915405fc8b9 --- /dev/null +++ b/charts/resources/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: provisioner-resources +description: Chart which installs resources of the Provisioner +version: 0.1.0 diff --git a/charts/resources/templates/claim-manager.yaml b/charts/resources/templates/claim-manager.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2e245634f98786cd108861f205be452a9d25dd01 --- /dev/null +++ b/charts/resources/templates/claim-manager.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: claim-manager + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: http://{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace}}.svc.cluster.local:3000/gitops_test/management-repo.git + path: applications + targetRevision: master + directory: + recurse: true + destination: + server: https://kubernetes.default.svc + syncPolicy: + automated: + selfHeal: true + prune: true + allowEmpty: true \ No newline at end of file diff --git a/charts/resources/templates/cli.yaml b/charts/resources/templates/cli.yaml new file mode 100644 index 0000000000000000000000000000000000000000..dab3b7759ecfbb9a7ec2c011ff2c91b5d918a790 --- /dev/null +++ b/charts/resources/templates/cli.yaml @@ -0,0 +1,187 @@ +{{- if .Values.cliEnabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cli + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cli + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update + - 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 + verbs: + - get + - list + - watch + - apiGroups: + - argoproj.io + resources: + - workflowtaskresults + verbs: + - list + - watch + - deletecollection + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - list + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - 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: RoleBinding +metadata: + name: cli + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cli +subjects: +- kind: ServiceAccount + name: cli + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: operate-workflow-role + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - argoproj.io + verbs: + - "*" + resources: + - workflows + - workflowtemplates + - cronworkflows + - clusterworkflowtemplates +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: operate-workflow-role-binding + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: operate-workflow-role +subjects: + - kind: ServiceAccount + name: {{ .Values.workflowOperatorSA }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: Secret +metadata: + namespace: {{ .Release.Namespace }} + name: cli.service-account-token + annotations: + kubernetes.io/service-account.name: cli +type: kubernetes.io/service-account-token +--- +apiVersion: v1 +kind: Secret +metadata: + name: argocli.service-account-token + annotations: + kubernetes.io/service-account.name: argocli +type: kubernetes.io/service-account-token +{{- end }} \ No newline at end of file diff --git a/charts/resources/templates/crossplane/provider-ionos-config.yaml b/charts/resources/templates/crossplane/provider-ionos-config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1d8795fc5a9e1f1131c6be3b48d3210e2c6ccb62 --- /dev/null +++ b/charts/resources/templates/crossplane/provider-ionos-config.yaml @@ -0,0 +1,12 @@ +apiVersion: ionoscloud.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: example + namespace: {{ .Release.Namespace }} +spec: + credentials: + source: Secret + secretRef: + namespace: {{ .Release.Namespace }} + name: ionos-provider + key: credentials diff --git a/charts/resources/templates/events/decommission/sensor.yaml b/charts/resources/templates/events/decommission/sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ca7506e5f8cde91999f2a38b9738fad21c39c702 --- /dev/null +++ b/charts/resources/templates/events/decommission/sensor.yaml @@ -0,0 +1,110 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: decommission + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + template: + serviceAccountName: {{ .Values.workflowOperatorSA }} + dependencies: + - name: message + eventSourceName: decommission + eventName: decommissionRequest + triggers: + - template: + name: argo-workflow-trigger + argoWorkflow: + operation: submit + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: decommission-gitops- + namespace: {{ .Release.Namespace }} + spec: + podGC: + strategy: OnWorkflowSuccess + deleteDelayDuration: 120s + imagePullSecrets: + - name: ec-pull-secret + volumes: + - name: repos + emptyDir: {} + serviceAccountName: cli + entrypoint: main + workflowMetadata: + labels: + track-workflow: "true" + workflow-type: to-decommission + labelsFrom: + scriptTriggerId: + expression: workflow.parameters.scriptTriggerId + arguments: + parameters: + - name: scriptTriggerId + value: default-is-overriden + templates: + - name: main + inputs: + artifacts: + - name: data-repo + path: repos/data + git: + repo: http://{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/data-repo.git + revision: "master" + - name: management-repo + path: /repos/management + git: + repo: http://{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/management-repo.git + revision: "master" + parameters: + - name: scriptTriggerId + value: {{`"'{{workflow.parameters.scriptTriggerId}}'"`}} + script: + image: alpine/git:v2.45.2 + env: + - name: GIT_USER + valueFrom: + secretKeyRef: + name: gitea-secret + key: username + - name: GIT_PASSWORD + valueFrom: + secretKeyRef: + name: gitea-secret + key: password + command: [sh, -c] + args: [' + echo --- decommissioning request with ID: {{`{{inputs.parameters.scriptTriggerId}}`}}; + echo ---ls ../work---; + ls; + echo ---ls ../repos---; + ls ../repos; + git config --global user.email "workflow@argo.kube"; + git config --global user.name "gitops_test"; + echo ---commit data changes---; + git -C ../repos/data checkout master; + git -C ../repos/data rm -r claims/claim_{{`{{inputs.parameters.scriptTriggerId}}`}}; + git -C ../repos/data commit -v -m "Remove claim_{{`{{inputs.parameters.scriptTriggerId}}`}}"; + git -C ../repos/data push http://$GIT_USER:$GIT_PASSWORD@{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/data-repo.git; + echo ---commit management changes---; + git -C ../repos/management checkout master; + git -C ../repos/management rm -r applications/application_{{`{{inputs.parameters.scriptTriggerId}}`}}; + git -C ../repos/management commit -v -m "Remove application_{{`{{inputs.parameters.scriptTriggerId}}`}}"; + git -C ../repos/management push http://$GIT_USER:$GIT_PASSWORD@{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/management-repo.git; + '] + volumeMounts: + - name: repos + mountPath: /repos + workingDir: /work + parameters: + - src: + dependencyName: message + dataKey: headers.scriptTriggerId + value: "" + dest: spec.arguments.parameters.0.value + retryStrategy: + steps: 2 + duration: 30s \ No newline at end of file diff --git a/charts/resources/templates/events/decommission/source.yaml b/charts/resources/templates/events/decommission/source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d005e94709f32aa325440df80d1e007d8640a2dd --- /dev/null +++ b/charts/resources/templates/events/decommission/source.yaml @@ -0,0 +1,28 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: decommission + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + kafka: + decommissionRequest: + url: {{ .Values.kafkaEndpoint }} + topic: to-decommission + jsonBody: false + partition: "0" + {{- if .Values.kafkaAuth.enable }} + sasl: + mechanism: {{ .Values.kafkaAuth.mechanism }} + userSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: username + passwordSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: password + {{- end }} + connectionBackoff: + duration: 10s + steps: 3 + factor: 1 + jitter: 0.2 \ No newline at end of file diff --git a/charts/resources/templates/events/decommission/status-sensor.yaml b/charts/resources/templates/events/decommission/status-sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fbd61f637ac36afad9760fa17080e3825b258790 --- /dev/null +++ b/charts/resources/templates/events/decommission/status-sensor.yaml @@ -0,0 +1,42 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: decommission-status + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + dependencies: + - name: message + eventSourceName: decommission-status + eventName: decommissionStatus + triggers: + - template: + name: kafka + kafka: + url: {{ .Values.kafkaEndpoint }} + topic: decommissioned + {{- if .Values.kafkaAuth.enable }} + sasl: + mechanism: {{ .Values.kafkaAuth.mechanism }} + userSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: username + passwordSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: password + {{- end }} + payload: + - src: + dependencyName: message + dataKey: body.metadata.labels.claim-uuid + value: "" + dest: scriptTriggerId + - src: + dependencyName: message + value: "Succeeded" + dest: status + - src: + dependencyName: message + dataKey: body.metadata.deletionTimestamp + value: "" + dest: deletionTimestamp \ No newline at end of file diff --git a/charts/resources/templates/events/decommission/status-source.yaml b/charts/resources/templates/events/decommission/status-source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cd136e742856549e1ae621f277114cc4e1da9e35 --- /dev/null +++ b/charts/resources/templates/events/decommission/status-source.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: decommission-status + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + template: + serviceAccountName: {{ .Values.applicationStatusViewerSA }} + resource: + decommissionStatus: + namespace: {{ .Release.Namespace }} + group: argoproj.io + version: v1alpha1 + resource: applications + eventTypes: + - DELETE + filter: + afterStart: true + labels: + - key: track-events + operation: "==" + value: "claim-application" + diff --git a/charts/resources/templates/events/eventbus.yaml b/charts/resources/templates/events/eventbus.yaml new file mode 100644 index 0000000000000000000000000000000000000000..abf1475325bd70d5055844e28a79fabee5ef3fb7 --- /dev/null +++ b/charts/resources/templates/events/eventbus.yaml @@ -0,0 +1,10 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventBus +metadata: + name: provisioner-eventbus + namespace: {{ .Release.Namespace }} +spec: + nats: + native: + replicas: 2 + auth: token diff --git a/charts/resources/templates/events/provision/gitops-status-sensor.yaml b/charts/resources/templates/events/provision/gitops-status-sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4dfb54db706c5ca95a900901b619aab3e9fa1ce5 --- /dev/null +++ b/charts/resources/templates/events/provision/gitops-status-sensor.yaml @@ -0,0 +1,54 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: provision-gitops + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + dependencies: + - name: gitops-status + eventSourceName: provision-gitops + eventName: provisionGitops + triggers: + - template: + name: kafka + kafka: + url: {{ .Values.kafkaEndpoint }} + topic: provisioned + partition: 0 + {{- if .Values.kafkaAuth.enable }} + sasl: + mechanism: {{ .Values.kafkaAuth.mechanism }} + userSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: username + passwordSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: password + {{- end }} + payload: + - src: + dependencyName: gitops-status + dataKey: body.metadata.name + value: "None" + dest: name + - src: + dependencyName: gitops-status + dataKey: "body.metadata.labels.workflows\\.argoproj\\.io/phase" + value: "Unknown" + dest: phase + - src: + dependencyName: gitops-status + dataKey: "body.metadata.labels.workflows\\.argoproj\\.io/completed" + value: "false" + dest: completed + - src: + dependencyName: gitops-status + dataKey: "body.metadata.labels.scriptTriggerId" + value: "" + dest: scriptTriggerId + - src: + dependencyName: gitops-status + dataKey: body.status.message + value: "" + dest: message \ No newline at end of file diff --git a/charts/resources/templates/events/provision/gitops-status-source.yaml b/charts/resources/templates/events/provision/gitops-status-source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c1b0fe4580714f683cac5d68b60bcfc396469d75 --- /dev/null +++ b/charts/resources/templates/events/provision/gitops-status-source.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: provision-gitops + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + template: + serviceAccountName: {{ .Values.workflowOperatorSA }} + resource: + provisionGitops: + namespace: {{ .Release.Namespace }} + group: argoproj.io + version: v1alpha1 + resource: workflows + eventTypes: + - ADD + - UPDATE + filter: + afterStart: true + labels: + - key: track-workflow + operation: "==" + value: "true" + - key: workflow-type + operation: "==" + value: provision-gitops diff --git a/charts/resources/templates/events/provision/sensor.yaml b/charts/resources/templates/events/provision/sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..704cc81550aa27b7456d91b7bf47898671b907ed --- /dev/null +++ b/charts/resources/templates/events/provision/sensor.yaml @@ -0,0 +1,126 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: provision + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + template: + serviceAccountName: {{ .Values.workflowOperatorSA }} + dependencies: + - name: message + eventSourceName: provision + eventName: provisionRequest + triggers: + - template: + name: argo-workflow-trigger + argoWorkflow: + operation: submit + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: provision-gitops- + namespace: {{ .Release.Namespace }} + spec: + podGC: + strategy: OnWorkflowSuccess + deleteDelayDuration: 30s + imagePullSecrets: + - name: ec-pull-secret + volumes: + - name: repos + emptyDir: {} + serviceAccountName: cli + entrypoint: main + workflowMetadata: + labels: + track-workflow: "true" + workflow-type: provision-gitops + labelsFrom: + scriptTriggerId: + expression: workflow.parameters.scriptTriggerId + arguments: + parameters: + - name: headers + value: default-is-overriden + - name: scriptTriggerId + value: default-is-overriden + - name: body + value: default-is-overriden + templates: + - name: main + inputs: + artifacts: + - name: data-repo + path: repos/data + git: + repo: http://{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/data-repo.git + revision: "master" + - name: management-repo + path: /repos/management + git: + repo: http://{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/management-repo.git + revision: "master" + parameters: + - name: headers + value: {{`"'{{workflow.parameters.headers}}'"`}} + - name: scriptTriggerId + value: {{`"'{{workflow.parameters.scriptTriggerId}}'"`}} + - name: body + value: {{`"'{{workflow.parameters.body}}'"`}} + container: + image: code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/to-provision-workflow:v0.2.1 + env: + - name: GIT_USER + valueFrom: + secretKeyRef: + name: gitea-secret + key: username + - name: GIT_PASSWORD + valueFrom: + secretKeyRef: + name: gitea-secret + key: password + command: [sh, -c] + args: [' + echo --- provisioning request with ID: {{`{{inputs.parameters.scriptTriggerId}}`}}; + echo ---ls ../work---; + ls; + echo ---ls ../repos---; + ls ../repos; + python doesntexist.py; + python main.py {{`{{inputs.parameters.scriptTriggerId}}`}} {{`{{inputs.parameters.body}}`}} || exit $?; + git config --global user.email "workflow@argo.kube"; + git config --global user.name "gitops_test"; + echo ---commit data changes---; + git -C ../repos/data checkout master; + git -C ../repos/data/claims add -A && git -C ../repos/data commit -m "add UUID {{`{{inputs.parameters.scriptTriggerId}}`}}"; + git -C ../repos/data push http://$GIT_USER:$GIT_PASSWORD@{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/data-repo.git; + echo ---commit management changes---; + git -C ../repos/management checkout master; + git -C ../repos/management/applications add -A && git -C ../repos/management commit -m "add UUID {{`{{inputs.parameters.scriptTriggerId}}`}}"; + git -C ../repos/management push http://$GIT_USER:$GIT_PASSWORD@{{ .Values.dependenciesReleaseName }}-gitea-http.{{ .Release.Namespace }}.svc.cluster.local:3000/gitops_test/management-repo.git; + '] + volumeMounts: + - name: repos + mountPath: /repos + workingDir: /work + parameters: + - src: + dependencyName: message + dataKey: headers + dest: spec.arguments.parameters.0.value + - src: + dependencyName: message + dataKey: headers.scriptTriggerId + value: dummy + dest: spec.arguments.parameters.1.value + - src: + dependencyName: message + dataKey: body + dest: spec.arguments.parameters.2.value + retryStrategy: + steps: 2 + duration: 30s \ No newline at end of file diff --git a/charts/resources/templates/events/provision/source.yaml b/charts/resources/templates/events/provision/source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fd56ff4c6078aa76e122b32317493a017118e96c --- /dev/null +++ b/charts/resources/templates/events/provision/source.yaml @@ -0,0 +1,28 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: provision + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + kafka: + provisionRequest: + url: {{ .Values.kafkaEndpoint }} + topic: to-provision + jsonBody: false + partition: "0" + {{- if .Values.kafkaAuth.enable }} + sasl: + mechanism: {{ .Values.kafkaAuth.mechanism }} + userSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: username + passwordSecret: + name: {{ .Values.kafkaAuth.secretName }} + key: password + {{- end }} + connectionBackoff: + duration: 10s + steps: 3 + factor: 1 + jitter: 0.2 \ No newline at end of file diff --git a/charts/resources/templates/events/provision/status-sensor.yaml b/charts/resources/templates/events/provision/status-sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..349feb598229ea3d4405c999596ca8964c04cd81 --- /dev/null +++ b/charts/resources/templates/events/provision/status-sensor.yaml @@ -0,0 +1,199 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: provision-status + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + template: + serviceAccountName: {{ .Values.workflowOperatorSA }} + dependencies: + - name: message + eventSourceName: provision-status + eventName: provisionStatus + triggers: + - template: + name: argo-workflow-trigger + argoWorkflow: + operation: submit + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: provision-status- + namespace: {{ .Release.Namespace }} + spec: + podGC: + strategy: OnPodCompletion + deleteDelayDuration: 120s + serviceAccountName: cli + entrypoint: main + workflowMetadata: + arguments: + parameters: + - name: body + value: default-is-overriden + templates: + - name: main + steps: + - - name: validate + template: validate-status + arguments: + parameters: + - name: message + value: "{{`{{workflow.parameters.body}}`}}" + - - name: label + template: done-label + arguments: + parameters: + - name: UUID + value: "{{`{{steps.validate.outputs.result}}`}}" + - - name: parse-application + template: parse-application + arguments: + parameters: + - name: resource + value: "{{`{{steps.label.outputs.parameters.application-resource}}`}}" + - - name: parse-claim + template: parse-claim + arguments: + parameters: + - name: claim-reference + value: "{{`{{steps.parse-application.outputs.result}}`}}" + - - name: payload + template: payload + arguments: + parameters: + - name: resource + value: "{{`{{steps.parse-claim.outputs.parameters.claim-resource}}`}}" + - - name: message + template: message + arguments: + parameters: + - name: payload + value: "{{`{{steps.payload.outputs.result}}`}}" + - name: validate-status + inputs: + parameters: + - name: message + script: + image: python:alpine3.10 + command: [python] + source: | + import json + rawstr = r'''{{`{{inputs.parameters.message}}`}}''' + d = json.loads(rawstr) + try: + app_status = d['status'] + app_meta = d['metadata'] + app_health = app_status['health']['status'] + app_sync = app_status['sync']['status'] + # Application only tracks the Claim, so one resource should be present + claim = app_status['resources'][0] + claim_health = claim['health']['status'] + claim_sync = claim['status'] + claim_id = app_meta['labels']['claim-uuid'] + assert app_health == "Healthy" and claim_health == "Healthy" and app_sync == "Synced" and claim_sync == "Synced" + print(f"{claim_id}") + except (KeyError, IndexError): + raise ValueError("Not all status fields available") + - name: done-label + inputs: + parameters: + - name: UUID + script: + image: bitnami/kubectl:latest + command: [sh] + source: | + echo "patching application with label claim-uuid:{{`{{inputs.parameters.UUID}}`}}" + kubectl label -n {{ .Release.Namespace }} applications crossplane-claim-{{`{{inputs.parameters.UUID}}`}} provisioning-status="finalized" + kubectl get -n {{ .Release.Namespace }} applications --selector=claim-uuid={{`{{inputs.parameters.UUID}}`}} -o json > /tmp/resource.json + outputs: + parameters: + - name: application-resource + valueFrom: + path: /tmp/resource.json + - name: parse-application + inputs: + parameters: + - name: resource + script: + image: python:alpine3.10 + command: [python] + source: | + import json + rawstr = r'''{{`{{inputs.parameters.resource}}`}}''' + d = json.loads(rawstr) + claim_kind = d['items'][0]['metadata']['labels']['claim-kind'] + claim_selector = d['items'][0]['metadata']['labels']['claim-uuid'] + print(f"{claim_kind} --selector=uuid={claim_selector}") + - name: parse-claim + inputs: + parameters: + - name: claim-reference + script: + image: bitnami/kubectl:latest + command: [sh] + source: | + kubectl get -n {{ .Release.Namespace }} {{`{{inputs.parameters.claim-reference}}`}} + kubectl get -n {{ .Release.Namespace }} -o json {{`{{inputs.parameters.claim-reference}}`}} > /tmp/resource.json + outputs: + parameters: + - name: claim-resource + valueFrom: + path: /tmp/resource.json + - name: payload + inputs: + parameters: + - name: resource + script: + image: python:alpine3.10 + command: [python] + source: | + import json + rawstr = r'''{{`{{inputs.parameters.resource}}`}}''' + d = json.loads(rawstr) + payload = {} + payload["vmIps"] = d["items"][0]["status"]["vmIps"] + payload["status"] = "Provisioning finalized successfully" + payload["scriptTriggerId"] = d["items"][0]["metadata"]["labels"]["uuid"] + print(json.dumps(payload).replace('"', '\\"')) + - name: message + inputs: + parameters: + - name: payload + script: + {{- if .Values.kafkaAuth.enable }} + env: + - name: MECHANISM + value: {{ .Values.kafkaAuth.mechanism }} + - name: USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.kafkaAuth.secretName }} + key: username + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.kafkaAuth.secretName }} + key: password + {{- end }} + image: confluentinc/cp-kafkacat:7.1.14 + command: [sh] + {{- if .Values.kafkaAuth.enable }} + source: | + echo {{`{{inputs.parameters.payload}}`}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -X security.protocol=SASL_PLAINTEXT -X sasl.username="$USERNAME" -X sasl.password="$PASSWORD" -X sasl.mechanism="$MECHANISM" -t provisioned -J + {{- else }} + source: | + echo {{`{{inputs.parameters.payload}}`}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -t provisioned -J + {{- end }} + + parameters: + - src: + dependencyName: message + dataKey: body + dest: spec.arguments.parameters.0.value + retryStrategy: + steps: 2 + duration: 30s \ No newline at end of file diff --git a/charts/resources/templates/events/provision/status-source.yaml b/charts/resources/templates/events/provision/status-source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f47ede78a87c20140743e8c974e1620aadf712c7 --- /dev/null +++ b/charts/resources/templates/events/provision/status-source.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: provision-status + namespace: {{ .Release.Namespace }} +spec: + eventBusName: provisioner-eventbus + template: + serviceAccountName: {{ .Values.applicationStatusViewerSA }} + resource: + provisionStatus: + namespace: {{ .Release.Namespace }} + group: argoproj.io + version: v1alpha1 + resource: applications + eventTypes: + - UPDATE + filter: + afterStart: true + labels: + - key: track-events + operation: "==" + value: "claim-application" + - key: provisioning-status + operation: "!=" + value: "finalized" + diff --git a/charts/resources/templates/rbac.yaml b/charts/resources/templates/rbac.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c4e53db5d395cf1e25e42aefd18ec678817e6bbe --- /dev/null +++ b/charts/resources/templates/rbac.yaml @@ -0,0 +1,77 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: view-application + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - argoproj.io + resources: + - applications + verbs: + - list + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: patch-application + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - argoproj.io + resources: + - applications + verbs: + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: view-application + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: view-application +subjects: +- kind: ServiceAccount + name: claim-application-status + namespace: {{ .Release.Namespace }} +- kind: ServiceAccount + name: operate-workflow + namespace: {{ .Release.Namespace }} +- kind: ServiceAccount + name: cli + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: patch-application + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: patch-application +subjects: +- kind: ServiceAccount + name: cli + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: view-composite-status-events +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: crossplane-view +subjects: + - kind: ServiceAccount + name: composite-status + namespace: {{ .Release.Namespace }} + - kind: ServiceAccount + name: cli + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/charts/resources/templates/service-account.yaml b/charts/resources/templates/service-account.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f70a04a34d5ec16b40a7fd9ea99286615e373121 --- /dev/null +++ b/charts/resources/templates/service-account.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.workflowOperatorSA }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.applicationStatusViewerSA }} + namespace: {{ .Release.Namespace }} diff --git a/charts/resources/values.yaml b/charts/resources/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5941d462ee643e22a414afa4c55611eab50caf86 --- /dev/null +++ b/charts/resources/values.yaml @@ -0,0 +1,10 @@ +workflowOperatorSA: operate-workflow +applicationStatusViewerSA: claim-application-status +cliEnabled: true +dependenciesReleaseName: provisioner-dependencies +# kafkaEndpoint: kafka:9092 +kafkaEndpoint: kafka.infrastructure.dev.simpl-europe.eu:9092 +kafkaAuth: + enable: true + mechanism: PLAIN + secretName: kafka-secret \ No newline at end of file diff --git a/k8s/argocd-claim-manager.yaml b/k8s/argocd-claim-manager.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7e8154a2503bb87b77193f4810b3310d66236366 --- /dev/null +++ b/k8s/argocd-claim-manager.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: claim-management + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: http://gitea-http.gitea.svc.cluster.local:3000/gitops_test/management-repo.git + path: applications + targetRevision: master + directory: + recurse: true + destination: + server: https://kubernetes.default.svc + syncPolicy: + automated: + selfHeal: true + prune: true + allowEmpty: true \ No newline at end of file diff --git a/k8s/crossplane-configuration.yaml b/k8s/crossplane-configuration.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f488bf227ce1b8608a319a599a2020b2571a461e --- /dev/null +++ b/k8s/crossplane-configuration.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: demo +spec: + package: code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/configuration:v0.3.3 + packagePullSecrets: + - name: ec-pull-secret + revisionHistoryLimit: 0 + packagePullPolicy: Always \ No newline at end of file diff --git a/k8s/debug.yaml b/k8s/debug.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d26eebb6e94d823f529e285527da387340af63c8 --- /dev/null +++ b/k8s/debug.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Pod +metadata: + name: kafkacat + namespace: infrastructure +spec: + containers: + - name: kafkacat + image: confluentinc/cp-kafkacat + command: + - sleep + - "3600" + imagePullPolicy: Always + restartPolicy: Never +--- +apiVersion: v1 +kind: Pod +metadata: + name: nettools + namespace: infrastructure +spec: + containers: + - name: nettools + image: jrecord/nettools:latest + command: + - sleep + - "3600" + imagePullPolicy: Always + restartPolicy: Never \ No newline at end of file diff --git a/k8s/events/decommission/sensor.yaml b/k8s/events/decommission/sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b4ebe68c0fedf8b234ce113834d2bfe39e3b0763 --- /dev/null +++ b/k8s/events/decommission/sensor.yaml @@ -0,0 +1,107 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: to-decommission-wf + namespace: argoev +spec: + eventBusName: provisioner + template: + serviceAccountName: operate-workflow + dependencies: + - name: message + eventSourceName: kafka-to-decommission + eventName: decommissionRequest + triggers: + - template: + name: argo-workflow-trigger + argoWorkflow: + operation: submit + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: workflow-gitops- + namespace: argowf + spec: + imagePullSecrets: + - name: ec-pull-secret + volumes: + - name: repos + emptyDir: {} + serviceAccountName: cli + entrypoint: main + workflowMetadata: + labels: + track-workflow: "true" + workflow-type: to-decommission + labelsFrom: + scriptTriggerId: + expression: workflow.parameters.scriptTriggerId + arguments: + parameters: + - name: scriptTriggerId + value: default-is-overriden + templates: + - name: main + inputs: + artifacts: + - name: data-repo + path: repos/data + git: + repo: http://gitea-http.gitea.svc.cluster.local:3000/gitops_test/data-repo.git + revision: "master" + - name: management-repo + path: /repos/management + git: + repo: http://gitea-http.gitea.svc.cluster.local:3000/gitops_test/management-repo.git + revision: "master" + parameters: + - name: scriptTriggerId + value: "'{{workflow.parameters.scriptTriggerId}}'" + script: + image: alpine/git:v2.45.2 + env: + - name: GIT_USER + valueFrom: + secretKeyRef: + name: gitea-secret + key: username + - name: GIT_PASSWORD + valueFrom: + secretKeyRef: + name: gitea-secret + key: password + command: [sh, -c] + args: [' + echo --- decommissioning request with ID: {{inputs.parameters.scriptTriggerId}}; + echo ---ls ../work---; + ls; + echo ---ls ../repos---; + ls ../repos; + git config --global user.email "workflow@argo.kube"; + git config --global user.name "gitops_test"; + echo ---commit data changes---; + git -C ../repos/data checkout master; + git -C ../repos/data rm -r claims/claim_{{inputs.parameters.scriptTriggerId}}; + git -C ../repos/data commit -v -m "Remove claim_{{inputs.parameters.scriptTriggerId}}"; + git -C ../repos/data push http://$GIT_USER:$GIT_PASSWORD@gitea-http.gitea.svc.cluster.local:3000/gitops_test/data-repo.git; + echo ---commit management changes---; + git -C ../repos/management checkout master; + git -C ../repos/management rm -r applications/application_{{inputs.parameters.scriptTriggerId}}; + git -C ../repos/management commit -v -m "Remove application_{{inputs.parameters.scriptTriggerId}}"; + git -C ../repos/management push http://$GIT_USER:$GIT_PASSWORD@gitea-http.gitea.svc.cluster.local:3000/gitops_test/management-repo.git; + '] + volumeMounts: + - name: repos + mountPath: /repos + workingDir: /work + parameters: + - src: + dependencyName: message + dataKey: headers.scriptTriggerId + value: "" + dest: spec.arguments.parameters.0.value + retryStrategy: + steps: 2 + duration: 30s \ No newline at end of file diff --git a/k8s/events/decommission/source.yaml b/k8s/events/decommission/source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4dcd5cd5ef26ec4195d4987669f57903987e3899 --- /dev/null +++ b/k8s/events/decommission/source.yaml @@ -0,0 +1,18 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: kafka-to-decommission + namespace: argoev +spec: + eventBusName: provisioner + kafka: + decommissionRequest: + url: {{ .Values.kafkaEndpoint }} + topic: to-decommission + jsonBody: false + partition: "0" + connectionBackoff: + duration: 10s + steps: 3 + factor: 1 + jitter: 0.2 \ No newline at end of file diff --git a/k8s/events/decommission/status-emitter.yaml b/k8s/events/decommission/status-emitter.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7ab6b17737ffa2292f17d88d97fea2ea899bb605 --- /dev/null +++ b/k8s/events/decommission/status-emitter.yaml @@ -0,0 +1,32 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: decommission-status-emitter + namespace: argoev +spec: + eventBusName: provisioner + dependencies: + - name: message + eventSourceName: decommission-status-tracker + eventName: decommissionStatus + triggers: + - template: + name: kafka + kafka: + url: {{ .Values.kafkaEndpoint }} + topic: decommissioned + payload: + - src: + dependencyName: message + dataKey: body.metadata.labels.claim-uuid + value: "" + dest: scriptTriggerId + - src: + dependencyName: message + value: "Succeeded" + dest: status + - src: + dependencyName: message + dataKey: body.metadata.deletionTimestamp + value: "" + dest: deletionTimestamp \ No newline at end of file diff --git a/k8s/events/decommission/status-source.yaml b/k8s/events/decommission/status-source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e9f3eda0976174ff608bf3690af7922c76147858 --- /dev/null +++ b/k8s/events/decommission/status-source.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: decommission-status-tracker + namespace: argoev +spec: + eventBusName: provisioner + template: + serviceAccountName: claim-application-status + resource: + decommissionStatus: + namespace: argocd + group: argoproj.io + version: v1alpha1 + resource: applications + eventTypes: + - DELETE + filter: + afterStart: true + labels: + - key: track-events + operation: "==" + value: "claim-application" + diff --git a/k8s/events/eventbus.yaml b/k8s/events/eventbus.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7ed73ad67a0cd130e5e24a6b5c097e77f9f87319 --- /dev/null +++ b/k8s/events/eventbus.yaml @@ -0,0 +1,10 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventBus +metadata: + name: provisioner + namespace: argoev +spec: + nats: + native: + replicas: 2 + auth: token diff --git a/k8s/events/provision/sensor.yaml b/k8s/events/provision/sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1aeafa660210d1488c23cd12f09b8dcb25f8ad4e --- /dev/null +++ b/k8s/events/provision/sensor.yaml @@ -0,0 +1,123 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: to-provision-wf + namespace: argoev +spec: + eventBusName: provisioner + template: + serviceAccountName: operate-workflow + dependencies: + - name: message + eventSourceName: kafka-to-provision + eventName: provisionRequest + triggers: + - template: + name: argo-workflow-trigger + argoWorkflow: + operation: submit + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: workflow-gitops- + namespace: argowf + spec: + imagePullSecrets: + - name: ec-pull-secret + volumes: + - name: repos + emptyDir: {} + serviceAccountName: cli + entrypoint: main + workflowMetadata: + labels: + track-workflow: "true" + workflow-type: to-provision + labelsFrom: + scriptTriggerId: + expression: workflow.parameters.scriptTriggerId + arguments: + parameters: + - name: headers + value: default-is-overriden + - name: scriptTriggerId + value: default-is-overriden + - name: body + value: default-is-overriden + templates: + - name: main + inputs: + artifacts: + - name: data-repo + path: repos/data + git: + repo: http://gitea-http.gitea.svc.cluster.local:3000/gitops_test/data-repo.git + revision: "master" + - name: management-repo + path: /repos/management + git: + repo: http://gitea-http.gitea.svc.cluster.local:3000/gitops_test/management-repo.git + revision: "master" + parameters: + - name: headers + value: "'{{workflow.parameters.headers}}'" + - name: scriptTriggerId + value: "'{{workflow.parameters.scriptTriggerId}}'" + - name: body + value: "'{{workflow.parameters.body}}'" + container: + image: code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/to-provision-workflow:v0.2.1 + env: + - name: GIT_USER + valueFrom: + secretKeyRef: + name: gitea-secret + key: username + - name: GIT_PASSWORD + valueFrom: + secretKeyRef: + name: gitea-secret + key: password + command: [sh, -c] + args: [' + echo --- provisioning request with ID: {{inputs.parameters.scriptTriggerId}}; + echo ---ls ../work---; + ls; + echo ---ls ../repos---; + ls ../repos; + python doesntexist.py; + python main.py {{inputs.parameters.scriptTriggerId}} {{inputs.parameters.body}} || exit $?; + git config --global user.email "workflow@argo.kube"; + git config --global user.name "gitops_test"; + echo ---commit data changes---; + git -C ../repos/data checkout master; + git -C ../repos/data/claims add -A && git -C ../repos/data commit -m "add UUID {{inputs.parameters.scriptTriggerId}}"; + git -C ../repos/data push http://$GIT_USER:$GIT_PASSWORD@gitea-http.gitea.svc.cluster.local:3000/gitops_test/data-repo.git; + echo ---commit management changes---; + git -C ../repos/management checkout master; + git -C ../repos/management/applications add -A && git -C ../repos/management commit -m "add UUID {{inputs.parameters.scriptTriggerId}}"; + git -C ../repos/management push http://$GIT_USER:$GIT_PASSWORD@gitea-http.gitea.svc.cluster.local:3000/gitops_test/management-repo.git; + '] + volumeMounts: + - name: repos + mountPath: /repos + workingDir: /work + parameters: + - src: + dependencyName: message + dataKey: headers + dest: spec.arguments.parameters.0.value + - src: + dependencyName: message + dataKey: headers.scriptTriggerId + value: dummy + dest: spec.arguments.parameters.1.value + - src: + dependencyName: message + dataKey: body + dest: spec.arguments.parameters.2.value + retryStrategy: + steps: 2 + duration: 30s \ No newline at end of file diff --git a/k8s/events/provision/source.yaml b/k8s/events/provision/source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..26ea9cbc3567be863e33a06a5246ec7b76aee4e4 --- /dev/null +++ b/k8s/events/provision/source.yaml @@ -0,0 +1,18 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: kafka-to-provision + namespace: argoev +spec: + eventBusName: provisioner + kafka: + provisionRequest: + url: {{ .Values.kafkaEndpoint }} + topic: to-provision + jsonBody: false + partition: "0" + connectionBackoff: + duration: 10s + steps: 3 + factor: 1 + jitter: 0.2 \ No newline at end of file diff --git a/k8s/events/provision/status-emitter.yaml b/k8s/events/provision/status-emitter.yaml new file mode 100644 index 0000000000000000000000000000000000000000..65fd351f3d1c7618a83250ddb8e5e1f097077d6f --- /dev/null +++ b/k8s/events/provision/status-emitter.yaml @@ -0,0 +1,44 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: to-provision-wf-status-emitter + namespace: argoev +spec: + eventBusName: provisioner + dependencies: + - name: wf-status + eventSourceName: to-provision-wf-status-tracker + eventName: toProvisionWorkflow + triggers: + - template: + name: kafka + kafka: + url: {{ .Values.kafkaEndpoint }} + topic: provisioned + partition: 0 + payload: + - src: + dependencyName: wf-status + dataKey: body.metadata.name + value: "None" + dest: name + - src: + dependencyName: wf-status + dataKey: "body.metadata.labels.workflows\\.argoproj\\.io/phase" + value: "Unknown" + dest: phase + - src: + dependencyName: wf-status + dataKey: "body.metadata.labels.workflows\\.argoproj\\.io/completed" + value: "false" + dest: completed + - src: + dependencyName: wf-status + dataKey: "body.metadata.labels.scriptTriggerId" + value: "" + dest: scriptTriggerId + - src: + dependencyName: wf-status + dataKey: body.status.message + value: "" + dest: message \ No newline at end of file diff --git a/k8s/events/provision/status-source.yaml b/k8s/events/provision/status-source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f95be3aabf2576e856e33b9e96fbbd84d660f889 --- /dev/null +++ b/k8s/events/provision/status-source.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: claim-application-status-tracker + namespace: argoev +spec: + eventBusName: provisioner + template: + serviceAccountName: claim-application-status + resource: + claimApplicationStatus: + namespace: argocd + group: argoproj.io + version: v1alpha1 + resource: applications + eventTypes: + - UPDATE + filter: + afterStart: true + labels: + - key: track-events + operation: "==" + value: "claim-application" + - key: provisioning-status + operation: "!=" + value: "finalized" + diff --git a/k8s/events/provision/workflow-status-sensor.yaml b/k8s/events/provision/workflow-status-sensor.yaml new file mode 100644 index 0000000000000000000000000000000000000000..75b9b988f4ccf69116958925422018af762f2587 --- /dev/null +++ b/k8s/events/provision/workflow-status-sensor.yaml @@ -0,0 +1,175 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: claim-application-status-wf + namespace: argoev +spec: + eventBusName: provisioner + template: + serviceAccountName: operate-workflow + dependencies: + - name: message + eventSourceName: claim-application-status-tracker + eventName: claimApplicationStatus + triggers: + - template: + name: argo-workflow-trigger + argoWorkflow: + operation: submit + source: + resource: + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: workflow-claim-application-status + namespace: argowf + spec: + serviceAccountName: cli + entrypoint: main + workflowMetadata: + arguments: + parameters: + - name: body + value: default-is-overriden + templates: + - name: main + steps: + - - name: validate + template: validate-status + arguments: + parameters: + - name: message + value: "{{workflow.parameters.body}}" + - - name: label + template: done-label + arguments: + parameters: + - name: UUID + value: "{{steps.validate.outputs.result}}" + - - name: parse-application + template: parse-application + arguments: + parameters: + - name: resource + value: "{{steps.label.outputs.parameters.application-resource}}" + - - name: parse-claim + template: parse-claim + arguments: + parameters: + - name: claim-reference + value: "{{steps.parse-application.outputs.result}}" + - - name: payload + template: payload + arguments: + parameters: + - name: resource + value: "{{steps.parse-claim.outputs.parameters.claim-resource}}" + - - name: message + template: message + arguments: + parameters: + - name: payload + value: "{{steps.payload.outputs.result}}" + - name: validate-status + inputs: + parameters: + - name: message + script: + image: python:alpine3.10 + command: [python] + source: | + import json + rawstr = r'''{{inputs.parameters.message}}''' + d = json.loads(rawstr) + try: + app_status = d['status'] + app_meta = d['metadata'] + app_health = app_status['health']['status'] + app_sync = app_status['sync']['status'] + # Application only tracks the Claim, so one resource should be present + claim = app_status['resources'][0] + claim_health = claim['health']['status'] + claim_sync = claim['status'] + claim_id = app_meta['labels']['claim-uuid'] + assert app_health == "Healthy" and claim_health == "Healthy" and app_sync == "Synced" and claim_sync == "Synced" + print(f"{claim_id}") + except (KeyError, IndexError): + raise ValueError("Not all status fields available") + - name: done-label + inputs: + parameters: + - name: UUID + script: + image: bitnami/kubectl:latest + command: [sh] + source: | + echo "patching application with label claim-uuid:{{inputs.parameters.UUID}}" + kubectl label -n argocd applications crossplane-claim-{{inputs.parameters.UUID}} provisioning-status="finalized" + kubectl get -n argocd applications --selector=claim-uuid={{inputs.parameters.UUID}} -o json > /tmp/resource.json + outputs: + parameters: + - name: application-resource + valueFrom: + path: /tmp/resource.json + - name: parse-application + inputs: + parameters: + - name: resource + script: + image: python:alpine3.10 + command: [python] + source: | + import json + rawstr = r'''{{inputs.parameters.resource}}''' + d = json.loads(rawstr) + claim_kind = d['items'][0]['metadata']['labels']['claim-kind'] + claim_selector = d['items'][0]['metadata']['labels']['claim-uuid'] + print(f"{claim_kind} --selector=uuid={claim_selector}") + - name: parse-claim + inputs: + parameters: + - name: claim-reference + script: + image: bitnami/kubectl:latest + command: [sh] + source: | + kubectl get -n crossplane-system {{inputs.parameters.claim-reference}} + kubectl get -n crossplane-system -o json {{inputs.parameters.claim-reference}} > /tmp/resource.json + outputs: + parameters: + - name: claim-resource + valueFrom: + path: /tmp/resource.json + - name: payload + inputs: + parameters: + - name: resource + script: + image: python:alpine3.10 + command: [python] + source: | + import json + rawstr = r'''{{inputs.parameters.resource}}''' + d = json.loads(rawstr) + payload = {} + payload["vmIps"] = d["items"][0]["status"]["vmIps"] + payload["status"] = "Provisioning finalized successfully" + payload["scriptTriggerId"] = d["items"][0]["metadata"]["labels"]["uuid"] + print(json.dumps(payload).replace('"', '\\"')) + - name: message + inputs: + parameters: + - name: payload + script: + image: confluentinc/cp-kafkacat:7.1.14 + command: [sh] + source: | + echo {{inputs.parameters.payload}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -t provisioned -J + parameters: + - src: + dependencyName: message + dataKey: body + dest: spec.arguments.parameters.0.value + retryStrategy: + steps: 2 + duration: 30s \ No newline at end of file diff --git a/k8s/events/provision/workflow-status-source.yaml b/k8s/events/provision/workflow-status-source.yaml new file mode 100644 index 0000000000000000000000000000000000000000..86441f1dd666c346d5470d9dc90917c7f6459d9c --- /dev/null +++ b/k8s/events/provision/workflow-status-source.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: EventSource +metadata: + name: to-provision-wf-status-tracker + namespace: argoev +spec: + eventBusName: provisioner + template: + serviceAccountName: operate-workflow + resource: + toProvisionWorkflow: + namespace: argowf + group: argoproj.io + version: v1alpha1 + resource: workflows + eventTypes: + - ADD + - UPDATE + filter: + afterStart: true + labels: + - key: track-workflow + operation: "==" + value: "true" + - key: workflow-type + operation: "==" + value: to-provision diff --git a/k8s/provider-ionos-configuration.yaml b/k8s/provider-ionos-configuration.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e4eda6e78fbb316c3c6f3de33ca959c496dbea1e --- /dev/null +++ b/k8s/provider-ionos-configuration.yaml @@ -0,0 +1,12 @@ +apiVersion: ionoscloud.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: example + namespace: crossplane-system +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: ionos-provider + key: credentials diff --git a/k8s/rbac/argocd-application-rbac.yaml b/k8s/rbac/argocd-application-rbac.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8a7c71fc9a5661d1b71f87fc1cddfce68caa574f --- /dev/null +++ b/k8s/rbac/argocd-application-rbac.yaml @@ -0,0 +1,62 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: view-application + namespace: argocd +rules: + - apiGroups: + - argoproj.io + resources: + - applications + verbs: + - list + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: view-application + namespace: argocd +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: view-application +subjects: +- kind: ServiceAccount + name: claim-application-status + namespace: argoev +- kind: ServiceAccount + name: operate-workflow + namespace: argoev +- kind: ServiceAccount + name: cli + namespace: argowf + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: patch-application + namespace: argocd +rules: + - apiGroups: + - argoproj.io + resources: + - applications + verbs: + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: patch-application + namespace: argocd +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: patch-application +subjects: +- kind: ServiceAccount + name: cli + namespace: argowf diff --git a/k8s/rbac/crossplane-composites.yaml b/k8s/rbac/crossplane-composites.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4c80c05587417b72fa791491d8ae1ae52637b85d --- /dev/null +++ b/k8s/rbac/crossplane-composites.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: view-composite-status-events +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: crossplane-view +subjects: + - kind: ServiceAccount + name: composite-status + namespace: argoev + - kind: ServiceAccount + name: cli + namespace: argowf \ No newline at end of file diff --git a/k8s/workflows/cli-token.yaml b/k8s/workflows/cli-token.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5aabb14b129585b8b9e8869711943c16d9fa5299 --- /dev/null +++ b/k8s/workflows/cli-token.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: argocli.service-account-token + annotations: + kubernetes.io/service-account.name: argocli +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/package/apis/demo/definition.yaml b/package/apis/demo/definition.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6de7c2ee9badbbc23e62178da18963dc1a23b0e4 --- /dev/null +++ b/package/apis/demo/definition.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xserversinstances.platform.example.org +spec: + defaultCompositeDeletePolicy: Foreground + group: platform.example.org + names: + kind: XServerInstance + plural: xserversinstances + claimNames: + kind: ServerInstance + plural: serversinstances + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + description: A XServersInstance is a composite resource + properties: + spec: + type: object + properties: + parameters: + type: object + properties: + datacenterName: + type: string + datacenterLocation: + type: string + enum: [es/vit, de/txl] + datacenterDescription: + type: string + serverName: + type: string + cores: + type: integer + minimum: 1 + maximum: 4 + ram: + type: integer + minimum: 1024 + maximum: 8192 + cpuFamily: + type: string + enum: [INTEL_ICELAKE, AMD_EPYC] + cloudConfig: + type: string + required: + - datacenterName + - datacenterLocation + - serverName + - cores + - ram + - cpuFamily + required: + - parameters + status: + type: object + properties: + vmIps: + type: array + items: + type: string + minItems: 2 + maxItems: 2 + datacenterId: + type: string + volumeId: + type: string \ No newline at end of file diff --git a/package/apis/demo/ionos.yaml b/package/apis/demo/ionos.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d15f786f6f2a018a056eea4ea81b4a45d4e80b19 --- /dev/null +++ b/package/apis/demo/ionos.yaml @@ -0,0 +1,197 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: example + labels: + crossplane.io/xrd: xserversinstances.platform.example.org + provider: ionoscloud +spec: + writeConnectionSecretsToNamespace: infrastructure + compositeTypeRef: + apiVersion: platform.example.org/v1alpha1 + kind: XServerInstance + resources: + # 0 + - name: datacenter + base: + apiVersion: compute.ionoscloud.crossplane.io/v1alpha1 + kind: Datacenter + managementPolicies: + - "*" + spec: + forProvider: + name: exampleDatacenter + location: us/las + description: test + providerConfigRef: + name: example + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.datacenterLocation + toFieldPath: spec.forProvider.location + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.datacenterName + toFieldPath: spec.forProvider.name + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.datacenterDescription + toFieldPath: spec.forProvider.description + - type: ToCompositeFieldPath + fromFieldPath: status.atProvider.datacenterId + toFieldPath: status.datacenterId + # 1 + - name: server1 + base: + apiVersion: compute.ionoscloud.crossplane.io/v1alpha1 + kind: Server + managementPolicies: + - "*" + spec: + forProvider: + datacenterConfig: + datacenterIdRef: + name: datacenter + name: server1 + volumeConfig: + volumeIdRef: + name: volume1 + cores: 1 + ram: 1024 + availabilityZone: AUTO + cpuFamily: INTEL_ICELAKE + providerConfigRef: + name: example + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.serverName + toFieldPath: spec.forProvider.name + transforms: + - type: string + string: + fmt: "%s_1" + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.cores + toFieldPath: spec.forProvider.cores + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.ram + toFieldPath: spec.forProvider.ram + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.cpuFamily + toFieldPath: spec.forProvider.cpuFamily + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[0].name + toFieldPath: spec.forProvider.datacenterConfig.datacenterIdRef.name + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[5].name + toFieldPath: spec.forProvider.volumeConfig.volumeIdRef.name + # 2 + - name: lan1 + base: + apiVersion: compute.ionoscloud.crossplane.io/v1alpha1 + kind: Lan + managementPolicies: + - "*" + spec: + forProvider: + name: lan1 + public: true + datacenterConfig: + datacenterIdRef: + name: datacenter + providerConfigRef: + name: example + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[0].name + toFieldPath: spec.forProvider.datacenterConfig.datacenterIdRef.name + # 3 + - name: ipblock1 + base: + apiVersion: compute.ionoscloud.crossplane.io/v1alpha1 + kind: IPBlock + managementPolicies: + - "*" + spec: + forProvider: + name: ipblock1 + size: 2 + location: us/las + providerConfigRef: + name: example + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.datacenterLocation + toFieldPath: spec.forProvider.location + - type: ToCompositeFieldPath + fromFieldPath: status.atProvider.ips + toFieldPath: status.vmIps + # 4 + - name: nic1 + base: + apiVersion: compute.ionoscloud.crossplane.io/v1alpha1 + kind: Nic + managementPolicies: + - "*" + spec: + forProvider: + name: Nic1 + dhcp: true + ipsConfigs: + ips: + - 0.0.0.0 + - 0.0.0.0 + datacenterConfig: + datacenterIdRef: + name: datacenter + serverConfig: + serverIdRef: + name: server1 + lanConfig: + lanIdRef: + name: lan1 + providerConfigRef: + name: example + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[0].name + toFieldPath: spec.forProvider.datacenterConfig.datacenterIdRef.name + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[1].name + toFieldPath: spec.forProvider.serverConfig.serverIdRef.name + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[2].name + toFieldPath: spec.forProvider.lanConfig.lanIdRef.name + - type: FromCompositeFieldPath + fromFieldPath: status.vmIps + toFieldPath: spec.forProvider.ipsConfigs.ips + # 5 + - name: volume1 + base: + apiVersion: compute.ionoscloud.crossplane.io/v1alpha1 + kind: Volume + managementPolicies: + - "*" + spec: + forProvider: + name: volume1 + size: 5 + type: SSD Standard + bus: VIRTIO + availabilityZone: AUTO + imageAlias: ubuntu:22.04 + imagePassword: testpassword1234 + userData: foo + datacenterConfig: + datacenterIdRef: + name: datacenter + providerConfigRef: + name: example + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.resourceRefs[0].name + toFieldPath: spec.forProvider.datacenterConfig.datacenterIdRef.name + - type: FromCompositeFieldPath + fromFieldPath: spec.parameters.cloudConfig + toFieldPath: spec.forProvider.userData + - type: ToCompositeFieldPath + fromFieldPath: status.atProvider.volumeId + toFieldPath: status.volumeId diff --git a/package/crossplane.yaml b/package/crossplane.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6afa4405c52148af54e440ffd38fdfb098e670a2 --- /dev/null +++ b/package/crossplane.yaml @@ -0,0 +1,20 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: simpl-config + annotations: + meta.crossplane.io/maintainer: SIMPL Infrastructure Team + meta.crossplane.io/source: "https://github.com/cbeti-ionos/crossplane-multicloud" + meta.crossplane.io/license: MIT + meta.crossplane.io/description: Catalogue offerings demo in IonosCloud + meta.crossplane.io/readme: A Configuration package demo that contains Definitions and Compositions for provisioning Catalogue offerings in IonosCloud + meta.crossplane.io/version: 0.3.6 + +spec: + crossplane: + version: ">=v1.16.0" + dependsOn: + - provider: ghcr.io/ionos-cloud/crossplane-provider-ionoscloud + version: ">=v1.1.3" + + diff --git a/package/examples/demo/ionos.yaml b/package/examples/demo/ionos.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2dbd5d8fb7b2c49d6223ad38cede93fc74ac3e66 --- /dev/null +++ b/package/examples/demo/ionos.yaml @@ -0,0 +1,14 @@ +apiVersion: platform.example.org/v1alpha1 +kind: ServerInstance +metadata: + namespace: infrastructure + name: offering-name-medium-instance-example +spec: + parameters: + datacenterName: crossplane_datacenter_medium_example + datacenterDescription: testExampleDescription + datacenterLocation: es/vit + serverName: server + cores: 2 + ram: 2048 + cpuFamily: INTEL_ICELAKE diff --git a/package/examples/demo/ionos_template.yaml b/package/examples/demo/ionos_template.yaml new file mode 100644 index 0000000000000000000000000000000000000000..630c7a5abfce01f0ca64982092ebb74d3cd7b0a2 --- /dev/null +++ b/package/examples/demo/ionos_template.yaml @@ -0,0 +1,18 @@ +apiVersion: platform.example.org/v1alpha1 +kind: ServerInstance +metadata: + namespace: infrastructure + name: offering-name-medium-instance-{UUID} + labels: + uuid: "{UUID}" + reference-kind: xserversinstances +spec: + parameters: + datacenterName: crossplane_datacenter_medium-{UUID} + datacenterDescription: testExampleDescription + datacenterLocation: es/vit + serverName: server + cores: 2 + ram: 2048 + cpuFamily: INTEL_ICELAKE + cloudConfig: I2Nsb3VkLWNvbmZpZwpzc2hfcHdhdXRoOiB0cnVlCnVzZXJzOgotIGRlZmF1bHQKLSBuYW1lOiB2ODZsNDFLTDE4CiAgcGFzc3dkOiAkNiRyb3VuZHM9NDA5NiRyY1J3eUhUbGZJTlZqOXY3JEdxdzZkbmNLRjlkdy9ra25DakxOR0I1NUtWcXRMcC5Mek54MjF0ZUwyL2FBY1JqbDZEUDA5R3JDZ0x3encxRHpwcEVuc0NQSE5LRFBaZy9HV0NwQ0ouCiAgc2hlbGw6IC9iaW4vYmFzaAogIGxvY2tfcGFzc3dkOiBmYWxzZQogIGNocGFzc3dkOgogICAgZXhwaXJlOiBmYWxzZQogIHN1ZG86IEFMTD0oQUxMKSBOT1BBU1NXRDpBTEwKICBncm91cHM6IHVzZXJzLCBhZG1pbgo= \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..2bd1c148d4465b5692ba92cde2de07af8f3d5d4f --- /dev/null +++ b/setup.sh @@ -0,0 +1,16 @@ +NS=infrastructure +kubectl create namespace $NS +kubectl create -n $NS secret docker-registry "ec-pull-secret" --docker-server="code.europa.eu:4567" --docker-username="$EC_USERNAME" --docker-password="$EC_PASSWORD" +kubectl create -n $NS secret generic gitea-secret --from-literal=username=gitops_test --from-literal=password=test1234 +kubectl create -n $NS secret generic kafka-secret --from-literal=username=demo --from-literal=password=demo-password +kubectl create -n $NS secret generic ionos-provider --from-literal=credentials="{\"token\":\"${IONOS_TOKEN}\"}" +helm install provisioner-dependencies -n $NS charts/dependencies +sleep 60 +helm install provisioner-resources -n $NS charts/resources + +# Use when installing locally to get access tokens and forward service ports +echo "Bearer $(kubectl get -n $NS secret cli.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)" > argowftoken +kubectl get -n $NS secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d > argopw +# kubectl port-forward -n $NS svc/argocd-server 8888:443 +# kubectl port-forward -n $NS svc/argowf-argo-workflows-server 8777:2746 +# kubectl port-forward -n $NS svc/gitea-http 8333:3000 \ No newline at end of file diff --git a/workflow-images/to-provision/Dockerfile b/workflow-images/to-provision/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..81777c5ec72c2eab5c69025ce85271488f767a37 --- /dev/null +++ b/workflow-images/to-provision/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3 + +WORKDIR /work + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +ENTRYPOINT ["python", "./main.py"] diff --git a/workflow-images/to-provision/main.py b/workflow-images/to-provision/main.py new file mode 100644 index 0000000000000000000000000000000000000000..a5db1f6cc29bb3d892aceaa2a2914b357d803e42 --- /dev/null +++ b/workflow-images/to-provision/main.py @@ -0,0 +1,73 @@ +import sys +import os +import base64 +import yaml +from pathlib import Path + +# This is the default setting, if you change it in the workflow, it must be changed here as well +LOGFILE = "/dev/termination-log" +SCRIPTS_PATH = "/repos/data/claims" +APPLICATIONS_PATH = "/repos/management/applications" +APPLICATION_TEMPLATE_PATH ="/repos/management/data-application-template.yaml" +CLAIM_KIND_REF_LABEL = "reference-kind" +KIND_REF_PLACEHOLDER = "KIND" +UUID_PLACEHOLDER = "UUID" + +def write_termination_log(msg, echo_stdout=True): + if echo_stdout: + print(msg) + with open(LOGFILE, mode="a", encoding="utf-8") as logfile: + print(msg, file=logfile) + +if len(sys.argv) != 3: + msg = "Parser received wrong number of arguments:" + f"{len(sys.argv)-1}, <provisioningRequestId> and <body> are required" + sys.exit(100) +UUID = sys.argv[1] +message = sys.argv[2] + +script_path = f"{SCRIPTS_PATH}/claim_{UUID}/claim_{UUID}.yaml" +print(f"Verifying claim duplication:\n{script_path}") +if Path(script_path).exists(): + msg = f"CLAIM ID DUPLICATION: claim with id {UUID} already exists!" + write_termination_log(msg) + sys.exit(101) +os.mkdir(f"{SCRIPTS_PATH}/claim_{UUID}") + +application_path = f"{APPLICATIONS_PATH}/application_{UUID}/application_{UUID}.yaml" +print(f"Verifying application duplication:\n{application_path}") +if Path(application_path).exists(): + msg = f"APPLICATION ID DUPLICATION: application with id {UUID} already exists!" + write_termination_log(msg) + sys.exit(102) +os.mkdir(f"{APPLICATIONS_PATH}/application_{UUID}") + +try: + b64decoded = base64.standard_b64decode(message) + decoded = b64decoded.decode("utf-8") + script_content = decoded.format(UUID=UUID) +except Exception as e: + msg = f"Cannot decode deployment manifest from message body" + write_termination_log(msg) + sys.exit(103) + +with open(script_path, mode="w", encoding="utf-8") as claim_file: + claim_file.write(script_content) +print(f"\n---[claim {UUID} created]---\n") + +###TODO Embed the application template to get rid of this read +application_content = "" +with open(APPLICATION_TEMPLATE_PATH, mode="r", encoding="utf-8") as template_file: + template = template_file.read() + try: + l = yaml.safe_load(script_content) + reference_kind = l["metadata"]["labels"][CLAIM_KIND_REF_LABEL] + except Exception as e: + msg = f"Cannot retrieve claim kind reference label from manifest" + write_termination_log(msg) + sys.exit(104) + application_content = template.format(**{UUID_PLACEHOLDER:UUID,KIND_REF_PLACEHOLDER:reference_kind}) + +with open(application_path, mode="w", encoding="utf-8") as application_file: + application_file.write(application_content) +print(f"---[application {UUID} created]---\n") + diff --git a/workflow-images/to-provision/requirements.txt b/workflow-images/to-provision/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..03edd27bb4904653cc75004b1d0102dff706a261 --- /dev/null +++ b/workflow-images/to-provision/requirements.txt @@ -0,0 +1 @@ +pyaml>=6.0.2 \ No newline at end of file