From 0de82dc961720b9f89d8ebc1bca1f3b6f9afae02 Mon Sep 17 00:00:00 2001 From: Marat Date: Sun, 19 Jun 2022 14:38:04 +0400 Subject: [PATCH] asd --- my-bloody-jenkins/.helmignore | 21 ++ my-bloody-jenkins/Chart.yaml | 17 ++ my-bloody-jenkins/README.md | 192 +++++++++++++ my-bloody-jenkins/logo/jenkins-logo.png | Bin 0 -> 29020 bytes my-bloody-jenkins/templates/NOTES.txt | 39 +++ my-bloody-jenkins/templates/_helpers.tpl | 67 +++++ my-bloody-jenkins/templates/config.yaml | 32 +++ my-bloody-jenkins/templates/deployment.yaml | 224 +++++++++++++++ .../templates/ingress-tls-secret.yaml | 17 ++ my-bloody-jenkins/templates/ingress.yaml | 64 +++++ my-bloody-jenkins/templates/pvc.yaml | 59 ++++ my-bloody-jenkins/templates/rbac.yaml | 58 ++++ my-bloody-jenkins/templates/secret.yaml | 16 ++ my-bloody-jenkins/templates/service.yaml | 40 +++ my-bloody-jenkins/values.yaml | 254 ++++++++++++++++++ 15 files changed, 1100 insertions(+) create mode 100644 my-bloody-jenkins/.helmignore create mode 100644 my-bloody-jenkins/Chart.yaml create mode 100644 my-bloody-jenkins/README.md create mode 100644 my-bloody-jenkins/logo/jenkins-logo.png create mode 100644 my-bloody-jenkins/templates/NOTES.txt create mode 100644 my-bloody-jenkins/templates/_helpers.tpl create mode 100644 my-bloody-jenkins/templates/config.yaml create mode 100644 my-bloody-jenkins/templates/deployment.yaml create mode 100644 my-bloody-jenkins/templates/ingress-tls-secret.yaml create mode 100644 my-bloody-jenkins/templates/ingress.yaml create mode 100644 my-bloody-jenkins/templates/pvc.yaml create mode 100644 my-bloody-jenkins/templates/rbac.yaml create mode 100644 my-bloody-jenkins/templates/secret.yaml create mode 100644 my-bloody-jenkins/templates/service.yaml create mode 100644 my-bloody-jenkins/values.yaml diff --git a/my-bloody-jenkins/.helmignore b/my-bloody-jenkins/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/my-bloody-jenkins/.helmignore @@ -0,0 +1,21 @@ +# 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 +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/my-bloody-jenkins/Chart.yaml b/my-bloody-jenkins/Chart.yaml new file mode 100644 index 0000000..ff89951 --- /dev/null +++ b/my-bloody-jenkins/Chart.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +name: my-bloody-jenkins +version: 0.1.186 +appVersion: "2.332.3-277" +icon: https://raw.githubusercontent.com/odavid/k8s-helm-charts/master/charts/my-bloody-jenkins/logo/jenkins-logo.png +description: > + A Helm chart for my-bloody-jenkins - a self configured jenkins docker image, based on Jenkins LTS. + Inspired by https://github.com/kubernetes/charts/tree/master/stable/jenkins, but better suites https://github.com/odavid/my-bloody-jenkins +sources: + - https://github.com/odavid/my-bloody-jenkins + - https://github.com/odavid/k8s-helm-charts/tree/master/charts/my-bloody-jenkins + - https://github.com/odavid/jenkins-jnlp-slave +maintainers: + - name: odavid + email: ohad.david@gmail.com +home: https://github.com/odavid/my-bloody-jenkins diff --git a/my-bloody-jenkins/README.md b/my-bloody-jenkins/README.md new file mode 100644 index 0000000..9b54ffb --- /dev/null +++ b/my-bloody-jenkins/README.md @@ -0,0 +1,192 @@ +# My Bloody Jenkins + +## Prerequisites Details +* Kubernetes 1.8+ + +## Chart Details +The chart will do the following: +* Deploy [My Bloody Jenkins](https://github.com/odavid/my-bloody-jenkins) +* Manage Configuration in a dedicated ConfigMap +* Configures Jenkins to use a default [k8s jenkins cloud](https://plugins.jenkins.io/kubernetes) +* Optionally expose Jenkins with [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) +* Manages a [Persistent Volume Claim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) for Jenkins Storage +* Optionally mount extenral [secrets](https://kubernetes.io/docs/concepts/configuration/secret/) as volumes to be used within the configuration [See docs](https://github.com/odavid/my-bloody-jenkins/pull/102) +* Optionally mount external [configMaps](https://kubernetes-v1-4.github.io/docs/user-guide/configmap/) to be used as configuration data sources [See docs](https://github.com/odavid/my-bloody-jenkins/pull/102) +* Optionally configures [rbac](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) and a dedicated [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) + + +## Installing the Chart +First add the following repo: + +```shell +helm repo add odavid https://odavid.github.io/k8s-helm-charts +``` + +To install the chart with the release name `jenkins`: +```shell +helm install --name jenkins odavid/my-bloody-jenkins +``` + +To install the chart with a custom configuration values.yml +```shell +helm install --name jenkins odavid/my-bloody-jenkins -f +``` + +## Upgrading the Release +To install the chart with a custom configuration values.yml +```shell +helm upgrade jenkins odavid/my-bloody-jenkins -f +``` + +## Deleting the Chart +```shell +helm delete jenkins +``` + +## Docker Image +By default the chart uses the [latest release of `odavid/my-bloody-jenkins`](https://hub.docker.com/r/odavid/my-bloody-jenkins/tags/) image. +The Helm Chart provides a way to use different repo or tags: +* `image.repository` - by default `odavid/my-bloody-jenkins` +* `image.tag` +* `image.pullPolicy` - by default `IfNotPresent` +* `image.imagePullSecret` - not set by default + + +## CPU and Memory Resources +The Helm chart comes with support for configured resource requests and limits. +By default these values are commented out. +It is __highly__ recommended to change this behavior on a production deployment. Also the Helm Chart provides a way to control Jenkins Java Memory Opts. When using Jenkins in production, you will need to set the values that suites your needs. + +## Persistence +By default the helm chart allocates a 20gb volume for jenkins storage. +The chart provides the ability to control: +* `persistence.jenkinsHome.enabled` - if set to false, jenkins home will be using empty{} volume instead of persistentVolumeClaim. Default is `true` +* `persistence.jenkinsHome.size` - the managed volume size +* `persistence.jenkinsHome.storageClass` - If set to `"-"`, then storageClass: `""`, which disables dynamic provisioning. If undefined (the default) or set to null, no storageClass spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) +* `persistence.jenkinsHome.existingClaim` - if provided, jenkins storage will be stored on an manually managed persistentVolumeClaim +* `persistence.jenkinsHome.annotations` - annotations that will be added to the managed persistentVolumeClaim + +## Secrets +My Bloody Jenkins natively supports [environment variable substitution](https://github.com/odavid/my-bloody-jenkins#environment-variable-substitution-and-remove-master-env-vars) within its configuration files. +The Helm Chart provides a simple way to map [k8s secrets] in dedicated folders that will be later on used as environment variables datasource. + +In order to use this feature, you will need to create external secrets and then use: `envSecrets` property to add these secrets to the search order. +For example: +```shell +echo -n 'admin' > ./username +echo -n 'password' > ./password +kubectl create secret generic my-jenkins-secret --from-file=./username --from-file=./password +``` + +Then add this secret to values.yml: +```yaml +envSecrets: + - my-jenkins-secret +``` +Now, you can refer these secrets as environmnet variables: +* `MY_JENKINS_SECRET_USERNAME` +* `MY_JENKINS_SECRET_PASSWORD` + +See [Support multiple data sources and secrets from files](https://github.com/odavid/my-bloody-jenkins/pull/102) for more details + +The chart also support creating a dedicated k8s secret, which all its keys will become `JENKINS_SECRET_`. In order to use it, you will need to provided a key/value dict under the `secrets` value + +## Managed Configuration and additional ConfigMaps +My Bloody Jenkins natively supports watching multiple config data sources and merge them into one config top to bottom +The Helm Chart provides a way to define a `managedConfig` yaml within the chart values.yml as well as add additional external `configMaps` that will be merged/override the default configuration. + +See [Support multiple data sources and secrets from files](https://github.com/odavid/my-bloody-jenkins/pull/102) for more details +The `managedConfig` is mounted as `/var/jenkins_managed_config/jenkins-config.yml` and contains the `managedConfig` yaml contents + +Additional `configMaps` list are mounted as `/var/jenkins_config/` within the container and are merged with the `managedConfig` + +## Default K8S Jenkins Cloud for provisioning slaves within k8s +By default the Helm Chart Configures a [kubernetes cloud](https://plugins.jenkins.io/kubernetes) with a simple jnlp slave template. +For disabling this behavior, you need to set `defaultK8sCloud.enabled` to `false` +The following attributes can control the default template: +* `defaultK8sCloud.name` - the name of the k8s cloud - default (`k8s`) +* `defaultK8sCloud.labels` - list of agent labels that are used to provision the node - e.g. ```node(labels){}``` pipeline step - default (`["generic"]`) +* `defaultK8sCloud.jvmArgs` - JVM Args for the JNLP Slave - default (`"-Xmx1g"`) +* `defaultK8sCloud.remoteFs` - JNLP Remote FS - default (`"/home/jenkins"`) +* `defaultK8sCloud.image` - JNLP Slave Image - default (`"odavid/jenkins-jnlp-slave:latest"`) + +## Configuration + +The following table lists the configurable parameters of the chart and their default values. + +| Parameter | Description | Default | +|---------------------------|-----------------------------------|----------------------------------------------------------| +| `managedConfig` | `My Bloody Jenkins` Configuration yaml - See [Configuration Reference](https://github.com/odavid/my-bloody-jenkins#configuration-reference) | +| `defaultK8sCloud.enabled` | If `true` a default k8s jenkins cloud will be configured to enable automatic slave provisioning | `true` +| `defaultK8sCloud.name` | The name of the default k8s cloud | `k8s` +| `defaultK8sCloud.labels` | List of labels that mark the k8s provisioned slaves, use `node(label){}` within pipeline | `["generic"]` +| `defaultK8sCloud.jvmArgs` | Default JVM Args to pass to the jnlp slave of the k8s cloud | `-Xmx1g` +| `defaultK8sCloud.remoteFs` | The remoteFS of the JNLP Slave | `/home/jenkins` +| `defaultK8sCloud.image` | The docker image of the JNLP Slave | `odavid/jenkins-jnlp-slave:latest` +| `image.repository` | `My Bloody Jenkins` Docker Image | `odavid/my-bloody-jenkins` +| `image.tag` | `My Bloody Jenkins` Docker Image Tag | `2.121.1-62` +| `image.pullPolicy` | Image Pull Policy | `IfNotPresent` +| `image.imagePullSecrets` | Docker registry pull secret | +| `service.type` | Service Type | `LoadBalanacer` +| `service.externalTrafficPolicy` | externalTrafficPolicy | +| `service.annotations` | Service Annotations | `{}` +| `service.loadBalancerSourceRanges` | Array Of IP CIDR ranges to whitelist (Only if service type is `LoadBalancer`) | +| `service.loadBalancerIP` | Service Load Balancer IP Address (Only if service type is `LoadBalancer`) | +| `ingress.enabled` | If `true` Ingress will be created | `false` +| `ingress.httpProtocol` | Change to https if the ingress uses tls or you are using external tls termination using annotations | `http` +| `ingress.path` | Ingress Path (Only if ingress is enabled)| `/` +| `ingress.additionalRules` | Additional Ingress Rules| `[]` that will be appended to the actual ingress rule. +| `ingress.preAdditionalRules` | Additional Ingress Rules| `[]` that will be pre-appended to the actual ingress rule. Useful when using alb ingress class with [actions](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/ingress/annotation/#actions) +| `ingress.annotations` | Ingress Annoations| `{}` +| `ingress.labels` | Ingress Labels| `{}` +| `ingress.hostname` | Ingress Hostname | +| `ingress.ingressClassName` | Ingress Class Name | +| `ingress.pathType` | Ingress Path Type | `Prefix` +| `ingress.tls.secretName` | Ingress TLS Secret Name - if provided, the ingress will terminate TLS using the certificate and private key in this secret. This setting is mutually exclusive with ingress.tls.certificate and ingress.tls.privateKey| +| `ingress.tls.certificate` | Ingress TLS Certificate - if provided, the ingress will use this certificate. Use in conjunction with ingress.tls.privateKey| +| `ingress.tls.privateKey` | Ingress TLS private key - if provided, the ingress will use this private key. Use in conjunction with ingress.tls.certificate | +| `rbac.create` | If `true` - a ServiceAccount, and a Role will be created| `true` +| `rbac.createServiceAccount` | If `createServiceAccount` = `false`, and `rbac.create` = `true`, the chart will only use the `rbac.serviceAaccountName` within RoleBindings | true +| `rbac.serviceAccountName` | Ignored if createServiceAccount = true | `default` +| `rbac.serviceAccount.annotations` | Specify ServiceAccount annotations | {} +| `rbac.clusterWideAccess` | If `true` - A ClusterRole will be created instead of Role - relevant only if `rbac.create` is `true`| `false` +| `resources.requests.cpu` | Initial CPU Request | +| `resources.requests.memory` | Initial Memory Request | +| `resources.limits.cpu` | CPU Limit | +| `resources.limits.memory` | Memory Limit | +| `readinessProbe.timeoutSeconds` | Readiness Probe Timeout in seconds | `5` +| `readinessProbe.initialDelaySeconds` | Readiness Probe Initial Delay in seconds | `5` +| `readinessProbe.periodSeconds` | Readiness Probe - check for readiess every `X` seconds | `5` +| `readinessProbe.failureThreshold` | Readiness Probe - Mark the pod as not ready for traffic after `X` consecutive failures | `3` +| `livenessProbe.timeoutSeconds` | Liveness Probe Timeout in seconds | `5` +| `livenessProbe.initialDelaySeconds` | Liveness Probe Initial Delay in seconds - a high value since it takes time to start| `600` +| `livenessProbe.periodSeconds` | Liveness Probe - check for liveness every `X` seconds | `5` +| `livenessProbe.failureThreshold` | Liveness Probe - Kill the pod after `X` consecutive failures | `3` +| `persistence.mountDockerSocket` | If `true` - `/var/run/docker.sock` will be mounted | `true` +| `persistence.jenkinsHome.enabled` | If `true` - Jenkins Storage will be persistent | `true` +| `persistence.jenkinsHome.existingClaim` | External Jenkins Storage PesistentVolumeClaim - if set, then no volume claim will be created by the Helm Chart| +| `persistence.jenkinsHome.annotations` | Jenkins Storage PesistentVolumeClaim annotations | `{}` +| `persistence.jenkinsHome.accessMode` | Jenkins Storage PesistentVolumeClaim accessMode | `ReadWriteOnce` +| `persistence.jenkinsHome.size` | Jenkins Storage PesistentVolumeClaim size | `20Gi` +| `persistence.jenkinsHome.storageClass` | External Jenkins Storage PesistentVolumeClaim | If set to `"-"`, then storageClass: `""`, which disables dynamic provisioning. If undefined (the default) or set to null, no storageClass spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) +| `persistence.jenkinsWorkspace.enabled` | If `true` - Jenkins Workspace Storage will be persistent | `false` +| `persistence.jenkinsWorkspace.existingClaim` | External Jenkins Workspace Storage PesistentVolumeClaim - if set, then no volume claim will be created by the Helm Chart| +| `persistence.jenkinsWorkspace.annotations` | Jenkins Workspace Storage PesistentVolumeClaim annotations | `{}` +| `persistence.jenkinsWorkspace.accessMode` | Jenkins Workspace Storage PesistentVolumeClaim accessMode | `ReadWriteOnce` +| `persistence.jenkinsWorkspace.size` | Jenkins Workspace Storage PesistentVolumeClaim size | `8Gi` +| `persistence.jenkinsWorkspace.storageClass` | External Jenkins Workspace Storage PesistentVolumeClaim | If set to `"-"`, then storageClass: `""`, which disables dynamic provisioning. If undefined (the default) or set to null, no storageClass spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) +| `podAnnotations` | Additional Pod Annotations | `{}` +| `persistence.volumes` | Additional volumes to be included within the Deployments | +| `persistence.mounts` | Additional mounts to be mounted to the container | +| `nodeSelector` | Node Selector | `{}` +| `tolerations` | Tolerations | `[]` +| `securityContxet` | Security Context for jenkins pod | `{}` +| `affinity` | Affinity | `{}` +| `env` | Additional Environment Variables to be passed to the container - format `key`: `value` | +| `secret` | A dict containing KEY/VALUE pairs. Each pair will become an environment variable `JENKINS_SECRET_`, if the `secrets` dict is not empty a k8s secret will be created| +| `envSecrets` | List of external secret names to be mounted as env secrets - see [Docs](https://github.com/odavid/my-bloody-jenkins/pull/102) | +| `configMaps` | List of external config maps to be used as configuration files - see [Docs](https://github.com/odavid/my-bloody-jenkins/pull/102) | +| `jenkinsAdminUser` | The name of the admin user - must be a valid user within the [Jenkins Security Realm](https://github.com/odavid/my-bloody-jenkins#security-section)| `admin` +| `javaMemoryOpts` | Jenkins Java Memory Opts | `-Xmx256m` +| `useHostNetwork` | If true, jenkins master will use hostNetwork | `false` +| `jenkinsURL` | Set the jenkinsURL configuration. If not set and ingress is enabled, then jenkins URL is {{ .Values.ingress.httpProtocol }}://{{ .Values.ingress.hostname }}{{ .Values.ingress.path }} | diff --git a/my-bloody-jenkins/logo/jenkins-logo.png b/my-bloody-jenkins/logo/jenkins-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb450cc12038c3e8fc1941d6f25005eb37d1c12 GIT binary patch literal 29020 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 WB|}ylEyzoVP;yerlC|O{VgCbbSQ455 literal 0 HcmV?d00001 diff --git a/my-bloody-jenkins/templates/NOTES.txt b/my-bloody-jenkins/templates/NOTES.txt new file mode 100644 index 0000000..2fcf78c --- /dev/null +++ b/my-bloody-jenkins/templates/NOTES.txt @@ -0,0 +1,39 @@ +1. Get Jenkins URL by running these commands: +{{- if .Values.jenkinsURL }} + {{ .Values.jenkinsURL | quote }} +{{- else if .Values.ingress.enabled }} + {{ .Values.ingress.httpProtocol }}://{{ .Values.ingress.hostname }}{{ .Values.ingress.path }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "my-bloody-jenkins.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc -w --namespace {{ .Release.Namespace }} {{ template "my-bloody-jenkins.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "my-bloody-jenkins.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') + echo http://$SERVICE_IP:{{ default (include "my-bloody-jenkins.httpPort" .) .Values.service.httpPort }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "my-bloody-jenkins.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:8080 +{{- end }} + +2. To watch Jenkins logs, run the following command: +export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "my-bloody-jenkins.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") +kubectl logs -f --namespace {{ .Release.Namespace }} $POD_NAME + +{{- if not .Values.persistence.jenkinsHome.enabled }} + +#################################################################################### +# WARNING: Persistent is not enabled!!! +# In order to enable persistent, please set persistence.jenkinsHome.enabled to 'true' +#################################################################################### +{{- end}} + +{{- if not .Values.rbac.create }} + +#################################################################################### +# WARNING: RBAC is not enabled +# In order to enable RBAC, please set rbac.create to 'true' +#################################################################################### +{{- end }} diff --git a/my-bloody-jenkins/templates/_helpers.tpl b/my-bloody-jenkins/templates/_helpers.tpl new file mode 100644 index 0000000..ff63a4f --- /dev/null +++ b/my-bloody-jenkins/templates/_helpers.tpl @@ -0,0 +1,67 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "my-bloody-jenkins.name" -}} +{{- default .Chart.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 "my-bloody-jenkins.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 "my-bloody-jenkins.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create pvc claim names +*/}} +{{- define "my-bloody-jenkins.jenkinsHome.claimName" -}} +{{- printf "%s-jenkins-home" (include "my-bloody-jenkins.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "my-bloody-jenkins.jenkinsWorkspace.claimName" -}} +{{- printf "%s-jenkins-workspace" (include "my-bloody-jenkins.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{/* +Define default values +*/}} +{{- define "my-bloody-jenkins.httpPort" -}} +{{- 8080 -}} +{{- end -}} + +{{- define "my-bloody-jenkins.jnlpPort" -}} +{{- 50000 -}} +{{- end -}} + +{{- define "my-bloody-jenkins.sshdPort" -}} +{{- 16022 -}} +{{- end -}} + +{{- define "my-bloody-jenkins.persistentVolumeClaimName" -}} +{{- .Values.persistenceExistingClaim | default (include "my-bloody-jenkins.fullname" .) -}} +{{- end -}} + +{{- define "my-bloody-jenkins.tlsSecretName" -}} +{{- printf "%s-tls-secret" (include "my-bloody-jenkins.fullname" .) -}} +{{- end -}} diff --git a/my-bloody-jenkins/templates/config.yaml b/my-bloody-jenkins/templates/config.yaml new file mode 100644 index 0000000..31e6ea1 --- /dev/null +++ b/my-bloody-jenkins/templates/config.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "my-bloody-jenkins.fullname" . }} + labels: + app: {{ template "my-bloody-jenkins.name" . }} + chart: {{ template "my-bloody-jenkins.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +data: + jenkins-config.yml: |- +{{- if .Values.managedConfig }} +{{ toYaml .Values.managedConfig | indent 4 }} +{{- end }} + + k8s-default-cloud.yml: |- +{{- if and (.Values.defaultK8sCloud) (.Values.defaultK8sCloud.enabled) }} + clouds: + {{ .Values.defaultK8sCloud.name | default "k8s" }}: + type: kubernetes + jenkinsUrl: http://{{ include "my-bloody-jenkins.fullname" . }}:8080 + namespace: {{ .Release.Namespace }} + templates: + - name: kubeslave + image: {{ .Values.defaultK8sCloud.slaveImage | default "odavid/jenkins-jnlp-slave:latest" }} + labels: +{{ toYaml (default .Values.defaultK8sCloud.labels) | indent 14 }} + remoteFs: {{ .Values.defaultK8sCloud.remoteFs | default "/home/jenkins" }} + jvmArgs: {{ .Values.defaultK8sCloud.jvmArgs | default "-Xmx1g" }} + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' +{{- end }} diff --git a/my-bloody-jenkins/templates/deployment.yaml b/my-bloody-jenkins/templates/deployment.yaml new file mode 100644 index 0000000..a513d2d --- /dev/null +++ b/my-bloody-jenkins/templates/deployment.yaml @@ -0,0 +1,224 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "my-bloody-jenkins.fullname" . }} + labels: + app: {{ template "my-bloody-jenkins.name" . }} + chart: {{ template "my-bloody-jenkins.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: 1 + strategy: + type: Recreate + rollingUpdate: null + selector: + matchLabels: + app: {{ template "my-bloody-jenkins.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "my-bloody-jenkins.name" . }} + release: {{ .Release.Name }} + {{- if .Values.podAnnotations }} + annotations: +{{ toYaml .Values.podAnnotations | indent 8 }} + {{- end }} + spec: + {{- if and .Values.useHostNetwork }} + hostNetwork: true + {{- end }} + {{- with .Values.securityContext }} + securityContext: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if and .Values.rbac .Values.rbac.create }} + serviceAccountName: {{ if .Values.rbac.createServiceAccount }}{{ (include "my-bloody-jenkins.fullname" .) | quote }}{{ else }}{{ .Values.rbac.serviceAccountName | quote }}{{ end }} + {{- end }} + {{- with .Values.image.imagePullSecrets }} + imagePullSecrets: +{{ toYaml . | indent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ template "my-bloody-jenkins.httpPort" . }} + protocol: TCP + - name: jnlp + containerPort: {{ template "my-bloody-jenkins.jnlpPort" . }} + protocol: TCP + - name: sshd + containerPort: {{ template "my-bloody-jenkins.sshdPort" . }} + protocol: TCP + + {{- if .Values.livenessProbe }} + livenessProbe: + httpGet: + path: /login + port: http + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + + {{- end }} + + {{- if .Values.readinessProbe }} + readinessProbe: + httpGet: + path: /login + port: http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + + {{- if .Values.resources }} + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- end }} + + env: + - name: K8S_NAMESPACE + value: "{{ .Release.Namespace }}" + {{- if .Values.javaMemoryOpts }} + - name: JAVA_OPTS_MEMORY + value: {{ .Values.javaMemoryOpts | quote }} + {{- end }} + {{- if .Values.jenkinsAdminUser }} + - name: JENKINS_ENV_ADMIN_USER + value: {{ .Values.jenkinsAdminUser | quote }} + {{- end }} + - name: JENKINS_ENV_CONFIG_YML_URL + value: file:///var/jenkins_managed_config/k8s-default-cloud.yml,file:///var/jenkins_managed_config/jenkins-config.yml{{ range $i, $configMapName := .Values.configMaps }},file:///var/jenkins_config/{{ $configMapName }}{{ end }} + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.jenkinsURL }} + - name: JENKINS_ENV_JENKINS_URL + value: {{ .Values.jenkinsURL | quote }} + {{- else if .Values.ingress.enabled }} + - name: JENKINS_ENV_JENKINS_URL + value: {{ .Values.ingress.httpProtocol }}://{{ .Values.ingress.hostname }}{{ .Values.ingress.path }} + {{- end }} + - name: ENVVARS_DIRS + value: /var/jenkins_secrets/JENKINS_SECRET{{ range $i, $name := .Values.envSecrets }},/var/jenkins_secrets/{{ $name }}{{ end }} + - name: JENKINS_ENV_HOST_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + + volumeMounts: +{{- if and .Values.persistence .Values.persistence.mounts }} +{{ toYaml .Values.persistence.mounts | indent 12 }} +{{- end }} + - mountPath: /var/jenkins_home + name: jenkins-home + readOnly: false + + - mountPath: /jenkins-workspace-home + name: jenkins-workspace-home + readOnly: false + {{- if .Values.persistence.mountDockerSocket }} + + - mountPath: /var/run/docker.sock + name: docker-socket + readOnly: false + {{- end }} + + {{/* Using internal secret - each key will become JENKINS_SECRET_${key} */}} + {{- if .Values.secrets }} + - mountPath: /var/jenkins_secrets/JENKINS_SECRET + name: {{ printf "%s-%s" (include "my-bloody-jenkins.fullname" .) "secrets" | quote }} + readOnly: true + {{- end }} + + {{/* Using external secret - each key will become ${SECRET_NAME}_${key} */}} + {{- if .Values.envSecrets }} + {{- range .Values.envSecrets }} + + - mountPath: /var/jenkins_secrets/{{ . }} + name: {{ . | quote }} + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.configMaps }} + {{- range .Values.configMaps }} + + - mountPath: /var/jenkins_config/{{ . }} + name: {{ . | quote }} + readOnly: true + {{- end }} + {{- end }} + + - mountPath: /var/jenkins_managed_config + name: {{ (include "my-bloody-jenkins.fullname" .) | quote }} + readOnly: true + + + + volumes: +{{- if and .Values.persistence .Values.persistence.volumes }} +{{ toYaml .Values.persistence.volumes | indent 8 }} +{{- end }} + {{- if .Values.persistence.mountDockerSocket }} + - name: docker-socket + hostPath: + path: /var/run/docker.sock + {{- end }} + - name: jenkins-home + {{- if and .Values.persistence .Values.persistence.jenkinsHome .Values.persistence.jenkinsHome.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.jenkinsHome.existingClaim | default (include "my-bloody-jenkins.jenkinsHome.claimName" .) }} + {{- else }} + emptyDir: {} + {{- end }} + + - name: jenkins-workspace-home + {{- if and .Values.persistence .Values.persistence.jenkinsWorkspace .Values.persistence.jenkinsWorkspace.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.jenkinsWorkspace.existingClaim | default (include "my-bloody-jenkins.jenkinsWorkspace.claimName" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.envSecrets }} + {{- range .Values.envSecrets }} + - name: {{ . | quote }} + secret: + secretName: {{ . }} + {{- end }} + {{- end }} + {{- if .Values.configMaps }} + {{- range .Values.configMaps }} + - name: {{ . | quote }} + configMap: + name: {{ . | quote }} + {{- end }} + {{- end }} + - name: {{ (include "my-bloody-jenkins.fullname" .) | quote }} + configMap: + name: {{ (include "my-bloody-jenkins.fullname" .) | quote }} + {{- if .Values.secrets }} + - name: {{ printf "%s-%s" (include "my-bloody-jenkins.fullname" .) "secrets" | quote }} + secret: + secretName: {{ (include "my-bloody-jenkins.fullname" .) | quote }} + {{- end }} + + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} diff --git a/my-bloody-jenkins/templates/ingress-tls-secret.yaml b/my-bloody-jenkins/templates/ingress-tls-secret.yaml new file mode 100644 index 0000000..fa6fc2e --- /dev/null +++ b/my-bloody-jenkins/templates/ingress-tls-secret.yaml @@ -0,0 +1,17 @@ +{{- with .Values.ingress.tls }} +{{- if and .privateKey .certificate }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "my-bloody-jenkins.tlsSecretName" $ }} + labels: + app: {{ template "my-bloody-jenkins.name" $ }} + chart: {{ template "my-bloody-jenkins.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} +type: kubernetes.io/tls +data: + tls.crt: {{ .certificate | b64enc }} + tls.key: {{ .privateKey | b64enc }} +{{- end }} +{{- end }} diff --git a/my-bloody-jenkins/templates/ingress.yaml b/my-bloody-jenkins/templates/ingress.yaml new file mode 100644 index 0000000..0efa165 --- /dev/null +++ b/my-bloody-jenkins/templates/ingress.yaml @@ -0,0 +1,64 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "my-bloody-jenkins.fullname" . -}} +{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} +apiVersion: networking.k8s.io/v1 +{{ else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }} +apiVersion: networking.k8s.io/v1beta1 +{{ else }} +apiVersion: extensions/v1beta1 +{{ end -}} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + app: {{ template "my-bloody-jenkins.name" . }} + chart: {{ template "my-bloody-jenkins.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.labels }} +{{ toYaml . | indent 4 }} +{{- end }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.ingressClassName }} + ingressClassName: {{ .Values.ingress.ingressClassName }} +{{- end }} +{{- if .Values.ingress.tls }} + tls: + - hosts: + - {{ .Values.ingress.hostname }} +{{- if .Values.ingress.tls.secretName }} + secretName: {{ .Values.ingress.tls.secretName }} +{{- else if (and .Values.ingress.tls.certificate .Values.ingress.tls.privateKey) }} + secretName: {{ template "my-bloody-jenkins.tlsSecretName" . }} +{{- end }} +{{- end }} + rules: +{{- if .Values.ingress.preAdditionalRules }} +{{ toYaml .Values.ingress.preAdditionalRules | indent 2 }} +{{- end }} + - http: + paths: + - path: {{ .Values.ingress.path }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + pathType: {{ .Values.ingress.pathType }} + backend: + service: + name: {{ $fullName }} + port: + name: http + {{ else }} + backend: + serviceName: {{ $fullName }} + servicePort: http + {{- end }} +{{- if .Values.ingress.hostname }} + host: {{ .Values.ingress.hostname }} +{{- end }} +{{- if .Values.ingress.additionalRules }} +{{ toYaml .Values.ingress.additionalRules | indent 2 }} +{{- end }} +{{- end }} diff --git a/my-bloody-jenkins/templates/pvc.yaml b/my-bloody-jenkins/templates/pvc.yaml new file mode 100644 index 0000000..a561e4e --- /dev/null +++ b/my-bloody-jenkins/templates/pvc.yaml @@ -0,0 +1,59 @@ +--- +{{- if and .Values.persistence .Values.persistence.jenkinsHome .Values.persistence.jenkinsHome.enabled (not .Values.persistence.jenkinsHome.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: +{{- if .Values.persistence.jenkinsHome.annotations }} + annotations: +{{ toYaml .Values.persistence.jenkinsHome.annotations | indent 4 }} +{{- end }} + name: {{ template "my-bloody-jenkins.jenkinsHome.claimName" . }} + labels: + app: {{ template "my-bloody-jenkins.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + accessModes: + - {{ .Values.persistence.jenkinsHome.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.jenkinsHome.size | quote }} +{{- if .Values.persistence.jenkinsHome.storageClass }} +{{- if (eq "-" .Values.persistence.jenkinsHome.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.persistence.jenkinsHome.storageClass }}" +{{- end }} +{{- end }} +{{- end }} + +--- +{{- if and .Values.persistence .Values.persistence.jenkinsWorkspace .Values.persistence.jenkinsWorkspace.enabled (not .Values.persistence.jenkinsWorkspace.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: +{{- if .Values.persistence.jenkinsWorkspace.annotations }} + annotations: +{{ toYaml .Values.persistence.jenkinsWorkspace.annotations | indent 4 }} +{{- end }} + name: {{ template "my-bloody-jenkins.jenkinsWorkspace.claimName" . }} + labels: + app: {{ template "my-bloody-jenkins.fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + accessModes: + - {{ .Values.persistence.jenkinsWorkspace.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.jenkinsWorkspace.size | quote }} +{{- if .Values.persistence.jenkinsWorkspace.storageClass }} +{{- if (eq "-" .Values.persistence.jenkinsWorkspace.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.persistence.jenkinsWorkspace.storageClass }}" +{{- end }} +{{- end }} +{{- end }} diff --git a/my-bloody-jenkins/templates/rbac.yaml b/my-bloody-jenkins/templates/rbac.yaml new file mode 100644 index 0000000..697c813 --- /dev/null +++ b/my-bloody-jenkins/templates/rbac.yaml @@ -0,0 +1,58 @@ +{{- if .Values.rbac.create }} +{{- $fullName := include "my-bloody-jenkins.fullname" . }} +{{- if .Values.rbac.createServiceAccount }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $fullName }} +{{- with .Values.rbac.serviceAccount.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +{{- end }} + +--- +kind: {{ if .Values.rbac.clusterWideAccess }}"ClusterRole"{{ else }}"Role"{{ end }} +{{- if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1" }} +apiVersion: rbac.authorization.k8s.io/v1 +{{ else }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +{{- end }} +metadata: + name: {{ $fullName }} +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["create","delete","get","list","patch","update","watch"] +- apiGroups: [""] + resources: ["pods/exec"] + verbs: ["create","delete","get","list","patch","update","watch"] +- apiGroups: [""] + resources: ["pods/log"] + verbs: ["get","list","watch"] +- apiGroups: [""] + resources: ["events"] + verbs: ["watch"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] + +--- +{{- if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1" }} +apiVersion: rbac.authorization.k8s.io/v1 +{{ else }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +{{- end }} +kind: {{ if .Values.rbac.clusterWideAccess }}"ClusterRoleBinding"{{ else }}"RoleBinding"{{ end }} +metadata: + name: {{ $fullName }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: {{ if .Values.rbac.clusterWideAccess }}"ClusterRole"{{ else }}"Role"{{ end }} + name: {{ $fullName }} +subjects: +- kind: ServiceAccount + name: {{ if .Values.rbac.createServiceAccount }}{{ $fullName }}{{ else }}{{ .Values.rbac.serviceAccountName }}{{ end }} + namespace: "{{ .Release.Namespace }}" +{{- end }} diff --git a/my-bloody-jenkins/templates/secret.yaml b/my-bloody-jenkins/templates/secret.yaml new file mode 100644 index 0000000..6467bdb --- /dev/null +++ b/my-bloody-jenkins/templates/secret.yaml @@ -0,0 +1,16 @@ +{{- if .Values.secrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "my-bloody-jenkins.fullname" . }} + labels: + app: {{ template "my-bloody-jenkins.name" . }} + chart: {{ template "my-bloody-jenkins.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +data: +{{- range $key, $value := .Values.secrets }} + {{ $key }}: {{ $value | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/my-bloody-jenkins/templates/service.yaml b/my-bloody-jenkins/templates/service.yaml new file mode 100644 index 0000000..708ef05 --- /dev/null +++ b/my-bloody-jenkins/templates/service.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "my-bloody-jenkins.fullname" . }} + labels: + app: {{ template "my-bloody-jenkins.name" . }} + chart: {{ template "my-bloody-jenkins.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.service.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + type: {{ .Values.service.type }} +{{- with .Values.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ . | quote }} +{{- end }} + selector: + app: {{ template "my-bloody-jenkins.name" . }} + release: {{ .Release.Name }} + ports: + - port: {{ default (include "my-bloody-jenkins.httpPort" .) .Values.service.httpPort }} + targetPort: http + protocol: TCP + name: http + - port: {{ default (include "my-bloody-jenkins.jnlpPort" .) .Values.service.jnlpPort }} + targetPort: jnlp + protocol: TCP + name: jnlp + - port: {{ default (include "my-bloody-jenkins.sshdPort" .) .Values.service.sshdPort }} + targetPort: sshd + protocol: TCP + name: sshd + {{- if eq .Values.service.type "LoadBalancer" }} + loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }} + {{- if .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{end}} + {{end}} diff --git a/my-bloody-jenkins/values.yaml b/my-bloody-jenkins/values.yaml new file mode 100644 index 0000000..3d7252a --- /dev/null +++ b/my-bloody-jenkins/values.yaml @@ -0,0 +1,254 @@ +--- +######################################################## +## Override image +image: + repository: odavid/my-bloody-jenkins + tag: 2.332.3-277 + pullPolicy: IfNotPresent + imagePullSecrets: +######################################################## + +######################################################## +## Exposing service +service: + # type: ClusterIP + type: ClusterIP + annotations: {} + # httpPort: 8080 + # jnlpPort: 50000 + # sshdPort: 16022 + # loadBalancerSourceRanges: 0.0.0.0/0 + # loadBalancerIP: +######################################################## + +######################################################## +## Exposing ingress +## +## Set the jenkinsURL configuration. +## If not set and ingress is enabled, then jenkins URL is +## {{ .Values.ingress.httpProtocol }}://{{ .Values.ingress.hostname }}{{ .Values.ingress.path }} +# jenkinsURL: https://jenkins.host.name +ingress: + ## Change to https if the ingress uses tls or you are using external + ## tls termination using annotations + httpProtocol: http + enabled: true + path: / + pathType: Prefix + ingressClassName: "nginx" + hostname: jenkins.172-18-0-241.nip.io + # annotations: {} + # labels: {} + # tls: + # secretName: + # certificate: + # privateKey: + + ## Ability to add more ingress rules + additionalRules: + # - http: + # paths: + # - path: path + # backend: + preAdditionalRules: +######################################################## + +######################################################## +## By default rbac are not used and default service account +## is being used. +rbac: + ## Create serviceAccount, Eole and RoleBindings + create: true + ## If createServiceAccount = false, and rbac.create = true, the chart will only use the rbac.serviceAaccountName within RoleBindings + createServiceAccount: true + ## Ignored if createServiceAccount = true + serviceAaccountName: "default" + serviceAccount: + annotations: {} + ## Instead of Role, create a ClusterRole and ClusterRoleBindings + clusterWideAccess: false +######################################################## + +######################################################## +## Control requests limit +## It is highly recommended to give jenkins the amount of +## cpu and memory in production usage +resources: +# requests: +# cpu: 200m +# memory: 256Mi +# limits: +# cpu: 200m +# memory: 256Mi +######################################################## + +######################################################## +## It can take a lot of time for jenkins to be started +## This is why the livenessProbe.initialDelaySeconds is high +readinessProbe: + timeoutSeconds: 5 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 3 + +livenessProbe: + timeoutSeconds: 5 + initialDelaySeconds: 600 + periodSeconds: 5 + failureThreshold: 3 +######################################################## + +######################################################## +## Control peristence of jenkins data: +## By default, the master workspace and master home are separated +## Since master should be used as executer, the workspace directory is +## mainly used for fetching pipeline libraries and some initial clone of +## projects. Therefore, the jenkinsWorkspace can be left as emptyDir (enabled=false). +## On the other hand, jenkinsHome must be persistent! +persistence: + mountDockerSocket: true + jenkinsHome: + enabled: true + annotations: {} + accessMode: ReadWriteOnce + size: 20Gi + ## A manually managed Persistent Volume and Claim + ## Requires persistence.jenkinsHome.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: + ## If defined, storageClass: + ## If set to "-", storageClass: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClass spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "local-path" + jenkinsWorkspace: + enabled: false + annotations: {} + accessMode: ReadWriteOnce + size: 8Gi + ## A manually managed Persistent Volume and Claim + ## Requires persistence.jenkinsWorkspace.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: + ## If defined, storageClass: + ## If set to "-", storageClass: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClass spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "local-path" + + ## Additional volumes and mounts that will be attached to the container. e.g. secrets + volumes: + # - name: nothing + # emptyDir: {} + mounts: + # - mountPath: /var/nothing + # name: nothing + # readOnly: true +######################################################## + +######################################################## +## See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ +nodeSelector: {} +tolerations: [] +affinity: {} +######################################################## + +######################################################## +## Add more annotations to pod +podAnnotations: {} +######################################################## + +######################################################## +## Security Context for jenkins pod +securityContext: {} +######################################################## + +######################################################## +## If true, will set the jenkins master to use hostNetwork=true +useHostNetwork: false +######################################################## + +######################################################## +## Additional Environment variables to be provided to the container +env: +# ENVIRONMENT_VARIABLE_NAME: VALUE +######################################################## + +######################################################## +## If specified, an internal secret will be created. +## Each key will become JENKINS_SECRET_ environment variable +secrets: +# MY_PASSWORD: Very Secret +######################################################## + +######################################################## +## Use external secrets as environment variables +## Each item in the list represents an existing secret name +## All its keys will be transformed to environment variables +## See https://github.com/odavid/my-bloody-jenkins/pull/102 +envSecrets: +# - my-jenkins-external-secret +######################################################## + +######################################################## +## List of ConfigMaps that will be mounted as configuration files +## All configuration files will be deep merged into single config +## See https://github.com/odavid/my-bloody-jenkins/pull/102 +configMaps: +# - my-config-map +######################################################## + +######################################################## +## The jenkins Admin Username - must be a valid username +## within the Jenkins Security Realm +jenkinsAdminUser: admin +######################################################## + +######################################################## +## Java Options for Jenkins Master. Make sure +## resource limits and requests are defined accordingly +javaMemoryOpts: "-Xmx256m" +######################################################## + +######################################################## +## If enabled = 'true', then +## a Default k8s Jenkins cloud will be configured to +## provision slaves automatically based on labels +defaultK8sCloud: + enabled: true + name: "k8s" + labels: + - "generic" + jvmArgs: "-Xmx1g" + remoteFs: "/home/jenkins" + image: "odavid/jenkins-jnlp-slave:latest" +######################################################## + +######################################################## +## A managed configuration based on +## My Bloody Jenkins YAML config. +## See: https://github.com/odavid/my-bloody-jenkins#configuration-reference +managedConfig: {} +## Configure Security - https://github.com/odavid/my-bloody-jenkins#security-section +# security: +## Configure tools - https://github.com/odavid/my-bloody-jenkins#tools-section +# tools: +## Configure credentials - https://github.com/odavid/my-bloody-jenkins#credentials-section +# credentials: +## Configure notifiers - https://github.com/odavid/my-bloody-jenkins#notifiers-section +# notifiers: +## Configure notifiers - https://github.com/odavid/my-bloody-jenkins#pipeline-libraries-section +# pipeline_libraries: +## Script Approvals - https://github.com/odavid/my-bloody-jenkins#script-approval-section +# script_approval: +## Configure Clouds - https://github.com/odavid/my-bloody-jenkins#clouds-section +# clouds: +## Configure Seed Jobs - https://github.com/odavid/my-bloody-jenkins#seed-jobs-section +# seed_jobs: +## Configure Job DSL Scripts - https://github.com/odavid/my-bloody-jenkins#jobdsl-scripts-section +# job_dsl_scripts: +########################################################