This commit is contained in:
Mani Marothu 2024-10-07 21:03:09 +00:00 committed by GitHub
commit a7d5a4570f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 15442 additions and 0 deletions

1
cicd/README.md Normal file
View file

@ -0,0 +1 @@
# kub308-workshop

21
cicd/argo-events.yaml Normal file
View file

@ -0,0 +1,21 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argo-events
namespace: argocd
labels:
env: dev
spec:
project: default
source:
repoURL: https://argoproj.github.io/argo-helm
targetRevision: 2.4.8
chart: argo-events
destination:
server: "https://kubernetes.default.svc"
namespace: argo-events
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
selfHeal: true

23
cicd/argo-workflows.yaml Normal file
View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
resources:
- install.yaml

View file

@ -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

View file

@ -0,0 +1,31 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argo-workflows-ingress
namespace: argo
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: "nginx"
rules:
- host: localhost
http:
paths:
- path: /argo-workflows(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: argo-server
port:
name: web
- host: cnoe.localtest.me
http:
paths:
- path: /argo-workflows(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: argo-server
port:
name: web

View file

@ -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

View file

@ -0,0 +1,26 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: workflow-controller-configmap
namespace: argo
data:
config: |
sso:
insecureSkipVerify: true
issuer: https://cnoe.localtest.me:8443/keycloak/realms/cnoe
clientId:
name: keycloak-oidc
key: client-id
clientSecret:
name: keycloak-oidc
key: secret-key
redirectUrl: https://cnoe.localtest.me:8443/argo-workflows/oauth2/callback
rbac:
enabled: true
scopes:
- openid
- profile
- email
- groups
nodeEvents:
enabled: false

View file

@ -0,0 +1,30 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: argo-server
namespace: argo
annotations:
argocd.argoproj.io/sync-wave: "20"
spec:
template:
spec:
containers:
- name: argo-server
readinessProbe:
httpGet:
path: /
port: 2746
scheme: HTTP
env:
- name: BASE_HREF
value: "/argo-workflows/"
args:
- server
- --configmap=workflow-controller-configmap
- --auth-mode=client
- --auth-mode=sso
- "--secure=false"
- "--loglevel"
- "info"
- "--log-format"
- "text"

View file

@ -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

View file

@ -0,0 +1,23 @@
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

BIN
cicd/backstage-templates/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,6 @@
site_name: 'Argo Sample Example'
nav:
- Home: index.md
- idpBuilder: idpbuilder.md
plugins:
- techdocs-core

View file

@ -0,0 +1,21 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ${{values.name | dump}}
namespace: argocd
labels:
example: basic
spec:
project: default
source:
repoURL: ${{values.repoUrl}}
targetRevision: HEAD
path: manifests
destination:
name: workshop
namespace: ${{values.name}}
syncPolicy:
automated:
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,36 @@
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{values.name | dump}}
description: This is a basic example application
annotations:
backstage.io/techdocs-ref: dir:.
backstage.io/kubernetes-label-selector: 'entity-id=${{values.name}}'
backstage.io/kubernetes-namespace: default
argocd/app-name: ${{values.name | dump}}
links:
- url: https://cnoe.localtest.me:8443/gitea
title: Repo URL
icon: github
spec:
owner: guest
lifecycle: experimental
type: service
system: ${{values.name | dump}}
---
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: ${{values.name | dump}}
description: An example system for demonstration purposes
annotations:
backstage.io/techdocs-ref: dir:.
links:
- url: https://github.com/cnoe-io/stacks/tree/main/ref-implementation
title: CNOE Repo
icon: github
spec:
owner: guest
lifecycle: experimental
type: service

View file

@ -0,0 +1,46 @@
[![Codespell][codespell-badge]][codespell-link]
[![E2E][e2e-badge]][e2e-link]
[![Go Report Card][report-badge]][report-link]
[![Commit Activity][commit-activity-badge]][commit-activity-link]
# IDP Builder
Internal development platform binary launcher.
> **WORK IN PROGRESS**: This tool is in a pre-release stage and is under active development.
## About
Spin up a complete internal developer platform using industry standard technologies like Kubernetes, Argo, and backstage with only Docker required as a dependency.
This can be useful in several ways:
* Create a single binary which can demonstrate an IDP reference implementation.
* Use within CI to perform integration testing.
* Use as a local development environment for platform engineers.
## Getting Started
Checkout our [documentation website](https://cnoe.io/docs/reference-implementation/installations/idpbuilder) for getting started with idpbuilder.
## Community
- If you have questions or concerns about this tool, please feel free to reach out to us on the [CNCF Slack Channel](https://cloud-native.slack.com/archives/C05TN9WFN5S).
- You can also join our community meetings to meet the team and ask any questions. Checkout [this calendar](https://calendar.google.com/calendar/embed?src=064a2adfce866ccb02e61663a09f99147f22f06374e7a8994066bdc81e066986%40group.calendar.google.com&ctz=America%2FLos_Angeles) for more information.
## Contribution
Checkout the [contribution doc](./CONTRIBUTING.md) for contribution guidelines and more information on how to set up your local environment.
<!-- JUST BADGES & LINKS -->
[codespell-badge]: https://github.com/cnoe-io/idpbuilder/actions/workflows/codespell.yaml/badge.svg
[codespell-link]: https://github.com/cnoe-io/idpbuilder/actions/workflows/codespell.yaml
[e2e-badge]: https://github.com/cnoe-io/idpbuilder/actions/workflows/e2e.yaml/badge.svg
[e2e-link]: https://github.com/cnoe-io/idpbuilder/actions/workflows/e2e.yaml
[report-badge]: https://goreportcard.com/badge/github.com/cnoe-io/idpbuilder
[report-link]: https://goreportcard.com/report/github.com/cnoe-io/idpbuilder
[commit-activity-badge]: https://img.shields.io/github/commit-activity/m/cnoe-io/idpbuilder
[commit-activity-link]: https://github.com/cnoe-io/idpbuilder/pulse

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -0,0 +1,11 @@
![cnoe logo](./images/cnoe-logo.png)
# Example Basic Application
Thanks for trying out this demo! In this example, we deployed a simple guestbook application to a remote cluster using Backstage.
### idpbuilder
Checkout idpbuilder website: https://cnoe.io/docs/reference-implementation/installations/idpbuilder
Checkout idpbuilder repository: https://github.com/cnoe-io/idpbuilder

View file

@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${{values.name | dump}}
labels:
entity-id: ${{values.name}}
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
entity-id: ${{values.name}}
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,55 @@
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
description: Creates a Simple Kubernetes Deployment to Remote EKS
name: basic-remote
title: Deploy an Application to EKS
spec:
owner: guest
type: service
parameters:
- title: Configuration Options
required:
- name
properties:
name:
type: string
default: demo
description: name of this application
steps:
- id: template
name: Generating component
action: fetch:template
input:
url: ./skeleton
values:
name: ${{parameters.name}}
repoUrl: https://cnoe.localtest.me:8443/gitea/giteaAdmin/${{parameters.name}}.git
- 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: cnoe.localtest.me:8443/gitea?repo=${{parameters.name}}
defaultBranch: main
- id: create-argocd-app
name: Create ArgoCD App
action: cnoe:kubernetes:apply
input:
manifestPath: argo-cd/app.yaml
namespaced: true
clusterName: local
- 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 }}

View file

@ -0,0 +1,6 @@
site_name: 'Simple Example'
nav:
- Home: index.md
- idpBuilder: idpbuilder.md
plugins:
- techdocs-core

View file

@ -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://cnoe.localtest.me:8443/gitea
title: Repo URL
icon: github
spec:
owner: guest
lifecycle: experimental
type: service

View file

@ -0,0 +1,46 @@
[![Codespell][codespell-badge]][codespell-link]
[![E2E][e2e-badge]][e2e-link]
[![Go Report Card][report-badge]][report-link]
[![Commit Activity][commit-activity-badge]][commit-activity-link]
# IDP Builder
Internal development platform binary launcher.
> **WORK IN PROGRESS**: This tool is in a pre-release stage and is under active development.
## About
Spin up a complete internal developer platform using industry standard technologies like Kubernetes, Argo, and backstage with only Docker required as a dependency.
This can be useful in several ways:
* Create a single binary which can demonstrate an IDP reference implementation.
* Use within CI to perform integration testing.
* Use as a local development environment for platform engineers.
## Getting Started
Checkout our [documentation website](https://cnoe.io/docs/reference-implementation/installations/idpbuilder) for getting started with idpbuilder.
## Community
- If you have questions or concerns about this tool, please feel free to reach out to us on the [CNCF Slack Channel](https://cloud-native.slack.com/archives/C05TN9WFN5S).
- You can also join our community meetings to meet the team and ask any questions. Checkout [this calendar](https://calendar.google.com/calendar/embed?src=064a2adfce866ccb02e61663a09f99147f22f06374e7a8994066bdc81e066986%40group.calendar.google.com&ctz=America%2FLos_Angeles) for more information.
## Contribution
Checkout the [contribution doc](./CONTRIBUTING.md) for contribution guidelines and more information on how to set up your local environment.
<!-- JUST BADGES & LINKS -->
[codespell-badge]: https://github.com/cnoe-io/idpbuilder/actions/workflows/codespell.yaml/badge.svg
[codespell-link]: https://github.com/cnoe-io/idpbuilder/actions/workflows/codespell.yaml
[e2e-badge]: https://github.com/cnoe-io/idpbuilder/actions/workflows/e2e.yaml/badge.svg
[e2e-link]: https://github.com/cnoe-io/idpbuilder/actions/workflows/e2e.yaml
[report-badge]: https://goreportcard.com/badge/github.com/cnoe-io/idpbuilder
[report-link]: https://goreportcard.com/report/github.com/cnoe-io/idpbuilder
[commit-activity-badge]: https://img.shields.io/github/commit-activity/m/cnoe-io/idpbuilder
[commit-activity-link]: https://github.com/cnoe-io/idpbuilder/pulse

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -0,0 +1,11 @@
![cnoe logo](./images/cnoe-logo.png)
# Example Basic Application
Thanks for trying out this demo! In this example, we deployed a simple application.
### idpbuilder
Checkout idpbuilder website: https://cnoe.io/docs/reference-implementation/installations/idpbuilder
Checkout idpbuilder repository: https://github.com/cnoe-io/idpbuilder

View file

@ -0,0 +1,24 @@
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
entity-id: ${{values.name}}
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

View file

@ -0,0 +1,58 @@
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
description: Creates a Simple Kubernetes Deployment on Remote EKS
name: basic
title: Deploy an Application on EKS
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: cnoe.localtest.me:8443/gitea?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: workshop-cluster
projectName: default
# necessary until we generate our own cert
repoUrl: https://cnoe.localtest.me:8443/gitea/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 }}

View file

@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: basic-example-templates
description: A collection of example templates
spec:
targets:
- ./basic-remote/template.yaml
- ./ci-cd/template.yaml

View file

@ -0,0 +1,9 @@
apiVersion: v2
name: workflowTemplates
description: A Helm chart for Workflow Templates
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -0,0 +1,321 @@
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: "{{ .Values.serviceName }}-deploy"
namespace: {{ .Values.namespace }}
spec:
service:
ports:
- port: 12000
targetPort: 12000
webhook:
github:
port: "12000"
endpoint: /
method: POST
---
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: "{{ .Values.serviceName }}-deploy"
namespace: {{ .Values.namespace }}
spec:
template:
serviceAccountName: operate-workflow-sa
dependencies:
- name: test-dep
eventSourceName: "{{ .Values.serviceName }}-deploy"
eventName: github
triggers:
- template:
name: "{{ .Values.serviceName }}-deploy-argo-workflow-trigger"
argoWorkflow:
operation: submit
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: "cd-{{ .Values.serviceName }}-"
namespace: {{ .Values.namespace }}
labels:
env: dev
entity-id: "{{ .Values.serviceName }}"
spec:
serviceAccountName: admin
entrypoint: deploy-workflow
arguments:
parameters:
- name: deployrepo
value: "{{ .Values.giteaBaseUrl }}/{{ .Values.serviceName}}-deploy"
- name: image-tag
value: "0.0.1"
volumeClaimTemplates:
- metadata:
name: workdir
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 256Mi
templates:
- name: deploy-workflow
dag:
tasks:
{{- range $v := .Values.envs }}
- name: {{ $v.name | upper }}
template: deployment-workflow
arguments:
parameters:
- name: deployrepo
value: "{{`{{workflow.parameters.deployrepo}}`}}"
- name: image-tag
value: "{{`{{workflow.parameters.image-tag}}`}}"
- name: envname
value: {{ $v.name }}
{{- if not (eq $v.depends "None") }}
depends: {{ $v.depends | upper}}
{{- end }}
{{- end }}
- name: deployment-workflow
inputs:
parameters:
- name: deployrepo
- name: image-tag
- name: envname
dag:
tasks:
- name: clone-deploy-repo
template: clonerepo
arguments:
parameters:
- name: deployrepo
value: "https://{{`{{inputs.parameters.deployrepo}}`}}"
- name: branch
value: "main"
- name: envname
value: "{{`{{inputs.parameters.envname}}`}}"
- name: update-image-version
template: update-manifest
arguments:
parameters:
- name: deployrepo
value: "{{`{{inputs.parameters.deployrepo}}`}}"
- name: tag-version
value: "{{`{{inputs.parameters.image-tag}}`}}"
- name: envname
value: "{{`{{inputs.parameters.envname}}`}}"
depends: "clone-deploy-repo"
- name: sync-argocd-app
template: sync-argocd-app
when: "'{{`{{tasks.update-image-version.outputs.parameters.argocd_flag}}`}}' == 'execute_cd'"
arguments:
parameters:
- name: appname
value: "{{`{{inputs.parameters.envname}}`}}-{{ .Values.serviceName }}"
- name: envname
value: "{{`{{inputs.parameters.envname}}`}}"
depends: "update-image-version"
- name: wait-for-app-sync
template: wait-for-sync
when: "'{{`{{tasks.update-image-version.outputs.parameters.argocd_flag}}`}}' == 'execute_cd'"
arguments:
parameters:
- name: appname
value: "{{`{{inputs.parameters.envname}}`}}-{{ .Values.serviceName }}"
- name: envname
value: "{{`{{inputs.parameters.envname}}`}}"
depends: "sync-argocd-app"
- name: clonerepo
inputs:
parameters:
- name: deployrepo
- name: branch
- name: envname
container:
volumeMounts:
- mountPath: /workdir
name: workdir
image: alpine/git:v2.45.1
workingDir: /workdir/{{`{{inputs.parameters.envname}}`}}
args:
- clone
- --depth
- "1"
- --branch
- "{{`{{inputs.parameters.branch}}`}}"
- --single-branch
- "{{`{{inputs.parameters.deployrepo}}`}}"
- -c
- http.sslVerify=false
- .
- name: update-manifest
inputs:
parameters:
- name: deployrepo
- name: tag-version
- name: envname
container:
volumeMounts:
- mountPath: /workdir
name: workdir
image: alpine/git:v2.45.1
envFrom:
- secretRef:
name: gitea-credentials
workingDir: /workdir/{{`{{inputs.parameters.envname}}`}}
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
mkdir deploy-repo && cd deploy-repo &&
git clone https://{{`{{inputs.parameters.deployrepo}}`}}.git -c http.sslVerify=false &&
repo={{`{{inputs.parameters.deployrepo}}`}} &&
repo_name=`echo -n $repo | awk -F/ '{print $NF}'` &&
env_name={{`{{inputs.parameters.envname}}`}} &&
cd $repo_name/environments/$env_name &&
image_version={{`{{inputs.parameters.tag-version}}`}} &&
sed -i "s/ tag: .*/ tag: $image_version/g" values.yaml &&
cat values.yaml &&
git status &&
echo -n "execute_cd" > /workdir/cd_argo_flag.txt &&
m_count=$(git status -s | wc -l) &&
if [ $m_count == 1 ]; then
git config --global user.email "noreply@aws.com"
git config --global user.name ARGO
git remote set-url origin https://$GITEA_USERNAME:$GITEA_TOKEN@{{`{{inputs.parameters.deployrepo}}`}}.git
git add values.yaml
git commit -m "Updated image version to $image_version"
git push --set-upstream origin main
else
echo "No updates to values file, skipping git push"
echo -n "skip_cd" > /workdir/cd_argo_flag.txt
fi
outputs:
parameters:
- name: argocd_flag
valueFrom:
path: /workdir/cd_argo_flag.txt
- name: sync-argocd-app
inputs:
parameters:
- name: appname
- name: envname
container:
volumeMounts:
- mountPath: /workdir
name: workdir
workingDir: /workdir/{{`{{inputs.parameters.envname}}`}}
image: argoproj/argocd:v2.6.15
command: ["/bin/sh"]
args:
- -c
- >-
argocd login argocd-server.argocd --plaintext --username $ARGOCD_USERNAME --password $ARGOCD_PASSWORD &&
argocd app sync "{{`{{inputs.parameters.appname}}`}}" --force --prune
env:
- name: ARGOCD_USERNAME
value: admin
- name: ARGOCD_PASSWORD
valueFrom:
secretKeyRef:
name: argocd-credentials
key: ARGOCD_ADMIN_PASSWORD
- name: wait-for-sync
inputs:
parameters:
- name: appname
- name: envname
container:
volumeMounts:
- mountPath: /workdir
name: workdir
workingDir: /workdir/{{`{{inputs.parameters.envname}}`}}
image: argoproj/argocd:v2.6.15
command: ["/bin/bash"]
args:
- -c
- >-
argocd login argocd-server.argocd --plaintext --username $ARGOCD_USERNAME --password $ARGOCD_PASSWORD &&
health_status=$(argocd app get "{{`{{inputs.parameters.appname}}`}}" --refresh | grep "Health Status:" | awk '{print $3}') &&
for i in {1..10};
do
if [ "${health_status}" = "Healthy" ]; then
echo "App is Healthy"
exit 0
fi
echo "App Health: $health_status"
health_status=$(argocd app get "{{`{{inputs.parameters.appname}}`}}" --refresh | grep "Health Status:" | awk '{print $3}')
sleep 10
done &&
echo "App Health: $health_status" &&
exit 1
env:
- name: ARGOCD_USERNAME
value: admin
- name: ARGOCD_PASSWORD
valueFrom:
secretKeyRef:
name: argocd-credentials
key: ARGOCD_ADMIN_PASSWORD
parameters:
- src:
dependencyName: test-dep
dataTemplate: "{{`{{ .Input.body.image_version }}`}}"
dest: spec.arguments.parameters.1.value
---
apiVersion: v1
kind: Service
metadata:
name: "{{ .Values.serviceName }}-deploy-webhook-es-svc"
namespace: {{ .Values.namespace }}
labels:
app: argo-events
app.kubernetes.io/name: argo-events
app.kubernetes.io/instance: my-argo-events
spec:
ports:
- port: 12000
protocol: TCP
targetPort: 12000
selector:
eventsource-name: "{{ .Values.serviceName }}-deploy"
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: "{{ .Values.serviceName }}-deploy-webhook-es-ing"
namespace: {{ .Values.namespace }}
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 512m
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.hostName }}
http:
paths:
- backend:
service:
name: "{{ .Values.serviceName }}-deploy-webhook-es-svc"
port:
number: 12000
path: /argo-events/{{ .Values.serviceName }}-deploy(/|$)(.*)
pathType: ImplementationSpecific
- host: localhost
http:
paths:
- backend:
service:
name: "{{ .Values.serviceName }}-deploy-webhook-es-svc"
port:
number: 12000
path: /argo-events/{{ .Values.serviceName }}-deploy(/|$)(.*)
pathType: ImplementationSpecific

View file

@ -0,0 +1,20 @@
serviceName: ${{values.name}}
namespace: ${{values.name}}
hostName: ${{values.repoHost}}
giteaBaseUrl: ${{values.repoHost}}:443/gitea/giteaAdmin
imageBase: ${{values.repoHost}}:443/gitea/giteaadmin
envs:
{%- if (values.envConfig.length) %}
{%- set depends = 'None' %}
{%- for envDetails in values.envConfig %}
- name: ${{ envDetails.environment }}
depends: ${{ depends }}
cluster: ${{ envDetails.clusterName }}
{%- set depends = envDetails.environment %}
{%- endfor %}
{%- endif %}

View file

@ -0,0 +1,12 @@
serviceName: ${{values.name}}
envs:
{%- if (values.envConfig.length) %}
{%- set depends = 'None' %}
{%- for envDetails in values.envConfig %}
- name: ${{ envDetails.environment }}
depends: ${{ depends }}
cluster: ${{ envDetails.clusterName }}
{%- set depends = envDetails.environment %}
{%- endfor %}
{%- endif %}

View file

@ -0,0 +1,31 @@
# Use the official Golang image as the base image
FROM golang:1.22 as builder
# Set the Current Working Directory inside the container
WORKDIR /app
# Copy go mod files if they exist
COPY go.mod ./
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
# Copy the source code into the container
COPY main.go .
# Build the Go app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp .
# Command to run the executable
CMD ["./myapp"]
# # Start a new stage from scratch
# FROM alpine:latest
# WORKDIR /root/
# # Copy the Pre-built binary file from the previous stage
# COPY --from=builder /app/myapp .
# # Command to run the executable
# CMD ["./myapp"]

View file

@ -0,0 +1,23 @@
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{values.name | dump}}
description: This is CICD Application Deployment on Remote EKS Cluster
annotations:
backstage.io/kubernetes-label-selector: 'entity-id=${{values.name}}'
argocd/app-selector: 'entity-id=${{values.name}}'
argo-workflows.cnoe.io/cluster-name: local
argo-workflows.cnoe.io/namespace: ${{values.name}}
argo-workflows.cnoe.io/label-selector: 'entity-id=${{values.name}}'
links:
- url: https://${{values.repoHost}}:443/gitea/giteaAdmin/${{values.name}}
title: App Repo URL
icon: github
- url: https://${{values.repoHost}}:443/gitea/giteaAdmin/${{values.name}}-deploy
title: Deploy Repo URL
icon: github
spec:
owner: guest
lifecycle: experimental
type: service

View file

@ -0,0 +1,9 @@
apiVersion: v2
name: workflowTemplates
description: A Helm chart for Workflow Templates
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -0,0 +1,253 @@
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: "{{ .Values.serviceName }}"
namespace: {{ .Values.namespace }}
spec:
service:
ports:
- port: 12000
targetPort: 12000
webhook:
github:
port: "12000"
endpoint: /
method: POST
---
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: "{{ .Values.serviceName }}-ci"
namespace: {{ .Values.namespace }}
spec:
template:
serviceAccountName: operate-workflow-sa
dependencies:
- name: test-dep
eventSourceName: "{{ .Values.serviceName }}"
eventName: github
triggers:
- template:
name: "{{ .Values.serviceName }}-argo-workflow-trigger"
argoWorkflow:
operation: submit
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: "ci-{{ .Values.serviceName }}-"
namespace: {{ .Values.namespace }}
labels:
env: dev
entity-id: "{{ .Values.serviceName }}"
spec:
serviceAccountName: admin
entrypoint: ci-build
arguments:
parameters:
- name: apprepo
value: "https://{{ .Values.giteaBaseUrl }}/{{ .Values.serviceName}}"
- name: branch
value: main
- name: deployrepo
value: "{{ .Values.giteaBaseUrl }}/{{ .Values.serviceName}}-deploy"
- name: image
value: "{{ .Values.imageBase }}/{{ .Values.serviceName}}"
- name: tag-version
value: 0.0.1
- name: hostname
value: {{ .Values.hostName }}
volumeClaimTemplates:
- metadata:
name: workdir
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 256Mi
templates:
- name: ci-build
dag:
tasks:
- name: clone
template: clonerepo
arguments:
parameters:
- name: apprepo
value: "{{`{{workflow.parameters.apprepo}}`}}"
- name: branch
value: "{{`{{workflow.parameters.branch}}`}}"
- name: build-and-push-image
template: build-image-kaniko
arguments:
parameters:
- name: image
value: "{{`{{workflow.parameters.image}}`}}"
- name: tag-version
value: "{{`{{workflow.parameters.tag-version}}`}}"
depends: "clone"
- name: invoke-cd-workflow
template: invoke-cd-workflow
arguments:
parameters:
- name: hostname
value: "{{`{{workflow.parameters.hostname}}`}}"
- name: cdpath
value: "{{ .Values.serviceName}}-deploy"
- name: imageversion
value: "{{`{{workflow.parameters.tag-version}}`}}"
depends: "build-and-push-image"
- name: clonerepo
inputs:
parameters:
- name: apprepo
- name: branch
container:
volumeMounts:
- mountPath: /workdir
name: workdir
image: alpine/git:v2.45.1
workingDir: /workdir
args:
- clone
- --depth
- "1"
- --branch
- "{{`{{inputs.parameters.branch}}`}}"
- --single-branch
- "{{`{{inputs.parameters.apprepo}}`}}"
- -c
- http.sslVerify=false
- .
- name: go-build
container:
image: golang:1.22
volumeMounts:
- mountPath: /workdir
name: workdir
workingDir: /workdir
command:
- go
args:
- build
- -v
- -o
- myapp
- name: build-image-kaniko
inputs:
parameters:
- name: image
- name: tag-version
volumes:
- name: docker-config
configMap:
name: docker-config
container:
image: gcr.io/kaniko-project/executor:latest
volumeMounts:
- name: workdir
mountPath: /workdir
- name: docker-config
mountPath: /kaniko/.docker
workingDir: /workdir
resources:
limits:
cpu: 1
memory: 2Gi
env:
- name: DOCKER_CONFIG
value: /kaniko/.docker
args:
- --dockerfile=Dockerfile
- --context=.
- --destination={{`{{inputs.parameters.image}}`}}:{{`{{inputs.parameters.tag-version}}`}}
- --skip-tls-verify
- --cache=false
- name: invoke-cd-workflow
inputs:
parameters:
- name: hostname
- name: cdpath
- name: imageversion
container:
image: alpine:3.20
envFrom:
- secretRef:
name: gitea-credentials
volumeMounts:
- mountPath: /workdir
name: workdir
workingDir: "/workdir"
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
apk add curl jq &&
cd_workflow_url=https://{{`{{inputs.parameters.hostname}}`}}:443/argo-events/{{`{{inputs.parameters.cdpath}}`}} &&
image_version={{`{{inputs.parameters.imageversion}}`}} &&
curl -k -s -d '{"image_version":"'${image_version}'"}' -H "Content-Type: application/json" -X POST ${cd_workflow_url}
parameters:
- src:
dependencyName: test-dep
dataTemplate: "{{`{{ .Input.body.head_commit.id }}`}}"
dest: spec.arguments.parameters.4.value
---
apiVersion: v1
kind: Service
metadata:
name: "{{ .Values.serviceName }}-webhook-es-svc"
namespace: {{ .Values.namespace }}
labels:
app: argo-events
app.kubernetes.io/name: argo-events
app.kubernetes.io/instance: my-argo-events
spec:
ports:
- port: 12000
protocol: TCP
targetPort: 12000
selector:
eventsource-name: "{{ .Values.serviceName }}"
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: "{{ .Values.serviceName }}-webhook-es-ing"
namespace: {{ .Values.namespace }}
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 512m
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.hostName }}
http:
paths:
- backend:
service:
name: "{{ .Values.serviceName }}-webhook-es-svc"
port:
number: 12000
path: /argo-events/{{ .Values.serviceName }}(/|$)(.*)
pathType: ImplementationSpecific
- host: localhost
http:
paths:
- backend:
service:
name: "{{ .Values.serviceName }}-webhook-es-svc"
port:
number: 12000
path: /argo-events/{{ .Values.serviceName }}(/|$)(.*)
pathType: ImplementationSpecific

View file

@ -0,0 +1,39 @@
# apiVersion: external-secrets.io/v1beta1
# kind: ExternalSecret
# metadata:
# name: gitea-credentials
# namespace: {{ .Values.namespace }}
# spec:
# secretStoreRef:
# name: gitea
# kind: ClusterSecretStore
# refreshInterval: "0"
# target:
# name: gitea-credentials
# data:
# - secretKey: GITEA_USERNAME
# remoteRef:
# key: gitea-credential
# property: username
# - secretKey: GITEA_PASSWORD
# remoteRef:
# key: gitea-credential
# property: password
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: argocd-credentials
namespace: {{ .Values.namespace }}
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

View file

@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: admin-binding
namespace: {{ .Values.namespace }}
subjects:
- kind: ServiceAccount
name: admin
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin
namespace: {{ .Values.namespace }}

View file

@ -0,0 +1,9 @@
serviceName: ${{values.name}}
namespace: ${{values.name}}
hostName: ${{values.repoHost}}
giteaBaseUrl: ${{values.repoHost}}:443/gitea/giteaAdmin
imageBase: ${{values.repoHost}}:443/gitea/giteaadmin

View file

@ -0,0 +1,3 @@
module goapi
go 1.22

View file

@ -0,0 +1,36 @@
package main
import (
"net/http"
"encoding/json"
"log"
"os"
)
type Response struct {
Message string `json:"message"`
}
// ping handler
func pingHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
hostname, err := os.Hostname()
if err != nil {
log.Println("Error : %v", err)
return
}
response := Response{Message: "pong from server : "+hostname}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main(){
http.HandleFunc("/ping", pingHandler)
log.Println("Server started on 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

View file

@ -0,0 +1,565 @@
apiVersion: v1
kind: Namespace
metadata:
name: ${{values.name}}
labels:
name: ${{values.name}}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${{values.name}}
namespace: ${{values.name}}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: operate-workflow-sa
namespace: ${{values.name}}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: operate-workflow-sa-binding
namespace: ${{values.name}}
subjects:
- kind: ServiceAccount
name: operate-workflow-sa
namespace: ${{values.name}}
roleRef:
kind: ClusterRole
name: argo-server-cluster-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ${{values.name}}-cluster-binding
namespace: ${{values.name}}
subjects:
- kind: ServiceAccount
name: ${{values.name}}
namespace: ${{values.name}}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gitea-credentials
namespace: ${{values.name}}
spec:
secretStoreRef:
name: gitea
kind: ClusterSecretStore
refreshInterval: "0"
target:
name: gitea-credentials
data:
- secretKey: GITEA_USERNAME
remoteRef:
key: gitea-credential
property: username
- secretKey: GITEA_PASSWORD
remoteRef:
key: gitea-credential
property: password
- secretKey: GITEA_TOKEN
remoteRef:
key: gitea-token
property: token
---
apiVersion: v1
kind: Secret
metadata:
name: k8s-config
namespace: ${{values.name}}
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: argoproj.io/v1alpha1
kind: EventBus
metadata:
name: default
namespace: ${{values.name}}
spec:
nats:
native:
auth: token
replicas: 3
---
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: "${{values.name}}-provisioner"
namespace: ${{values.name}}
spec:
service:
ports:
- port: 12000
targetPort: 12000
webhook:
github:
port: "12000"
endpoint: /
method: POST
---
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: "${{values.name}}-provisioner"
namespace: ${{values.name}}
spec:
template:
serviceAccountName: operate-workflow-sa
dependencies:
- name: test-dep
eventSourceName: "${{values.name}}-provisioner"
eventName: github
filters:
data:
- path: "[body.commits.#.modified.#()#]"
type: string
value:
- 'provisioner.yaml'
triggers:
- template:
name: "${{values.name}}-provisioner-argo-workflow-trigger"
argoWorkflow:
operation: submit
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: "provisioner-${{values.name}}-"
namespace: ${{values.name}}
labels:
env: dev
entity-id: "${{values.name}}"
spec:
serviceAccountName: ${{values.name}}
entrypoint: instantiate-provisioner
volumes:
- name: k8s-config
secret:
secretName: k8s-config
arguments:
parameters:
- name: service-name
value: ${{values.name}}
- name: repobaseurl
value: ${{values.repoHost}}:443/gitea
- name: argoeventsBaseUrl
value: ${{values.repoHost}}:443/argo-events
- name: repoHostName
value: ${{values.repoHost}}
volumeClaimTemplates:
- metadata:
name: workdir
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 256Mi
templates:
- name: instantiate-provisioner
dag:
tasks:
- name: clone-shared-helm-repo
template: clone-repo
arguments:
parameters:
- name: repo-name
value: "https://{{workflow.parameters.repobaseurl}}/giteaAdmin/idpbuilder-localdev-shared-helm-charts-entities"
- name: target-dir
value: shared-repo
- name: service-name
value: "{{workflow.parameters.service-name}}"
- name: cicd-argocd-apps
template: cicd-argocd-apps
arguments:
parameters:
- name: app-repo
value: "https://{{workflow.parameters.repobaseurl}}/giteaAdmin/{{workflow.parameters.service-name}}"
- name: deploy-repo
value: "https://{{workflow.parameters.repobaseurl}}/giteaAdmin/{{workflow.parameters.service-name}}-deploy"
- name: service-name
value: "{{workflow.parameters.service-name}}"
depends: "clone-shared-helm-repo"
- name: create-dockerconfig
template: create-dockerconfig
arguments:
parameters:
- name: gitea-url
value: "https://{{workflow.parameters.repobaseurl}}"
- name: service-name
value: "{{workflow.parameters.service-name}}"
depends: "cicd-argocd-apps"
- name: clone-deploy-repo
template: clone-repo
arguments:
parameters:
- name: repo-name
value: "https://{{workflow.parameters.repobaseurl}}/giteaAdmin/{{workflow.parameters.service-name}}-deploy"
- name: target-dir
value: "{{workflow.parameters.service-name}}-deploy"
- name: service-name
value: "{{workflow.parameters.service-name}}"
depends: "create-dockerconfig"
- name: helm-folders-create
template: create-helm-env-directories
arguments:
parameters:
- name: deploy-repo
value: "https://{{workflow.parameters.repobaseurl}}/giteaAdmin/{{workflow.parameters.service-name}}-deploy"
- name: service-name
value: "{{workflow.parameters.service-name}}"
- name: host-name
value: "{{workflow.parameters.repoHostName}}"
depends: "clone-deploy-repo"
- name: push-to-deploy-repo
template: push-to-repo
when: "'{{tasks.helm-folders-create.outputs.parameters.commit-flag}}' == 'commit'"
arguments:
parameters:
- name: deploy-repo
value: "{{workflow.parameters.repobaseurl}}/giteaAdmin/{{workflow.parameters.service-name}}-deploy"
- name: service-name
value: "{{workflow.parameters.service-name}}"
depends: "helm-folders-create"
- name: create-argocd-apps-for-services
template: argo-cd-apps-for-services
arguments:
parameters:
- name: deploy-repo
value: https://"{{workflow.parameters.repobaseurl}}/giteaAdmin/{{workflow.parameters.service-name}}-deploy"
- name: service-name
value: "{{workflow.parameters.service-name}}"
depends: "push-to-deploy-repo || push-to-deploy-repo.Skipped"
- name: add-webhook-to-apprepo
template: gitea-webhook-creation
arguments:
parameters:
- name: gitea-url
value: "https://{{workflow.parameters.repobaseurl}}"
- name: repo-name
value: "{{workflow.parameters.service-name}}"
- name: webhook-url
value: "https://{{workflow.parameters.argoeventsBaseUrl}}/{{workflow.parameters.service-name}}"
depends: "create-argocd-apps-for-services"
- name: add-webhook-to-provisioner
template: gitea-webhook-creation
arguments:
parameters:
- name: gitea-url
value: "https://{{workflow.parameters.repobaseurl}}"
- name: repo-name
value: "{{workflow.parameters.service-name}}-deploy"
- name: webhook-url
value: "https://{{workflow.parameters.argoeventsBaseUrl}}/{{workflow.parameters.service-name}}-provisioner"
depends: "create-argocd-apps-for-services"
- name: clone-repo
inputs:
parameters:
- name: repo-name
- name: target-dir
container:
volumeMounts:
- mountPath: /workdir
name: workdir
image: alpine/git:v2.45.1
workingDir: "/workdir/{{inputs.parameters.target-dir}}"
args:
- clone
- --depth
- "1"
- --branch
- "main"
- --single-branch
- "{{inputs.parameters.repo-name}}.git"
- -c
- http.sslVerify=false
- .
- name: cicd-argocd-apps
inputs:
parameters:
- name: app-repo
- name: deploy-repo
- name: service-name
container:
image: alpine/k8s:1.31.0
volumeMounts:
- mountPath: /workdir
name: workdir
- mountPath: "/.kube/"
name: k8s-config
workingDir: /workdir
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
ls -lrt &&
cd shared-repo/argocd-apps &&
app_repo={{inputs.parameters.app-repo}} &&
deploy_repo={{inputs.parameters.deploy-repo}} &&
service_name={{inputs.parameters.service-name}} &&
kubectl get ns &&
echo "serviceName: $service_name" > test.yaml &&
echo "envList: " >> test.yaml &&
echo " ci:" >> test.yaml &&
echo " repo: $app_repo" >> test.yaml &&
echo " path: ci " >> test.yaml &&
echo " namespace: $service_name " >> test.yaml &&
echo " cd:" >> test.yaml &&
echo " repo: $deploy_repo" >> test.yaml &&
echo " path: cd " >> test.yaml &&
echo " namespace: $service_name " >> test.yaml &&
helm template . -f test.yaml &&
helm template . -f test.yaml | kubectl apply -f -
- name: create-dockerconfig
inputs:
parameters:
- name: gitea-url
- name: service-name
container:
image: alpine/k8s:1.31.0
envFrom:
- secretRef:
name: gitea-credentials
volumeMounts:
- mountPath: /workdir
name: workdir
- mountPath: "/.kube/"
name: k8s-config
workingDir: /workdir
command: ["/bin/sh"]
args:
- -c
- >-
apk add yq &&
echo "Creating Configmap for docker config" &&
gitea_auth=$(echo -n $GITEA_USERNAME:$GITEA_PASSWORD | base64) &&
echo "apiVersion: v1
kind: ConfigMap
metadata:
name: docker-config
namespace: {{inputs.parameters.service-name}}
data:
config.json: |
{
\"auths\": {
\"{{inputs.parameters.gitea-url}}\": {
\"auth\": \"$gitea_auth\"
}
}
}
" > docker_config.yaml &&
kubectl apply -f docker_config.yaml
- name: create-helm-env-directories
inputs:
parameters:
- name: deploy-repo
- name: service-name
- name: host-name
container:
image: alpine:3.20
volumeMounts:
- mountPath: /workdir
name: workdir
workingDir: "/workdir/{{ inputs.parameters.service-name }}-deploy"
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
apk add yq &&
ls -lrt && pwd &&
sed -i "s/appName: .*/appName: {{ inputs.parameters.service-name }}/g" /workdir/shared-repo/app-deploy/values.yaml &&
sed -i "s/REGISTRY_HOST/{{ inputs.parameters.host-name }}/g" /workdir/shared-repo/app-deploy/values.yaml &&
sed -i "s/INGRESS_HOST/{{ inputs.parameters.host-name }}/g" /workdir/shared-repo/app-deploy/values.yaml &&
echo "Check all environment specific helm templates exists or not" &&
echo -n "flag" > /workdir/commit_flag.txt &&
if [ ! -d environments ]; then
mkdir environments
fi &&
cd environments/ &&
image_tag="0.0.0" &&
for env in $(yq -r '.envs[].name' ../provisioner.yaml); do
echo "*** Processing ${env} Env ***"
if [ -d $env ]; then
echo "$env env helm chart exists, skipping helm directory creation"
image_tag=$(yq -r '.image.tag' $env/values.yaml)
else
echo "Creating $env env helm skelton structure"
echo -n "commit" > /workdir/commit_flag.txt
mkdir $env
# cp -pR ../helm ${env}/
cp -pR /workdir/shared-repo/app-deploy/* ${env}/
ls -lrt ${env}/
sed -i "s/envName: .*/envName: $env/" ${env}/values.yaml
sed -i "s/tag: .*/tag: $image_tag/" ${env}/values.yaml
fi
done &&
if grep -q "commit" "/workdir/commit_flag.txt"; then
echo "Adding new environment to CD helm chart"
yq -i '.envs = (load("../provisioner.yaml") | .envs)' ../cd/values.yaml
fi
outputs:
parameters:
- name: commit-flag
valueFrom:
path: /workdir/commit_flag.txt
- name: push-to-repo
inputs:
parameters:
- name: deploy-repo
- name: service-name
container:
volumeMounts:
- mountPath: /workdir
name: workdir
image: alpine/git:v2.45.1
envFrom:
- secretRef:
name: gitea-credentials
workingDir: /workdir/{{ inputs.parameters.service-name }}-deploy
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
ls -lrt && pwd &&
git status &&
git config --global user.email "noreply@aws.com" &&
git config --global user.name ARGO &&
git remote set-url origin https://$GITEA_USERNAME:$GITEA_TOKEN@{{inputs.parameters.deploy-repo}}.git &&
git add . &&
git commit -m "Adding helm charts" &&
git push --set-upstream origin main
- name: argo-cd-apps-for-services
inputs:
parameters:
- name: deploy-repo
- name: service-name
container:
image: alpine/k8s:1.31.0
volumeMounts:
- mountPath: /workdir
name: workdir
- mountPath: "/.kube/"
name: k8s-config
workingDir: /workdir
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
ls -lrt &&
apk add yq &&
cd shared-repo/argocd-apps &&
deploy_repo={{inputs.parameters.deploy-repo}} &&
service_name={{inputs.parameters.service-name}} &&
echo "serviceName: $service_name" > cd-test.yaml &&
echo "k8sServer: workshop" >> cd-test.yaml &&
echo "envList: " >> cd-test.yaml &&
for env in $(yq -r '.envs[].name' /workdir/$service_name-deploy/provisioner.yaml); do
echo "*** Creating ${env} Argo CD App ***"
echo " $env:" >> cd-test.yaml
echo " repo: $deploy_repo" >> cd-test.yaml
echo " path: environments/$env " >> cd-test.yaml
echo " namespace: $service_name-$env " >> cd-test.yaml
done &&
helm template . -f cd-test.yaml &&
helm template . -f cd-test.yaml | kubectl apply -f -
- name: gitea-webhook-creation
inputs:
parameters:
- name: gitea-url
- name: repo-name
- name: webhook-url
container:
image: alpine:3.20
envFrom:
- secretRef:
name: gitea-credentials
volumeMounts:
- mountPath: /workdir
name: workdir
workingDir: "/workdir"
command: ["/bin/sh"]
args:
- -c
- >-
set -x &&
apk add curl jq &&
hooks_api_url={{inputs.parameters.gitea-url}}/api/v1/repos/giteaAdmin/{{inputs.parameters.repo-name}}/hooks &&
webhook_url={{inputs.parameters.webhook-url}} &&
webhook_details=`curl -k -X 'GET' "${hooks_api_url}" -H "accept: application/json" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" | jq '.[].config.url'` &&
if [ -z "$webhook_details" ]; then
echo "Webhook is not found, creating webhook with $webhook_url"
curl -k -X 'POST' "${hooks_api_url}" -H "accept: application/json" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d '{"branch_filter": "main","type": "gitea", "events":["push"], "config": {"url": "'${webhook_url}'", "content_type": "json"}, "active": true}'
else
echo "Webhook is already exists"
fi
---
apiVersion: v1
kind: Service
metadata:
name: "${{values.name}}-provisioner-webhook-es-svc"
namespace: ${{values.name}}
labels:
app: argo-events
app.kubernetes.io/name: argo-events
app.kubernetes.io/instance: my-argo-events
spec:
ports:
- port: 12000
protocol: TCP
targetPort: 12000
selector:
eventsource-name: "${{values.name}}-provisioner"
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: "${{values.name}}-provisioner-webhook-es-ing"
namespace: ${{values.name}}
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 512m
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: ${{values.repoHost}}
http:
paths:
- backend:
service:
name: "${{values.name}}-provisioner-webhook-es-svc"
port:
number: 12000
path: /argo-events/${{values.name}}-provisioner(/|$)(.*)
pathType: ImplementationSpecific
- host: localhost
http:
paths:
- backend:
service:
name: "${{values.name}}-provisioner-webhook-es-svc"
port:
number: 12000
path: /argo-events/${{values.name}}-provisioner(/|$)(.*)
pathType: ImplementationSpecific

View file

@ -0,0 +1,220 @@
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
description: Creates CI and CD Workflows and Deploy Apps to Remote EKS
name: eks-ci-cd
title: Deploy an Application with CICD to EKS
tags:
- go
spec:
owner: user:guest
type: service
parameters:
- title: Service Info
required:
- serviceName
properties:
serviceName:
title: Service Name
type: string
default: riv2024demo
pattern: ^[a-z\d]{1,64}$
description: Provide service name
costCenter:
type: string
title: Cost Center
default: C0001
description: Provide CostCenter ID for finance management
language:
title: Language
type: string
default: go
enum:
- go
readOnlyGroup:
type: string
title: Access Group Name (ReadOnly)
default: cnoe-ro-ad@cnoe.io
description: AD Group Name to provide read only access
readWriteGroup:
type: string
title: Access Group Name (ReadWrite)
default: cnoe-rw-ad@cnoe.io
description: AD Group Name to provide read & write only access
- title: Source Control Configuration
required:
- repoHost
properties:
repoHost:
title: Repository Host
type: string
default: cnoe.localtest.me
enum:
- cnoe.localtest.me
owner:
type: string
title: Owner
default: cnoe.io
description: Repo Owner Name
isNewAppRepo:
title: Would you like us to create the application repository for you?
type: string
default: Yes
enum:
- Yes
isNewDeployRepo:
title: Would you like us to create the deploy repository for you?
type: string
default: Yes
enum:
- Yes
- title: Artifact and Credentials Management
required:
- artifactStore
properties:
artifactStore:
title: Select Artifact Store
type: string
default: gitea
enum:
- gitea
artifactStoreCredentialsPath:
title: Artifact Store Credentials Path
type: string
default: /team1/artifact-store
sourceControlCredentialsPath:
title: Source Control Credentials Path
default: /team1/source-control
type: string
notificationChannelCredentialsPath:
title: Notification Channel Credentials Path
type: string
default: /team1/notification-channel
- title: Environment Configuration
properties:
envConfig:
title: Environments
type: array
minItems: 1
ui:options:
addable: true
orderable: true
removable: true
items:
title: ''
type: object
required:
- environment
- clusterName
properties:
environment:
title: Environment Name
type: string
default: dev
description: Allowed values ci, dev, qa, stage, production, prod, preprod
pattern: '^(|dev|test|ci|qa|stage|production|prod|preprod)[0-9]*$'
clusterName:
title: Cluster Name
type: string
default: workshop
enum:
- 'workshop'
steps:
- id: deploy-template
name: Generating application deployment template
action: fetch:template
input:
url: ./deploy
targetPath: ./app-deploy
values:
name: ${{parameters.serviceName}}
repoHost: ${{parameters.repoHost}}
envConfig: ${{parameters.envConfig}}
- id: app-template
name: Generating application code template
action: fetch:template
input:
url: ./go-api
targetPath: ./app
values:
name: ${{parameters.serviceName}}
repoHost: ${{parameters.repoHost}}
- id: provisioner-template
name: Generating application code template
action: fetch:template
input:
url: ./provisioner
targetPath: ./provisioner
values:
name: ${{parameters.serviceName}}
repoHost: ${{parameters.repoHost}}
- id: publish-deploy-repo
name: Publishing to a gitea deploy git repository
action: publish:gitea
input:
description: This is an example app
sourcePath: ./app-deploy
repoUrl: ${{parameters.repoHost}}:443/gitea?repo=${{parameters.serviceName}}-deploy
defaultBranch: main
- id: publish-app-repo
name: Publishing to a gitea application git repository
action: publish:gitea
input:
description: This is an example app
sourcePath: ./app
repoUrl: ${{parameters.repoHost}}:443/gitea?repo=${{parameters.serviceName}}
defaultBranch: main
- id: create-provisioner-workflow
name: Create Provisioner Workflow
action: cnoe:kubernetes:apply
input:
namespaced: true
manifestPath: ./provisioner/workflow.yaml
clusterName: local
values:
name: ${{parameters.serviceName}}
repoHost: ${{parameters.repoHost}}
- id: wait-for-provisioner
name: Wait for provisioner workflow to be available
action: debug:wait
input:
seconds: 70
- id: run-provisioner-workflow
name: Invoke Provisioner Workflow
action: http:backstage:request
input:
path: '/proxy/provisioner/argo-events/${{parameters.serviceName}}-provisioner'
method: POST
headers:
content-type: 'application/json'
body:
{
"commits": [
{
"modified": ["provisioner.yaml"]
}
]
}
- id: register
name: Register
action: catalog:register
input:
repoContentsUrl: ${{ steps['publish-app-repo'].output.repoContentsUrl }}
catalogInfoPath: 'catalog-info.yaml'
output:
links:
- title: Open in catalog
icon: catalog
entityRef: ${{ steps['register'].output.entityRef }}

21
cicd/backstage.yaml Normal file
View file

@ -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

View file

@ -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

View file

@ -0,0 +1,465 @@
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
- applications
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: https://cnoe.localtest.me:8443
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: https://cnoe.localtest.me:8443
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: https://cnoe.localtest.me:8443
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://cnoe.localtest.me:8443/gitea
host: cnoe.localtest.me:8443
username: ${GITEA_USERNAME}
password: ${GITEA_PASSWORD}
- baseUrl: https://cnoe.localtest.me/gitea
host: 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}
proxy:
### Example for how to add a proxy endpoint for the frontend.
### A typical reason to do this is to handle HTTPS and CORS for internal services.
endpoints:
'/provisioner': https://cnoe.localtest.me/
# 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: '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}
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://cnoe.localtest.me/gitea/giteaAdmin/idpbuilder-localdev-backstage-templates-entities/raw/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://cnoe.localtest.me:8443/argocd
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
annotations:
argocd.argoproj.io/sync-wave: "20"
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: ghcr.io/cnoe-io/backstage-app:135c0cb26f3e004a27a11edb6a4779035aff9805
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
annotations:
argocd.argoproj.io/sync-wave: "10"
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
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: "500Mi"
---
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://cnoe.localtest.me:8443/backstage
POSTGRES_HOST: postgresql.backstage.svc.cluster.local
POSTGRES_PORT: '5432'
POSTGRES_DB: backstage
POSTGRES_USER: backstage
POSTGRES_PASSWORD: "{{.POSTGRES_PASSWORD}}"
ARGO_WORKFLOWS_URL: https://cnoe.localtest.me:8443/argo-workflows
KEYCLOAK_NAME_METADATA: https://cnoe.localtest.me:8443/keycloak/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-credential
property: username
- secretKey: GITEA_PASSWORD
remoteRef:
key: gitea-credential
property: password
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backstage
namespace: backstage
spec:
ingressClassName: "nginx"
rules:
- host: localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backstage
port:
name: http
- host: cnoe.localtest.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backstage
port:
name: http

View file

@ -0,0 +1,64 @@
apiVersion: batch/v1
kind: Job
metadata:
name: gitea-token
namespace: backstage
annotations:
argocd.argoproj.io/hook: PostSync
spec:
template:
metadata:
generateName: gitea-token
spec:
serviceAccountName: backstage
restartPolicy: Never
volumes:
- name: k8s-config
secret:
secretName: k8s-config
containers:
- name: kubectl
image: alpine/k8s:1.31.0
envFrom:
- secretRef:
name: gitea-credentials
volumeMounts:
- name: k8s-config
readOnly: true
mountPath: "/.kube/"
command: ["/bin/bash", "-c"]
args:
- |
#! /bin/bash
apt -qq update && apt -qq install curl jq -y
GITEA_URL=http://my-gitea-http.gitea.svc.cluster.local:3000
GITEA_TOKENS_URL="${GITEA_URL}/api/v1/users/giteaAdmin/tokens"
TOKENS_COUNT=$(curl -k -s -X 'GET' "${GITEA_TOKENS_URL}" -H "accept: application/json" -H "Content-Type: application/json" -u $GITEA_USERNAME:$GITEA_PASSWORD | jq '. | length')
curl -sS -LO "https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl"
chmod +x kubectl
if [ ${TOKENS_COUNT} -eq 0 ]; then
echo "No tokens found, creating token and k8s secret"
NEW_TOKEN=$(curl -k -X POST "${GITEA_TOKENS_URL}" \
-H "Content-Type: application/json" \
-d '{"name": "token1", "scopes": ["write:repository", "read:issue", "read:organization", "read:misc"]}' \
-u $GITEA_USERNAME:$GITEA_PASSWORD | jq -r '.sha1')
TOKEN=$(echo -n $NEW_TOKEN | base64)
echo \
"apiVersion: v1
kind: Secret
metadata:
name: gitea-token
namespace: gitea
type: Opaque
data:
token: ${TOKEN}
" > /tmp/gitea_secret.yaml
./kubectl apply -f /tmp/gitea_secret.yaml
else
echo "$TOKENS_COUNT tokens found, skipping secret creation"
fi

View file

@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: backstage-cluster-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: backstage
namespace: backstage

73
cicd/codespaces.md Normal file
View file

@ -0,0 +1,73 @@
## Running idpbuilder in Codespaces in Browser
**_NOTE:_**: __Steps described below applies to running this implementation in Codespaces in **web browsers** (e.g. Firefox and Chrome).
If you are using Codespaces with GitHub CLI, steps described here do not apply to you.__
Let's create an instance of Codespaces.
![img.png](images/codespaces-create.png)
It may take a few minutes for it to be ready. Once it's ready, you can either get the latest release of idpbuilder or build from the main branch.
Build the idpbuilder binary.
- Get the latest release:
```bash
version=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/cnoe-io/idpbuilder/releases/latest)
version=${version##*/}
wget https://github.com/cnoe-io/idpbuilder/releases/download/${version}/idpbuilder-linux-amd64.tar.gz
tar xzf idpbuilder-linux-amd64.tar.gz
sudo mv ./idpbuilder /usr/local/bin/
```
- Alternatively, build from the main branch
```bash
make build
sudo mv ./idpbuilder /usr/local/bin/
```
Codespaces assigns random hostname to your specific instance. You need to make sure they are reflected correctly.
Instance host name is available as an environment variable (`CODESPACE_NAME`). Let's use it to setup our host names.
Run the following commands to update host name and ports. Port is set to 443 because this is the port used by the browser to access your instance.
Clone the [stacks](https://github.com/cnoe-io/stacks) repo.
```bash
cd ref-implementation
./replace.sh ${CODESPACE_NAME}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN} 443
cd -
```
Now you are ready to run idpbuilder with reference implementation.
```bash
idpbuilder create --protocol http \
--host ${CODESPACE_NAME}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN} \
--port 8080 --use-path-routing --package ref-implementation
```
Once idpbuilder finishes bootstrapping, you should have port 8080 forward in the port tab within Codespaces.
![](images/port.png)
You may get a 404 page after clicking the port 8080 forwarded address. This is completely normal because Backstage may not be ready yet.
Give it a few more minutes and it should redirect you to a Backstage page.
### Accessing UIs
If you'd like to track progress of deployment, go to `/argocd` path and login with your ArgoCD credentials.
For example run this command to get the URL for Argo CD:
```bash
echo https://${CODESPACE_NAME}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}/argocd
```
From here on, you can follow the instructions in the [README](./README.md) file. The only difference is that the URL to access UIs is given by:
```echo
echo https://${CODESPACE_NAME}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}
```
For example, if you need to access Argo Workflows UI, instead of going to `https://cnoe.localtest.me:8443/argo`,
you go to `https://${CODESPACE_NAME}-8080.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}/argo`

View file

@ -0,0 +1,23 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-secrets
namespace: argocd
labels:
env: dev
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
destination:
namespace: external-secrets
server: "https://kubernetes.default.svc"
source:
repoURL: cnoe://external-secrets/manifests
targetRevision: HEAD
path: "."
project: default
syncPolicy:
automated:
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,12 @@
#!/bin/bash
set -e
INSTALL_YAML="manifests/install.yaml"
CHART_VERSION="0.9.11"
echo "# EXTERNAL SECRETS INSTALL RESOURCES" >${INSTALL_YAML}
echo "# This file is auto-generated with 'ref-impelmentation/external-secrets/generate-manifests.sh'" >>${INSTALL_YAML}
helm repo add external-secrets --force-update https://charts.external-secrets.io
helm repo update
helm template --namespace external-secrets external-secrets external-secrets/external-secrets -f values.yaml --version ${CHART_VERSION} >>${INSTALL_YAML}

File diff suppressed because it is too large Load diff

View file

45
cicd/get-credentials.sh Executable file
View file

@ -0,0 +1,45 @@
#!/bin/bash
# Execute the idpbuilder command and store the output in a variable
output=$(idpbuilder get secrets)
# Extract the ArgoCD password using grep and awk
argocd_password=$(echo "$output" | grep -A 3 "argocd-initial-admin-secret" | grep "password" | awk '{print $3}')
gitea_password=$(echo "$output" | grep -A 3 "gitea-credential" | grep "password" | awk '{print $3}')
keycloak_password=$(echo "$output" | grep -A 9 "keycloak-config" | grep "USER_PASSWORD" | awk '{print $3}')
# Create the credentials.txt file with the required ArgoCD details
cat <<EOF > ~/environment/credentials.txt
ArgoCD
URL : https://${IDE_DOMAIN}/argocd
Username: admin
Password: ${argocd_password}
ArgoWorkflows
URL: https://${IDE_DOMAIN}/argo-workflows
Username: user1
Password: ${keycloak_password}
BackStage
URL: https://${IDE_DOMAIN}/
Username: user1
Password: ${keycloak_password}
Gitea
URL: https://${IDE_DOMAIN}/gitea
Username: giteaAdmin
Password: ${gitea_password}
EOF
echo "credentials.txt file created with ArgoCD details."
# Hack : Removing internal svc resolution for backstage
kubectl patch configmap coredns-conf-default --patch '{"data":{"default.conf":""}}' -n kube-system
# Setting up gitconfig
git config --global user.name "CNOE"
git config --global user.email "cnoe@io"
GITEA_PAT=$(kubectl get secret gitea-token -n gitea -o jsonpath='{.data.token}' | base64 -d)
GIT_CREDS="$HOME/.git-credentials"
cat > $GIT_CREDS << EOT
https://giteaAdmin:${GITEA_PAT}@${IDE_DOMAIN}/gitea
EOT
git config --global credential.helper 'store'
echo "Git config update completed."

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
cicd/images/demo-entity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
cicd/images/port.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -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": {}
}

21
cicd/keycloak.yaml Normal file
View file

@ -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

View file

@ -0,0 +1,30 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak-ingress-localhost
namespace: keycloak
annotations:
argocd.argoproj.io/sync-wave: "100"
spec:
ingressClassName: "nginx"
rules:
- host: localhost
http:
paths:
- path: /keycloak
pathType: ImplementationSpecific
backend:
service:
name: keycloak
port:
name: http
- host: cnoe.localtest.me
http:
paths:
- path: /keycloak
pathType: ImplementationSpecific
backend:
service:
name: keycloak
port:
name: http

View file

@ -0,0 +1,164 @@
---
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
annotations:
argocd.argoproj.io/sync-wave: "10"
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- args:
- start-dev
env:
- name: KEYCLOAK_ADMIN
value: cnoe-admin
- name: KEYCLOAK_LOGLEVEL
value: ALL
- name: QUARKUS_TRANSACTION_MANAGER_ENABLE_RECOVERY
value: 'true'
envFrom:
- secretRef:
name: keycloak-config
image: quay.io/keycloak/keycloak:22.0.3
name: keycloak
ports:
- containerPort: 8080
name: http
readinessProbe:
httpGet:
path: /keycloak/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-url=jdbc:postgresql://postgresql.keycloak.svc.cluster.local:5432/postgres
# The proxy address forwarding mode if the server is behind a reverse proxy.
proxy=edge
# hostname configuration
hostname=cnoe.localtest.me
hostname-port=8443
http-relative-path=keycloak
# the admin url requires its own configuration to reflect correct url
hostname-admin=cnoe.localtest.me:8443
hostname-debug=true
# 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
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: "500Mi"

View file

@ -0,0 +1,366 @@
# 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://cnoe.localtest.me:8443/argo-workflows/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://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 -ex -o pipefail
apt -qq update && apt -qq install curl jq -y
ADMIN_PASSWORD=$(cat /var/secrets/KEYCLOAK_ADMIN_PASSWORD)
USER1_PASSWORD=$(cat /var/secrets/USER_PASSWORD)
KEYCLOAK_URL=http://keycloak.keycloak.svc.cluster.local:8080/keycloak
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" \
${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token | jq -e -r '.access_token')
set +e
curl --fail-with-body -H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/admin/realms
curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X POST --data @/var/config/client-scope-groups-payload.json \
${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/admin/realms/cnoe/users
curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X POST --data @/var/config/user-user2.json \
${KEYCLOAK_URL}/admin/realms/cnoe/users
USER1ID=$(curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/admin/realms/cnoe/users?lastName=one" | jq -r '.[0].id')
USER2ID=$(curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" "${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/admin/realms/cnoe/clients
CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X GET ${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 \
${KEYCLOAK_URL}/admin/realms/cnoe/clients
CLIENT_ID=$(curl -sS -H "Content-Type: application/json" \
-H "Authorization: bearer ${KEYCLOAK_TOKEN}" \
-X GET ${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 ${KEYCLOAK_URL}/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 http://argocd-server.argocd.svc.cluster.local:443/api/v1/session -H 'Content-Type: application/json' -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

View file

@ -0,0 +1,179 @@
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:
metadata:
labels:
cnoe.io/cli-secret: "true"
cnoe.io/package-name: keycloak
engineVersion: v2
data:
KEYCLOAK_ADMIN_PASSWORD: "{{.KEYCLOAK_ADMIN_PASSWORD}}"
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: "{{.KC_DB_PASSWORD}}"
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

29
cicd/metric-server.yaml Normal file
View file

@ -0,0 +1,29 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: metric-server
namespace: argocd
labels:
env: dev
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://kubernetes-sigs.github.io/metrics-server
targetRevision: 3.12.1
helm:
releaseName: metrics-server
values: |
args:
- --kubelet-insecure-tls #required for kind/minikube
chart: metrics-server
destination:
server: 'https://kubernetes.default.svc'
namespace: kube-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

36
cicd/replace.sh Executable file
View file

@ -0,0 +1,36 @@
# this script replaces hostname and port used by this implementation.
# intended for use in environments such as Codespaces where external host and port need to be updated to access in-cluster resources.
#!/bin/bash
set -e
# Check if the new port number is provided as an argument
if [ "$#" -ne 2 ]; then
echo "Usage: NEW_HOST NEW_PORT"
exit 1
fi
# Assign the first script argument to NEW_PORT
NEW_HOST="$1"
NEW_PORT="$2"
# Base directory to start from, "." means the current directory
CURRENT_DIR=$(echo "${PWD##*/}")
if [[ ${CURRENT_DIR} != "cicd" ]]; then
echo "please run this script from the ref-implementation directory"
exit 10
fi
BASE_DIRECTORY="."
# Find all .yaml files recursively starting from the base directory
# and perform an in-place search and replace from 8443 to the new port
find "$BASE_DIRECTORY" -type f -name "*.yaml" -exec sed -i "s/8443/${NEW_PORT}/g" {} +
find "$BASE_DIRECTORY" -type f -name "*.yaml" -exec sed -i "s/cnoe\.localtest\.me/${NEW_HOST}/g" {} +
# Remove hostname-port configuration if the new port is 443. Browsers strip 443 but keycloak still expects 443 in url.
if [[ ${NEW_PORT} == "443" ]]; then
sed -i "/hostname-port/d" keycloak/manifests/install.yaml
sed -i "/hostname-admin/d" keycloak/manifests/install.yaml
sed -i '0,/:443/{s/:443//}' argo-workflows/manifests/dev/patches/cm-argo-workflows.yaml
fi
echo "Replacement complete."

View file

@ -0,0 +1,19 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: shared-helm-charts
namespace: argocd
labels:
env: dev
spec:
project: default
source:
repoURL: cnoe://shared-helm-charts/entities
targetRevision: HEAD
path: "."
destination:
server: "https://kubernetes.default.svc"
namespace: default
syncPolicy:
automated:
selfHeal: true

View file

@ -0,0 +1,9 @@
apiVersion: v2
name: simple-go
description: A Helm chart for Kubernetes
type: application
version: 0.0.0
appVersion: "0.1.0"

View file

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.appName }}
namespace: "{{ .Values.appName }}-{{ .Values.envName }}"
labels:
entity-id: {{ .Values.appName }}
app: {{ .Values.appName }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Values.appName }}
template:
metadata:
labels:
app: {{ .Values.appName }}
entity-id: {{ .Values.appName }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.registry }}/{{ .Values.appName }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080

View file

@ -0,0 +1,25 @@
{{ if .Values.ingress.enable }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.appName }}
namespace: "{{ .Values.appName }}-{{ .Values.envName }}"
labels:
entity-id: {{ .Values.appName }}
app: {{ .Values.appName }}
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- backend:
service:
name: {{ .Values.appName }}
port:
number: {{ .Values.service.port }}
path: /{{ .Values.appName }}/{{ .Values.envName }}(/|$)(.*)
pathType: ImplementationSpecific
{{ end }}

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .Values.appName }}-{{ .Values.envName }}"
labels:
name: "{{ .Values.appName }}-{{ .Values.envName }}"

View file

@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.appName }}
namespace: "{{ .Values.appName }}-{{ .Values.envName }}"
labels:
entity-id: {{ .Values.appName }}
app: {{ .Values.appName }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: 8080
protocol: TCP
name: http
selector:
app: {{ .Values.appName }}

View file

@ -0,0 +1,17 @@
appName: demoapp
envName: dev
replicaCount: 1
image:
registry: REGISTRY_HOST:443/gitea/giteaadmin
tag: 0.0.0
service:
type: ClusterIP
port: 8080
ingress:
enable: false
host: INGRESS_HOST

View file

@ -0,0 +1,9 @@
apiVersion: v2
name: provisioner
description: A Helm chart for provisioner
type: application
version: 0.1.0
appVersion: "0.1.0"

View file

@ -0,0 +1,23 @@
{{- range $env, $config := .Values.envList }}
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: "{{ $env }}-{{ $.Values.serviceName }}"
namespace: {{ $.Values.namespace }}
labels:
entity-id: {{ $.Values.serviceName }}
spec:
project: {{ $.Values.project }}
source:
path: {{ $config.path }}
repoURL: "{{ $config.repo }}"
destination:
name: {{ $.Values.k8sServer }}
namespace: {{ $config.namespace }}
syncPolicy:
automated:
selfHeal: true
syncOptions:
- CreateNamespace=true
---
{{- end }}

View file

@ -0,0 +1,15 @@
serviceName: demo-app01
project: default
namespace: argocd
k8sServer: in-cluster
# envList:
# dev:
# repo: dev
# path: "abc"
# namespace: demo-app01-dev
# test:
# repo: test
# path: "abc"
# namespace: demo-app01-test

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
restartPolicy: Always

View file

@ -0,0 +1,6 @@
site_name: 'Argo Spark Example'
nav:
- Home: index.md
- idpBuilder: idpbuilder.md
plugins:
- techdocs-core

View file

@ -0,0 +1,36 @@
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{values.name | dump}}
description: This is a basic example application
annotations:
backstage.io/techdocs-ref: dir:.
backstage.io/kubernetes-label-selector: 'entity-id=${{values.name}}'
backstage.io/kubernetes-namespace: default
argocd/app-name: ${{values.name | dump}}
links:
- url: https://cnoe.localtest.me:8443/gitea
title: Repo URL
icon: github
spec:
owner: guest
lifecycle: experimental
type: service
system: ${{values.name | dump}}
---
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: ${{values.name | dump}}
description: An example system for demonstration purposes
annotations:
backstage.io/techdocs-ref: dir:.
links:
- url: https://github.com/cnoe-io/stacks/tree/main/ref-implementation
title: CNOE Repo
icon: github
spec:
owner: guest
lifecycle: experimental
type: service

View file

@ -0,0 +1,46 @@
[![Codespell][codespell-badge]][codespell-link]
[![E2E][e2e-badge]][e2e-link]
[![Go Report Card][report-badge]][report-link]
[![Commit Activity][commit-activity-badge]][commit-activity-link]
# IDP Builder
Internal development platform binary launcher.
> **WORK IN PROGRESS**: This tool is in a pre-release stage and is under active development.
## About
Spin up a complete internal developer platform using industry standard technologies like Kubernetes, Argo, and backstage with only Docker required as a dependency.
This can be useful in several ways:
* Create a single binary which can demonstrate an IDP reference implementation.
* Use within CI to perform integration testing.
* Use as a local development environment for platform engineers.
## Getting Started
Checkout our [documentation website](https://cnoe.io/docs/reference-implementation/installations/idpbuilder) for getting started with idpbuilder.
## Community
- If you have questions or concerns about this tool, please feel free to reach out to us on the [CNCF Slack Channel](https://cloud-native.slack.com/archives/C05TN9WFN5S).
- You can also join our community meetings to meet the team and ask any questions. Checkout [this calendar](https://calendar.google.com/calendar/embed?src=064a2adfce866ccb02e61663a09f99147f22f06374e7a8994066bdc81e066986%40group.calendar.google.com&ctz=America%2FLos_Angeles) for more information.
## Contribution
Checkout the [contribution doc](./CONTRIBUTING.md) for contribution guidelines and more information on how to set up your local environment.
<!-- JUST BADGES & LINKS -->
[codespell-badge]: https://github.com/cnoe-io/idpbuilder/actions/workflows/codespell.yaml/badge.svg
[codespell-link]: https://github.com/cnoe-io/idpbuilder/actions/workflows/codespell.yaml
[e2e-badge]: https://github.com/cnoe-io/idpbuilder/actions/workflows/e2e.yaml/badge.svg
[e2e-link]: https://github.com/cnoe-io/idpbuilder/actions/workflows/e2e.yaml
[report-badge]: https://goreportcard.com/badge/github.com/cnoe-io/idpbuilder
[report-link]: https://goreportcard.com/report/github.com/cnoe-io/idpbuilder
[commit-activity-badge]: https://img.shields.io/github/commit-activity/m/cnoe-io/idpbuilder
[commit-activity-link]: https://github.com/cnoe-io/idpbuilder/pulse

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -0,0 +1,11 @@
![cnoe logo](./images/cnoe-logo.png)
# Example Basic Application
Thanks for trying out this demo! In this example, we deployed a simple guestbook application to a remote cluster using Backstage.
### idpbuilder
Checkout idpbuilder website: https://cnoe.io/docs/reference-implementation/installations/idpbuilder
Checkout idpbuilder repository: https://github.com/cnoe-io/idpbuilder

View file

@ -0,0 +1,21 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ${{values.name | dump}}
namespace: argocd
labels:
example: basic
spec:
project: default
source:
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
path: guestbook
destination:
name: workshop
namespace: guestbook
syncPolicy:
automated:
selfHeal: true
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,53 @@
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
description: Creates a Basic Kubernetes Deployment
name: basic-remote
title: Create a Basic Deployment in a Remote Cluster
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: cnoe.localtest.me:8443/gitea?repo=${{parameters.name}}
defaultBranch: main
- id: create-argocd-app
name: Create ArgoCD App
action: cnoe:kubernetes:apply
input:
manifestPath: manifests/argocd-app.yaml
namespaced: true
clusterName: local
- 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 }}

View file

@ -8,3 +8,4 @@ spec:
- ./basic/template.yaml
- ./argo-workflows/template.yaml
- ./app-with-bucket/template.yaml
- ./basic-remote/template.yaml

View file

@ -18,6 +18,7 @@ rules:
- argoproj.io
resources:
- workflows
- applications
verbs:
- create
---