chore(git): code staging

Signed-off-by: zhenyus <zhenyus@mathmast.com>
This commit is contained in:
zhenyus 2025-02-17 18:14:40 +08:00
parent 0e256f8708
commit f34f9838a5
35 changed files with 1180 additions and 769 deletions

View File

@ -0,0 +1,33 @@
package com.freeleaps.devops
class ArgoApplicationVersionUpdater {
def steps
def workspace
ArgoApplicationVersionUpdater(steps) {
this.steps = steps
}
def update(environmentSlug, component) {
steps.log.info("ArgoApplicationVersionUpdater", "[${environmentSlug}] Update Argo application image version for ${component.name}...")
def valuesFile = "${workspace}/../helm/${component.name}/values-${environmentSlug}.yaml"
def data = steps.readYaml (file: valuesFile)
data.${component.name}.image.registry = steps.env.BUILD_IMAGE_REGISTRY
data.${component.name}.image.repository = steps.env.BUILD_IMAGE_REPOSITORY
data.${component.name}.image.tag = steps.env.BUILD_IMAGE_VERSION
data.${component.name}.image.name = steps.env.BUILD_IMAGE_NAME
steps.writeFile file: valuesFile, data: data, overwrite: true
steps.withCredentials([steps.usernamePassword(credentialsId: 'freeleaps-ops-credentials', passwordVariable: 'OPS_GIT_PASSWORD', usernameVariable: 'OPS_GIT_USERNAME')]) {
steps.sh """
git config user.name "freeleaps-gitops-bot"
git config user.email "gitops@mathmast.com"
git remote add ci_origin https://${OPS_GIT_USERNAME}:${OPS_GIT_PASSWORD}@dev.azure.com/freeleaps/freeleaps-ops/_git/freeleaps-ops
git add ${valuesFile}
git commit -m "ci(bump): bump ${component.name} image version for ${environmentSlug} to ${steps.env.BUILD_IMAGE_VERSION}"
git push ci_origin HEAD:master
"""
steps.log.info("ArgoApplicationVersionUpdater", "[${environmentSlug}] ${component.name} image version bump to ${steps.env.BUILD_IMAGE_VERSION}")
}
}
}

View File

@ -114,6 +114,10 @@ class ImageBuilder {
steps.log.info("ImageBuilder", "Set builder timeout to 10min...")
steps.env.BUILDKIT_TIMEOUT = "1800s"
steps.sh "docker buildx build --builder ${buildxBuilderName} --platform ${architectures.join(",")} -t ${registry}/${repository}/${name}:${version} -f ${dockerfile} --push ${contextRoot}"
steps.env.BUILD_IMAGE_REGISTRY = "${registry}"
steps.env.BUILD_IMAGE_REPO = "${repository}"
steps.env.BUILD_IMAGE_NAME = "${name}"
steps.env.BUILD_IMAGE_VERSION = "${version}"
} else {
architectures.each { architecture ->
def archTag = architecture.split("/")[1]
@ -121,6 +125,10 @@ class ImageBuilder {
steps.sh "docker build -t ${registry}/${repository}/${name}:${version}-${archTag} --platform ${architecture} -f ${dockerfile} ${contextRoot}"
steps.sh "docker push ${registry}/${repository}/${name}:${version}-${archTag}"
}
steps.env.BUILD_IMAGE_REGISTRY = "${registry}"
steps.env.BUILD_IMAGE_REPO = "${repository}"
steps.env.BUILD_IMAGE_NAME = "${name}"
steps.env.BUILD_IMAGE_VERSION = "${version}-linux-amd64"
}
}
break
@ -131,6 +139,10 @@ class ImageBuilder {
steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}-${archTag}")
steps.sh "/kaniko/executor --log-format text --context ${contextRoot} --dockerfile ${dockerfile} --destination ${registry}/${repository}/${name}:${version}-${archTag} --custom-platform ${architecture}"
}
steps.env.BUILD_IMAGE_REGISTRY = "${registry}"
steps.env.BUILD_IMAGE_REPO = "${repository}"
steps.env.BUILD_IMAGE_NAME = "${name}"
steps.env.BUILD_IMAGE_VERSION = "${version}-linux-amd64"
}
break
default:

View File

@ -378,8 +378,9 @@ def generateComponentStages(component, configurations) {
}
}
}},
// Image Building & Publishing
{stage("${component.name} :: Image Building & Publishing") {
{
// Image Building & Publishing
stage("${component.name} :: Image Building & Publishing") {
podTemplate(
label: "image-builder-${component.name}",
containers: [
@ -448,7 +449,18 @@ def generateComponentStages(component, configurations) {
}
}
}
}}
},
{
stage("${component.name} :: Argo Application Version Updating") {
script {
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
def argoApplicationVersionUpdater = new ArgoApplicationVersionUpdater(this)
argoApplicationVersionUpdater.update(configurations.environmentSlug, component)
}
}
}
},
}
])
return {
stages.each { stageClosure ->

View File

@ -1,141 +1,56 @@
def REGISTRY_URL = 'docker.io'
def REPO_NAME = 'zhenyus'
def IMAGE_NAME = 'magicleaps'
def APP_NAME = 'magicleaps'
def TAG_PREFIX = 'snapshot'
def DOCKER_REGISTRY_SECRET = 'kaniko-secret'
library 'first-class-pipeline'
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git credentialsId: 'freeleaps-azure-dev', url: 'https://freeleaps@dev.azure.com/freeleaps/magicleaps/_git/magicleaps'
}
}
stage('Set Commit Hash') {
steps {
script {
def commitHash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
def shortCommitHash = commitHash.take(7)
env.COMMIT_HASH = shortCommitHash
echo "Commit Hash: ${env.COMMIT_HASH}"
env.TRIGGERED_BRANCH = "${GIT_BRANCH}"
echo "Triggered Branch: ${env.TRIGGERED_BRANCH}"
}
}
}
stage('Build Docker Images For Each Components') {
matrix {
axes {
axis {
name 'COMPONENT'
values 'backend', 'frontend'
}
axis {
name 'ARCH'
values 'linux/amd64'
}
}
agent {
kubernetes {
defaultContainer 'kaniko'
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
freeleaps-devops-job: magicleaps-app-build
freeleaps-devops-app: magicleaps
spec:
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:debug
command:
- cat
tty: true
volumeMounts:
- name: kaniko-secret
mountPath: /kaniko/.docker/config.json
subPath: .dockerconfigjson
- name: workspace
mountPath: /workspace
volumes:
- name: kaniko-secret
secret:
secretName: kaniko-secret
- name: workspace
emptyDir: {}
"""
}
}
stages {
stage('Image Building') {
steps {
script {
def dockerfilePath = "${COMPONENT}/Dockerfile"
def arch = "${ARCH}"
def archTag = arch.replace('/', '-')
def targetImage = "${REGISTRY_URL}/${REPO_NAME}/${IMAGE_NAME}:${COMPONENT}-${TAG_PREFIX}-${COMMIT_HASH}-${archTag}"
echo "Building Docker image ${targetImage}..."
sh """
/kaniko/executor \
--dockerfile=${dockerfilePath} \
--context=${COMPONENT} \
--destination=${targetImage} \
--custom-platform=${ARCH} \
--skip-tls-verify=true \
--ignore-path=/product_uuid
"""
}
}
}
}
}
}
stage('Deploy with Argo CD') {
stages {
stage('Clone GitOps Manifests Repo') {
steps {
git credentialsId: 'freeleaps-azure-dev', url: 'https://freeleaps@dev.azure.com/freeleaps/freeleaps-ops/_git/freeleaps-ops'
}
}
stage('Automate Update Application Image Tag') {
steps {
script {
def triggeredBranch = "${TRIGGERED_BRANCH}"
if (triggeredBranch == 'origin/master') {
echo "Triggered branch is master, deploying to alpha..."
def valuesFile = APP_NAME + '/helm-pkg/' + APP_NAME + '/values.alpha.yaml'
def data = readYaml (file: valuesFile)
data.backend.image.tag = "backend-${TAG_PREFIX}-${env.COMMIT_HASH}-linux-amd64"
data.frontend.image.tag = "frontend-${TAG_PREFIX}-${env.COMMIT_HASH}-linux-amd64"
writeYaml file: valuesFile, data: data, overwrite: true
// git push
withCredentials([string(credentialsId: 'freeleaps-azure-dev-token-only', variable: 'GIT_CREDENTIALS')]) {
sh """
git config user.name "zhenyus"
git config user.email "zhenyus@mathmast.com"
git remote add ci_origin https://zhenyus:${GIT_CREDENTIALS}@dev.azure.com/freeleaps/freeleaps-ops/_git/freeleaps-ops
git add ${valuesFile}
git commit -m "ci(bot-auto-bump): bump ${APP_NAME} image tags for alpha to ${TAG_PREFIX}-${env.COMMIT_HASH}-linux-amd64"
git push ci_origin HEAD:master
"""
}
echo "Update ${APP_NAME} image tags for alpha to ${TAG_PREFIX}-${env.COMMIT_HASH}-linux-amd64."
}
}
}
}
}
}
}
executeFreeleapsPipeline {
serviceName = 'magicleaps'
environmentSlug = 'alpha'
serviceGitBranch = 'master'
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/magicleaps/_git/magicleaps"
serviceGitRepoType = 'monorepo'
serviceGitCredentialsId = 'magicleaps-azure-devops-credentials'
executeMode = 'fully'
commitMessageLintEnabled = true
components = [
[
name: 'frontend',
root: 'frontend',
language: 'javascript',
dependenciesManager: 'npm',
npmPackageJsonFile: 'package.json',
buildCacheEnabled: true,
buildAgentImage: 'node:lts',
buildCommand: 'npm run build',
buildArtifacts: ['dist'],
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'sunzhenyucn',
imageName: 'magicleaps-frontend',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'magicleaps-dockerhub-credentials',
semanticReleaseEnabled: true
],
[
name: 'backend',
root: 'backend',
language: 'python',
dependenciesManager: 'pip',
buildAgentImage: 'python:3.8-slim-buster',
buildArtifacts: ['.'],
buildCacheEnabled: true,
lintEnabled: false,
sastEnabled: false,
imageRegistry: 'docker.io',
imageRepository: 'sunzhenyucn',
imageName: 'magicleaps-backend',
imageBuilder: 'dind',
dockerfilePath: 'Dockerfile',
imageBuildRoot: '.',
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'],
registryCredentialsId: 'magicleaps-dockerhub-credentials',
semanticReleaseEnabled: true
]
]
}

View File

@ -1,23 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -1,6 +1,6 @@
apiVersion: v2
name: magicleaps
description: A Helm chart for Magic Leaps application.
description: A Helm Chart of magicleaps, powered by Freeleaps.
type: application
version: 0.1.0
appVersion: "0.1.0"
version: 0.0.1
appVersion: "0.0.1"

View File

@ -1,107 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "magicleaps.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Expand the name of the frontend.
*/}}
{{- define "magicleaps.frontend.name" -}}
{{- default .Values.frontend.name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Expand the name of the backend.
*/}}
{{- define "magicleaps.backend.name" -}}
{{- default .Values.backend.name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "magicleaps.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "magicleaps.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Backend labels
*/}}
{{- define "magicleaps.backend.labels" -}}
helm.sh/chart: {{ include "magicleaps.chart" . }}
{{ include "magicleaps.backend.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Backend Selector labels
*/}}
{{- define "magicleaps.backend.selectorLabels" -}}
app.kubernetes.io/name: {{ include "magicleaps.backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Frontend labels
*/}}
{{- define "magicleaps.frontend.labels" -}}
helm.sh/chart: {{ include "magicleaps.chart" . }}
{{ include "magicleaps.frontend.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Frontend Selector labels
*/}}
{{- define "magicleaps.frontend.selectorLabels" -}}
app.kubernetes.io/name: {{ include "magicleaps.frontend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Return the appropriate apiVersion for deployment.
*/}}
{{- define "magicleaps.deployment.apiVersion" -}}
{{- if semverCompare "<1.9-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "extensions/v1beta1" -}}
{{- else if semverCompare "^1.9-0" .Capabilities.KubeVersion.GitVersion -}}
{{- print "apps/v1" -}}
{{- end -}}
{{- end -}}
{{/*
Define the magicleaps.namespace template if set with forceNamespace or .Release.Namespace is set
*/}}
{{- define "magicleaps.namespace" -}}
{{- if .Values.forceNamespace -}}
{{ printf "namespace: %s" .Values.forceNamespace }}
{{- else -}}
{{ printf "namespace: %s" .Release.Namespace }}
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,98 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/name: "backend"
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: "backend"
namespace: {{ .Release.Namespace | quote }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: "backend"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
replicas: {{ .Values.backend.replicas }}
template:
metadata:
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/name: "backend"
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: "backend"
image: "{{ coalesce .Values.backend.image.registry .Values.global.registry "docker.io"}}/{{ coalesce .Values.backend.image.repository .Values.global.repository }}/{{ .Values.backend.image.name }}:{{ .Values.backend.image.tag | default "latest" }}"
imagePullPolicy: {{ .Values.backend.image.imagePullPolicy | default "IfNotPresent" }}
ports:
{{- range $port := .Values.backend.ports }}
- containerPort: {{ $port.containerPort }}
name: {{ $port.name }}
protocol: {{ $port.protocol }}
{{- end }}
{{- if .Values.backend.resources }}
resources:
{{- toYaml .Values.backend.resources | nindent 12 }}
{{- end }}
{{- if .Values.backend.probes }}
{{- if and (.Values.backend.probes.liveness) (eq .Values.backend.probes.liveness.type "httpGet") }}
livenessProbe:
httpGet:
path: {{ .Values.backend.probes.liveness.config.path }}
port: {{ .Values.backend.probes.liveness.config.port }}
{{- if .Values.backend.probes.liveness.config.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.backend.probes.liveness.config.initialDelaySeconds }}
{{- end }}
{{- if .Values.backend.probes.liveness.config.periodSeconds }}
periodSeconds: {{ .Values.backend.probes.liveness.config.periodSeconds }}
{{- end }}
{{- if .Values.backend.probes.liveness.config.timeoutSeconds }}
timeoutSeconds: {{ .Values.backend.probes.liveness.config.timeoutSeconds }}
{{- end }}
{{- if .Values.backend.probes.liveness.config.successThreshold }}
successThreshold: {{ .Values.backend.probes.liveness.config.successThreshold }}
{{- end }}
{{- if .Values.backend.probes.liveness.config.failureThreshold }}
failureThreshold: {{ .Values.backend.probes.liveness.config.failureThreshold }}
{{- end }}
{{- if .Values.backend.probes.liveness.config.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.backend.probes.liveness.config.terminationGracePeriodSeconds }}
{{- end }}
{{- end }}
{{- if and (.Values.backend.probes.readiness) (eq .Values.backend.probes.readiness.type "httpGet") }}
readinessProbe:
httpGet:
path: {{ .Values.backend.probes.readiness.config.path }}
port: {{ .Values.backend.probes.readiness.config.port }}
{{- if .Values.backend.probes.readiness.config.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.backend.probes.readiness.config.initialDelaySeconds }}
{{- end }}
{{- if .Values.backend.probes.readiness.config.periodSeconds }}
periodSeconds: {{ .Values.backend.probes.readiness.config.periodSeconds }}
{{- end }}
{{- if .Values.backend.probes.readiness.config.timeoutSeconds }}
timeoutSeconds: {{ .Values.backend.probes.readiness.config.timeoutSeconds }}
{{- end }}
{{- if .Values.backend.probes.readiness.config.successThreshold }}
successThreshold: {{ .Values.backend.probes.readiness.config.successThreshold }}
{{- end }}
{{- if .Values.backend.probes.readiness.config.failureThreshold }}
failureThreshold: {{ .Values.backend.probes.readiness.config.failureThreshold }}
{{- end }}
{{- if .Values.backend.probes.readiness.config.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.backend.probes.readiness.config.terminationGracePeriodSeconds }}
{{- end }}
{{- end }}
{{- end}}
env:
{{- range $key, $value := .Values.backend.configs }}
- name: {{ $key | snakecase | upper }}
valueFrom:
secretKeyRef:
name: magicleaps-backend-config
key: {{ $key | snakecase | upper }}
{{- end }}

View File

@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: magicleaps-backend-config
namespace: {{ .Release.Namespace }}
type: Opaque
data:
TZ: {{ .Values.backend.configs.tz | b64enc | quote }}
MONGODB_HOST: {{ .Values.backend.configs.mongodbHost | b64enc | quote }}
MONGODB_PORT: {{ .Values.backend.configs.mongodbPort | b64enc | quote }}
MONGODB_NAME: {{ .Values.backend.configs.mongodbName | b64enc | quote }}
EMAIL_USER: {{ .Values.backend.configs.emailUser | b64enc | quote }}
EMAIL_PASSWORD: {{ .Values.backend.configs.emailPassword | b64enc | quote }}
SUPER_ADMIN: {{ .Values.backend.configs.superAdmin | b64enc | quote }}
TWILIO_ACCOUNT_SID: {{ .Values.backend.configs.twilioAccountSid | b64enc | quote }}
TWILIO_AUTH_TOKEN: {{ .Values.backend.configs.twilioAuthToken | b64enc | quote }}
EVELUATION_TASK_FOLDER_BASE: {{ .Values.backend.configs.eveluationTaskFolderBase | b64enc | quote }}
LOG_DIR: {{ .Values.backend.configs.logDir | b64enc | quote }}
APP_LOG_FILE: {{ .Values.backend.configs.appLogFile | b64enc | quote }}
APP_LOG_LEVEL: {{ .Values.backend.configs.appLogLevel | b64enc | quote }}

View File

@ -0,0 +1,26 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseService := .Release.Service }}
{{ $releaseName := .Release.Name }}
{{- range $service := .Values.backend.services }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $service.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $service.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
ports:
- port: {{ $service.port }}
targetPort: {{ $service.targetPort }}
selector:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: "backend"
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
{{- end }}

View File

@ -1,87 +0,0 @@
{{- if .Values.backend.enabled -}}
apiVersion: {{ template "magicleaps.deployment.apiVersion" . }}
kind: Deployment
metadata:
labels:
{{- include "magicleaps.backend.labels" . | nindent 4 }}
name: {{ .Values.backend.name }}
{{ include "magicleaps.namespace" . | indent 2 }}
spec:
selector:
matchLabels:
{{- include "magicleaps.backend.labels" . | nindent 6 }}
replicas: {{ .Values.backend.replicaCount }}
template:
metadata:
labels:
{{- include "magicleaps.backend.labels" . | nindent 8 }}
spec:
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- end }}
containers:
- name: {{ .Values.backend.name }}
image: "{{ .Values.backend.image.repository }}/{{ .Values.backend.image.name }}{{ if .Values.backend.image.tag }}:{{ .Values.backend.image.tag }}{{ end }}"
imagePullPolicy: {{ .Values.backend.image.imagePullPolicy }}
ports:
- containerPort: {{ .Values.backend.port }}
{{- if .Values.backend.extraEnv }}
env:
{{- toYaml .Values.backend.extraEnv | nindent 12 }}
{{- end }}
livenessProbe:
{{- toYaml .Values.backend.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.backend.readinessProbe | nindent 12 }}
{{- if .Values.backend.resources }}
resources:
{{- toYaml .Values.backend.resources | nindent 12 }}
{{- end }}
env:
- name: MONGO_DB
valueFrom:
secretKeyRef:
name: backend-secrets
key: MONGO_DB
- name: MONGO_HOST
valueFrom:
secretKeyRef:
name: backend-secrets
key: MONGO_HOST
- name: MONGO_PORT
valueFrom:
secretKeyRef:
name: backend-secrets
key: MONGO_PORT
- name: EMAIL_USER
valueFrom:
secretKeyRef:
name: backend-secrets
key: EMAIL_USER
- name: EMAIL_PASSWORD
valueFrom:
secretKeyRef:
name: backend-secrets
key: EMAIL_PASSWORD
- name: SUPER_ADMIN
valueFrom:
secretKeyRef:
name: backend-secrets
key: SUPER_ADMIN
- name: TWILIO_ACCOUNT_SID
valueFrom:
secretKeyRef:
name: backend-secrets
key: TWILIO_ACCOUNT_SID
- name: TWILIO_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: backend-secrets
key: TWILIO_AUTH_TOKEN
- name: LOG_LEVEL
valueFrom:
secretKeyRef:
name: backend-secrets
key: LOG_LEVEL
{{- end -}}

View File

@ -1,41 +0,0 @@
{{- if .Values.frontend.enabled }}
apiVersion: {{ template "magicleaps.deployment.apiVersion" . }}
kind: Deployment
metadata:
labels:
{{- include "magicleaps.frontend.labels" . | nindent 4 }}
name: {{ .Values.frontend.name }}
{{ include "magicleaps.namespace" . | indent 2 }}
spec:
selector:
matchLabels:
{{- include "magicleaps.frontend.labels" . | nindent 6 }}
replicas: {{ .Values.frontend.replicaCount }}
template:
metadata:
labels:
{{- include "magicleaps.frontend.labels" . | nindent 8 }}
spec:
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml .Values.imagePullSecrets | nindent 8 }}
{{- end }}
containers:
- name: {{ .Values.frontend.name }}
image: "{{ .Values.frontend.image.repository }}/{{ .Values.frontend.image.name }}{{ if .Values.frontend.image.tag }}:{{ .Values.frontend.image.tag }}{{ end }}"
imagePullPolicy: {{ .Values.frontend.image.imagePullPolicy }}
ports:
- containerPort: {{ .Values.frontend.port }}
{{- if .Values.frontend.extraEnv }}
env:
{{- toYaml .Values.frontend.extraEnv | nindent 12 }}
{{- end }}
livenessProbe:
{{- toYaml .Values.frontend.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.frontend.readinessProbe | nindent 12 }}
{{- if .Values.frontend.resources }}
resources:
{{- toYaml .Values.frontend.resources | nindent 12 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,27 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseCertificate := .Release.Certificate }}
{{ $releaseName := .Release.Name }}
{{- range $ingress := .Values.frontend.ingresses }}
{{- if not $ingress.tls.exists }}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ $ingress.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $ingress.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseCertificate }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
commonName: {{ $ingress.host }}
dnsNames:
- {{ $ingress.host }}
issuerRef:
name: {{ $ingress.tls.issuerRef.name }}
kind: {{ $ingress.tls.issuerRef.kind }}
secretName: {{ $ingress.tls.name }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,98 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/name: "frontend"
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: "frontend"
namespace: {{ .Release.Namespace | quote }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: "frontend"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
replicas: {{ .Values.frontend.replicas }}
template:
metadata:
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/name: "frontend"
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: "frontend"
image: "{{ coalesce .Values.frontend.image.registry .Values.global.registry "docker.io"}}/{{ coalesce .Values.frontend.image.repository .Values.global.repository }}/{{ .Values.frontend.image.name }}:{{ .Values.frontend.image.tag | default "latest" }}"
imagePullPolicy: {{ .Values.frontend.image.imagePullPolicy | default "IfNotPresent" }}
ports:
{{- range $port := .Values.frontend.ports }}
- containerPort: {{ $port.containerPort }}
name: {{ $port.name }}
protocol: {{ $port.protocol }}
{{- end }}
{{- if .Values.frontend.resources }}
resources:
{{- toYaml .Values.frontend.resources | nindent 12 }}
{{- end }}
{{- if .Values.frontend.probes }}
{{- if and (.Values.frontend.probes.liveness) (eq .Values.frontend.probes.liveness.type "httpGet") }}
livenessProbe:
httpGet:
path: {{ .Values.frontend.probes.liveness.config.path }}
port: {{ .Values.frontend.probes.liveness.config.port }}
{{- if .Values.frontend.probes.liveness.config.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.frontend.probes.liveness.config.initialDelaySeconds }}
{{- end }}
{{- if .Values.frontend.probes.liveness.config.periodSeconds }}
periodSeconds: {{ .Values.frontend.probes.liveness.config.periodSeconds }}
{{- end }}
{{- if .Values.frontend.probes.liveness.config.timeoutSeconds }}
timeoutSeconds: {{ .Values.frontend.probes.liveness.config.timeoutSeconds }}
{{- end }}
{{- if .Values.frontend.probes.liveness.config.successThreshold }}
successThreshold: {{ .Values.frontend.probes.liveness.config.successThreshold }}
{{- end }}
{{- if .Values.frontend.probes.liveness.config.failureThreshold }}
failureThreshold: {{ .Values.frontend.probes.liveness.config.failureThreshold }}
{{- end }}
{{- if .Values.frontend.probes.liveness.config.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.frontend.probes.liveness.config.terminationGracePeriodSeconds }}
{{- end }}
{{- end }}
{{- if and (.Values.frontend.probes.readiness) (eq .Values.frontend.probes.readiness.type "httpGet") }}
readinessProbe:
httpGet:
path: {{ .Values.frontend.probes.readiness.config.path }}
port: {{ .Values.frontend.probes.readiness.config.port }}
{{- if .Values.frontend.probes.readiness.config.initialDelaySeconds }}
initialDelaySeconds: {{ .Values.frontend.probes.readiness.config.initialDelaySeconds }}
{{- end }}
{{- if .Values.frontend.probes.readiness.config.periodSeconds }}
periodSeconds: {{ .Values.frontend.probes.readiness.config.periodSeconds }}
{{- end }}
{{- if .Values.frontend.probes.readiness.config.timeoutSeconds }}
timeoutSeconds: {{ .Values.frontend.probes.readiness.config.timeoutSeconds }}
{{- end }}
{{- if .Values.frontend.probes.readiness.config.successThreshold }}
successThreshold: {{ .Values.frontend.probes.readiness.config.successThreshold }}
{{- end }}
{{- if .Values.frontend.probes.readiness.config.failureThreshold }}
failureThreshold: {{ .Values.frontend.probes.readiness.config.failureThreshold }}
{{- end }}
{{- if .Values.frontend.probes.readiness.config.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.frontend.probes.readiness.config.terminationGracePeriodSeconds }}
{{- end }}
{{- end }}
{{- end}}
env:
{{- range $key, $value := .Values.frontend.configs }}
- name: {{ $key | snakecase | upper }}
valueFrom:
secretKeyRef:
name: magicleaps-frontend-config
key: {{ $key | snakecase | upper }}
{{- end }}

View File

@ -0,0 +1,38 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseIngress := .Release.Ingress }}
{{ $releaseName := .Release.Name }}
{{- range $ingress := .Values.frontend.ingresses }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $ingress.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $ingress.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseIngress }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
{{- if $ingress.class }}
ingressClassName: {{ $ingress.class }}
{{- end }}
{{- if $ingress.tls }}
tls:
- hosts:
- {{ $ingress.host }}
{{- if $ingress.tls.exists }}
secretName: {{ $ingress.tls.secretRef.name }}
{{- else }}
secretName: {{ $ingress.tls.name }}
{{- end }}
{{- end }}
rules:
- host: {{ $ingress.host }}
http:
paths:
{{- range $path := $ingress.rules }}
{{ toYaml $path | indent 10 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: magicleaps-frontend-config
namespace: {{ .Release.Namespace }}
type: Opaque
data:
TZ: {{ .Values.frontend.configs.tz | b64enc | quote }}

View File

@ -0,0 +1,26 @@
{{ $namespace := .Release.Namespace }}
{{ $appVersion := .Chart.AppVersion | quote }}
{{ $releaseService := .Release.Service }}
{{ $releaseName := .Release.Name }}
{{- range $service := .Values.frontend.services }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $service.name }}
namespace: {{ $namespace }}
labels:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: {{ $service.name | quote }}
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
spec:
ports:
- port: {{ $service.port }}
targetPort: {{ $service.targetPort }}
selector:
app.kubernetes.io/version: {{ $appVersion }}
app.kubernetes.io/name: "frontend"
app.kubernetes.io/managed-by: {{ $releaseService }}
app.kubernetes.io/instance: {{ $releaseName }}
{{- end }}

View File

@ -1,24 +0,0 @@
{{- if and .Values.backend.enabled .Values.backend.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.backend.name }}-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
{{- if .Values.backend.ingress.annotations }}
{{ .Values.backend.ingress.annotations | toYaml | nindent 4 }}
{{- end }}
{{ include "magicleaps.namespace" . | indent 2 }}
spec:
rules:
- host: {{ .Values.backend.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Values.backend.name }}-svc
port:
number: {{ .Values.backend.service.port }}
{{- end -}}

View File

@ -1,24 +0,0 @@
{{- if .Values.frontend.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.frontend.name }}-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
{{- if .Values.frontend.ingress.annotations }}
{{ .Values.frontend.ingress.annotations | toYaml | nindent 4 }}
{{- end }}
{{ include "magicleaps.namespace" . | indent 2 }}
spec:
rules:
- host: {{ .Values.frontend.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Values.frontend.name }}-svc
port:
number: {{ .Values.frontend.service.port }}
{{- end -}}

View File

@ -1,16 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: backend-secrets
namespace: magicleaps-alpha
type: Opaque
data:
MONGO_DB: {{ .Values.backend.config.mongo.db | b64enc | quote }}
MONGO_HOST: {{ .Values.backend.config.mongo.host | b64enc | quote }}
MONGO_PORT: {{ .Values.backend.config.mongo.port | toString | b64enc | quote }}
EMAIL_USER: {{ .Values.backend.config.email.user | b64enc | quote }}
EMAIL_PASSWORD: {{ .Values.backend.config.email.password | b64enc | quote }}
SUPER_ADMIN: {{ .Values.backend.config.superAdmin | b64enc | quote }}
TWILIO_ACCOUNT_SID: {{ .Values.backend.config.twilio.accountSid | b64enc | quote }}
TWILIO_AUTH_TOKEN: {{ .Values.backend.config.twilio.authToken | b64enc | quote }}
LOG_LEVEL: {{ .Values.backend.config.log.level | b64enc | quote }}

View File

@ -1,17 +0,0 @@
{{- if .Values.backend.enabled -}}
apiVersion: v1
kind: Service
metadata:
labels:
{{- include "magicleaps.backend.labels" . | nindent 4 }}
name: {{ .Values.backend.name }}-svc
{{ include "magicleaps.namespace" . | indent 2 }}
spec:
ports:
- port: {{ .Values.backend.service.port }}
targetPort: 8081
selector:
{{- include "magicleaps.backend.selectorLabels" . | nindent 4 }}
sessionAffinity: {{ .Values.backend.service.sessionAffinity }}
type: {{ .Values.backend.service.type }}
{{- end -}}

View File

@ -1,17 +0,0 @@
{{- if .Values.frontend.enabled -}}
apiVersion: v1
kind: Service
metadata:
labels:
{{- include "magicleaps.frontend.labels" . | nindent 4 }}
name: {{ .Values.frontend.name }}-svc
{{ include "magicleaps.namespace" . | indent 2 }}
spec:
ports:
- port: {{ .Values.frontend.service.port }}
targetPort: 80
selector:
{{- include "magicleaps.frontend.selectorLabels" . | nindent 4 }}
sessionAffinity: {{ .Values.frontend.service.sessionAffinity }}
type: {{ .Values.frontend.service.type }}
{{- end -}}

View File

@ -1,76 +1,110 @@
imagePullSecrets:
- name: docker-secret
backend:
enabled: true
name: magicleaps-backend
image:
repository: docker.io/zhenyus
name: magicleaps
imagePullPolicy: IfNotPresent
tag: backend-snapshot-ce69a02-linux-amd64
extraEnv: {}
port: 8081
ingress:
enabled: false
annotations: {}
host: ''
replicaCount: 1
service:
type: ClusterIP
port: 8081
sessionAffinity: None
resources: {}
livenessProbe:
enabled: true
httpGet:
path: /api/_/probe/liveness
port: 8081
readinessProbe:
enabled: true
httpGet:
path: /api/_/probe/readiness
port: 8081
config:
mongo:
db: magicleaps_alpha
host: mongo-mongodb.magicleaps-alpha.svc.cluster.local
port: 27017
email:
user: your@freeleaps.com
password: your-password
superAdmin: your@email.com
twilio:
accountSid: ''
authToken: ''
log:
level: INFO
frontend:
enabled: true
name: magicleaps-frontend
image:
repository: docker.io/zhenyus
name: magicleaps
imagePullPolicy: IfNotPresent
tag: frontend-snapshot-ce69a02-linux-amd64
extraEnv: {}
port: 80
ingress:
annotations: {}
host: magicleaps-alpha.wearelsp.com
global:
registry: docker.io
repository: sunzhenyucn
nodeSelector: {}
affinity: {}
replicaCount: 1
service:
type: ClusterIP
port: 80
sessionAffinity: None
livenessProbe:
enabled: true
httpGet:
path: /
port: 80
readinessProbe:
enabled: true
httpGet:
path: /
frontend:
replicas: 1
image:
registry:
repository: sunzhenyucn
name: magicleaps-frontend
tag: 1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "256Mi"
probes:
liveness:
type: httpGet
config:
path: /
port: 8080
readiness:
type: httpGet
config:
path: /
port: 8080
services:
- name: magicleaps-frontend-service
type: ClusterIP
port: 80
targetPort: 8080
ingresses:
- name: magicleaps-frontend-ingress
host: alpha.magicleaps.mathmast.com
class: nginx
rules:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: magicleaps-frontend-service
port:
number: 80
tls:
exists: false
issuerRef:
name: mathmast-dot-com
kind: ClusterIssuer
name: magicleaps-alpha-frontend-ingress-tls
configs:
tz: "America/Settle"
backend:
replicas: 1
image:
registry:
repository: sunzhenyucn
name: magicleaps-backend
tag: 1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8081
protocol: TCP
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "200m"
memory: "512Mi"
probes:
liveness:
type: httpGet
config:
path: /api/_/probe/liveness
port: 8081
readiness:
type: httpGet
config:
path: /api/_/probe/readiness
port: 8081
services:
- name: magicleaps-backend-service
type: ClusterIP
port: 8081
targetPort: 8081
ingresses:
configs:
tz: "America/Settle"
mongodbHost: "mongodb.magicleaps-alpha.svc.cluster.local"
mongodbPort: "27017"
mongodbName: "interview"
emailUser: "your@freeleaps.com"
emailPassword: "your-password"
superAdmin: "your@email.com"
twilioAccountSid: ""
twilioAuthToken: ""
eveluationTaskFolderBase: "temp/interview/eveluation_task/"
logDir: "logs"
appLogFile: "app.log"
appLogLevel: "INFO"

View File

@ -1,74 +1,110 @@
imagePullSecrets:
- name: docker-secret
backend:
enabled: true
name: magicleaps-backend
image:
repository: docker.io/magicleaps
name: magicleaps
imagePullPolicy: IfNotPresent
tag: "backend-snapshot-5e9dea2-linux-amd64"
extraEnv: {}
port: 8081
ingress:
enabled: false
annotations: {}
host: ''
replicaCount: 1
service:
type: ClusterIP
port: 8081
sessionAffinity: None
resources: {}
livenessProbe:
httpGet:
path: /api/_/probe/liveness
port: 8081
readinessProbe:
httpGet:
path: /api/_/probe/readiness
port: 8081
config:
mongo:
db: magicleaps_alpha
host: ''
port: 27017
email:
user: ''
password: ''
superAdmin: ''
twilio:
accountSid: ''
authToken: ''
log:
level: 'INFO'
frontend:
enabled: true
name: magicleaps-frontend
image:
repository: docker.io/magicleaps
name: magicleaps
imagePullPolicy: IfNotPresent
tag: "frontend-snapshot-5e9dea2-linux-amd64"
extraEnv: {}
port: 80
ingress:
annotations: {}
host: ''
global:
registry: docker.io
repository: sunzhenyucn
nodeSelector: {}
affinity: {}
replicaCount: 1
service:
type: ClusterIP
port: 80
sessionAffinity: None
livenessProbe:
httpGet:
path: /
frontend:
replicas: 1
image:
registry:
repository: sunzhenyucn
name: magicleaps-frontend
tag: 1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "256Mi"
probes:
liveness:
type: httpGet
config:
path: /
port: 8080
readiness:
type: httpGet
config:
path: /
port: 8080
services:
- name: magicleaps-frontend-service
type: ClusterIP
port: 80
readinessProbe:
httpGet:
path: /
port: 80
targetPort: 8080
ingresses:
- name: magicleaps-frontend-ingress
host: alpha.magicleaps.mathmast.com
class: nginx
rules:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: magicleaps-frontend-service
port:
number: 80
tls:
exists: false
issuerRef:
name: mathmast-dot-com
kind: ClusterIssuer
name: magicleaps-alpha-frontend-ingress-tls
configs:
tz: "America/Settle"
backend:
replicas: 1
image:
registry:
repository: sunzhenyucn
name: magicleaps-backend
tag: 1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8081
protocol: TCP
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "200m"
memory: "512Mi"
probes:
liveness:
type: httpGet
config:
path: /api/_/probe/liveness
port: 8081
readiness:
type: httpGet
config:
path: /api/_/probe/readiness
port: 8081
services:
- name: magicleaps-backend-service
type: ClusterIP
port: 8081
targetPort: 8081
ingresses:
configs:
tz: "America/Settle"
mongodbHost: "localhost"
mongodbPort: "27017"
mongodbName: "interview"
emailUser: "your@freeleaps.com"
emailPassword: "your-password"
superAdmin: "your@email.com"
twilioAccountSid: ""
twilioAuthToken: ""
eveluationTaskFolderBase: "temp/interview/eveluation_task/"
logDir: "logs"
appLogFile: "app.log"
appLogLevel: "INFO"

View File

@ -102,6 +102,8 @@ type RepositorySpec struct {
Name string `json:"name,omitempty"`
// Type defines the type of the Git repository
Type RepositoryType `json:"type,omitempty"`
// Branch defines the branch of the Git repository
Branch string `json:"branch,omitempty"`
// HookRegister defines the Git repository hook registration
HookRegister RepositoryHookRegisterSpec `json:"hookRegister,omitempty"`
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package main
import (
"context"
"crypto/tls"
"flag"
"os"
@ -38,6 +39,7 @@ import (
gitopsv1alpha1 "freeleaps.com/gitops/initializer/api/v1alpha1"
"freeleaps.com/gitops/initializer/internal/controller"
"freeleaps.com/gitops/initializer/internal/repo"
// +kubebuilder:scaffold:imports
)
@ -145,10 +147,48 @@ func main() {
os.Exit(1)
}
// read configurations from the environment
gitopsRepoName := os.Getenv("FREELEAPS_GITOPS_REPO_NAME")
gitopsProjectName := os.Getenv("FREELEAPS_GITOPS_PROJECT_NAME")
gitopsBranchName := os.Getenv("FREELEAPS_GITOPS_BRANCH_NAME")
gitopsRepoPAT := os.Getenv("FREELEAPS_GITOPS_REPO_PAT")
gitopsOrganizationUrl := os.Getenv("FREELEAPS_GITOPS_ORGANIZATION_URL")
if gitopsRepoName == "" || gitopsProjectName == "" || gitopsBranchName == "" || gitopsRepoPAT == "" || gitopsOrganizationUrl == "" {
setupLog.Error(nil, "missing environment variables")
os.Exit(1)
}
// create repo manager
repoMgr, ok := repo.GetPlugin("azure-devops")
if !ok {
setupLog.Error(nil, "repo plugin not found")
os.Exit(1)
}
// create plugin config
pluginConfig := repo.NewPluginConfig()
pluginConfig.Set("pat", gitopsRepoPAT)
pluginConfig.Set("organizationUrl", gitopsOrganizationUrl)
// initialize repo manager
err = repoMgr.Initialize(context.Background(),
repo.WithProject(gitopsProjectName),
repo.WithName(gitopsRepoName),
repo.WithBranch(gitopsBranchName),
repo.WithAuthor("freeleaps-gitops-bot", "gitops@mathmast.com"),
repo.WithPluginConfig(pluginConfig),
)
if err != nil {
setupLog.Error(err, "failed to initialize repo manager")
os.Exit(1)
}
if err = (&controller.ProjectInitializeReconciler{
Log: ctrl.Log.WithName("controllers").WithName("ProjectInitialize"),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: ctrl.Log.WithName("controllers").WithName("ProjectInitialize"),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
GitOpsRepoManager: repoMgr,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ProjectInitialize")
os.Exit(1)

View File

@ -12,7 +12,6 @@ spec:
name: magicleaps-alpha
project: magicleaps
namespace: magicleaps-alpha
branch: develop
syncPolicy:
automated: true
prune: true
@ -21,6 +20,7 @@ spec:
plugins: azure-devops
project: magicleaps
name: magicleaps
branch: develop
pluginConfig:
organizationUrl: https://dev.azure.com/freeleaps
pat: 3E9J1v0si6HRvyc1RSRgT791W9ZvMi4kBmrznfcIXB8mq6Trj9VkJQQJ99BBACAAAAArj0WRAAASAZDO32n4

View File

@ -31,14 +31,16 @@ import (
gitopsv1alpha1 "freeleaps.com/gitops/initializer/api/v1alpha1"
"freeleaps.com/gitops/initializer/internal/helm"
"freeleaps.com/gitops/initializer/internal/metrics"
"freeleaps.com/gitops/initializer/internal/repo"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
// ProjectInitializeReconciler reconciles a ProjectInitialize object
type ProjectInitializeReconciler struct {
client.Client
Scheme *runtime.Scheme
Log logr.Logger
Scheme *runtime.Scheme
Log logr.Logger
GitOpsRepoManager repo.RepoManager
}
// +kubebuilder:rbac:groups=gitops.freeleaps.com,resources=projectinitializes,verbs=get;list;watch;create;update;patch;delete
@ -107,6 +109,8 @@ func (r *ProjectInitializeReconciler) Reconcile(ctx context.Context, req ctrl.Re
}
}
// reconcile Jenkinsfile for the ProjectInitialize instance
return ctrl.Result{}, nil
}
@ -139,6 +143,31 @@ func (r *ProjectInitializeReconciler) reconcileHelmPackage(ctx context.Context,
return err
}
requests := make([]repo.ChangeRequest, 0)
// commit generated files to gitops repo
err = generator.Walk(func(f *helm.HelmGeneratedFile) error {
if blob, err := f.ReadAll(); err != nil {
return err
} else {
req := repo.ChangeRequest{
Path: fmt.Sprintf("%s/helm-pkg/%s", cr.ObjectMeta.GetName(), f.QualifiedPath),
Content: blob,
Operation: repo.OpCreate,
}
requests = append(requests, req)
}
return nil
})
if err != nil {
return err
}
// apply changes to gitops repo
if err := r.GitOpsRepoManager.ApplyChanges(ctx, requests, "chore(freeleaps-gitios-initializer): commit generated helm package"); err != nil {
return err
}
return nil
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"text/template"
"freeleaps.com/gitops/initializer/api/v1alpha1"
@ -18,6 +19,7 @@ type HelmGenerator struct {
io.Closer
Instance *v1alpha1.ProjectInitialize
workingDir string
generated bool
}
const leftDelim = "[["
@ -205,9 +207,36 @@ func (hg *HelmGenerator) Generate() error {
return err
}
}
hg.generated = true
return nil
}
type HelmGeneratedFile struct {
QualifiedPath string
}
func (hgf *HelmGeneratedFile) ReadAll() ([]byte, error) {
return os.ReadFile(hgf.QualifiedPath)
}
type HelmGeneratedFileCallback func(f *HelmGeneratedFile) error
func (hg *HelmGenerator) Walk(cb HelmGeneratedFileCallback) error {
if !hg.generated {
return errors.New("helm package not generated")
}
return filepath.Walk(hg.workingDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return cb(&HelmGeneratedFile{QualifiedPath: path})
}
return nil
})
}
func (hg *HelmGenerator) Close() error {
if hg.workingDir != "" {
return os.RemoveAll(hg.workingDir)

View File

@ -0,0 +1,261 @@
package helm
import (
"testing"
"freeleaps.com/gitops/initializer/api/v1alpha1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var pathType = networkingv1.PathTypeImplementationSpecific
var testCase = v1alpha1.ProjectInitialize{
ObjectMeta: metav1.ObjectMeta{
Name: "magicleaps-alpha",
},
Spec: v1alpha1.ProjectInitializeSpec{
Repository: &v1alpha1.RepositorySpec{
Plugin: "azure-devops",
PluginConfig: map[string]string{
"pat": "3E9J1v0si6HRvyc1RSRgT791W9ZvMi4kBmrznfcIXB8mq6Trj9VkJQQJ99BBACAAAAArj0WRAAASAZDO32n4",
"organizationUrl": "https://dev.azure.com/freeleaps",
},
RegisterToArgo: true,
Project: "magicleaps",
Name: "magicleaps",
Type: v1alpha1.Monorepo,
Branch: "develop",
},
Helm: &v1alpha1.HelmSpec{
Generate: true,
Metadata: v1alpha1.HelmMetadataSpec{
Name: "magicleaps",
Description: "A Helm Chart of magicleaps, powered by Freeleaps.",
},
Global: v1alpha1.HelmGlobalValuesSpec{
Registry: "docker.io",
Repository: "sunzhenyucn",
},
Components: []v1alpha1.HelmComponentSpec{
{
Name: "frontend",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8080,
Protocol: corev1.ProtocolTCP,
},
},
Replicas: 1,
Resources: v1alpha1.HelmComponentResourceRequirementsSpec{
Limits: v1alpha1.HelmComponentResourceSettingSpec{
CPU: "100m",
Memory: "256Mi",
},
Requests: v1alpha1.HelmComponentResourceSettingSpec{
CPU: "50m",
Memory: "128Mi",
},
},
Image: v1alpha1.HelmComponentImageSpec{
Repository: "sunzhenyucn",
Name: "magicleaps-frontend",
Tag: "1.0.0",
ImagePullPolicy: corev1.PullIfNotPresent,
},
Probes: v1alpha1.HelmComponentProbesSpec{
Liveness: v1alpha1.HelmComponentProbeSpec{
Type: v1alpha1.HTTPGet,
Config: v1alpha1.HelmComponentProbeConfigSpec{
Path: "/",
Port: 8080,
},
},
Readiness: v1alpha1.HelmComponentProbeSpec{
Type: v1alpha1.HTTPGet,
Config: v1alpha1.HelmComponentProbeConfigSpec{
Path: "/",
Port: 8080,
},
},
},
Services: []v1alpha1.HelmComponentServiceSpec{
{
Name: "magicleaps-frontend-service",
Type: corev1.ServiceTypeClusterIP,
Port: 80,
TargetPort: 8080,
},
},
Ingresses: []v1alpha1.HelmComponentIngressSpec{
{
Name: "magicleaps-frontend-ingress",
Class: "nginx",
Host: "alpha.magicleaps.mathmast.com",
Rules: []networkingv1.HTTPIngressPath{
{
Path: "/*",
PathType: &pathType,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "magicleaps-frontend-service",
Port: networkingv1.ServiceBackendPort{
Number: 80,
},
},
},
},
},
TLS: v1alpha1.HelmComponentIngressTLSSpec{
Exists: false,
Name: "magicleaps-alpha-frontend-ingress-tls",
IssuerRef: &cmmeta.ObjectReference{
Name: "mathmast-dot-com",
Kind: "ClusterIssuer",
Group: "cert-manager.io",
},
},
},
},
Configs: []v1alpha1.HelmComponentConfigSpec{
{
Name: "magicleaps-frontend-config",
Type: v1alpha1.EnvironmentSecret,
Data: []corev1.EnvVar{
{
Name: "TZ",
Value: "America/Settle",
},
},
},
},
},
{
Name: "backend",
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8081,
Protocol: corev1.ProtocolTCP,
},
},
Replicas: 1,
Resources: v1alpha1.HelmComponentResourceRequirementsSpec{
Limits: v1alpha1.HelmComponentResourceSettingSpec{
CPU: "200m",
Memory: "512Mi",
},
Requests: v1alpha1.HelmComponentResourceSettingSpec{
CPU: "100m",
Memory: "256Mi",
},
},
Image: v1alpha1.HelmComponentImageSpec{
Repository: "sunzhenyucn",
Name: "magicleaps-backend",
Tag: "1.0.0",
ImagePullPolicy: corev1.PullIfNotPresent,
},
Probes: v1alpha1.HelmComponentProbesSpec{
Liveness: v1alpha1.HelmComponentProbeSpec{
Type: v1alpha1.HTTPGet,
Config: v1alpha1.HelmComponentProbeConfigSpec{
Path: "/api/_/probe/liveness",
Port: 8081,
},
},
Readiness: v1alpha1.HelmComponentProbeSpec{
Type: v1alpha1.HTTPGet,
Config: v1alpha1.HelmComponentProbeConfigSpec{
Path: "/api/_/probe/readiness",
Port: 8081,
},
},
},
Services: []v1alpha1.HelmComponentServiceSpec{
{
Name: "magicleaps-backend-service",
Type: corev1.ServiceTypeClusterIP,
Port: 8081,
TargetPort: 8081,
},
},
Ingresses: []v1alpha1.HelmComponentIngressSpec{},
Configs: []v1alpha1.HelmComponentConfigSpec{
{
Name: "magicleaps-backend-config",
Type: v1alpha1.EnvironmentSecret,
Data: []corev1.EnvVar{
{
Name: "TZ",
Value: "America/Settle",
},
{
Name: "MONGODB_HOST",
Value: "localhost",
},
{
Name: "MONGODB_PORT",
Value: "27017",
},
{
Name: "MONGODB_NAME",
Value: "interview",
},
{
Name: "EMAIL_USER",
Value: "your@freeleaps.com",
},
{
Name: "EMAIL_PASSWORD",
Value: "your-password",
},
{
Name: "SUPER_ADMIN",
Value: "your@email.com",
},
{
Name: "TWILIO_ACCOUNT_SID",
Value: "",
},
{
Name: "TWILIO_AUTH_TOKEN",
Value: "",
},
{
Name: "EVELUATION_TASK_FOLDER_BASE",
Value: "temp/interview/eveluation_task/",
},
{
Name: "LOG_DIR",
Value: "logs",
},
{
Name: "APP_LOG_FILE",
Value: "app.log",
},
{
Name: "APP_LOG_LEVEL",
Value: "INFO",
},
},
},
},
},
},
},
},
}
func TestGenerate(t *testing.T) {
gen := &HelmGenerator{
Instance: &testCase,
}
if err := gen.Generate(); err != nil {
t.Error(err)
}
}

View File

@ -1,109 +0,0 @@
global:
registry: docker.io
repository: test
nodeSelector:
beta.kubernetes.io/os: linux
frontend:
replicas: 1
image:
registry: docker.io
repository: test
name: frontend
tag: latest
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
probes:
liveness:
type: httpGet
config:
path: /healthz
port: 8080
readiness:
type: httpGet
config:
path: /readyz
port: 8080
services:
- name: frontend
type: ClusterIP
port: 8080
targetPort: 8080
ingresses:
- name: frontend
host: test.com
class: nginx
rules:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: frontend
port:
number: 8080
tls:
exists: false
issuerRef:
name: test-issuer
kind: ClusterIssuer
name: test-cert
configs:
testEnvVar: "test"
mongoDb: "mongodb"
backend:
replicas: 1
image:
registry: docker.io
repository: test
name: backend
tag: latest
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
probes:
liveness:
type: httpGet
config:
path: /healthz
port: 8081
readiness:
type: httpGet
config:
path: /readyz
port: 8081
services:
- name: backend
type: ClusterIP
port: 8081
targetPort: 8081
ingresses:
- name: backend
host: test.com
class: nginx
rules:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: backend
port:
number: 8080
tls:
exists: false
issuerRef:
name: test-issuer
kind: ClusterIssuer
name: test-cert
configs:
testEnvVar1: "test"
mongoDb1: "mongodb"

View File

@ -0,0 +1,96 @@
package jenkins
import (
"embed"
"fmt"
"html/template"
"io"
"os"
"freeleaps.com/gitops/initializer/api/v1alpha1"
)
//go:embed templates/*
var embeddedTemplates embed.FS
type JenkinsfileGenerator struct {
io.Closer
Instance *v1alpha1.ProjectInitialize
workingDir string
generated bool
}
const leftDelim = "[["
const rightDelim = "]]"
func (jg *JenkinsfileGenerator) prepareWorkingDir() error {
path, err := os.MkdirTemp("", fmt.Sprintf("jenkinsfile-gen-%s-*", jg.Instance.Name))
if err != nil {
return fmt.Errorf("failed to create jenkinsfile generator temp dir: %w", err)
}
jg.workingDir = path
return nil
}
func (jg *JenkinsfileGenerator) readTemplate(name string) (*template.Template, error) {
tpl := template.New(name)
templateBytes, err := embeddedTemplates.ReadFile(name)
if err != nil {
return nil, fmt.Errorf("failed to read template %s: %w", name, err)
}
tpl, err = tpl.
Delims(leftDelim, rightDelim).
Parse(string(templateBytes))
if err != nil {
return nil, fmt.Errorf("failed to parse template %s: %w", name, err)
}
return tpl, nil
}
func (jg *JenkinsfileGenerator) Generate() error {
if jg.Instance == nil {
return fmt.Errorf("instance is nil")
}
if err := jg.prepareWorkingDir(); err != nil {
return err
}
tpl, err := jg.readTemplate("templates/Jenkinsfile.tpl")
if err != nil {
return err
}
jenkinsfileFd, err := os.Create(jg.workingDir + "/Jenkinsfile")
if err != nil {
return fmt.Errorf("failed to create Jenkinsfile: %w", err)
}
defer jenkinsfileFd.Close()
if err := tpl.Execute(jenkinsfileFd, jg.Instance.Spec); err != nil {
return fmt.Errorf("failed to render Jenkinsfile with template: %w", err)
}
jg.generated = true
return nil
}
func (jg *JenkinsfileGenerator) Close() error {
if jg.workingDir != "" {
return os.RemoveAll(jg.workingDir)
}
return nil
}
func (jg *JenkinsfileGenerator) ReadGeneratedJenkinsfile() ([]byte, error) {
if !jg.generated {
return nil, fmt.Errorf("jenkinsfile not generated")
}
jenkinsfileBytes, err := os.ReadFile(jg.workingDir + "/Jenkinsfile")
if err != nil {
return nil, fmt.Errorf("failed to read generated Jenkinsfile: %w", err)
}
return jenkinsfileBytes, nil
}

View File

@ -0,0 +1,28 @@
library 'first-class-pipeline'
executeFreeleapsPipeline {
serviceName = [[ .ServiceName | quote ]]
environmentSlug = [[ .EnvironmentSlug | quote ]]
serviceGitBranch = [[ .Spec.Repository.Branch | quote ]]
serviceGitRepo = [[ .GitRepoURL | quote ]]
serviceGitRepoType = [[ .Spec.Repository.Type | quote ]]
serviceGitCredentialsId = [[ .JenkinsGitCredentialsId | quote ]]
executeMode = [[ .Spec.ContinuousIntegrationSpec.ExecuteMode | quote ]]
commitMessageLintEnabled = [[ .Spec.ContinuousIntegrationSpec.CommitMessageLintEnabled ]]
components = [
[[- range $component := .Spec.ContinuousIntegrationSpec.Components ]]
[
name: [[ $component.Name | quote ]],
root: [[ $component.Root | quote ]],
language: [[ $component.Language | quote ]],
dependenciesManager: [[ $component.DependenciesManager | quote ]],
[[- if and (eq $component.DependenciesManager "npm") ($component.NPMPackageJsonFile) ]]
npmPackageJsonFile: [[ $component.NPMPackageJsonFile | quote ]],
[[- end ]]
[[- if $component.BuildCacheEnabled ]]
buildCacheEnabled: [[ $component.BuildCacheEnabled ]],
[[- end ]]
]
[[- end ]]
]
}

View File

@ -7,9 +7,11 @@ import (
var (
pluginsMu sync.RWMutex
plugins = make(map[string]RepoManager)
plugins = make(map[string]PluginCreateFunc)
)
type PluginCreateFunc func() RepoManager
type PluginConfig interface {
Get(key string) (any, error)
Set(key string, value any)
@ -37,25 +39,25 @@ func NewPluginConfig() PluginConfig {
}
}
func RegisterPlugin(name string, plugin RepoManager) {
func RegisterPlugin(name string, fun PluginCreateFunc) {
pluginsMu.Lock()
defer pluginsMu.Unlock()
if plugin == nil {
panic("plugin cannot be nil")
if fun == nil {
panic("plugin create func cannot be nil")
}
if old, exists := plugins[name]; exists {
fmt.Printf("WARNING: plugin '%s' already registered (%T), overwriting\n", name, old)
}
plugins[name] = plugin
plugins[name] = fun
}
func GetPlugin(name string) (RepoManager, bool) {
pluginsMu.RLock()
defer pluginsMu.RUnlock()
plugin, exists := plugins[name]
return plugin, exists
fun, exists := plugins[name]
return fun(), exists
}

View File

@ -19,7 +19,9 @@ type AzureDevOpsRepoManager struct {
var _ repo.RepoManager = &AzureDevOpsRepoManager{}
func init() {
repo.RegisterPlugin("azure-devops", &AzureDevOpsRepoManager{})
repo.RegisterPlugin("azure-devops", func() repo.RepoManager {
return &AzureDevOpsRepoManager{}
})
}
// Initialize implements repo.RepoManager.