Browse Source

asd

pull/149/head
Marat 3 years ago
parent
commit
0de82dc961
  1. 21
      my-bloody-jenkins/.helmignore
  2. 17
      my-bloody-jenkins/Chart.yaml
  3. 192
      my-bloody-jenkins/README.md
  4. BIN
      my-bloody-jenkins/logo/jenkins-logo.png
  5. 39
      my-bloody-jenkins/templates/NOTES.txt
  6. 67
      my-bloody-jenkins/templates/_helpers.tpl
  7. 32
      my-bloody-jenkins/templates/config.yaml
  8. 224
      my-bloody-jenkins/templates/deployment.yaml
  9. 17
      my-bloody-jenkins/templates/ingress-tls-secret.yaml
  10. 64
      my-bloody-jenkins/templates/ingress.yaml
  11. 59
      my-bloody-jenkins/templates/pvc.yaml
  12. 58
      my-bloody-jenkins/templates/rbac.yaml
  13. 16
      my-bloody-jenkins/templates/secret.yaml
  14. 40
      my-bloody-jenkins/templates/service.yaml
  15. 254
      my-bloody-jenkins/values.yaml

21
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

17
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

192
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 <valueFiles>
```
## Upgrading the Release
To install the chart with a custom configuration values.yml
```shell
helm upgrade jenkins odavid/my-bloody-jenkins -f <valueFiles>
```
## 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_<KEY>`. 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/<ConfigMapName>` 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_<KEY>`, 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 }} |

BIN
my-bloody-jenkins/logo/jenkins-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

39
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 }}

67
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 -}}

32
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 }}

224
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 }}

17
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 }}

64
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 }}

59
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 }}

58
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 }}

16
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 }}

40
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}}

254
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: <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: <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_<KEY> 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:
########################################################
Loading…
Cancel
Save