From bb1f1d193df4fc71b6c4488cc718f08a54a51f72 Mon Sep 17 00:00:00 2001 From: actions Date: Fri, 26 Feb 2021 18:55:49 +0000 Subject: [PATCH] Generated changes --- .../artifactory-ha/artifactory-ha-4.7.600.tgz | Bin 0 -> 156835 bytes assets/index.yaml | 31 +- charts/artifactory-ha/CHANGELOG.md | 161 +++++++- charts/artifactory-ha/Chart.yaml | 16 +- charts/artifactory-ha/README.md | 378 ++---------------- charts/artifactory-ha/UPGRADE_NOTES.md | 6 +- .../charts/postgresql/Chart.yaml | 6 +- .../charts/postgresql/README.md | 136 ++++++- .../postgresql/charts/common/.helmignore | 22 + .../postgresql/charts/common/Chart.yaml | 22 + .../charts/postgresql/charts/common/README.md | 274 +++++++++++++ .../charts/common/templates/_capabilities.tpl | 22 + .../charts/common/templates/_errors.tpl | 20 + .../charts/common/templates/_images.tpl | 43 ++ .../charts/common/templates/_labels.tpl | 18 + .../charts/common/templates/_names.tpl | 32 ++ .../charts/common/templates/_secrets.tpl | 49 +++ .../charts/common/templates/_storage.tpl | 23 ++ .../charts/common/templates/_tplvalues.tpl | 13 + .../charts/common/templates/_utils.tpl | 26 ++ .../charts/common/templates/_validations.tpl | 219 ++++++++++ .../charts/common/templates/_warnings.tpl | 14 + .../postgresql/charts/common/values.yaml | 3 + .../postgresql/ci/commonAnnotations.yaml | 3 + .../charts/postgresql/requirements.lock | 6 + .../charts/postgresql/requirements.yaml | 4 + .../charts/postgresql/templates/NOTES.txt | 9 +- .../charts/postgresql/templates/_helpers.tpl | 91 ++++- .../postgresql/templates/configmap.yaml | 8 +- .../templates/extended-config-configmap.yaml | 8 +- .../templates/initialization-configmap.yaml | 8 +- .../templates/metrics-configmap.yaml | 8 +- .../postgresql/templates/metrics-svc.yaml | 13 +- .../postgresql/templates/networkpolicy.yaml | 14 +- .../templates/podsecuritypolicy.yaml | 37 ++ .../postgresql/templates/prometheusrule.yaml | 14 +- .../charts/postgresql/templates/role.yaml | 19 + .../postgresql/templates/rolebinding.yaml | 19 + .../charts/postgresql/templates/secrets.yaml | 8 +- .../postgresql/templates/serviceaccount.yaml | 10 +- .../postgresql/templates/servicemonitor.yaml | 14 +- .../templates/statefulset-slaves.yaml | 92 +++-- .../postgresql/templates/statefulset.yaml | 103 ++++- .../postgresql/templates/svc-headless.yaml | 11 +- .../charts/postgresql/templates/svc-read.yaml | 14 +- .../charts/postgresql/templates/svc.yaml | 14 +- .../charts/postgresql/values-production.yaml | 96 ++++- .../charts/postgresql/values.schema.json | 4 +- .../charts/postgresql/values.yaml | 100 +++-- .../artifactory-ha/ci/access-tls-values.yaml | 6 +- charts/artifactory-ha/ci/default-values.yaml | 3 + charts/artifactory-ha/ci/global-values.yaml | 47 +++ .../ci/migration-disabled-values.yaml | 3 + charts/artifactory-ha/questions.yml | 2 +- charts/artifactory-ha/requirements.lock | 6 +- charts/artifactory-ha/requirements.yaml | 2 +- .../artifactory-ha/security-mitigation.yaml | 20 + charts/artifactory-ha/templates/_helpers.tpl | 165 ++++++++ .../templates/additional-resources.yaml | 3 + .../templates/artifactory-access-config.yaml | 2 +- .../templates/artifactory-custom-secrets.yaml | 2 +- .../templates/artifactory-node-pdb.yaml | 4 +- .../artifactory-node-statefulset.yaml | 89 +++-- .../templates/artifactory-primary-pdb.yaml | 20 + .../artifactory-primary-statefulset.yaml | 115 ++++-- .../templates/artifactory-secrets.yaml | 16 +- .../templates/artifactory-service.yaml | 20 +- .../templates/artifactory-system-yaml.yaml | 2 + charts/artifactory-ha/templates/ingress.yaml | 51 ++- .../templates/nginx-certificate-secret.yaml | 2 +- .../templates/nginx-deployment.yaml | 23 +- .../artifactory-ha/templates/nginx-pvc.yaml | 6 +- .../templates/nginx-service.yaml | 2 +- charts/artifactory-ha/values.yaml | 132 +++++- index.yaml | 31 +- sha256sum/artifactory-ha/artifactory-ha.sum | 6 +- 76 files changed, 2349 insertions(+), 692 deletions(-) create mode 100644 assets/artifactory-ha/artifactory-ha-4.7.600.tgz create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/.helmignore create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/Chart.yaml create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/README.md create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_capabilities.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_errors.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_images.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_labels.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_names.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_secrets.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_storage.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_tplvalues.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_utils.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_validations.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/templates/_warnings.tpl create mode 100755 charts/artifactory-ha/charts/postgresql/charts/common/values.yaml create mode 100755 charts/artifactory-ha/charts/postgresql/ci/commonAnnotations.yaml create mode 100755 charts/artifactory-ha/charts/postgresql/requirements.lock create mode 100755 charts/artifactory-ha/charts/postgresql/requirements.yaml create mode 100755 charts/artifactory-ha/charts/postgresql/templates/podsecuritypolicy.yaml create mode 100755 charts/artifactory-ha/charts/postgresql/templates/role.yaml create mode 100755 charts/artifactory-ha/charts/postgresql/templates/rolebinding.yaml create mode 100644 charts/artifactory-ha/ci/global-values.yaml create mode 100644 charts/artifactory-ha/security-mitigation.yaml create mode 100644 charts/artifactory-ha/templates/additional-resources.yaml create mode 100644 charts/artifactory-ha/templates/artifactory-primary-pdb.yaml diff --git a/assets/artifactory-ha/artifactory-ha-4.7.600.tgz b/assets/artifactory-ha/artifactory-ha-4.7.600.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0ea201c47f4f93de8eeb5eaa7208e0d20fd7e3e6 GIT binary patch literal 156835 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

WERq5pC! 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 lhD7AyvwBv~>iI68{~rJV|Nn@mQ;7hM3; This can be controlled by the parameter `artifactory.service.pool`. +This can be controlled by the parameter `artifactory.service.pool`. +**NOTE:** + Using artifactory pro license (which supports single node only), set `artifactory.node.replicaCount=0` in values.yaml. + To scale from single node to multiple nodes(>1), use Enterprise(+) license and then do an helm upgrade (Each node need a seperate license). ## Installing the Chart @@ -62,7 +67,7 @@ artifactory: ``` -### Deploying Artifactory for small/medium/large instllations +### Deploying Artifactory for small/medium/large installations In the chart directory, we have added three values files, one for each installation type - small/medium/large. These values files are recommendations for setting resources requests and limits for your installation. The values are derived from the following [documentation](https://www.jfrog.com/confluence/display/EP/Installing+on+Kubernetes#InstallingonKubernetes-Systemrequirements). You can find them in the corresponding chart directory - values-small.yaml, values-medium.yaml and values-large.yaml ### Accessing Artifactory @@ -98,6 +103,10 @@ artifactory: enabled: true timeoutSeconds: 3600 ``` +* Note: If you are upgrading from 1.x to 4.x and above chart versions, please delete the existing statefulset of postgresql before upgrading the chart due to breaking changes in postgresql subchart. +```bash +kubectl delete statefulsets -postgresql +``` ### Artifactory memory and CPU resources The Artifactory HA Helm chart comes with support for configured resource requests and limits to all pods. By default, these settings are commented out. @@ -334,6 +343,13 @@ Use this template if you want to attach an IAM role to the Artifactory pod direc ... ``` +To enable [Direct Cloud Storage Download](https://www.jfrog.com/confluence/display/JFROG/Direct+Cloud+Storage+Download#DirectCloudStorageDownload-1.ConfiguretheArtifactoryFilestore) +```bash +... +--set artifactory.persistence.awsS3V3.enableSignedUrlRedirect=true \ +... +``` + #### Microsoft Azure Blob Storage To use Azure Blob Storage as the cluster's filestore. See [Azure Blob Storage Binary Provider](https://www.jfrog.com/confluence/display/RTF/Configuring+the+Filestore#ConfiguringtheFilestore-AzureBlobStorageClusterBinaryProvider) - Pass Azure Blob Storage parameters to `helm install` and `helm upgrade` @@ -1217,11 +1233,11 @@ If you are running a load balancer, that is used to offload the TLS, in front of To enable it with `helm install` ```bash -helm upgrade --install nginx-ingress --namespace nginx-ingress stable/nginx-ingress --set-string controller.config.use-forwarded-headers=true +helm upgrade --install nginx-ingress --namespace nginx-ingress center/kubernetes-ingress-nginx/ingress-nginx --set-string controller.config.use-forwarded-headers=true ``` or `helm upgrade` ```bash -helm upgrade nginx-ingress --set-string controller.config.use-forwarded-headers=true stable/nginx-ingress +helm upgrade nginx-ingress --set-string controller.config.use-forwarded-headers=true center/kubernetes-ingress-nginx/ingress-nginx ``` or create a values.yaml file with the following content: ```yaml @@ -1231,355 +1247,19 @@ controller: ``` Then install nginx-ingress with the values file you created: ```bash -helm upgrade --install nginx-ingress --namespace nginx-ingress stable/nginx-ingress -f values.yaml +helm upgrade --install nginx-ingress --namespace nginx-ingress center/kubernetes-ingress-nginx/ingress-nginx -f values.yaml ``` -## Configuration -The following table lists the configurable parameters of the artifactory chart and their default values. +### Log Analytics -| Parameter | Description | Default | -|------------------------------|-----------------------------------|-------------------------------------------------------| -| `imagePullSecrets` | Docker registry pull secret | | -| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | -| `serviceAccount.name` | The name of the ServiceAccount to create | Generated using the fullname template | -| `serviceAccount.annotations` | Artifactory service account annotations | `` | -| `rbac.create` | Specifies whether RBAC resources should be created | `true` | -| `rbac.role.rules` | Rules to create | `[]` | -| `logger.image.repository` | repository for logger image | `busybox` | -| `logger.image.tag` | tag for logger image | `1.30` | -| `artifactory.name` | Artifactory name | `artifactory` | -| `artifactory.image.pullPolicy` | Container pull policy | `IfNotPresent` | -| `artifactory.image.repository` | Container image | `docker.bintray.io/jfrog/artifactory-pro` | -| `artifactory.image.version` | Container image tag | `.Chart.AppVersion` | -| `artifactory.priorityClass.create` | Create a PriorityClass object | `false` | -| `artifactory.priorityClass.value` | Priority Class value | `1000000000` | -| `artifactory.priorityClass.name` | Priority Class name | `{{ template "artifactory-ha.fullname" . }}` | -| `artifactory.priorityClass.existingPriorityClass` | Use existing priority class | `` | -| `artifactory.loggers` | Artifactory loggers (see values.yaml for possible values) | `[]` | -| `artifactory.loggersResources.requests.memory` | Artifactory loggers initial memory request | | -| `artifactory.loggersResources.requests.cpu` | Artifactory loggers initial cpu request | | -| `artifactory.loggersResources.limits.memory` | Artifactory loggers memory limit | | -| `artifactory.loggersResources.limits.cpu` | Artifactory loggers cpu limit | | -| `artifactory.catalinaLoggers` | Artifactory Tomcat loggers (see values.yaml for possible values) | `[]` | -| `artifactory.catalinaLoggersResources.requests.memory` | Artifactory Tomcat loggers initial memory request | | -| `artifactory.catalinaLoggersResources.requests.cpu` | Artifactory Tomcat loggers initial cpu request | | -| `artifactory.catalinaLoggersResources.limits.memory` | Artifactory Tomcat loggers memory limit | | -| `artifactory.catalinaLoggersResources.limits.cpu` | Artifactory Tomcat loggers cpu limit | | -| `artifactory.customInitContainersBegin`| Custom init containers to run before existing init containers | | -| `artifactory.customInitContainers`| Custom init containers to run after existing init containers | | -| `artifactory.customSidecarContainers`| Custom sidecar containers | | -| `artifactory.customVolumes` | Custom volumes | | -| `artifactory.customVolumeMounts` | Custom Artifactory volumeMounts | | -| `artifactory.customPersistentPodVolumeClaim` | Custom PVC spec to create and attach a unique PVC for each pod on startup with the volumeClaimTemplates feature in StatefulSet | | -| `artifactory.customPersistentVolumeClaim` | Custom PVC spec to be mounted to the all artifactory containers using a volume | | -| `artifactory.customSecrets` | Custom secrets | | -| `artifactory.userPluginSecrets` | Array of secret names for Artifactory user plugins | | -| `artifactory.masterKey` | Artifactory master key. A 128-Bit key size (hexadecimal encoded) string (32 hex characters). Can be generated with `openssl rand -hex 32`. NOTE: This key can be generated only once and cannot be updated once created |``| -| `artifactory.masterKeySecretName` | Artifactory Master Key secret name | | -| `artifactory.joinKey` | Join Key to connect other services to Artifactory. Can be generated with `openssl rand -hex 32` | `` | -| `artifactory.joinKeySecretName` | Artifactory join Key secret name | | -| `artifactory.admin.ip` | Artifactory admin ip to be set upon startup, can use (*) for 0.0.0.0| `127.0.0.1` | -| `artifactory.admin.username` | Artifactory admin username to be set upon startup| `admin` | -| `artifactory.admin.password` | Artifactory admin password to be set upon startup| | -| `artifactory.admin.secret` | Artifactory admin secret name | | -| `artifactory.admin.dataKey` | Artifactory admin secret data key | | -| `artifactory.preStartCommand` | Command to run before entrypoint starts | | -| `artifactory.postStartCommand` | Command to run after container starts. Supports templating with `tpl` | | -| `artifactory.license.licenseKey` | Artifactory license key. Providing the license key as a parameter will cause a secret containing the license key to be created as part of the release. Use either this setting or the license.secret and license.dataKey. If you use both, the latter will be used. | | -| `artifactory.configMaps` | configMaps to be created as volume by the name `artifactory-configmaps`. In order to use these configMaps, you will need to add `customVolumeMounts` to point to the created volume and mount it onto a container | | -| `artifactory.license.secret` | Artifactory license secret name | | -| `artifactory.license.dataKey`| Artifactory license secret data key | | -| `artifactory.service.name` | Artifactory service name to be set in Nginx configuration | `artifactory` | -| `artifactory.service.type` | Artifactory service type | `ClusterIP` | -| `artifactory.service.clusterIP`| Specific cluster IP or `None` for headless services | `nil` | -| `artifactory.service.loadBalancerSourceRanges`| Artifactory service array of IP CIDR ranges to whitelist (only when service type is LoadBalancer) | | -| `artifactory.service.annotations` | Artifactory service annotations | `{}` | -| `artifactory.service.pool` | Artifactory instances to be in the load balancing pool. `members` or `all` | `members` | -| `artifactory.externalPort` | Artifactory service external port | `8082` | -| `artifactory.internalPort` | Artifactory service internal port (**DO NOT** use port lower than 1024) | `8082` | -| `artifactory.internalArtifactoryPort` | Artifactory service internal port (**DO NOT** use port lower than 1024) | `8081` | -| `artifactory.externalArtifactoryPort` | Artifactory service external port | `8081` | -| `artifactory.extraEnvironmentVariables` | Extra environment variables to pass to Artifactory. Supports evaluating strings as templates via the [`tpl`](https://helm.sh/docs/charts_tips_and_tricks/#using-the-tpl-function) function. See [documentation](https://www.jfrog.com/confluence/display/RTF/Installing+with+Docker#InstallingwithDocker-SupportedEnvironmentVariables) | | -| `artifactory.livenessProbe.enabled` | Enable liveness probe | `true` | -| `artifactory.livenessProbe.path` | liveness probe HTTP Get path | `/router/api/v1/system/health` | -| `artifactory.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 180 | -| `artifactory.livenessProbe.periodSeconds` | How often to perform the probe | 10 | -| `artifactory.livenessProbe.timeoutSeconds` | When the probe times out | 10 | -| `artifactory.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | 1 | -| `artifactory.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | -| `artifactory.readinessProbe.enabled` | would you like a readinessProbe to be enabled | `true` | -| `artifactory.readinessProbe.path` | readiness probe HTTP Get path | `/router/api/v1/system/health` | -| `artifactory.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | 60 | -| `artifactory.readinessProbe.periodSeconds` | How often to perform the probe | 10 | -| `artifactory.readinessProbe.timeoutSeconds` | When the probe times out | 10 | -| `artifactory.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | 1 | -| `artifactory.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | -| `artifactory.copyOnEveryStartup` | List of files to copy on startup from source (which is absolute) to target (which is relative to ARTIFACTORY_HOME | | -| `artifactory.deleteDBPropertiesOnStartup` | Whether to delete the ARTIFACTORY_HOME/etc/db.properties file on startup. Disabling this will remove the ability for the db.properties to be updated with any DB-related environment variables change (e.g. DB_HOST, DB_URL) | `true` | -| `artifactory.database.maxOpenConnections` | Maximum amount of open connections from Artifactory to the DB | `80` | -| `artifactory.haDataDir.enabled` | Enable haDataDir for eventual storage in the HA cluster | `false` | -| `artifactory.haDataDir.path` | Path to the directory intended for use with NFS eventual configuration for HA | | -| `artifactory.haBackupDir.enabled` | Enable haBackupDir for eventual storage in the HA cluster | `false` | -| `artifactory.haBackupDir.path` | Path to the directory intended for use with NFS eventual configuration for HA | | -| `artifactory.haBackupDir.enabled` | Enable haBackupDir for eventual storage in the HA cluster | `false` | -| `artifactory.haBackupDir.path` | Path to the directory intended for use with NFS eventual configuration for HA | | -| `artifactory.migration.timeoutSeconds` | Artifactory migration Maximum Time out in seconds| `3600` | -| `artifactory.migration.enabled` | Artifactory migration enabled or disabled | `true` | -| `artifactory.persistence.mountPath` | Artifactory persistence volume mount path | `"/var/opt/jfrog/artifactory"` | -| `artifactory.persistence.enabled` | Artifactory persistence volume enabled | `true` | -| `artifactory.persistence.accessMode` | Artifactory persistence volume access mode | `ReadWriteOnce` | -| `artifactory.persistence.size` | Artifactory persistence or local volume size | `200Gi` | -| `artifactory.persistence.binarystore.enabled` | whether you want to mount the binarystore.xml file from a secret created by the chart. If `false` you will need need to get the binarystore.xml file into the file-system from either an `initContainer` or using a `preStartCommand` | `true` | -| `artifactory.persistence.binarystoreXml` | Artifactory binarystore.xml template | See `values.yaml` | -| `artifactory.persistence.customBinarystoreXmlSecret` | A custom Secret for binarystore.xml | `` | -| `artifactory.persistence.maxCacheSize` | Artifactory cache-fs provider maxCacheSize in bytes | `50000000000` | -| `artifactory.persistence.cacheProviderDir` | the root folder of binaries for the filestore cache. If the value specified starts with a forward slash ("/") it is considered the fully qualified path to the filestore folder. Otherwise, it is considered relative to the *baseDataDir*. | `cache` | -| `artifactory.persistence.type` | Artifactory HA storage type | `file-system` | -| `artifactory.persistence.redundancy` | Artifactory HA storage redundancy | `3` | -| `artifactory.persistence.nfs.ip` | NFS server IP | | -| `artifactory.persistence.nfs.haDataMount` | NFS data directory | `/data` | -| `artifactory.persistence.nfs.haBackupMount` | NFS backup directory | `/backup` | -| `artifactory.persistence.nfs.dataDir` | HA data directory | `/var/opt/jfrog/artifactory-ha` | -| `artifactory.persistence.nfs.backupDir` | HA backup directory | `/var/opt/jfrog/artifactory-backup` | -| `artifactory.persistence.nfs.capacity` | NFS PVC size | `200Gi` | -| `artifactory.persistence.nfs.mountOptions` | NFS mount options | `[]` | -| `artifactory.persistence.eventual.numberOfThreads` | Eventual number of threads | `10` | -| `artifactory.persistence.googleStorage.endpoint` | Google Storage API endpoint| `storage.googleapis.com` | -| `artifactory.persistence.googleStorage.httpsOnly` | Google Storage API has to be consumed https only| `false` | -| `artifactory.persistence.googleStorage.bucketName` | Google Storage bucket name | `artifactory-ha` | -| `artifactory.persistence.googleStorage.identity` | Google Storage service account id | | -| `artifactory.persistence.googleStorage.credential` | Google Storage service account key | | -| `artifactory.persistence.googleStorage.path` | Google Storage path in bucket | `artifactory-ha/filestore` | -| `artifactory.persistence.googleStorage.bucketExists`| Google Storage bucket exists therefore does not need to be created.| `false` | -| `artifactory.persistence.awsS3.bucketName` | AWS S3 bucket name | `artifactory-ha` | -| `artifactory.persistence.awsS3.endpoint` | AWS S3 bucket endpoint | See https://docs.aws.amazon.com/general/latest/gr/rande.html | -| `artifactory.persistence.awsS3.region` | AWS S3 bucket region | | -| `artifactory.persistence.awsS3.roleName` | AWS S3 IAM role name | | -| `artifactory.persistence.awsS3.identity` | AWS S3 AWS_ACCESS_KEY_ID | | -| `artifactory.persistence.awsS3.credential` | AWS S3 AWS_SECRET_ACCESS_KEY | | -| `artifactory.persistence.awsS3.properties` | AWS S3 additional properties | | -| `artifactory.persistence.awsS3.path` | AWS S3 path in bucket | `artifactory-ha/filestore` | -| `artifactory.persistence.awsS3.refreshCredentials` | AWS S3 renew credentials on expiration | `true` (When roleName is used, this parameter will be set to true) | -| `artifactory.persistence.awsS3.httpsOnly` | AWS S3 https access to the bucket only | `true` | -| `artifactory.persistence.awsS3.testConnection` | AWS S3 test connection on start up | `false` | -| `artifactory.persistence.awsS3.s3AwsVersion` | AWS S3 signature version | `AWS4-HMAC-SHA256` | -| `artifactory.persistence.awsS3V3.testConnection` | AWS S3 test connection on start up | `false` | -| `artifactory.persistence.awsS3V3.identity` | AWS S3 AWS_ACCESS_KEY_ID | | -| `artifactory.persistence.awsS3V3.credential` | AWS S3 AWS_SECRET_ACCESS_KEY | | -| `artifactory.persistence.awsS3V3.region` | AWS S3 bucket region | | -| `artifactory.persistence.awsS3V3.bucketName` | AWS S3 bucket name | `artifactory-aws` | -| `artifactory.persistence.awsS3V3.path` | AWS S3 path in bucket | `artifactory/filestore` | -| `artifactory.persistence.awsS3V3.endpoint` | AWS S3 bucket endpoint | See https://docs.aws.amazon.com/general/latest/gr/rande.html | -| `artifactory.persistence.awsS3V3.maxConnections` | AWS S3 bucket maxConnections | `50` | -| `artifactory.persistence.awsS3V3.kmsServerSideEncryptionKeyId` | AWS S3 encryption key ID or alias | | -| `artifactory.persistence.awsS3V3.kmsKeyRegion` | AWS S3 KMS Key region | | -| `artifactory.persistence.awsS3V3.kmsCryptoMode` | AWS S3 KMS encryption mode | See https://www.jfrog.com/confluence/display/RTF/Configuring+the+Filestore#ConfiguringtheFilestore-AmazonS3OfficialSDKTemplate | -| `artifactory.persistence.awsS3V3.useInstanceCredentials` | AWS S3 Use default authentication mechanism | See https://www.jfrog.com/confluence/display/RTF/Configuring+the+Filestore#ConfiguringtheFilestore-authentication | -| `artifactory.persistence.awsS3V3.usePresigning` | AWS S3 Use URL signing | `false` | -| `artifactory.persistence.awsS3V3.signatureExpirySeconds` | AWS S3 Validity period in seconds for signed URLs | `300` | -| `artifactory.persistence.awsS3V3.cloudFrontDomainName` | AWS CloudFront domain name | See https://www.jfrog.com/confluence/display/RTF/Direct+Cloud+Storage+Download#DirectCloudStorageDownload-UsingCloudFront(Optional)| -| `artifactory.persistence.awsS3V3.cloudFrontKeyPairId` | AWS CloudFront key pair ID | See https://www.jfrog.com/confluence/display/RTF/Direct+Cloud+Storage+Download#DirectCloudStorageDownload-UsingCloudFront(Optional)| -| `artifactory.persistence.awsS3V3.cloudFrontPrivateKey` | AWS CloudFront private key | See https://www.jfrog.com/confluence/display/RTF/Direct+Cloud+Storage+Download#DirectCloudStorageDownload-UsingCloudFront(Optional)| -| `artifactory.persistence.azureBlob.accountName` | Azure Blob Storage account name | `` | -| `artifactory.persistence.azureBlob.accountKey` | Azure Blob Storage account key | `` | -| `artifactory.persistence.azureBlob.endpoint` | Azure Blob Storage endpoint | `` | -| `artifactory.persistence.azureBlob.containerName` | Azure Blob Storage container name | `` | -| `artifactory.persistence.azureBlob.testConnection` | Azure Blob Storage test connection | `false` | -| `artifactory.persistence.fileSystem.existingSharedClaim` | Enable using an existing shared pvc | `false` | -| `artifactory.persistence.fileStorage.dataDir` | HA data directory | `/var/opt/jfrog/artifactory/artifactory-data` | -| `artifactory.persistence.fileStorage.backupDir` | HA backup directory | `/var/opt/jfrog/artifactory-backup` | -| `artifactory.javaOpts.other` | Artifactory additional java options (for all nodes) | | -| `artifactory.replicator.enabled` | Enable the Replicator service (relevant for Enterprise+ only) | `false` | -| `artifactory.ssh.enabled` | Enable Artifactory SSH access | | -| `artifactory.ssh.internalPort` | Artifactory SSH internal port | `1339` | -| `artifactory.ssh.externalPort` | Artifactory SSH external port | `1339` | -| `artifactory.primary.preStartCommand` | Artifactory primary node preStartCommand to be run after `artifactory.preStartCommand` | | -| `artifactory.primary.labels` | Artifactory primary node labels | `{}` | -| `artifactory.primary.resources.requests.memory` | Artifactory primary node initial memory request | | -| `artifactory.primary.resources.requests.cpu` | Artifactory primary node initial cpu request | | -| `artifactory.primary.resources.limits.memory` | Artifactory primary node memory limit | | -| `artifactory.primary.resources.limits.cpu` | Artifactory primary node cpu limit | | -| `artifactory.primary.javaOpts.xms` | Artifactory primary node java Xms size | | -| `artifactory.primary.javaOpts.xmx` | Artifactory primary node java Xms size | | -| `artifactory.primary.javaOpts.corePoolSize` | The number of async processes that can run in parallel in the primary node - https://jfrog.com/knowledge-base/how-do-i-tune-artifactory-for-heavy-loads/ | `16` | -| `artifactory.primary.javaOpts.jmx.enabled` | Enable JMX monitoring | `false` | -| `artifactory.primary.javaOpts.jmx.port` | JMX Port number | `9010` | -| `artifactory.primary.javaOpts.jmx.host` | JMX hostname (parsed as a helm template) | `{{ template "artifactory-ha.primary.name" $ }}` | -| `artifactory.primary.javaOpts.jmx.ssl` | Enable SSL | `false` | -| `artifactory.primary.javaOpts.jmx.authenticate` | Enable JMX authentication | `false` | -| `artifactory.primary.javaOpts.jmx.accessFile` | The path to the JMX access file, when JMX authentication is enabled | | -| `artifactory.primary.javaOpts.jmx.passwordFile` | The path to the JMX password file, when JMX authentication is enabled | | -| `artifactory.primary.javaOpts.other` | Artifactory primary node additional java options | | -| `artifactory.primary.persistence.existingClaim` | Whether to use an existing pvc for the primary node | `false` | -| `artifactory.node.preStartCommand` | Artifactory member node preStartCommand to be run after `artifactory.preStartCommand` | | -| `artifactory.node.labels` | Artifactory member node labels | `{}` | -| `artifactory.node.replicaCount` | Artifactory member node replica count | `2` | -| `artifactory.node.minAvailable` | Artifactory member node min available count | `1` | -| `artifactory.node.resources.requests.memory` | Artifactory member node initial memory request | | -| `artifactory.node.resources.requests.cpu` | Artifactory member node initial cpu request | | -| `artifactory.node.resources.limits.memory` | Artifactory member node memory limit | | -| `artifactory.node.resources.limits.cpu` | Artifactory member node cpu limit | | -| `artifactory.node.javaOpts.xms` | Artifactory member node java Xms size | | -| `artifactory.node.javaOpts.xmx` | Artifactory member node java Xms size | | -| `artifactory.node.javaOpts.corePoolSize` | The number of async processes that can run in parallel in the member nodes - https://jfrog.com/knowledge-base/how-do-i-tune-artifactory-for-heavy-loads/ | `16` | -| `artifactory.node.javaOpts.jmx.enabled` | Enable JMX monitoring | `false` | -| `artifactory.node.javaOpts.jmx.port` | JMX Port number | `9010` | -| `artifactory.node.javaOpts.jmx.host` | JMX hostname (parsed as a helm template) | `{{ template "artifactory-ha.fullname" $ }}` | -| `artifactory.node.javaOpts.jmx.ssl` | Enable SSL | `false` | -| `artifactory.node.javaOpts.jmx.authenticate` | Enable JMX authentication | `false` | -| `artifactory.node.javaOpts.jmx.accessFile` | The path to the JMX access file, when JMX authentication is enabled | | -| `artifactory.node.javaOpts.jmx.passwordFile` | The path to the JMX password file, when JMX authentication is enabled | | -| `artifactory.node.javaOpts.other` | Artifactory member node additional java options | | -| `artifactory.node.persistence.existingClaim` | Whether to use existing PVCs for the member nodes | `false` | -| `artifactory.terminationGracePeriodSeconds` | Termination grace period (seconds) | `30s` | -| `artifactory.node.waitForPrimaryStartup.enabled` | Whether to wait for the primary node to start before starting up the member nodes | `false` | -| `artifactory.node.waitForPrimaryStartup.time` | The amount of time to wait for the primary node to start before starting up the member nodes | `60` | -| `artifactory.tomcat.connector.maxThreads` | The max number of connections to Artifactory connector | `200` | -| `artifactory.tomcat.connector.extraConfig` | The max queue length for incoming connections to Artifactory connector | `'acceptCount="100"'` | -| `artifactory.systemYaml` | Artifactory system configuration (`system.yaml`) as described here - https://www.jfrog.com/confluence/display/JFROG/Artifactory+System+YAML | `see values.yaml` | -| `artifactory.primary.affinity` | Artifactory primary node affinity | `{}` | -| `artifactory.node.affinity` | Artifactory member node affinity | `{}` | -| `access.database.maxOpenConnections` | Maximum amount of open connections from Access to the DB | `80` | -| `access.tomcat.connector.maxThreads` | The max number of connections to Aceess connector | `50` | -| `access.tomcat.connector.extraConfig` | The max queue length for incoming connections to Access connector | `'acceptCount="100"'` | -| `initContainers.resources.requests.memory` | Init containers initial memory request | | -| `initContainers.resources.requests.cpu` | Init containers initial cpu request | | -| `initContainers.resources.limits.memory` | Init containers memory limit | | -| `initContainers.resources.limits.cpu` | Init containers cpu limit | | -| `ingress.enabled` | If true, Artifactory Ingress will be created | `false` | -| `ingress.annotations` | Artifactory Ingress annotations | `{}` | -| `ingress.labels` | Artifactory Ingress labels | `{}` | -| `ingress.hosts` | Artifactory Ingress hostnames | `[]` | -| `ingress.routerPath` | Router Ingress path | `/` | -| `ingress.artifactoryPath` | Artifactory Ingress path | `/artifactory` | -| `ingress.tls` | Artifactory Ingress TLS configuration (YAML) | `[]` | -| `ingress.defaultBackend.enabled` | If true, the default `backend` will be added using serviceName and servicePort | `true` | -| `ingress.annotations` | Ingress annotations, which are written out if annotations section exists in values. Everything inside of the annotations section will appear verbatim inside the resulting manifest. See `Ingress annotations` section below for examples of how to leverage the annotations, specifically for how to enable docker authentication. | | -| `ingress.additionalRules` | Ingress additional rules to be added to the Artifactory ingress. | `[]` | -| `metadata.database.maxOpenConnections` | Maximum amount of open connections from metadata to the DB | `80` | -| `nginx.enabled` | Deploy nginx server | `true` | -| `nginx.kind` | Nginx object kind, for example `DaemonSet`, `Deployment` or `StatefulSet` | `Deployment` | -| `nginx.name` | Nginx name | `nginx` | -| `nginx.replicaCount` | Nginx replica count | `1` | -| `nginx.uid` | Nginx User Id | `104` | -| `nginx.gid` | Nginx Group Id | `107` | -| `nginx.image.repository` | Container image | `docker.bintray.io/jfrog/nginx-artifactory-pro` | -| `nginx.image.version` | Container version | `.Chart.AppVersion` | -| `nginx.image.pullPolicy` | Container pull policy | `IfNotPresent` | -| `nginx.labels` | Nginx deployment labels | `{}` | -| `nginx.minAvailable` | Nginx node min available count | `0` | -| `nginx.loggers` | Nginx loggers (see values.yaml for possible values) | `[]` | -| `nginx.loggersResources.requests.memory` | Nginx logger initial memory request | | -| `nginx.loggersResources.requests.cpu` | Nginx logger initial cpu request | | -| `nginx.loggersResources.limits.memory` | Nginx logger memory limit | | -| `nginx.loggersResources.limits.cpu` | Nginx logger cpu limit | | -| `nginx.logs.stderr` | Send nginx logs to stderr | false | -| `nginx.logs.level` | Nginx log level: debug, info, notice, warn, error, crit, alert, or emerg | warn | -| `nginx.mainConf` | Content of the Artifactory nginx main nginx.conf config file | `see values.yaml` | -| `nginx.artifactoryConf` | Content of Artifactory nginx artifactory.conf config file | `see values.yaml` | -| `nginx.service.type` | Nginx service type | `LoadBalancer` | -| `nginx.service.clusterIP` | Specific cluster IP or `None` for headless services | `nil` | -| `nginx.service.loadBalancerSourceRanges`| Nginx service array of IP CIDR ranges to whitelist (only when service type is LoadBalancer) | | -| `nginx.service.labels` | Nginx service labels | `{}` | -| `nginx.service.annotations` | Nginx service annotations | `{}` | -| `nginx.service.ssloffload` | Nginx service SSL offload | false | -| `nginx.service.externalTrafficPolicy`| Nginx service desires to route external traffic to node-local or cluster-wide endpoints. | `Cluster` | -| `nginx.loadBalancerIP`| Provide Static IP to configure with Nginx | | -| `nginx.http.enabled` | Nginx http service enabled/disabled | true | -| `nginx.http.externalPort` | Nginx service external port | `80` | -| `nginx.http.internalPort` | Nginx service internal port | `80` | -| `nginx.https.enabled` | Nginx http service enabled/disabled | true | -| `nginx.https.externalPort` | Nginx service external port | `443` | -| `nginx.https.internalPort` | Nginx service internal port | `443` | -| `nginx.ssh.internalPort` | Nginx SSH internal port | `22` | -| `nginx.ssh.externalPort` | Nginx SSH external port | `22` | -| `nginx.externalPortHttp` | DEPRECATED: Nginx service external port | `80` | -| `nginx.internalPortHttp` | DEPRECATED: Nginx service internal port | `80` | -| `nginx.externalPortHttps` | DEPRECATED: Nginx service external port | `443` | -| `nginx.internalPortHttps` | DEPRECATED: Nginx service internal port | `443` | -| `nginx.livenessProbe.enabled` | would you like a liveness Probe to be enabled | `true` | -| `nginx.livenessProbe.path` | liveness probe HTTP Get path | `/router/api/v1/system/health` | -| `nginx.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 100 | -| `nginx.livenessProbe.periodSeconds` | How often to perform the probe | 10 | -| `nginx.livenessProbe.timeoutSeconds` | When the probe times out | 10 | -| `nginx.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | 1 | -| `nginx.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | -| `nginx.readinessProbe.enabled` | would you like a readinessProbe to be enabled | `true` | -| `nginx.readinessProbe.path` | Readiness probe HTTP Get path | `/router/api/v1/system/health` | -| `nginx.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | 60 | -| `nginx.readinessProbe.periodSeconds` | How often to perform the probe | 10 | -| `nginx.readinessProbe.timeoutSeconds` | When the probe times out | 10 | -| `nginx.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | 1 | -| `nginx.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | -| `nginx.tlsSecretName` | SSL secret that will be used by the Nginx pod | | -| `nginx.customConfigMap` | Nginx CustomeConfigMap name for `nginx.conf` | ` ` | -| `nginx.customArtifactoryConfigMap`| Nginx CustomeConfigMap name for `artifactory-ha.conf` | ` ` | -| `nginx.resources.requests.memory` | Nginx initial memory request | `250Mi` | -| `nginx.resources.requests.cpu` | Nginx initial cpu request | `100m` | -| `nginx.resources.limits.memory` | Nginx memory limit | `250Mi` | -| `nginx.resources.limits.cpu` | Nginx cpu limit | `500m` | -| `nginx.persistence.mountPath` | Nginx persistence volume mount path | `"/var/opt/jfrog/nginx"` | -| `nginx.persistence.enabled` | Nginx persistence volume enabled. This is only available when the nginx.replicaCount is set to 1 | `false` | -| `nginx.persistence.accessMode` | Nginx persistence volume access mode | `ReadWriteOnce` | -| `nginx.persistence.size` | Nginx persistence volume size | `5Gi` | -| `waitForDatabase` | Wait for database (using wait-for-db init container) | `true` | -| `postgresql.enabled` | Use enclosed PostgreSQL as database | `true` | -| `postgresql.image.registry` | PostgreSQL image registry | `docker.bintray.io` | -| `postgresql.image.repository` | PostgreSQL image repository | `bitnami/postgresql` | -| `postgresql.image.tag` | PostgreSQL image tag | `9.6.18-debian-10-r7` | -| `postgresql.postgresqlDatabase` | PostgreSQL database name | `artifactory` | -| `postgresql.postgresqlUsername` | PostgreSQL database user | `artifactory` | -| `postgresql.postgresqlPassword` | PostgreSQL database password | | -| `postgresql.postgresqlExtendedConf.listenAddresses` | PostgreSQL listen address | `"'*'"` | -| `postgresql.postgresqlExtendedConf.maxConnections` | PostgreSQL max_connections parameter | `1500` | -| `postgresql.persistence.enabled` | PostgreSQL use persistent storage | `true` | -| `postgresql.persistence.size` | PostgreSQL persistent storage size | `50Gi` | -| `postgresql.service.port` | PostgreSQL database port | `5432` | -| `postgresql.resources.requests.memory` | PostgreSQL initial memory request | | -| `postgresql.resources.requests.cpu` | PostgreSQL initial cpu request | | -| `postgresql.resources.limits.memory` | PostgreSQL memory limit | | -| `postgresql.resources.limits.cpu` | PostgreSQL cpu limit | | -| `postgresql.master.nodeSelector` | PostgreSQL master node selector | `{}` | -| `postgresql.master.affinity` | PostgreSQL master node affinity | `{}` | -| `postgresql.master.tolerations` | PostgreSQL master node tolerations | `[]` | -| `postgresql.slave.nodeSelector` | PostgreSQL slave node selector | `{}` | -| `postgresql.slave.affinity` | PostgreSQL slave node affinity | `{}` | -| `postgresql.slave.tolerations` | PostgreSQL slave node tolerations | `[]` | -| `database.type` | External database type (`postgresql`, `mysql`, `oracle` or `mssql`) | | -| `database.driver` | External database driver e.g. `org.postgresql.Driver` | | -| `database.url` | External database connection URL | | -| `database.user` | External database username | | -| `database.password` | External database password | | -| `database.secrets.user.name` | External database username `Secret` name | | -| `database.secrets.user.key` | External database username `Secret` key | | -| `database.secrets.password.name` | External database password `Secret` name | | -| `database.secrets.password.key` | External database password `Secret` key | | -| `database.secrets.url.name ` | External database url `Secret` name | | -| `database.secrets.url.key` | External database url `Secret` key | | -| `networkpolicy.name` | Becomes part of the NetworkPolicy object name | `artifactory` | -| `networkpolicy.podselector` | Contains the YAML that specifies how to match pods. Usually using matchLabels. | | -| `networkpolicy.ingress` | YAML snippet containing to & from rules applied to incoming traffic | `- {}` (open to all inbound traffic) | -| `networkpolicy.egress` | YAML snippet containing to & from rules applied to outgoing traffic | `- {}` (open to all outbound traffic) | -| `filebeat.enabled` | Enable a filebeat container to send your logs to a log management solution like ELK | `false` | -| `filebeat.name` | filebeat container name | `artifactory-filebeat` | -| `filebeat.image.repository` | filebeat Docker image repository | `docker.elastic.co/beats/filebeat` | -| `filebeat.image.version` | filebeat Docker image version | `7.5.1` | -| `filebeat.logstashUrl` | The URL to the central Logstash service, if you have one | `logstash:5044` | -| `filebeat.livenessProbe.exec.command` | liveness probe exec command | see [values.yaml](stable/artifactory-ha/values.yaml) | -| `filebeat.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | -| `filebeat.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 180 | -| `filebeat.livenessProbe.periodSeconds` | How often to perform the probe | 10 | -| `filebeat.readinessProbe.exec.command` | readiness probe exec command | see [values.yaml](stable/artifactory-ha/values.yaml) | -| `filebeat.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | -| `filebeat.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | 180 | -| `filebeat.readinessProbe.periodSeconds` | How often to perform the probe | 10 | -| `filebeat.resources.requests.memory` | Filebeat initial memory request | | -| `filebeat.resources.requests.cpu` | Filebeat initial cpu request | | -| `filebeat.resources.limits.memory` | Filebeat memory limit | | -| `filebeat.resources.limits.cpu` | Filebeat cpu limit | | -| `filebeat.filebeatYml` | Filebeat yaml configuration file | see [values.yaml](stable/artifactory-ha/values.yaml) | +#### FluentD, Prometheus and Grafana + +To configure Prometheus and Grafana to gather metrics from Artifactory through the use of FluentD, please refer to the log analytics repo: + +https://github.com/jfrog/log-analytics-prometheus + +That repo contains a file `artifactory-ha-values.yaml` that can be used to deploy Prometheus, Service Monitor, and Grafana with this chart. -Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. ## Useful links - https://www.jfrog.com/confluence/display/EP/Getting+Started diff --git a/charts/artifactory-ha/UPGRADE_NOTES.md b/charts/artifactory-ha/UPGRADE_NOTES.md index 9c7f771be..b3326dccc 100644 --- a/charts/artifactory-ha/UPGRADE_NOTES.md +++ b/charts/artifactory-ha/UPGRADE_NOTES.md @@ -1,10 +1,14 @@ # JFrog Artifactory Chart Upgrade Notes This file describes special upgrade notes needed at specific versions -## Upgrade from 1.X to 2.X (Chart Versions) +## Upgrade from 1.X to 2.X and above (Chart Versions) * If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you!** * To upgrade from a version prior to 1.x, you first need to upgrade to latest version of 1.x as described in https://github.com/jfrog/charts/blob/master/stable/artifactory-ha/CHANGELOG.md. +* Note: If you are upgrading from 1.x to 4.x and above chart versions, please delete the existing statefulset of postgresql before upgrading the chart due to breaking changes in postgresql subchart. +```bash +kubectl delete statefulsets -postgresql +``` ## Upgrade from 0.X to 1.X (Chart Versions) **DOWNTIME IS REQUIRED FOR AN UPGRADE!** diff --git a/charts/artifactory-ha/charts/postgresql/Chart.yaml b/charts/artifactory-ha/charts/postgresql/Chart.yaml index a40485f21..8365f1fb7 100755 --- a/charts/artifactory-ha/charts/postgresql/Chart.yaml +++ b/charts/artifactory-ha/charts/postgresql/Chart.yaml @@ -1,5 +1,7 @@ +annotations: + category: Database apiVersion: v1 -appVersion: 11.7.0 +appVersion: 11.9.0 description: Chart for PostgreSQL, an object-relational database management system (ORDBMS) with an emphasis on extensibility and on standards-compliance. engine: gotpl @@ -20,4 +22,4 @@ maintainers: name: postgresql sources: - https://github.com/bitnami/bitnami-docker-postgresql -version: 8.7.3 +version: 9.3.4 diff --git a/charts/artifactory-ha/charts/postgresql/README.md b/charts/artifactory-ha/charts/postgresql/README.md index c2b848af1..319291bc6 100755 --- a/charts/artifactory-ha/charts/postgresql/README.md +++ b/charts/artifactory-ha/charts/postgresql/README.md @@ -4,7 +4,7 @@ For HA, please see [this repo](https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha) -## TL;DR; +## TL;DR ```console $ helm repo add bitnami https://charts.bitnami.com/bitnami @@ -20,7 +20,7 @@ Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment ## Prerequisites - Kubernetes 1.12+ -- Helm 2.11+ or Helm 3.0-beta3+ +- Helm 2.12+ or Helm 3.0-beta3+ - PV provisioner support in the underlying infrastructure ## Installing the Chart @@ -42,7 +42,15 @@ To uninstall/delete the `my-release` deployment: $ helm delete my-release ``` -The command removes all the Kubernetes components associated with the chart and deletes the release. +The command removes all the Kubernetes components but PVC's associated with the chart and deletes the release. + +To delete the PVC's associated with `my-release`: + +```console +$ kubectl delete pvc -l release=my-release +``` + +> **Note**: Deleting the PVC's will delete postgresql data as well. Please be cautious before doing it. ## Parameters @@ -95,10 +103,10 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `replication.synchronousCommit` | Set synchronous commit mode. Allowed values: `on`, `remote_apply`, `remote_write`, `local` and `off` | `off` | | `replication.numSynchronousReplicas` | Number of replicas that will have synchronous replication. Note: Cannot be greater than `replication.slaveReplicas`. | `0` | | `replication.applicationName` | Cluster application name. Useful for advanced replication settings | `my_application` | -| `existingSecret` | Name of existing secret to use for PostgreSQL passwords. The secret has to contain the keys `postgresql-postgres-password` which is the password for `postgresqlUsername` when it is different of `postgres`, `postgresql-password` which will override `postgresqlPassword`, `postgresql-replication-password` which will override `replication.password` and `postgresql-ldap-password` which will be sed to authenticate on LDAP. The value is evaluated as a template. | `nil` | -| `postgresqlPostgresPassword` | PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`) | _random 10 character alphanumeric string_ | -| `postgresqlUsername` | PostgreSQL admin user | `postgres` | -| `postgresqlPassword` | PostgreSQL admin password | _random 10 character alphanumeric string_ | +| `existingSecret` | Name of existing secret to use for PostgreSQL passwords. The secret has to contain the keys `postgresql-postgres-password` which is the password for `postgresqlUsername` when it is different of `postgres`, `postgresql-password` which will override `postgresqlPassword`, `postgresql-replication-password` which will override `replication.password` and `postgresql-ldap-password` which will be sed to authenticate on LDAP. The value is evaluated as a template. | `nil` | +| `postgresqlPostgresPassword` | PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`, in which case`postgres` is the admin username). | _random 10 character alphanumeric string_ | +| `postgresqlUsername` | PostgreSQL user (creates a non-admin user when `postgresqlUsername` is not `postgres`) | `postgres` | +| `postgresqlPassword` | PostgreSQL user password | _random 10 character alphanumeric string_ | | `postgresqlDatabase` | PostgreSQL database | `nil` | | `postgresqlDataDir` | PostgreSQL data dir folder | `/bitnami/postgresql` (same value as persistence.mountPath) | | `extraEnv` | Any extra environment variables you would like to pass on to the pod. The value is evaluated as a template. | `[]` | @@ -112,7 +120,7 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `extendedConfConfigMap` | ConfigMap with the extended PostgreSQL configuration files. The value is evaluated as a template. | `nil` | | `initdbScripts` | Dictionary of initdb scripts | `nil` | | `initdbUser` | PostgreSQL user to execute the .sql and sql.gz scripts | `nil` | -| `initdbPassword` | Password for the user specified in `initdbUser` | `nil` | +| `initdbPassword` | Password for the user specified in `initdbUser` | `nil` | | `initdbScriptsConfigMap` | ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`). The value is evaluated as a template. | `nil` | | `initdbScriptsSecret` | Secret with initdb scripts that contain sensitive information (Note: can be used with `initdbScriptsConfigMap` or `initdbScripts`). The value is evaluated as a template. | `nil` | | `service.type` | Kubernetes Service type | `ClusterIP` | @@ -132,6 +140,7 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `persistence.accessModes` | PVC Access Mode for PostgreSQL volume | `[ReadWriteOnce]` | | `persistence.size` | PVC Storage Request for PostgreSQL volume | `8Gi` | | `persistence.annotations` | Annotations for the PVC | `{}` | +| `commonAnnotations` | Annotations to be added to all deployed resources (rendered as a template) | `{}` | | `master.nodeSelector` | Node labels for pod assignment (postgresql master) | `{}` | | `master.affinity` | Affinity labels for pod assignment (postgresql master) | `{}` | | `master.tolerations` | Toleration labels for pod assignment (postgresql master) | `[]` | @@ -139,7 +148,7 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `master.labels` | Map of labels to add to the statefulset (postgresql master) | `{}` | | `master.podAnnotations` | Map of annotations to add to the pods (postgresql master) | `{}` | | `master.podLabels` | Map of labels to add to the pods (postgresql master) | `{}` | -| `master.priorityClassName` | Priority Class to use for each pod (postgresql master) | `nil` | +| `master.priorityClassName` | Priority Class to use for each pod (postgresql master) | `nil` | | `master.extraInitContainers` | Additional init containers to add to the pods (postgresql master) | `[]` | | `master.extraVolumeMounts` | Additional volume mounts to add to the pods (postgresql master) | `[]` | | `master.extraVolumes` | Additional volumes to add to the pods (postgresql master) | `[]` | @@ -154,7 +163,7 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `slave.labels` | Map of labels to add to the statefulsets (postgresql slave) | `{}` | | `slave.podAnnotations` | Map of annotations to add to the pods (postgresql slave) | `{}` | | `slave.podLabels` | Map of labels to add to the pods (postgresql slave) | `{}` | -| `slave.priorityClassName` | Priority Class to use for each pod (postgresql slave) | `nil` | +| `slave.priorityClassName` | Priority Class to use for each pod (postgresql slave) | `nil` | | `slave.extraInitContainers` | Additional init containers to add to the pods (postgresql slave) | `[]` | | `slave.extraVolumeMounts` | Additional volume mounts to add to the pods (postgresql slave) | `[]` | | `slave.extraVolumes` | Additional volumes to add to the pods (postgresql slave) | `[]` | @@ -162,13 +171,14 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `slave.service.type` | Allows using a different service type for Slave | `nil` | | `slave.service.nodePort` | Allows using a different nodePort for Slave | `nil` | | `slave.service.clusterIP` | Allows using a different clusterIP for Slave | `nil` | +| `slave.persistence.enabled` | Whether to enable slave replicas persistence | `true` | | `terminationGracePeriodSeconds` | Seconds the pod needs to terminate gracefully | `nil` | | `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `250m` | | `securityContext.enabled` | Enable security context | `true` | | `securityContext.fsGroup` | Group ID for the container | `1001` | | `securityContext.runAsUser` | User ID for the container | `1001` | | `serviceAccount.enabled` | Enable service account (Note: Service Account will only be automatically created if `serviceAccount.name` is not set) | `false` | -| `serviceAcccount.name` | Name of existing service account | `nil` | +| `serviceAccount.name` | Name of existing service account | `nil` | | `livenessProbe.enabled` | Would you like a livenessProbe to be enabled | `true` | | `networkPolicy.enabled` | Enable NetworkPolicy | `false` | | `networkPolicy.allowExternal` | Don't require client label for connections | `true` | @@ -184,6 +194,13 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `readinessProbe.timeoutSeconds` | When the probe times out | 5 | | `readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | | `readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `tls.enabled` | Enable TLS traffic support | `false` | +| `tls.preferServerCiphers` | Whether to use the server's TLS cipher preferences rather than the client's | `true` | +| `tls.certificatesSecret` | Name of an existing secret that contains the certificates | `nil` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename. If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate. |`nil` | +| `tls.crlFilename` | File containing a Certificate Revocation List |`nil` | | `metrics.enabled` | Start a prometheus exporter | `false` | | `metrics.service.type` | Kubernetes Service type | `ClusterIP` | | `service.clusterIP` | Static clusterIP or None for headless services | `nil` | @@ -198,12 +215,13 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `metrics.prometheusRule.additionalLabels` | Additional labels that can be used so prometheusRules will be discovered by Prometheus | `{}` | | `metrics.prometheusRule.namespace` | namespace where prometheusRules resource should be created | the same namespace as postgresql | | `metrics.prometheusRule.rules` | [rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) to be created, check values for an example. | `[]` | -| `metrics.image.registry` | PostgreSQL Image registry | `docker.io` | -| `metrics.image.repository` | PostgreSQL Image name | `bitnami/postgres-exporter` | -| `metrics.image.tag` | PostgreSQL Image tag | `{TAG_NAME}` | -| `metrics.image.pullPolicy` | PostgreSQL Image pull policy | `IfNotPresent` | +| `metrics.image.registry` | PostgreSQL Exporter Image registry | `docker.io` | +| `metrics.image.repository` | PostgreSQL Exporter Image name | `bitnami/postgres-exporter` | +| `metrics.image.tag` | PostgreSQL Exporter Image tag | `{TAG_NAME}` | +| `metrics.image.pullPolicy` | PostgreSQL Exporter Image pull policy | `IfNotPresent` | | `metrics.image.pullSecrets` | Specify Image pull secrets | `nil` (does not add image pull secrets to deployed pods) | | `metrics.customMetrics` | Additional custom metrics | `nil` | +| `metrics.extraEnvVars` | Extra environment variables to add to exporter | `{}` (evaluated as a template) | | `metrics.securityContext.enabled` | Enable security context for metrics | `false` | | `metrics.securityContext.runAsUser` | User ID for the container for metrics | `1001` | | `metrics.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | @@ -218,6 +236,9 @@ The following tables lists the configurable parameters of the PostgreSQL chart a | `metrics.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | | `metrics.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | | `updateStrategy` | Update strategy policy | `{type: "RollingUpdate"}` | +| `psp.create` | Create Pod Security Policy | `false` | +| `rbac.create` | Create Role and RoleBinding (required for PSP to work) | `false` | + Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, @@ -287,7 +308,7 @@ At the top level, there is a service object which defines the services for both ### Change PostgreSQL version -To modify the PostgreSQL version used in this chart you can specify a [valid image tag](https://hub.docker.com/r/bitnami/postgresql/tags/) using the `image.tag` parameter. For example, `image.tag=12.0.0` +To modify the PostgreSQL version used in this chart you can specify a [valid image tag](https://hub.docker.com/r/bitnami/postgresql/tags/) using the `image.tag` parameter. For example, `image.tag=X.Y.Z`. This approach is also applicable to other images like exporters. ### postgresql.conf / pg_hba.conf files as configMap @@ -316,6 +337,35 @@ In addition to these options, you can also set an external ConfigMap with all th The allowed extensions are `.sh`, `.sql` and `.sql.gz`. +### Securing traffic using TLS + +TLS support can be enabled in the chart by specifying the `tls.` parameters while creating a release. The following parameters should be configured to properly enable the TLS support in the chart: + +- `tls.enabled`: Enable TLS support. Defaults to `false` +- `tls.certificatesSecret`: Name of an existing secret that contains the certificates. No defaults. +- `tls.certFilename`: Certificate filename. No defaults. +- `tls.certKeyFilename`: Certificate key filename. No defaults. + +For example: + +* First, create the secret with the cetificates files: + + ```console + kubectl create secret generic certificates-tls-secret --from-file=./cert.crt --from-file=./cert.key --from-file=./ca.crt + ``` + +* Then, use the following parameters: + + ```console + volumePermissions.enabled=true + tls.enabled=true + tls.certificatesSecret="certificates-tls-secret" + tls.certFilename="cert.crt" + tls.certKeyFilename="cert.key" + ``` + + > Note TLS and VolumePermissions: PostgreSQL requires certain permissions on sensitive files (such as certificate keys) to start up. Due to an on-going [issue](https://github.com/kubernetes/kubernetes/issues/57923) regarding kubernetes permissions and the use of `securityContext.runAsUser`, you must enable `volumePermissions` to ensure everything works as expected. + ### Sidecars If you need additional containers to run within the same pod as PostgreSQL (e.g. an additional metrics or logging exporter), you can do so via the `sidecars` config parameter. Simply define your container according to the Kubernetes container spec. @@ -443,6 +493,60 @@ $ helm upgrade my-release stable/postgresql \ > Note: you need to substitute the placeholders _[POSTGRESQL_PASSWORD]_, and _[REPLICATION_PASSWORD]_ with the values obtained from instructions in the installation notes. +## 9.0.0 + +In this version the chart was adapted to follow the Helm label best practices, see [PR 3021](https://github.com/bitnami/charts/pull/3021). That means the backward compatibility is not guarantee when upgrading the chart to this major version. + +As a workaround, you can delete the existing statefulset (using the `--cascade=false` flag pods are not deleted) before upgrade the chart. For example, this can be a valid workflow: + +- Deploy an old version (8.X.X) +```console +$ helm install postgresql bitnami/postgresql --version 8.10.14 +``` + +- Old version is up and running +```console +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 1 2020-08-04 13:39:54.783480286 +0000 UTC deployed postgresql-8.10.14 11.8.0 + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 76s +``` + +- The upgrade to the latest one (9.X.X) is going to fail +```console +$ helm upgrade postgresql bitnami/postgresql +Error: UPGRADE FAILED: cannot patch "postgresql-postgresql" with kind StatefulSet: StatefulSet.apps "postgresql-postgresql" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden +``` + +- Delete the statefulset +```console +$ kubectl delete statefulsets.apps --cascade=false postgresql-postgresql +statefulset.apps "postgresql-postgresql" deleted +``` + +- Now the upgrade works +```cosnole +$ helm upgrade postgresql bitnami/postgresql +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 3 2020-08-04 13:42:08.020385884 +0000 UTC deployed postgresql-9.1.2 11.8.0 +``` + +- We can kill the existing pod and the new statefulset is going to create a new one: +```console +$ kubectl delete pod postgresql-postgresql-0 +pod "postgresql-postgresql-0" deleted + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 19s +``` + +Please, note that without the `--cascade=false` both objects (statefulset and pod) are going to be removed and both objects will be deployed again with the `helm upgrade` command + ## 8.0.0 Prefixes the port names with their protocols to comply with Istio conventions. diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/.helmignore b/charts/artifactory-ha/charts/postgresql/charts/common/.helmignore new file mode 100755 index 000000000..50af03172 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/.helmignore @@ -0,0 +1,22 @@ +# 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 +.vscode/ diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/Chart.yaml b/charts/artifactory-ha/charts/postgresql/charts/common/Chart.yaml new file mode 100755 index 000000000..a56ef8bf5 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/Chart.yaml @@ -0,0 +1,22 @@ +annotations: + category: Infrastructure +apiVersion: v1 +appVersion: 0.6.2 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +engine: gotpl +home: http://www.bitnami.com/ +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: common +sources: +- https://github.com/bitnami/charts +version: 0.6.2 diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/README.md b/charts/artifactory-ha/charts/postgresql/charts/common/README.md new file mode 100755 index 000000000..e04391a3f --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/README.md @@ -0,0 +1,274 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 0.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 2.12+ or Helm 3.0-beta3+ + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +**Names** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +**Images** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | + +**Labels** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Return the proper Docker Image Registry Secret Names | `.` Chart context | + +**Storage** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.storage.class` | Return the proper Storage Class | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +**TplValues** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frecuently is the chart context `$` or `.` | + +**Capabilities** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | + +**Validations** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "context" $` secret and field are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | When a chart is using `bitnami/mariadb` as subchart you should use this to validate required password are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "context" $` | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | + +**Warnings** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +**Errors** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +**Utils** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | + +**Secrets** + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets. + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +**Example of use** + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possiblity of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +**NOTES.txt** + +``` +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 --decode) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 --decode) +``` + +## Notable changes + +N/A diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_capabilities.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_capabilities.tpl new file mode 100755 index 000000000..c0ea2c70c --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_capabilities.tpl @@ -0,0 +1,22 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- end -}} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_errors.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_errors.tpl new file mode 100755 index 000000000..d6d3ec65a --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_errors.tpl @@ -0,0 +1,20 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: you must provide your current passwords when upgrade the release%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_images.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_images.tpl new file mode 100755 index 000000000..aafde9f3b --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_images.tpl @@ -0,0 +1,43 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $tag := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_labels.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_labels.tpl new file mode 100755 index 000000000..252066c7e --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_names.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_names.tpl new file mode 100755 index 000000000..adf2a74f4 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_names.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | 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 "common.names.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 -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_secrets.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_secrets.tpl new file mode 100755 index 000000000..d6165a294 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_secrets.tpl @@ -0,0 +1,49 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret - Optional. The path to the existing secrets in the values.yaml given by the user + to be used istead of the default one. +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = cat $name .defaultNameSuffix -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- $name = .name -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret - Optional. The path to the existing secrets in the values.yaml given by the user + to be used istead of the default one. +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_storage.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_storage.tpl new file mode 100755 index 000000000..60e2a844f --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_tplvalues.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_tplvalues.tpl new file mode 100755 index 000000000..2db166851 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_utils.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_utils.tpl new file mode 100755 index 000000000..7d02f2ef6 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_utils.tpl @@ -0,0 +1,26 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ .context.Release.Namespace }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 --decode) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_validations.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_validations.tpl new file mode 100755 index 000000000..62635b30e --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_validations.tpl @@ -0,0 +1,219 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $valueKeyArray := splitList "." .valueKey -}} + {{- $value := "" -}} + {{- $latestObj := $.context.Values -}} + {{- range $valueKeyArray -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.valueKey | fail -}} + {{- end -}} + + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} + {{- end -}} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + + {{- printf "\n '%s' must not be empty, please add '--set %s=$%s' to the command.%s" .valueKey .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a mariadb required password must not be empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "context" $) }} + +Validate value params: + - secret - String - Required. Name of the secret where mysql values are stored, e.g: "mysql-passwords-secret" +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- if and (not .context.Values.mariadb.existingSecret) .context.Values.mariadb.enabled -}} + {{- $requiredPasswords := list -}} + + {{- if .context.Values.mariadb.secret.requirePasswords -}} + {{- $requiredRootMariadbPassword := dict "valueKey" "mariadb.rootUser.password" "secret" .secretName "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootMariadbPassword -}} + + {{- if not (empty .context.Values.mariadb.db.user) -}} + {{- $requiredMariadbPassword := dict "valueKey" "mariadb.db.password" "secret" .secretName "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredMariadbPassword -}} + {{- end -}} + + {{- if .context.Values.mariadb.replication.enabled -}} + {{- $requiredReplicationPassword := dict "valueKey" "mariadb.replication.password" "secret" .secretName "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a postgresql required password must not be empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + + {{- if and (not $existingSecret) $enabled -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- if $enabledReplication -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliar function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliar function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliar function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- .context.Values.postgresql.enabled | quote -}} + {{- else -}} + true + {{- end -}} +{{- end -}} + +{{/* +Auxiliar function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliar function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- .context.Values.postgresql.replication.enabled | quote -}} + {{- else -}} + {{- .context.Values.replication.enabled | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliar function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/templates/_warnings.tpl b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_warnings.tpl new file mode 100755 index 000000000..ae10fa41e --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/charts/common/values.yaml b/charts/artifactory-ha/charts/postgresql/charts/common/values.yaml new file mode 100755 index 000000000..9ecdc93f5 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/charts/common/values.yaml @@ -0,0 +1,3 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +exampleValue: common-chart diff --git a/charts/artifactory-ha/charts/postgresql/ci/commonAnnotations.yaml b/charts/artifactory-ha/charts/postgresql/ci/commonAnnotations.yaml new file mode 100755 index 000000000..f6977823c --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/ci/commonAnnotations.yaml @@ -0,0 +1,3 @@ +commonAnnotations: + helm.sh/hook: 'pre-install, pre-upgrade' + helm.sh/hook-weight: '-1' diff --git a/charts/artifactory-ha/charts/postgresql/requirements.lock b/charts/artifactory-ha/charts/postgresql/requirements.lock new file mode 100755 index 000000000..72e1642e2 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/requirements.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 0.6.2 +digest: sha256:740783295d301fdd168fafdbaa760de27ab54b0ff36b513589a5a2515072b885 +generated: "2020-09-01T17:40:02.795096189Z" diff --git a/charts/artifactory-ha/charts/postgresql/requirements.yaml b/charts/artifactory-ha/charts/postgresql/requirements.yaml new file mode 100755 index 000000000..2c28bfe14 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/requirements.yaml @@ -0,0 +1,4 @@ +dependencies: + - name: common + version: 0.x.x + repository: https://charts.bitnami.com/bitnami diff --git a/charts/artifactory-ha/charts/postgresql/templates/NOTES.txt b/charts/artifactory-ha/charts/postgresql/templates/NOTES.txt index 3b5e6c60d..596e969ce 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/NOTES.txt +++ b/charts/artifactory-ha/charts/postgresql/templates/NOTES.txt @@ -7,7 +7,7 @@ PostgreSQL can be accessed via port {{ template "postgresql.port" . }} on the fo {{ template "postgresql.fullname" . }}-read.{{ .Release.Namespace }}.svc.cluster.local - Read only connection {{- end }} -{{- if and .Values.postgresqlPostgresPassword (not (eq .Values.postgresqlUsername "postgres")) }} +{{- if and (not (eq .Values.postgresqlUsername "postgres")) (or .Values.postgresqlPostgresPassword (include "postgresql.useExistingSecret" .)) }} To get the password for "postgres" run: @@ -52,9 +52,8 @@ To connect to your database from outside the cluster execute the following comma {{- include "postgresql.validateValues" . -}} -{{- if and (contains "bitnami/" .Values.image.repository) (not (.Values.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} +{{- include "common.warnings.rollingTag" .Values.image -}} -WARNING: Rolling tag detected ({{ .Values.image.repository }}:{{ .Values.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. -+info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- $passwordValidationErrors := include "common.validations.values.postgresql.passwords" (dict "secret" (include "postgresql.fullname" .) "context" $) -}} -{{- end }} +{{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $passwordValidationErrors) "context" $) -}} diff --git a/charts/artifactory-ha/charts/postgresql/templates/_helpers.tpl b/charts/artifactory-ha/charts/postgresql/templates/_helpers.tpl index 708434856..68cd0dc0e 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/_helpers.tpl +++ b/charts/artifactory-ha/charts/postgresql/templates/_helpers.tpl @@ -220,13 +220,20 @@ Get the password secret. {{- end -}} {{- end -}} +{{/* +Return true if we should use an existingSecret. +*/}} +{{- define "postgresql.useExistingSecret" -}} +{{- if or .Values.global.postgresql.existingSecret .Values.existingSecret -}} + {{- true -}} +{{- end -}} +{{- end -}} + {{/* Return true if a secret object should be created */}} {{- define "postgresql.createSecret" -}} -{{- if .Values.global.postgresql.existingSecret }} -{{- else if .Values.existingSecret -}} -{{- else -}} +{{- if not (include "postgresql.useExistingSecret" .) -}} {{- true -}} {{- end -}} {{- end -}} @@ -253,6 +260,15 @@ Get the extended configuration ConfigMap name. {{- end -}} {{- end -}} +{{/* +Return true if a configmap should be mounted with PostgreSQL configuration +*/}} +{{- define "postgresql.mountConfigurationCM" -}} +{{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + {{- true -}} +{{- end -}} +{{- end -}} + {{/* Get the initialization scripts ConfigMap name. */}} @@ -325,9 +341,9 @@ Get the readiness probe command {{- define "postgresql.readinessProbeCommand" -}} - | {{- if (include "postgresql.database" .) }} - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d {{ (include "postgresql.database" .) | quote }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} {{- else }} - exec pg_isready -U {{ include "postgresql.username" . | quote }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} {{- end }} {{- if contains "bitnami/" .Values.image.repository }} [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ] @@ -399,6 +415,8 @@ Compile all warnings into a single message, and call fail. {{- define "postgresql.validateValues" -}} {{- $messages := list -}} {{- $messages := append $messages (include "postgresql.validateValues.ldapConfigurationMethod" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.psp" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.tls" .) -}} {{- $messages := without $messages "" -}} {{- $message := join "\n" $messages -}} @@ -418,3 +436,66 @@ postgresql: ldap.url, ldap.server More info at https://www.postgresql.org/docs/current/auth-ldap.html {{- end -}} {{- end -}} + +{{/* +Validate values of Postgresql - If PSP is enabled RBAC should be enabled too +*/}} +{{- define "postgresql.validateValues.psp" -}} +{{- if and .Values.psp.create (not .Values.rbac.create) }} +postgresql: psp.create, rbac.create + RBAC should be enabled if PSP is enabled in order for PSP to work. + More info at https://kubernetes.io/docs/concepts/policy/pod-security-policy/#authorizing-policies +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for podsecuritypolicy. +*/}} +{{- define "podsecuritypolicy.apiVersion" -}} +{{- if semverCompare "<1.10-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "policy/v1beta1" -}} +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql TLS - When TLS is enabled, so must be VolumePermissions +*/}} +{{- define "postgresql.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.volumePermissions.enabled) }} +postgresql: tls.enabled, volumePermissions.enabled + When TLS is enabled you must enable volumePermissions as well to ensure certificates files have + the right permissions. +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "postgresql.tlsCert" -}} +{{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "postgresql.tlsCertKey" -}} +{{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "postgresql.tlsCACert" -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.certCAFilename -}} +{{- end -}} + +{{/* +Return the path to the CRL file. +*/}} +{{- define "postgresql.tlsCRL" -}} +{{- if .Values.tls.crlFilename -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.crlFilename -}} +{{- end -}} +{{- end -}} diff --git a/charts/artifactory-ha/charts/postgresql/templates/configmap.yaml b/charts/artifactory-ha/charts/postgresql/templates/configmap.yaml index d2178c077..b29ef6040 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/configmap.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/configmap.yaml @@ -4,10 +4,10 @@ kind: ConfigMap metadata: name: {{ template "postgresql.fullname" . }}-configuration labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} data: {{- if (.Files.Glob "files/postgresql.conf") }} {{ (.Files.Glob "files/postgresql.conf").AsConfig | indent 2 }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/extended-config-configmap.yaml b/charts/artifactory-ha/charts/postgresql/templates/extended-config-configmap.yaml index 8a4119578..f21a97654 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/extended-config-configmap.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/extended-config-configmap.yaml @@ -4,10 +4,10 @@ kind: ConfigMap metadata: name: {{ template "postgresql.fullname" . }}-extended-configuration labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} data: {{- with .Files.Glob "files/conf.d/*.conf" }} {{ .AsConfig | indent 2 }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/initialization-configmap.yaml b/charts/artifactory-ha/charts/postgresql/templates/initialization-configmap.yaml index 8eb5e0588..6637867a3 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/initialization-configmap.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/initialization-configmap.yaml @@ -4,10 +4,10 @@ kind: ConfigMap metadata: name: {{ template "postgresql.fullname" . }}-init-scripts labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} {{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.sql.gz" }} binaryData: {{- range $path, $bytes := . }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/metrics-configmap.yaml b/charts/artifactory-ha/charts/postgresql/templates/metrics-configmap.yaml index 524aa2f6a..6b7a3171e 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/metrics-configmap.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/metrics-configmap.yaml @@ -4,10 +4,10 @@ kind: ConfigMap metadata: name: {{ template "postgresql.metricsCM" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} data: custom-metrics.yaml: {{ toYaml .Values.metrics.customMetrics | quote }} {{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/metrics-svc.yaml b/charts/artifactory-ha/charts/postgresql/templates/metrics-svc.yaml index c610f09af..b993c9971 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/metrics-svc.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/metrics-svc.yaml @@ -4,12 +4,12 @@ kind: Service metadata: name: {{ template "postgresql.fullname" . }}-metrics labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} annotations: -{{ toYaml .Values.metrics.service.annotations | indent 4 }} + {{- if .Values.commonAnnotations }} + {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- toYaml .Values.metrics.service.annotations | nindent 4 }} spec: type: {{ .Values.metrics.service.type }} {{- if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerIP }} @@ -20,7 +20,6 @@ spec: port: 9187 targetPort: http-metrics selector: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name }} + {{- include "common.labels.matchLabels" . | nindent 4 }} role: master {{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/networkpolicy.yaml b/charts/artifactory-ha/charts/postgresql/templates/networkpolicy.yaml index ea1fc9b3a..2a7b372fe 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/networkpolicy.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/networkpolicy.yaml @@ -4,15 +4,14 @@ apiVersion: {{ template "postgresql.networkPolicy.apiVersion" . }} metadata: name: {{ template "postgresql.fullname" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} spec: podSelector: matchLabels: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 6 }} ingress: # Allow inbound connections - ports: @@ -28,8 +27,7 @@ spec: {{- end }} - podSelector: matchLabels: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 14 }} role: slave {{- end }} # Allow prometheus scrapes diff --git a/charts/artifactory-ha/charts/postgresql/templates/podsecuritypolicy.yaml b/charts/artifactory-ha/charts/postgresql/templates/podsecuritypolicy.yaml new file mode 100755 index 000000000..da0b3ab11 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/templates/podsecuritypolicy.yaml @@ -0,0 +1,37 @@ +{{- if .Values.psp.create }} +apiVersion: {{ include "podsecuritypolicy.apiVersion" . }} +kind: PodSecurityPolicy +metadata: + name: {{ template "postgresql.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + privileged: false + volumes: + - 'configMap' + - 'secret' + - 'persistentVolumeClaim' + - 'emptyDir' + - 'projected' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'MustRunAsNonRoot' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/prometheusrule.yaml b/charts/artifactory-ha/charts/postgresql/templates/prometheusrule.yaml index 44f1242dd..b0c41b1a4 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/prometheusrule.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/prometheusrule.yaml @@ -7,13 +7,13 @@ metadata: namespace: {{ . }} {{- end }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -{{- with .Values.metrics.prometheusRule.additionalLabels }} -{{ toYaml . | indent 4 }} -{{- end }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- with .Values.metrics.prometheusRule.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} spec: {{- with .Values.metrics.prometheusRule.rules }} groups: diff --git a/charts/artifactory-ha/charts/postgresql/templates/role.yaml b/charts/artifactory-ha/charts/postgresql/templates/role.yaml new file mode 100755 index 000000000..6d3cf50a4 --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/templates/role.yaml @@ -0,0 +1,19 @@ +{{- if .Values.rbac.create }} +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "postgresql.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +rules: + {{- if .Values.psp.create }} + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + verbs: ["use"] + resourceNames: + - {{ template "postgresql.fullname" . }} + {{- end }} +{{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/rolebinding.yaml b/charts/artifactory-ha/charts/postgresql/templates/rolebinding.yaml new file mode 100755 index 000000000..f7837388d --- /dev/null +++ b/charts/artifactory-ha/charts/postgresql/templates/rolebinding.yaml @@ -0,0 +1,19 @@ +{{- if .Values.rbac.create }} +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "postgresql.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +roleRef: + kind: Role + name: {{ template "postgresql.fullname" . }} + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ default (include "postgresql.fullname" . ) .Values.serviceAccount.name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/secrets.yaml b/charts/artifactory-ha/charts/postgresql/templates/secrets.yaml index 094d18b49..c93dbe0bd 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/secrets.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/secrets.yaml @@ -4,10 +4,10 @@ kind: Secret metadata: name: {{ template "postgresql.fullname" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} type: Opaque data: {{- if and .Values.postgresqlPostgresPassword (not (eq .Values.postgresqlUsername "postgres")) }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/serviceaccount.yaml b/charts/artifactory-ha/charts/postgresql/templates/serviceaccount.yaml index 27e5b516e..17f7ff399 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/serviceaccount.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/serviceaccount.yaml @@ -3,9 +3,9 @@ apiVersion: v1 kind: ServiceAccount metadata: labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} name: {{ template "postgresql.fullname" . }} -{{- end }} \ No newline at end of file + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/servicemonitor.yaml b/charts/artifactory-ha/charts/postgresql/templates/servicemonitor.yaml index f3a529a96..d57b7fb48 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/servicemonitor.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/servicemonitor.yaml @@ -7,13 +7,14 @@ metadata: namespace: {{ .Values.metrics.serviceMonitor.namespace }} {{- end }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} {{- if .Values.metrics.serviceMonitor.additionalLabels }} -{{ toYaml .Values.metrics.serviceMonitor.additionalLabels | indent 4 }} + {{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }} {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + spec: endpoints: - port: http-metrics @@ -28,6 +29,5 @@ spec: - {{ .Release.Namespace }} selector: matchLabels: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name }} + {{- include "common.labels.matchLabels" . | nindent 6 }} {{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/statefulset-slaves.yaml b/charts/artifactory-ha/charts/postgresql/templates/statefulset-slaves.yaml index b6d607672..54d24099f 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/statefulset-slaves.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/statefulset-slaves.yaml @@ -4,33 +4,29 @@ kind: StatefulSet metadata: name: "{{ template "postgresql.fullname" . }}-slave" labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} {{- with .Values.slave.labels }} {{ toYaml . | indent 4 }} {{- end }} -{{- with .Values.slave.annotations }} annotations: -{{ toYaml . | indent 4 }} -{{- end }} + {{- if .Values.commonAnnotations }} + {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.slave.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} spec: serviceName: {{ template "postgresql.fullname" . }}-headless replicas: {{ .Values.replication.slaveReplicas }} selector: matchLabels: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 6 }} role: slave template: metadata: name: {{ template "postgresql.fullname" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 8 }} role: slave {{- with .Values.slave.podLabels }} {{ toYaml . | indent 8 }} @@ -68,7 +64,7 @@ spec: {{- end }} {{- if or .Values.slave.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} initContainers: - {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled)) }} + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} - name: init-chmod-data image: {{ template "postgresql.volumePermissions.image" . }} imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} @@ -79,10 +75,15 @@ spec: - /bin/sh - -cx - | - {{ if .Values.persistence.enabled }} - mkdir -p {{ .Values.persistence.mountPath }}/conf {{ .Values.persistence.mountPath }}/data - chmod 700 {{ .Values.persistence.mountPath }}/conf {{ .Values.persistence.mountPath }}/data - find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | \ + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} xargs chown -R `id -u`:`id -G | cut -d " " -f2` {{- else }} @@ -92,6 +93,15 @@ spec: {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} chmod -R 777 /dev/shm {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- end }} {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} securityContext: {{- else }} @@ -108,6 +118,12 @@ spec: - name: dshm mountPath: /dev/shm {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} {{- end }} {{- if .Values.slave.extraInitContainers }} {{ tpl .Values.slave.extraInitContainers . | indent 8 }} @@ -158,7 +174,7 @@ spec: value: {{ template "postgresql.fullname" . }} - name: POSTGRES_MASTER_PORT_NUMBER value: {{ include "postgresql.port" . | quote }} - {{- if and .Values.postgresqlPostgresPassword (not (eq .Values.postgresqlUsername "postgres")) }} + {{- if and (not (eq .Values.postgresqlUsername "postgres")) (or .Values.postgresqlPostgresPassword (include "postgresql.useExistingSecret" .)) }} {{- if .Values.usePasswordFile }} - name: POSTGRES_POSTGRES_PASSWORD_FILE value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" @@ -180,6 +196,24 @@ spec: name: {{ template "postgresql.secretName" . }} key: postgresql-password {{- end }} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} ports: - name: tcp-postgresql containerPort: {{ template "postgresql.port" . }} @@ -190,9 +224,9 @@ spec: - /bin/sh - -c {{- if (include "postgresql.database" .) }} - - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d {{ (include "postgresql.database" .) | quote }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} {{- else }} - - exec pg_isready -U {{ include "postgresql.username" . | quote }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} {{- end }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} @@ -236,6 +270,11 @@ spec: - name: postgresql-config mountPath: /bitnami/postgresql/conf {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} {{- if .Values.slave.extraVolumeMounts }} {{- toYaml .Values.slave.extraVolumeMounts | nindent 12 }} {{- end }} @@ -258,13 +297,20 @@ spec: configMap: name: {{ template "postgresql.extendedConfigurationCM" . }} {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} {{- if .Values.shmVolume.enabled }} - name: dshm emptyDir: medium: Memory sizeLimit: 1Gi {{- end }} - {{- if not .Values.persistence.enabled }} + {{- if or (not .Values.persistence.enabled) (not .Values.slave.persistence.enabled) }} - name: data emptyDir: {} {{- end }} @@ -276,7 +322,7 @@ spec: {{- if (eq "Recreate" .Values.updateStrategy.type) }} rollingUpdate: null {{- end }} -{{- if .Values.persistence.enabled }} +{{- if and .Values.persistence.enabled .Values.slave.persistence.enabled }} volumeClaimTemplates: - metadata: name: data diff --git a/charts/artifactory-ha/charts/postgresql/templates/statefulset.yaml b/charts/artifactory-ha/charts/postgresql/templates/statefulset.yaml index 66eaa01d1..0e6eefebb 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/statefulset.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/statefulset.yaml @@ -3,15 +3,16 @@ kind: StatefulSet metadata: name: {{ template "postgresql.master.fullname" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} {{- with .Values.master.labels }} {{- toYaml . | nindent 4 }} {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} {{- with .Values.master.annotations }} - annotations: {{ toYaml . | nindent 4 }} + {{- toYaml . | nindent 4 }} {{- end }} spec: serviceName: {{ template "postgresql.fullname" . }}-headless @@ -23,20 +24,16 @@ spec: {{- end }} selector: matchLabels: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 6 }} role: master template: metadata: name: {{ template "postgresql.fullname" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 8 }} role: master {{- with .Values.master.podLabels }} - {{- toYaml . | indent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.master.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} @@ -67,7 +64,7 @@ spec: {{- end }} {{- if or .Values.master.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} initContainers: - {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled)) }} + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} - name: init-chmod-data image: {{ template "postgresql.volumePermissions.image" . }} imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} @@ -79,9 +76,14 @@ spec: - -cx - | {{- if .Values.persistence.enabled }} - mkdir -p {{ .Values.persistence.mountPath }}/conf {{ .Values.persistence.mountPath }}/data - chmod 700 {{ .Values.persistence.mountPath }}/conf {{ .Values.persistence.mountPath }}/data - find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} xargs chown -R `id -u`:`id -G | cut -d " " -f2` {{- else }} @@ -91,6 +93,15 @@ spec: {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} chmod -R 777 /dev/shm {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- end }} {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} securityContext: {{- else }} @@ -107,9 +118,15 @@ spec: - name: dshm mountPath: /dev/shm {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} {{- end }} {{- if .Values.master.extraInitContainers }} - {{- tpl .Values.master.extraInitContainers . | nindent 8 }} + {{- include "postgresql.tplValue" ( dict "value" .Values.master.extraInitContainers "context" $ ) | nindent 8 }} {{- end }} {{- end }} {{- if .Values.master.priorityClassName }} @@ -177,7 +194,7 @@ spec: - name: POSTGRES_CLUSTER_APP_NAME value: {{ .Values.replication.applicationName }} {{- end }} - {{- if and .Values.postgresqlPostgresPassword (not (eq .Values.postgresqlUsername "postgres")) }} + {{- if and (not (eq .Values.postgresqlUsername "postgres")) (or .Values.postgresqlPostgresPassword (include "postgresql.useExistingSecret" .)) }} {{- if .Values.usePasswordFile }} - name: POSTGRES_POSTGRES_PASSWORD_FILE value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" @@ -243,6 +260,24 @@ spec: - name: POSTGRESQL_LDAP_URL value: {{ .Values.ldap.url }} {{- end}} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} {{- if .Values.extraEnvVarsCM }} envFrom: - configMapRef: @@ -258,9 +293,9 @@ spec: - /bin/sh - -c {{- if (include "postgresql.database" .) }} - - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d {{ (include "postgresql.database" .) | quote }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} {{- else }} - - exec pg_isready -U {{ include "postgresql.username" . | quote }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} {{- end }} initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.livenessProbe.periodSeconds }} @@ -299,6 +334,11 @@ spec: - name: postgresql-password mountPath: /opt/bitnami/postgresql/secrets/ {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} {{- if .Values.shmVolume.enabled }} - name: dshm mountPath: /dev/shm @@ -328,8 +368,14 @@ spec: {{- end }} env: {{- $database := required "In order to enable metrics you need to specify a database (.Values.postgresqlDatabase or .Values.global.postgresql.postgresqlDatabase)" (include "postgresql.database" .) }} + {{- $sslmode := ternary "require" "disable" .Values.tls.enabled }} + {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} + - name: DATA_SOURCE_NAME + value: {{ printf "host=127.0.0.1 port=%d user=%s sslmode=%s sslcert=%s sslkey=%s" (int (include "postgresql.port" .)) (include "postgresql.username" .) $sslmode (include "postgresql.tlsCert" .) (include "postgresql.tlsCertKey" .) }} + {{- else }} - name: DATA_SOURCE_URI - value: {{ printf "127.0.0.1:%d/%s?sslmode=disable" (int (include "postgresql.port" .)) $database | quote }} + value: {{ printf "127.0.0.1:%d/%s?sslmode=%s" (int (include "postgresql.port" .)) $database $sslmode }} + {{- end }} {{- if .Values.usePasswordFile }} - name: DATA_SOURCE_PASS_FILE value: "/opt/bitnami/postgresql/secrets/postgresql-password" @@ -342,6 +388,9 @@ spec: {{- end }} - name: DATA_SOURCE_USER value: {{ template "postgresql.username" . }} + {{- if .Values.metrics.extraEnvVars }} + {{- include "postgresql.tplValue" (dict "value" .Values.metrics.extraEnvVars "context" $) | nindent 12 }} + {{- end }} {{- if .Values.livenessProbe.enabled }} livenessProbe: httpGet: @@ -369,6 +418,11 @@ spec: - name: postgresql-password mountPath: /opt/bitnami/postgresql/secrets/ {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} {{- if .Values.metrics.customMetrics }} - name: custom-metrics mountPath: /conf @@ -408,6 +462,13 @@ spec: secret: secretName: {{ template "postgresql.initdbScriptsSecret" . }} {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} {{- if .Values.master.extraVolumes }} {{- toYaml .Values.master.extraVolumes | nindent 8 }} {{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/svc-headless.yaml b/charts/artifactory-ha/charts/postgresql/templates/svc-headless.yaml index 5c71f468d..49131578a 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/svc-headless.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/svc-headless.yaml @@ -3,10 +3,10 @@ kind: Service metadata: name: {{ template "postgresql.fullname" . }}-headless labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} spec: type: ClusterIP clusterIP: None @@ -15,5 +15,4 @@ spec: port: {{ template "postgresql.port" . }} targetPort: tcp-postgresql selector: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 4 }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/svc-read.yaml b/charts/artifactory-ha/charts/postgresql/templates/svc-read.yaml index 92bdda80e..885c7bb04 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/svc-read.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/svc-read.yaml @@ -10,12 +10,13 @@ kind: Service metadata: name: {{ template "postgresql.fullname" . }}-read labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} {{- if $serviceAnnotations }} - annotations: {{- include "postgresql.tplValue" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- include "postgresql.tplValue" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} {{- end }} spec: type: {{ $serviceType }} @@ -36,7 +37,6 @@ spec: nodePort: {{ $serviceNodePort }} {{- end }} selector: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 4 }} role: slave {{- end }} diff --git a/charts/artifactory-ha/charts/postgresql/templates/svc.yaml b/charts/artifactory-ha/charts/postgresql/templates/svc.yaml index 299e8d0b7..e9fc50456 100755 --- a/charts/artifactory-ha/charts/postgresql/templates/svc.yaml +++ b/charts/artifactory-ha/charts/postgresql/templates/svc.yaml @@ -9,12 +9,13 @@ kind: Service metadata: name: {{ template "postgresql.fullname" . }} labels: - app: {{ template "postgresql.name" . }} - chart: {{ template "postgresql.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "postgresql.tplValue" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} {{- if $serviceAnnotations }} - annotations: {{- include "postgresql.tplValue" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- include "postgresql.tplValue" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} {{- end }} spec: type: {{ $serviceType }} @@ -35,6 +36,5 @@ spec: nodePort: {{ $serviceNodePort }} {{- end }} selector: - app: {{ template "postgresql.name" . }} - release: {{ .Release.Name | quote }} + {{- include "common.labels.matchLabels" . | nindent 4 }} role: master diff --git a/charts/artifactory-ha/charts/postgresql/values-production.yaml b/charts/artifactory-ha/charts/postgresql/values-production.yaml index d34e326ee..c08014549 100755 --- a/charts/artifactory-ha/charts/postgresql/values-production.yaml +++ b/charts/artifactory-ha/charts/postgresql/values-production.yaml @@ -15,7 +15,7 @@ global: image: registry: docker.io repository: bitnami/postgresql - tag: 11.7.0-debian-10-r65 + tag: 11.9.0-debian-10-r1 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -94,6 +94,16 @@ serviceAccount: ## Name of an already existing service account. Setting this value disables the automatic service account creation. # name: +## Pod Security Policy +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +psp: + create: false + +## Creates role for ServiceAccount +## Required for PSP +rbac: + create: false + replication: enabled: true user: repl_user @@ -101,7 +111,7 @@ replication: slaveReplicas: 2 ## Set synchronous commit mode: on, off, remote_apply, remote_write and local ## ref: https://www.postgresql.org/docs/9.6/runtime-config-wal.html#GUC-WAL-LEVEL - synchronousCommit: "on" + synchronousCommit: 'on' ## From the number of `slaveReplicas` defined above, set the number of those that will have synchronous replication ## NOTE: It cannot be > slaveReplicas numSynchronousReplicas: 1 @@ -221,17 +231,17 @@ extraEnv: [] ## ldap: enabled: false - url: "" - server: "" - port: "" - prefix: "" - suffix: "" - baseDN: "" - bindDN: "" + url: '' + server: '' + port: '' + prefix: '' + suffix: '' + baseDN: '' + bindDN: '' bind_password: - search_attr: "" - search_filter: "" - scheme: "" + search_attr: '' + search_filter: '' + scheme: '' tls: false ## PostgreSQL service configuration @@ -253,7 +263,6 @@ service: ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer ## # loadBalancerIP: - ## Load Balancer sources. Evaluated as a template. ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service ## @@ -301,7 +310,7 @@ persistence: ## The subdirectory of the volume to mount to, useful in dev environments ## and one PV for multiple services. ## - subPath: "" + subPath: '' # storageClass: "-" accessModes: @@ -330,7 +339,7 @@ master: annotations: {} podLabels: {} podAnnotations: {} - priorityClassName: "" + priorityClassName: '' ## Additional PostgreSQL Master Volume mounts ## extraVolumeMounts: [] @@ -372,14 +381,14 @@ slave: annotations: {} podLabels: {} podAnnotations: {} - priorityClassName: "" + priorityClassName: '' ## Extra init containers ## Example - ## + ## ## extraInitContainers: ## - name: do-something ## image: busybox - ## command: ['do', 'something'] + ## command: ['do', 'something'] extraInitContainers: [] ## Additional PostgreSQL Slave Volume mounts ## @@ -405,6 +414,10 @@ slave: # type: # nodePort: # clusterIP: + ## Whether to enable PostgreSQL slave replicas data Persistent + ## + persistence: + enabled: true ## Configure resource requests and limits ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ @@ -414,6 +427,10 @@ resources: memory: 256Mi cpu: 250m +## Add annotations to all the deployed resources +## +commonAnnotations: {} + networkPolicy: ## Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. ## @@ -457,6 +474,33 @@ readinessProbe: failureThreshold: 6 successThreshold: 1 +## +## TLS configuration +## +tls: + # Enable TLS traffic + enabled: false + # + # Whether to use the server's TLS cipher preferences rather than the client's. + preferServerCiphers: true + # + # Name of the Secret that contains the certificates + certificatesSecret: '' + # + # Certificate filename + certFilename: '' + # + # Certificate Key filename + certKeyFilename: '' + # + # CA Certificate filename + # If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate + # ref: https://www.postgresql.org/docs/9.6/auth-methods.html + certCAFilename: + # + # File containing a Certificate Revocation List + crlFilename: + ## Configure metrics exporter ## metrics: @@ -465,8 +509,8 @@ metrics: service: type: ClusterIP annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9187" + prometheus.io/scrape: 'true' + prometheus.io/port: '9187' loadBalancerIP: serviceMonitor: enabled: false @@ -480,7 +524,7 @@ metrics: prometheusRule: enabled: false additionalLabels: {} - namespace: "" + namespace: '' ## These are just examples rules, please adapt them to your needs. ## Make sure to constraint the rules to the current postgresql service. ## rules: @@ -497,7 +541,7 @@ metrics: image: registry: docker.io repository: bitnami/postgres-exporter - tag: 0.8.0-debian-10-r72 + tag: 0.8.0-debian-10-r188 pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. @@ -517,6 +561,14 @@ metrics: # - size_bytes: # usage: "GAUGE" # description: "Size of the database in bytes" + ## An array to add extra env vars to configure postgres-exporter + ## see: https://github.com/wrouesnel/postgres_exporter#environment-variables + ## For example: + # extraEnvVars: + # - name: PG_EXPORTER_DISABLE_DEFAULT_METRICS + # value: "true" + extraEnvVars: {} + ## Pod Security Context ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## diff --git a/charts/artifactory-ha/charts/postgresql/values.schema.json b/charts/artifactory-ha/charts/postgresql/values.schema.json index ac2de6e94..7b5e2efc3 100755 --- a/charts/artifactory-ha/charts/postgresql/values.schema.json +++ b/charts/artifactory-ha/charts/postgresql/values.schema.json @@ -72,8 +72,8 @@ "title": "Slave Replicas", "form": true, "hidden": { - "condition": false, - "value": "replication.enabled" + "value": false, + "path": "replication/enabled" } } } diff --git a/charts/artifactory-ha/charts/postgresql/values.yaml b/charts/artifactory-ha/charts/postgresql/values.yaml index e14709a5e..f45c4183d 100755 --- a/charts/artifactory-ha/charts/postgresql/values.yaml +++ b/charts/artifactory-ha/charts/postgresql/values.yaml @@ -15,7 +15,7 @@ global: image: registry: docker.io repository: bitnami/postgresql - tag: 11.7.0-debian-10-r65 + tag: 11.9.0-debian-10-r1 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -79,7 +79,6 @@ volumePermissions: ## # schedulerName: - ## Pod Security Context ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## @@ -95,6 +94,16 @@ serviceAccount: ## Name of an already existing service account. Setting this value disables the automatic service account creation. # name: +## Pod Security Policy +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +psp: + create: false + +## Creates role for ServiceAccount +## Required for PSP +rbac: + create: false + replication: enabled: false user: repl_user @@ -102,7 +111,7 @@ replication: slaveReplicas: 1 ## Set synchronous commit mode: on, off, remote_apply, remote_write and local ## ref: https://www.postgresql.org/docs/9.6/runtime-config-wal.html#GUC-WAL-LEVEL - synchronousCommit: "off" + synchronousCommit: 'off' ## From the number of `slaveReplicas` defined above, set the number of those that will have synchronous replication ## NOTE: It cannot be > slaveReplicas numSynchronousReplicas: 0 @@ -222,17 +231,17 @@ extraEnv: [] ## ldap: enabled: false - url: "" - server: "" - port: "" - prefix: "" - suffix: "" - baseDN: "" - bindDN: "" + url: '' + server: '' + port: '' + prefix: '' + suffix: '' + baseDN: '' + bindDN: '' bind_password: - search_attr: "" - search_filter: "" - scheme: "" + search_attr: '' + search_filter: '' + scheme: '' tls: false ## PostgreSQL service configuration @@ -254,7 +263,6 @@ service: ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer ## # loadBalancerIP: - ## Load Balancer sources. Evaluated as a template. ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service ## @@ -302,7 +310,7 @@ persistence: ## The subdirectory of the volume to mount to, useful in dev environments ## and one PV for multiple services. ## - subPath: "" + subPath: '' # storageClass: "-" accessModes: @@ -331,14 +339,14 @@ master: annotations: {} podLabels: {} podAnnotations: {} - priorityClassName: "" + priorityClassName: '' ## Extra init containers ## Example - ## + ## ## extraInitContainers: ## - name: do-something ## image: busybox - ## command: ['do', 'something'] + ## command: ['do', 'something'] extraInitContainers: [] ## Additional PostgreSQL Master Volume mounts @@ -382,7 +390,7 @@ slave: annotations: {} podLabels: {} podAnnotations: {} - priorityClassName: "" + priorityClassName: '' extraInitContainers: | # - name: do-something # image: busybox @@ -411,6 +419,10 @@ slave: # type: # nodePort: # clusterIP: + ## Whether to enable PostgreSQL slave replicas data Persistent + ## + persistence: + enabled: true ## Configure resource requests and limits ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ @@ -420,6 +432,10 @@ resources: memory: 256Mi cpu: 250m +## Add annotations to all the deployed resources +## +commonAnnotations: {} + networkPolicy: ## Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. ## @@ -463,6 +479,33 @@ readinessProbe: failureThreshold: 6 successThreshold: 1 +## +## TLS configuration +## +tls: + # Enable TLS traffic + enabled: false + # + # Whether to use the server's TLS cipher preferences rather than the client's. + preferServerCiphers: true + # + # Name of the Secret that contains the certificates + certificatesSecret: '' + # + # Certificate filename + certFilename: '' + # + # Certificate Key filename + certKeyFilename: '' + # + # CA Certificate filename + # If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate + # ref: https://www.postgresql.org/docs/9.6/auth-methods.html + certCAFilename: + # + # File containing a Certificate Revocation List + crlFilename: + ## Configure metrics exporter ## metrics: @@ -471,8 +514,8 @@ metrics: service: type: ClusterIP annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9187" + prometheus.io/scrape: 'true' + prometheus.io/port: '9187' loadBalancerIP: serviceMonitor: enabled: false @@ -486,7 +529,7 @@ metrics: prometheusRule: enabled: false additionalLabels: {} - namespace: "" + namespace: '' ## These are just examples rules, please adapt them to your needs. ## Make sure to constraint the rules to the current postgresql service. ## rules: @@ -503,7 +546,7 @@ metrics: image: registry: docker.io repository: bitnami/postgres-exporter - tag: 0.8.0-debian-10-r72 + tag: 0.8.0-debian-10-r188 pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. @@ -515,7 +558,7 @@ metrics: ## ref: https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file # customMetrics: # pg_database: - # query: "SELECT d.datname AS name, CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') THEN pg_catalog.pg_database_size(d.datname) ELSE 0 END AS size FROM pg_catalog.pg_database d where datname not in ('template0', 'template1', 'postgres')" + # query: "SELECT d.datname AS name, CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') THEN pg_catalog.pg_database_size(d.datname) ELSE 0 END AS size_bytes FROM pg_catalog.pg_database d where datname not in ('template0', 'template1', 'postgres')" # metrics: # - name: # usage: "LABEL" @@ -523,6 +566,15 @@ metrics: # - size_bytes: # usage: "GAUGE" # description: "Size of the database in bytes" + # + ## An array to add extra env vars to configure postgres-exporter + ## see: https://github.com/wrouesnel/postgres_exporter#environment-variables + ## For example: + # extraEnvVars: + # - name: PG_EXPORTER_DISABLE_DEFAULT_METRICS + # value: "true" + extraEnvVars: {} + ## Pod Security Context ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ ## diff --git a/charts/artifactory-ha/ci/access-tls-values.yaml b/charts/artifactory-ha/ci/access-tls-values.yaml index 2ba5a7af1..e11939170 100644 --- a/charts/artifactory-ha/ci/access-tls-values.yaml +++ b/charts/artifactory-ha/ci/access-tls-values.yaml @@ -1,7 +1,11 @@ databaseUpgradeReady: true artifactory: masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + image: + tag: 12.3.0-debian-10-r71 + postgresqlPassword: password access: accessConfig: security: diff --git a/charts/artifactory-ha/ci/default-values.yaml b/charts/artifactory-ha/ci/default-values.yaml index 01310a3b7..1ebf93823 100644 --- a/charts/artifactory-ha/ci/default-values.yaml +++ b/charts/artifactory-ha/ci/default-values.yaml @@ -4,3 +4,6 @@ databaseUpgradeReady: true ## Please refer https://github.com/jfrog/charts/blob/master/stable/artifactory-ha/README.md#special-upgrade-notes-1 artifactory: masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password diff --git a/charts/artifactory-ha/ci/global-values.yaml b/charts/artifactory-ha/ci/global-values.yaml new file mode 100644 index 000000000..d2ce1d675 --- /dev/null +++ b/charts/artifactory-ha/ci/global-values.yaml @@ -0,0 +1,47 @@ +databaseUpgradeReady: true +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password +global: + versions: + artifactory: 7.11.2 + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + customInitContainers: | + - name: "custom-setup" + image: "{{ .Values.initContainerImage }}" + imagePullPolicy: "{{ .Values.artifactory.image.pullPolicy }}" + command: + - 'sh' + - '-c' + - 'touch {{ .Values.artifactory.persistence.mountPath }}/example-custom-setup' + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: volume + # Add custom volumes + customVolumes: | + - name: custom-script + emptyDir: + sizeLimit: 100Mi + # Add custom volumesMounts + customVolumeMounts: | + - name: custom-script + mountPath: "/scripts" + # Add custom sidecar containers + customSidecarContainers: | + - name: "sidecar-list-etc" + image: "{{ .Values.initContainerImage }}" + imagePullPolicy: "{{ .Values.artifactory.image.pullPolicy }}" + securityContext: + allowPrivilegeEscalation: false + command: ["sh","-c","echo 'Sidecar is running' >> /scripts/sidecar.txt; cat /scripts/sidecar.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scripts" + name: custom-script + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" diff --git a/charts/artifactory-ha/ci/migration-disabled-values.yaml b/charts/artifactory-ha/ci/migration-disabled-values.yaml index 148618353..6c1e2587f 100644 --- a/charts/artifactory-ha/ci/migration-disabled-values.yaml +++ b/charts/artifactory-ha/ci/migration-disabled-values.yaml @@ -1,4 +1,7 @@ databaseUpgradeReady: true +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password artifactory: masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF migration: diff --git a/charts/artifactory-ha/questions.yml b/charts/artifactory-ha/questions.yml index 500ae707b..b5fbdea2b 100644 --- a/charts/artifactory-ha/questions.yml +++ b/charts/artifactory-ha/questions.yml @@ -419,6 +419,6 @@ questions: # Internal Settings - variable: installerInfo - default: '\{\"productId\": \"RancherHelm_artifactory-ha/7.6.3\", \"features\": \[\{\"featureId\": \"Partner/ACC-007246\"\}\]\}' + default: '\{\"productId\": \"RancherHelm_artifactory-ha/7.12.6\", \"features\": \[\{\"featureId\": \"Partner/ACC-007246\"\}\]\}' type: string group: "Internal Settings (Do not modify)" diff --git a/charts/artifactory-ha/requirements.lock b/charts/artifactory-ha/requirements.lock index 5035c3ee5..0cfb2c54a 100644 --- a/charts/artifactory-ha/requirements.lock +++ b/charts/artifactory-ha/requirements.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 8.7.3 -digest: sha256:7c0ecc958c9d90f0b5c3843621674788b414ea0497ea6053e8c46531545a47d3 -generated: "2020-08-03T02:21:09.654710336Z" + version: 9.3.4 +digest: sha256:6c6c7ebc7f0c35a6df917879cd7c51e226f31a4d320e053b3620c5476287e9b8 +generated: "2020-09-02T09:49:07.304103+05:30" diff --git a/charts/artifactory-ha/requirements.yaml b/charts/artifactory-ha/requirements.yaml index bedbccf5a..3e98e794a 100644 --- a/charts/artifactory-ha/requirements.yaml +++ b/charts/artifactory-ha/requirements.yaml @@ -1,5 +1,5 @@ dependencies: - name: postgresql - version: 8.7.3 + version: 9.3.4 repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled diff --git a/charts/artifactory-ha/security-mitigation.yaml b/charts/artifactory-ha/security-mitigation.yaml new file mode 100644 index 000000000..679edfe45 --- /dev/null +++ b/charts/artifactory-ha/security-mitigation.yaml @@ -0,0 +1,20 @@ +## Apache License Version 2.0 +## http://www.apache.org/licenses/LICENSE-2.0.txt + +## Schema version of this YAML file +schemaVersion: v1 + +## Overall mitigation summary +summary: Security mitigation information for this application is tracked by the security-mitigation.yaml file that's part of this helm chart. + +## Mitigation notes for individual CVEs +mitigations: + - cves: + - CVE-2017-8399 + ## Indicates package Uri for which the security mitigation is provided. helm://… || docker://… + affectedPackageUri: helm://jfrog/artifactory-ha + ## Which chart versions this cve note belongs to + affectedVersions: ">= 3.1.0" + ## Description / note + description: This CVE needs to be fixed in the alpine base image of nginx container. + diff --git a/charts/artifactory-ha/templates/_helpers.tpl b/charts/artifactory-ha/templates/_helpers.tpl index 8f9dc060c..04109d8c4 100644 --- a/charts/artifactory-ha/templates/_helpers.tpl +++ b/charts/artifactory-ha/templates/_helpers.tpl @@ -68,6 +68,19 @@ If release name contains chart name it will be used as a full name. {{- end -}} {{- end -}} +{{/* +Create a default fully qualified replicator tracker ingress 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 "artifactory-ha.replicator.tracker.fullname" -}} +{{- if .Values.artifactory.replicator.trackerIngress.name -}} +{{- .Values.artifactory.replicator.trackerIngress.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-replication-tracker" .Chart.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- 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). @@ -125,3 +138,155 @@ Scheme (http/https) based on Access TLS enabled/disabled {{- printf "%s" "http" -}} {{- end -}} {{- end -}} + +{{/* +Resolve joinKey value +*/}} +{{- define "artifactory-ha.joinKey" -}} +{{- if .Values.global.joinKey -}} +{{- .Values.global.joinKey -}} +{{- else if .Values.artifactory.joinKey -}} +{{- .Values.artifactory.joinKey -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve masterKey value +*/}} +{{- define "artifactory-ha.masterKey" -}} +{{- if .Values.global.masterKey -}} +{{- .Values.global.masterKey -}} +{{- else if .Values.artifactory.masterKey -}} +{{- .Values.artifactory.masterKey -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve joinKeySecretName value +*/}} +{{- define "artifactory-ha.joinKeySecretName" -}} +{{- if .Values.global.joinKeySecretName -}} +{{- .Values.global.joinKeySecretName -}} +{{- else if .Values.artifactory.joinKeySecretName -}} +{{- .Values.artifactory.joinKeySecretName -}} +{{- else -}} +{{ include "artifactory-ha.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve masterKeySecretName value +*/}} +{{- define "artifactory-ha.masterKeySecretName" -}} +{{- if .Values.global.masterKeySecretName -}} +{{- .Values.global.masterKeySecretName -}} +{{- else if .Values.artifactory.masterKeySecretName -}} +{{- .Values.artifactory.masterKeySecretName -}} +{{- else -}} +{{ include "artifactory-ha.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve imagePullSecrets value +*/}} +{{- define "artifactory-ha.imagePullSecrets" -}} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if .Values.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainersBegin value +*/}} +{{- define "artifactory-ha.customInitContainersBegin" -}} +{{- if .Values.global.customInitContainersBegin -}} +{{- .Values.global.customInitContainersBegin -}} +{{- else if .Values.artifactory.customInitContainersBegin -}} +{{- .Values.artifactory.customInitContainersBegin -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainers value +*/}} +{{- define "artifactory-ha.customInitContainers" -}} +{{- if .Values.global.customInitContainers -}} +{{- .Values.global.customInitContainers -}} +{{- else if .Values.artifactory.customInitContainers -}} +{{- .Values.artifactory.customInitContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumes value +*/}} +{{- define "artifactory-ha.customVolumes" -}} +{{- if .Values.global.customVolumes -}} +{{- .Values.global.customVolumes -}} +{{- else if .Values.artifactory.customVolumes -}} +{{- .Values.artifactory.customVolumes -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumeMounts value +*/}} +{{- define "artifactory-ha.customVolumeMounts" -}} +{{- if .Values.global.customVolumeMounts -}} +{{- .Values.global.customVolumeMounts -}} +{{- else if .Values.artifactory.customVolumeMounts -}} +{{- .Values.artifactory.customVolumeMounts -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customSidecarContainers value +*/}} +{{- define "artifactory-ha.customSidecarContainers" -}} +{{- if .Values.global.customSidecarContainers -}} +{{- .Values.global.customSidecarContainers -}} +{{- else if .Values.artifactory.customSidecarContainers -}} +{{- .Values.artifactory.customSidecarContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper artifactory chart image names +*/}} +{{- define "artifactory-ha.getImageInfoByValue" -}} +{{- $dot := index . 0 }} +{{- $indexReference := index . 1 }} +{{- $registryName := index $dot.Values $indexReference "image" "registry" -}} +{{- $repositoryName := index $dot.Values $indexReference "image" "repository" -}} +{{- $tag := default $dot.Chart.AppVersion (index $dot.Values $indexReference "image" "tag") | toString -}} +{{- if $dot.Values.global }} + {{- if and $dot.Values.global.versions.artifactory (or (eq $indexReference "artifactory") (eq $indexReference "nginx") ) }} + {{- $tag = $dot.Values.global.versions.artifactory | toString -}} + {{- end -}} + {{- if $dot.Values.global.imageRegistry }} + {{- printf "%s/%s:%s" $dot.Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper artifactory app version +*/}} +{{- define "artifactory-ha.app.version" -}} +{{- $image := split ":" ((include "artifactory-ha.getImageInfoByValue" (list . "artifactory")) | toString) -}} +{{- $tag := $image._1 -}} +{{- printf "%s" $tag -}} +{{- end -}} diff --git a/charts/artifactory-ha/templates/additional-resources.yaml b/charts/artifactory-ha/templates/additional-resources.yaml new file mode 100644 index 000000000..c4d06f08a --- /dev/null +++ b/charts/artifactory-ha/templates/additional-resources.yaml @@ -0,0 +1,3 @@ +{{ if .Values.additionalResources }} +{{ tpl .Values.additionalResources . }} +{{- end -}} diff --git a/charts/artifactory-ha/templates/artifactory-access-config.yaml b/charts/artifactory-ha/templates/artifactory-access-config.yaml index 6552d45dc..4eac505bc 100644 --- a/charts/artifactory-ha/templates/artifactory-access-config.yaml +++ b/charts/artifactory-ha/templates/artifactory-access-config.yaml @@ -10,6 +10,6 @@ metadata: release: {{ .Release.Name }} type: Opaque stringData: - access.config.import.yml: | + access.config.patch.yml: | {{ tpl (toYaml .Values.access.accessConfig) . | indent 4 }} {{- end }} diff --git a/charts/artifactory-ha/templates/artifactory-custom-secrets.yaml b/charts/artifactory-ha/templates/artifactory-custom-secrets.yaml index c22188a64..67473fc58 100644 --- a/charts/artifactory-ha/templates/artifactory-custom-secrets.yaml +++ b/charts/artifactory-ha/templates/artifactory-custom-secrets.yaml @@ -4,7 +4,7 @@ apiVersion: v1 kind: Secret metadata: - name: {{ .name }} + name: {{ template "artifactory-ha.fullname" $ }}-{{ .name }} labels: app: "{{ template "artifactory-ha.name" $ }}" chart: "{{ template "artifactory-ha.chart" $ }}" diff --git a/charts/artifactory-ha/templates/artifactory-node-pdb.yaml b/charts/artifactory-ha/templates/artifactory-node-pdb.yaml index d588e0a85..cb45027cf 100644 --- a/charts/artifactory-ha/templates/artifactory-node-pdb.yaml +++ b/charts/artifactory-ha/templates/artifactory-node-pdb.yaml @@ -1,3 +1,4 @@ +{{- if .Values.artifactory.node.minAvailable -}} apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: @@ -13,8 +14,7 @@ spec: matchLabels: component: {{ .Values.artifactory.name }} app: {{ template "artifactory-ha.name" . }} - {{- if eq .Values.artifactory.service.pool "members" }} role: {{ template "artifactory-ha.node.name" . }} - {{- end }} release: {{ .Release.Name }} minAvailable: {{ .Values.artifactory.node.minAvailable }} +{{- end }} \ No newline at end of file diff --git a/charts/artifactory-ha/templates/artifactory-node-statefulset.yaml b/charts/artifactory-ha/templates/artifactory-node-statefulset.yaml index af5ce966d..c5b6f08f4 100644 --- a/charts/artifactory-ha/templates/artifactory-node-statefulset.yaml +++ b/charts/artifactory-ha/templates/artifactory-node-statefulset.yaml @@ -34,6 +34,7 @@ spec: {{ toYaml . | indent 8 }} {{- end }} annotations: + checksum/database-secrets: {{ include (print $.Template.BasePath "/artifactory-database-secrets.yaml") . | sha256sum }} checksum/binarystore: {{ include (print $.Template.BasePath "/artifactory-binarystore-secret.yaml") . | sha256sum }} checksum/systemyaml: {{ include (print $.Template.BasePath "/artifactory-system-yaml.yaml") . | sha256sum }} {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} @@ -52,16 +53,17 @@ spec: {{- end }} serviceAccountName: {{ template "artifactory-ha.serviceAccountName" . }} terminationGracePeriodSeconds: {{ .Values.artifactory.terminationGracePeriodSeconds }} - {{- if .Values.imagePullSecrets }} - imagePullSecrets: - - name: {{ .Values.imagePullSecrets }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory-ha.imagePullSecrets" . | indent 6 }} {{- end }} + {{- if .Values.artifactory.setSecurityContext }} securityContext: runAsUser: {{ .Values.artifactory.uid }} - fsGroup: {{ .Values.artifactory.uid }} + fsGroup: {{ .Values.artifactory.gid }} + {{- end }} initContainers: - {{- if .Values.artifactory.customInitContainersBegin }} -{{ tpl .Values.artifactory.customInitContainersBegin . | indent 6 }} + {{- if or .Values.artifactory.customInitContainersBegin .Values.global.customInitContainersBegin }} +{{ tpl (include "artifactory-ha.customInitContainersBegin" .) . | indent 6 }} {{- end }} {{- if .Values.artifactory.persistence.enabled }} {{- if eq .Values.artifactory.persistence.type "file-system" }} @@ -126,7 +128,11 @@ spec: echo "Copy system.yaml to {{ .Values.artifactory.persistence.mountPath }}/etc"; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; + {{- if .Values.systemYamlOverride.existingSecret }} + cp -fv /tmp/etc/{{ .Values.systemYamlOverride.dataKey }} {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- else }} cp -fv /tmp/etc/system.yaml {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- end }} echo "Remove {{ .Values.artifactory.persistence.mountPath }}/lost+found folder if exists"; rm -rfv {{ .Values.artifactory.persistence.mountPath }}/lost+found; echo "Removing join.key file"; @@ -137,7 +143,7 @@ spec: {{- if .Values.access.customCertificatesSecretName }} echo "Load custom certificates from database"; {{- end }} - {{- if or .Values.artifactory.masterKey .Values.artifactory.masterKeySecretName }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} echo "Copy masterKey to {{ .Values.artifactory.persistence.mountPath }}/etc/security"; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security; echo -n ${ARTIFACTORY_MASTER_KEY} > {{ .Values.artifactory.persistence.mountPath }}/etc/security/master.key; @@ -145,7 +151,7 @@ spec: - name: ARTIFACTORY_MASTER_KEY valueFrom: secretKeyRef: - name: "{{ .Values.artifactory.masterKeySecretName | default (include "artifactory-ha.fullname" .) }}" + name: {{ include "artifactory-ha.masterKeySecretName" . }} key: master-key {{- end }} resources: @@ -153,17 +159,24 @@ spec: volumeMounts: - name: volume mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + {{- if or .Values.systemYamlOverride.existingSecret .Values.artifactory.systemYaml }} - name: systemyaml + {{- if .Values.systemYamlOverride.existingSecret }} + mountPath: "/tmp/etc/{{.Values.systemYamlOverride.dataKey}}" + subPath: {{ .Values.systemYamlOverride.dataKey }} + {{- else if .Values.artifactory.systemYaml }} mountPath: "/tmp/etc/system.yaml" subPath: system.yaml - {{- if .Values.artifactory.customPersistentPodVolumeClaim }} + {{- end }} + {{- end }} + {{- if and .Values.artifactory.customPersistentPodVolumeClaim (not .Values.artifactory.customPersistentPodVolumeClaim.skipPrepareContainer) }} - name: "prepare-custom-persistent-volume" image: "{{ .Values.initContainerImage }}" command: - 'sh' - '-c' - > - chown -Rv {{ .Values.artifactory.uid }}:{{ .Values.artifactory.uid }} {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} + chown -Rv {{ .Values.artifactory.uid }}:{{ .Values.artifactory.gid }} {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} securityContext: runAsUser: 0 resources: @@ -187,12 +200,12 @@ spec: {{ toYaml .Values.initContainers.resources | indent 10 }} {{- end }} {{- end }} - {{- if .Values.artifactory.customInitContainers }} -{{ tpl .Values.artifactory.customInitContainers . | indent 6 }} + {{- if or .Values.artifactory.customInitContainers .Values.global.customInitContainers }} +{{ tpl (include "artifactory-ha.customInitContainers" .) . | indent 6 }} {{- end }} {{- if .Values.artifactory.migration.enabled }} - name: 'migration-artifactory-ha' - image: '{{ .Values.artifactory.image.repository }}:{{ default .Chart.AppVersion .Values.artifactory.image.version }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} securityContext: allowPrivilegeEscalation: false @@ -211,10 +224,14 @@ spec: cp -fv /tmp/migrationHelmInfo.yaml $scriptsPath/migrationHelmInfo.yaml; cp -fv /tmp/migrationStatus.sh $scriptsPath/migrationStatus.sh; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/log; - bash $scriptsPath/migrationStatus.sh {{ default .Chart.AppVersion .Values.artifactory.image.version }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; + bash $scriptsPath/migrationStatus.sh {{ include "artifactory-ha.app.version" . }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; resources: {{ toYaml .Values.artifactory.node.resources | indent 10 }} env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} {{- if or .Values.database.secrets.user .Values.database.user }} - name: JF_SHARED_DATABASE_USERNAME valueFrom: @@ -283,8 +300,8 @@ spec: mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" {{- end }} {{- end }} - {{- if .Values.artifactory.customVolumeMounts }} -{{ tpl .Values.artifactory.customVolumeMounts . | indent 8 }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} {{- end }} {{- if eq .Values.artifactory.persistence.type "nfs" }} - name: artifactory-ha-data @@ -306,7 +323,7 @@ spec: {{- end }} containers: - name: {{ .Values.artifactory.name }} - image: '{{ .Values.artifactory.image.repository }}:{{ default .Chart.AppVersion .Values.artifactory.image.version }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} securityContext: allowPrivilegeEscalation: false @@ -324,6 +341,7 @@ spec: {{ tpl . $ }}; {{- end }} exec /entrypoint-artifactory.sh + {{- with .Values.artifactory.postStartCommand }} lifecycle: postStart: exec: @@ -331,11 +349,14 @@ spec: - '/bin/bash' - '-c' - > - echo; - {{- with .Values.artifactory.postStartCommand }} + echo "Running custom postStartCommand command"; {{ tpl . $ }} - {{- end }} + {{- end }} env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} {{- if or .Values.database.secrets.user .Values.database.user }} - name: JF_SHARED_DATABASE_USERNAME valueFrom: @@ -384,12 +405,16 @@ spec: {{- end }} ports: - containerPort: {{ .Values.artifactory.internalPort }} + name: http - containerPort: {{ .Values.artifactory.internalArtifactoryPort }} + name: http-internal {{- if .Values.artifactory.node.javaOpts.jmx.enabled }} - containerPort: {{ .Values.artifactory.node.javaOpts.jmx.port }} + name: tcp-jmx {{- end }} {{- if .Values.artifactory.ssh.enabled }} - containerPort: {{ .Values.artifactory.ssh.internalPort }} + name: tcp-ssh {{- end }} volumeMounts: {{- if .Values.artifactory.customPersistentVolumeClaim }} @@ -445,8 +470,8 @@ spec: - name: installer-info mountPath: "/artifactory_bootstrap/info/installer-info.json" subPath: installer-info.json - {{- if .Values.artifactory.customVolumeMounts }} -{{ tpl .Values.artifactory.customVolumeMounts . | indent 8 }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} {{- end }} resources: {{ toYaml .Values.artifactory.node.resources | indent 10 }} @@ -474,12 +499,10 @@ spec: failureThreshold: {{ .Values.artifactory.livenessProbe.failureThreshold }} successThreshold: {{ .Values.artifactory.livenessProbe.successThreshold }} {{- end }} - {{- $image := .Values.logger.image.repository }} - {{- $tag := .Values.logger.image.tag }} {{- $mountPath := .Values.artifactory.persistence.mountPath }} {{- range .Values.artifactory.loggers }} - name: {{ . | replace "_" "-" | replace "." "-" }} - image: '{{ $image }}:{{ $tag }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list $ "logger") }} command: - 'sh' - '-c' @@ -496,7 +519,7 @@ spec: {{ if .Values.artifactory.catalinaLoggers }} {{- range .Values.artifactory.catalinaLoggers }} - name: {{ . | replace "_" "-" | replace "." "-" }} - image: '{{ $image }}:{{ $tag }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list $ "logger") }} command: - 'sh' - '-c' @@ -536,8 +559,8 @@ spec: {{ toYaml .Values.filebeat.resources | indent 10 }} terminationGracePeriodSeconds: {{ .Values.terminationGracePeriod }} {{- end }} - {{- if .Values.artifactory.customSidecarContainers }} -{{ tpl .Values.artifactory.customSidecarContainers . | indent 6 }} + {{- if or .Values.artifactory.customSidecarContainers .Values.global.customSidecarContainers }} +{{ tpl (include "artifactory-ha.customSidecarContainers" .) . | indent 6 }} {{- end }} {{- with .Values.artifactory.node.nodeSelector }} nodeSelector: @@ -619,7 +642,7 @@ spec: - name: bootstrap-config configMap: name: {{ .Values.artifactory.configMapName }} - {{- end}} + {{- end}} {{- if or .Values.artifactory.loggers .Values.artifactory.catalinaLoggers }} - name: tail-logger-script configMap: @@ -650,9 +673,11 @@ spec: persistentVolumeClaim: claimName: {{ template "artifactory-ha.fullname" . }}-backup-pvc {{- end }} + {{- if or .Values.systemYamlOverride.existingSecret .Values.artifactory.systemYaml }} - name: systemyaml secret: - secretName: {{ template "artifactory-ha.primary.name" . }}-system-yaml + secretName: {{ default (printf "%s-%s" (include "artifactory-ha.primary.name" .) "system-yaml") .Values.systemYamlOverride.existingSecret }} + {{- end }} {{- if .Values.artifactory.customPersistentVolumeClaim }} - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} persistentVolumeClaim: @@ -663,8 +688,8 @@ spec: configMap: name: {{ template "artifactory-ha.fullname" . }}-filebeat-config {{- end }} - {{- if .Values.artifactory.customVolumes }} -{{ tpl .Values.artifactory.customVolumes . | indent 6 }} + {{- if or .Values.artifactory.customVolumes .Values.global.customVolumes }} +{{ tpl (include "artifactory-ha.customVolumes" .) . | indent 6 }} {{- end }} {{- if not .Values.artifactory.persistence.enabled }} - name: volume diff --git a/charts/artifactory-ha/templates/artifactory-primary-pdb.yaml b/charts/artifactory-ha/templates/artifactory-primary-pdb.yaml new file mode 100644 index 000000000..cc4dfab65 --- /dev/null +++ b/charts/artifactory-ha/templates/artifactory-primary-pdb.yaml @@ -0,0 +1,20 @@ +{{- if .Values.artifactory.primary.minAvailable -}} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "artifactory-ha.fullname" . }}-primary + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + component: {{ .Values.artifactory.name }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +spec: + selector: + matchLabels: + component: {{ .Values.artifactory.name }} + app: {{ template "artifactory-ha.name" . }} + role: {{ template "artifactory-ha.primary.name" . }} + release: {{ .Release.Name }} + minAvailable: {{ .Values.artifactory.primary.minAvailable }} +{{- end }} diff --git a/charts/artifactory-ha/templates/artifactory-primary-statefulset.yaml b/charts/artifactory-ha/templates/artifactory-primary-statefulset.yaml index 9de929da2..afe1d223d 100644 --- a/charts/artifactory-ha/templates/artifactory-primary-statefulset.yaml +++ b/charts/artifactory-ha/templates/artifactory-primary-statefulset.yaml @@ -6,18 +6,18 @@ metadata: app: {{ template "artifactory-ha.name" . }} chart: {{ template "artifactory-ha.chart" . }} component: {{ .Values.artifactory.name }} - version: {{ default .Chart.AppVersion .Values.artifactory.image.version }} + version: {{ include "artifactory-ha.app.version" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} {{- with .Values.artifactory.primary.labels }} {{ toYaml . | indent 4 }} {{- end }} {{- if and .Release.IsUpgrade .Values.postgresql.enabled }} - databaseUpgradeReady: {{ required "\n\n*********\nIMPORTANT: UPGRADE STOPPED to prevent data loss!\nReview CHANGELOG.md (https://github.com/jfrog/charts/blob/master/stable/artifactory/CHANGELOG.md), pass postgresql.image.tag=9.6.18-debian-10-r7 and databaseUpgradeReady=true if you are upgrading from chart version which has postgresql version 9.6.x." .Values.databaseUpgradeReady | quote }} + databaseUpgradeReady: {{ required "\n\n*********\nIMPORTANT: UPGRADE STOPPED to prevent data loss!\nReview CHANGELOG.md (https://github.com/jfrog/charts/blob/master/stable/artifactory-ha/CHANGELOG.md) \nNote: This applies only when you are using bundled postgresql (postgresql.enabled=true) \nIf you are upgrading from a chart version (< 4.x) that has postgresql.image.tag of 9.x or 10.x, make sure to pass the current postgresql.image.tag and set databaseUpgradeReady=true \nOR \nIf you are upgrading from a chart version (>= 4.x), just set databaseUpgradeReady=true \n" .Values.databaseUpgradeReady | quote }} {{- end }} spec: serviceName: {{ template "artifactory-ha.primary.name" . }} - replicas: 1 + replicas: {{ .Values.artifactory.primary.replicaCount }} updateStrategy: type: RollingUpdate selector: @@ -38,6 +38,7 @@ spec: {{ toYaml . | indent 8 }} {{- end }} annotations: + checksum/database-secrets: {{ include (print $.Template.BasePath "/artifactory-database-secrets.yaml") . | sha256sum }} checksum/binarystore: {{ include (print $.Template.BasePath "/artifactory-binarystore-secret.yaml") . | sha256sum }} checksum/systemyaml: {{ include (print $.Template.BasePath "/artifactory-system-yaml.yaml") . | sha256sum }} {{- if .Values.access.accessConfig }} @@ -62,16 +63,17 @@ spec: {{- end }} serviceAccountName: {{ template "artifactory-ha.serviceAccountName" . }} terminationGracePeriodSeconds: {{ .Values.artifactory.terminationGracePeriodSeconds }} - {{- if .Values.imagePullSecrets }} - imagePullSecrets: - - name: {{ .Values.imagePullSecrets }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory-ha.imagePullSecrets" . | indent 6 }} {{- end }} + {{- if .Values.artifactory.setSecurityContext }} securityContext: runAsUser: {{ .Values.artifactory.uid }} - fsGroup: {{ .Values.artifactory.uid }} + fsGroup: {{ .Values.artifactory.gid }} + {{- end }} initContainers: - {{- if .Values.artifactory.customInitContainersBegin }} -{{ tpl .Values.artifactory.customInitContainersBegin . | indent 6 }} + {{- if or .Values.artifactory.customInitContainersBegin .Values.global.customInitContainersBegin }} +{{ tpl (include "artifactory-ha.customInitContainersBegin" .) . | indent 6 }} {{- end }} {{- if .Values.artifactory.persistence.enabled }} {{- if eq .Values.artifactory.persistence.type "file-system" }} @@ -167,13 +169,17 @@ spec: echo "Copy system.yaml to {{ .Values.artifactory.persistence.mountPath }}/etc"; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; + {{- if .Values.systemYamlOverride.existingSecret }} + cp -fv /tmp/etc/{{ .Values.systemYamlOverride.dataKey }} {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- else }} cp -fv /tmp/etc/system.yaml {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- end }} echo "Remove {{ .Values.artifactory.persistence.mountPath }}/lost+found folder if exists"; rm -rfv {{ .Values.artifactory.persistence.mountPath }}/lost+found; {{- if .Values.access.accessConfig }} - echo "Copy access.config.latest.yml to {{ .Values.artifactory.persistence.mountPath }}/etc"; + echo "Copy access.config.patch.yml to {{ .Values.artifactory.persistence.mountPath }}/etc/access"; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access; - cp -fv /tmp/etc/access.config.import.yml {{ .Values.artifactory.persistence.mountPath }}/etc/access/access.config.import.yml; + cp -fv /tmp/etc/access.config.patch.yml {{ .Values.artifactory.persistence.mountPath }}/etc/access/access.config.patch.yml; {{- end }} {{- if .Values.access.resetAccessCAKeys }} echo "Resetting Access CA Keys"; @@ -186,41 +192,48 @@ spec: cp -fv /tmp/etc/tls.crt {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/ca.crt; cp -fv /tmp/etc/tls.key {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/ca.private.key; {{- end }} - {{- if or .Values.artifactory.joinKey .Values.artifactory.joinKeySecretName }} + {{- if or .Values.artifactory.joinKey .Values.global.joinKey .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} echo "Copy joinKey to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security"; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security; echo -n ${ARTIFACTORY_JOIN_KEY} > {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security/join.key; {{- end }} - {{- if or .Values.artifactory.masterKey .Values.artifactory.masterKeySecretName }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} echo "Copy masterKey to {{ .Values.artifactory.persistence.mountPath }}/etc/security"; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security; echo -n ${ARTIFACTORY_MASTER_KEY} > {{ .Values.artifactory.persistence.mountPath }}/etc/security/master.key; {{- end }} env: - {{- if or .Values.artifactory.joinKey .Values.artifactory.joinKeySecretName}} + {{- if or .Values.artifactory.joinKey .Values.global.joinKey .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} - name: ARTIFACTORY_JOIN_KEY valueFrom: secretKeyRef: - name: "{{ .Values.artifactory.joinKeySecretName | default (include "artifactory-ha.fullname" .) }}" + name: {{ include "artifactory-ha.joinKeySecretName" . }} key: join-key {{- end }} - {{- if or .Values.artifactory.masterKey .Values.artifactory.masterKeySecretName }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} - name: ARTIFACTORY_MASTER_KEY valueFrom: secretKeyRef: - name: "{{ .Values.artifactory.masterKeySecretName | default (include "artifactory-ha.fullname" .) }}" + name: {{ include "artifactory-ha.masterKeySecretName" . }} key: master-key {{- end }} volumeMounts: - name: volume mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + {{- if or .Values.systemYamlOverride.existingSecret .Values.artifactory.systemYaml }} - name: systemyaml + {{- if .Values.systemYamlOverride.existingSecret }} + mountPath: "/tmp/etc/{{.Values.systemYamlOverride.dataKey}}" + subPath: {{ .Values.systemYamlOverride.dataKey }} + {{- else if .Values.artifactory.systemYaml }} mountPath: "/tmp/etc/system.yaml" subPath: system.yaml + {{- end }} + {{- end }} {{- if .Values.access.accessConfig }} - name: access-config - mountPath: "/tmp/etc/access.config.import.yml" - subPath: access.config.import.yml + mountPath: "/tmp/etc/access.config.patch.yml" + subPath: access.config.patch.yml {{- end }} {{- if .Values.access.customCertificatesSecretName }} - name: access-certs @@ -230,7 +243,7 @@ spec: mountPath: "/tmp/etc/tls.key" subPath: tls.key {{- end }} - {{- if .Values.artifactory.customPersistentPodVolumeClaim }} + {{- if and .Values.artifactory.customPersistentPodVolumeClaim (not .Values.artifactory.customPersistentPodVolumeClaim.skipPrepareContainer) }} - name: "prepare-custom-persistent-volume" image: "{{ .Values.initContainerImage }}" resources: @@ -239,9 +252,11 @@ spec: - 'sh' - '-c' - > - chown -Rv {{ .Values.artifactory.uid }}:{{ .Values.artifactory.uid }} {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} + chown -Rv {{ .Values.artifactory.uid }}:{{ .Values.artifactory.gid }} {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} securityContext: - runAsUser: 0 + capabilities: + add: + - CHOWN resources: {{ toYaml .Values.initContainers.resources | indent 10 }} volumeMounts: @@ -263,12 +278,12 @@ spec: {{ toYaml .Values.initContainers.resources | indent 10 }} {{- end }} {{- end }} - {{- if .Values.artifactory.customInitContainers }} -{{ tpl .Values.artifactory.customInitContainers . | indent 6 }} + {{- if or .Values.artifactory.customInitContainers .Values.global.customInitContainers }} +{{ tpl (include "artifactory-ha.customInitContainers" .) . | indent 6 }} {{- end }} {{- if .Values.artifactory.migration.enabled }} - name: 'migration-artifactory-ha' - image: '{{ .Values.artifactory.image.repository }}:{{ default .Chart.AppVersion .Values.artifactory.image.version }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} resources: {{ toYaml .Values.artifactory.primary.resources | indent 10 }} @@ -289,8 +304,12 @@ spec: cp -fv /tmp/migrationHelmInfo.yaml $scriptsPath/migrationHelmInfo.yaml; cp -fv /tmp/migrationStatus.sh $scriptsPath/migrationStatus.sh; mkdir -p {{ .Values.artifactory.persistence.mountPath }}/log; - bash $scriptsPath/migrationStatus.sh {{ default .Chart.AppVersion .Values.artifactory.image.version }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; + bash $scriptsPath/migrationStatus.sh {{ include "artifactory-ha.app.version" . }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} {{- if or .Values.database.secrets.user .Values.database.user }} - name: JF_SHARED_DATABASE_USERNAME valueFrom: @@ -359,8 +378,8 @@ spec: mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" {{- end }} {{- end }} - {{- if .Values.artifactory.customVolumeMounts }} -{{ tpl .Values.artifactory.customVolumeMounts . | indent 8 }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} {{- end }} {{- if eq .Values.artifactory.persistence.type "nfs" }} - name: artifactory-ha-data @@ -382,7 +401,7 @@ spec: {{- end }} containers: - name: {{ .Values.artifactory.name }} - image: '{{ .Values.artifactory.image.repository }}:{{ default .Chart.AppVersion .Values.artifactory.image.version }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} securityContext: allowPrivilegeEscalation: false @@ -418,6 +437,7 @@ spec: {{ tpl . $ }}; {{- end }} exec /entrypoint-artifactory.sh + {{- with .Values.artifactory.postStartCommand }} lifecycle: postStart: exec: @@ -425,11 +445,14 @@ spec: - '/bin/bash' - '-c' - > - echo; - {{- with .Values.artifactory.postStartCommand }} - {{ tpl . $ }} - {{- end }} + echo "Running custom postStartCommand command"; + {{ tpl . $ }}; + {{- end }} env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} {{- if or .Values.database.secrets.user .Values.database.user }} - name: JF_SHARED_DATABASE_USERNAME valueFrom: @@ -478,12 +501,16 @@ spec: {{- end }} ports: - containerPort: {{ .Values.artifactory.internalPort }} + name: http - containerPort: {{ .Values.artifactory.internalArtifactoryPort }} + name: http-internal {{- if .Values.artifactory.primary.javaOpts.jmx.enabled }} - containerPort: {{ .Values.artifactory.primary.javaOpts.jmx.port }} + name: tcp-jmx {{- end }} {{- if .Values.artifactory.ssh.enabled }} - containerPort: {{ .Values.artifactory.ssh.internalPort }} + name: tcp-ssh {{- end }} volumeMounts: {{- if .Values.artifactory.customPersistentVolumeClaim }} @@ -547,8 +574,8 @@ spec: - name: installer-info mountPath: "/artifactory_bootstrap/info/installer-info.json" subPath: installer-info.json - {{- if .Values.artifactory.customVolumeMounts }} -{{ tpl .Values.artifactory.customVolumeMounts . | indent 8 }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} {{- end }} resources: {{ toYaml .Values.artifactory.primary.resources | indent 10 }} @@ -576,12 +603,10 @@ spec: failureThreshold: {{ .Values.artifactory.livenessProbe.failureThreshold }} successThreshold: {{ .Values.artifactory.livenessProbe.successThreshold }} {{- end }} - {{- $image := .Values.logger.image.repository }} - {{- $tag := .Values.logger.image.tag }} {{- $mountPath := .Values.artifactory.persistence.mountPath }} {{- range .Values.artifactory.loggers }} - name: {{ . | replace "_" "-" | replace "." "-" }} - image: '{{ $image }}:{{ $tag }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list $ "logger") }} command: - 'sh' - '-c' @@ -598,7 +623,7 @@ spec: {{ if .Values.artifactory.catalinaLoggers }} {{- range .Values.artifactory.catalinaLoggers }} - name: {{ . | replace "_" "-" | replace "." "-" }} - image: '{{ $image }}:{{ $tag }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list $ "logger") }} command: - 'sh' - '-c' @@ -638,8 +663,8 @@ spec: {{ toYaml .Values.filebeat.resources | indent 10 }} terminationGracePeriodSeconds: {{ .Values.terminationGracePeriod }} {{- end }} - {{- if .Values.artifactory.customSidecarContainers }} -{{ tpl .Values.artifactory.customSidecarContainers . | indent 6 }} + {{- if or .Values.artifactory.customSidecarContainers .Values.global.customSidecarContainers }} +{{ tpl (include "artifactory-ha.customSidecarContainers" .) . | indent 6 }} {{- end }} {{- with .Values.artifactory.primary.nodeSelector }} nodeSelector: @@ -764,9 +789,11 @@ spec: persistentVolumeClaim: claimName: {{ template "artifactory-ha.fullname" . }}-backup-pvc {{- end }} + {{- if or .Values.systemYamlOverride.existingSecret .Values.artifactory.systemYaml }} - name: systemyaml secret: - secretName: {{ template "artifactory-ha.primary.name" . }}-system-yaml + secretName: {{ default (printf "%s-%s" (include "artifactory-ha.primary.name" .) "system-yaml") .Values.systemYamlOverride.existingSecret }} + {{- end }} {{- if .Values.access.accessConfig }} - name: access-config secret: @@ -787,8 +814,8 @@ spec: configMap: name: {{ template "artifactory-ha.fullname" . }}-filebeat-config {{- end }} - {{- if .Values.artifactory.customVolumes }} -{{ tpl .Values.artifactory.customVolumes . | indent 6 }} + {{- if or .Values.artifactory.customVolumes .Values.global.customVolumes }} +{{ tpl (include "artifactory-ha.customVolumes" .) . | indent 6 }} {{- end }} {{- if not .Values.artifactory.persistence.enabled }} - name: volume diff --git a/charts/artifactory-ha/templates/artifactory-secrets.yaml b/charts/artifactory-ha/templates/artifactory-secrets.yaml index 260e516b9..5870428ca 100644 --- a/charts/artifactory-ha/templates/artifactory-secrets.yaml +++ b/charts/artifactory-ha/templates/artifactory-secrets.yaml @@ -9,9 +9,13 @@ metadata: release: {{ .Release.Name }} type: Opaque data: -{{- if and .Values.artifactory.masterKey (not .Values.artifactory.masterKeySecretName) }} - master-key: {{ .Values.artifactory.masterKey | b64enc | quote }} -{{- end }} -{{- if and .Values.artifactory.joinKey (not .Values.artifactory.joinKeySecretName) }} - join-key: {{ .Values.artifactory.joinKey | b64enc | quote }} -{{- end }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey }} + {{- if not (or .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName) }} + master-key: {{ include "artifactory-ha.masterKey" . | b64enc | quote }} + {{- end }} + {{- end }} + {{- if or .Values.artifactory.joinKey .Values.global.joinKey }} + {{- if not (or .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName) }} + join-key: {{ include "artifactory-ha.joinKey" . | b64enc | quote }} + {{- end }} + {{- end }} diff --git a/charts/artifactory-ha/templates/artifactory-service.yaml b/charts/artifactory-ha/templates/artifactory-service.yaml index 68b467ea5..baacb970f 100644 --- a/charts/artifactory-ha/templates/artifactory-service.yaml +++ b/charts/artifactory-ha/templates/artifactory-service.yaml @@ -29,27 +29,29 @@ spec: - port: {{ .Values.artifactory.externalPort }} targetPort: {{ .Values.artifactory.internalPort }} protocol: TCP - name: router + name: http-router {{- if .Values.artifactory.ssh.enabled }} - port: {{ .Values.artifactory.ssh.externalPort }} targetPort: {{ .Values.artifactory.ssh.internalPort }} protocol: TCP - name: ssh + name: tcp-ssh {{- end }} - port: {{ .Values.artifactory.externalArtifactoryPort }} targetPort: {{ .Values.artifactory.internalArtifactoryPort }} protocol: TCP - name: artifactory + name: http-artifactory {{- with .Values.artifactory.node.javaOpts.jmx }} {{- if .enabled }} - port: {{ .port }} targetPort: {{ .port }} protocol: TCP - name: jmx + name: tcp-jmx {{- end }} {{- end }} selector: -{{- if eq .Values.artifactory.service.pool "members" }} +{{- if eq (int .Values.artifactory.node.replicaCount) 0 }} + role: {{ template "artifactory-ha.primary.name" . }} +{{- else if eq .Values.artifactory.service.pool "members" }} role: {{ template "artifactory-ha.node.name" . }} {{- end }} app: {{ template "artifactory-ha.name" . }} @@ -81,23 +83,23 @@ spec: - port: {{ .Values.artifactory.externalPort }} targetPort: {{ .Values.artifactory.internalPort }} protocol: TCP - name: router + name: http-router - port: {{ .Values.artifactory.externalArtifactoryPort }} targetPort: {{ .Values.artifactory.internalArtifactoryPort }} protocol: TCP - name: artifactory + name: http-artifactory {{- if .Values.artifactory.ssh.enabled }} - port: {{ .Values.artifactory.ssh.externalPort }} targetPort: {{ .Values.artifactory.ssh.internalPort }} protocol: TCP - name: ssh + name: tcp-ssh {{- end }} {{- with .Values.artifactory.primary.javaOpts.jmx }} {{- if .enabled }} - port: {{ .port }} targetPort: {{ .port }} protocol: TCP - name: jmx + name: tcp-jmx {{- end }} {{- end }} selector: diff --git a/charts/artifactory-ha/templates/artifactory-system-yaml.yaml b/charts/artifactory-ha/templates/artifactory-system-yaml.yaml index cf8ffbac8..aaa1be152 100644 --- a/charts/artifactory-ha/templates/artifactory-system-yaml.yaml +++ b/charts/artifactory-ha/templates/artifactory-system-yaml.yaml @@ -1,3 +1,4 @@ +{{- if not .Values.systemYamlOverride.existingSecret }} apiVersion: v1 kind: Secret metadata: @@ -12,3 +13,4 @@ type: Opaque stringData: system.yaml: | {{ tpl .Values.artifactory.systemYaml . | indent 4 }} +{{- end }} diff --git a/charts/artifactory-ha/templates/ingress.yaml b/charts/artifactory-ha/templates/ingress.yaml index 262647f9c..53f7c93ec 100644 --- a/charts/artifactory-ha/templates/ingress.yaml +++ b/charts/artifactory-ha/templates/ingress.yaml @@ -3,7 +3,7 @@ {{- $servicePort := .Values.artifactory.externalPort -}} {{- $artifactoryServicePort := .Values.artifactory.externalArtifactoryPort -}} {{- $ingressName := default ( include "artifactory-ha.fullname" . ) .Values.ingress.name -}} -{{- if semverCompare ">=v1.14.0" .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=v1.14.0-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1beta1 {{- else }} apiVersion: extensions/v1beta1 @@ -55,7 +55,7 @@ spec: {{- if .Values.artifactory.replicator.enabled }} --- {{- $replicationIngressName := default ( include "artifactory-ha.replicator.fullname" . ) .Values.artifactory.replicator.ingress.name -}} -{{- if semverCompare ">=v1.14.0" .Capabilities.KubeVersion.GitVersion }} +{{- if semverCompare ">=v1.14.0-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1beta1 {{- else }} apiVersion: extensions/v1beta1 @@ -99,4 +99,51 @@ spec: {{ toYaml .Values.artifactory.replicator.ingress.tls | indent 4 }} {{- end -}} {{- end -}} +{{- if and .Values.artifactory.replicator.enabled .Values.artifactory.replicator.trackerIngress.enabled }} +--- +{{- $replicatorTrackerIngressName := default ( include "artifactory-ha.replicator.tracker.fullname" . ) .Values.artifactory.replicator.trackerIngress.name -}} + {{- if semverCompare ">=v1.14.0-0" .Capabilities.KubeVersion.GitVersion }} +apiVersion: networking.k8s.io/v1beta1 + {{- else }} +apiVersion: extensions/v1beta1 + {{- end }} +kind: Ingress +metadata: + name: {{ $replicatorTrackerIngressName }} + labels: + app: "{{ template "artifactory-ha.name" $ }}" + chart: "{{ template "artifactory-ha.chart" $ }}" + release: {{ $.Release.Name | quote }} + heritage: {{ $.Release.Service | quote }} + {{- if .Values.artifactory.replicator.trackerIngress.annotations }} + annotations: +{{ .Values.artifactory.replicator.trackerIngress.annotations | toYaml | trimSuffix "\n" | indent 4 -}} + {{- end }} +spec: + {{- if .Values.ingress.defaultBackend.enabled }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} + rules: +{{- if .Values.artifactory.replicator.trackerIngress.hosts }} + {{- range $host := .Values.artifactory.replicator.trackerIngress.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: / + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} +{{- end -}} + {{- if .Values.artifactory.replicator.trackerIngress.tls }} + tls: +{{ toYaml .Values.artifactory.replicator.trackerIngress.tls | indent 4 }} + {{- end -}} +{{- end -}} +{{- if .Values.customIngress }} +--- +{{ .Values.customIngress | toYaml | trimSuffix "\n" }} +{{- end -}} {{- end -}} diff --git a/charts/artifactory-ha/templates/nginx-certificate-secret.yaml b/charts/artifactory-ha/templates/nginx-certificate-secret.yaml index 2c1430a18..c4aceb951 100644 --- a/charts/artifactory-ha/templates/nginx-certificate-secret.yaml +++ b/charts/artifactory-ha/templates/nginx-certificate-secret.yaml @@ -1,4 +1,4 @@ -{{- if and (not .Values.nginx.tlsSecretName) .Values.nginx.enabled }} +{{- if and (not .Values.nginx.tlsSecretName) .Values.nginx.https.enabled }} apiVersion: v1 kind: Secret type: kubernetes.io/tls diff --git a/charts/artifactory-ha/templates/nginx-deployment.yaml b/charts/artifactory-ha/templates/nginx-deployment.yaml index 4327e7479..7d321b3f9 100644 --- a/charts/artifactory-ha/templates/nginx-deployment.yaml +++ b/charts/artifactory-ha/templates/nginx-deployment.yaml @@ -36,10 +36,12 @@ spec: release: {{ .Release.Name }} spec: serviceAccountName: {{ template "artifactory-ha.serviceAccountName" . }} - {{- if .Values.imagePullSecrets }} - imagePullSecrets: - - name: {{ .Values.imagePullSecrets }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory-ha.imagePullSecrets" . | indent 6 }} {{- end }} + {{- if .Values.nginx.priorityClassName }} + priorityClassName: {{ .Values.nginx.priorityClassName | quote }} + {{- end }} initContainers: - name: "setup" image: "{{ .Values.initContainerImage }}" @@ -58,7 +60,7 @@ spec: fsGroup: {{ .Values.nginx.gid }} containers: - name: {{ .Values.nginx.name }} - image: '{{ .Values.nginx.image.repository }}:{{ default .Chart.AppVersion .Values.nginx.image.version }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "nginx") }} imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} command: - 'nginx' @@ -70,19 +72,24 @@ spec: {{- if .Values.nginx.http }} {{- if .Values.nginx.http.enabled }} - containerPort: {{ .Values.nginx.http.internalPort }} + name: http {{- end }} {{- else }} # DEPRECATED - containerPort: {{ .Values.nginx.internalPortHttp }} + name: http-internal {{- end }} {{- if .Values.nginx.https }} {{- if .Values.nginx.https.enabled }} - containerPort: {{ .Values.nginx.https.internalPort }} + name: https {{- end }} {{- else }} # DEPRECATED - containerPort: {{ .Values.nginx.internalPortHttps }} + name: https-internal {{- end }} {{- if .Values.artifactory.ssh.enabled }} - containerPort: {{ .Values.nginx.ssh.internalPort }} + name: tcp-ssh {{- end }} volumeMounts: - name: nginx-conf @@ -92,8 +99,10 @@ spec: mountPath: "{{ .Values.nginx.persistence.mountPath }}/conf.d/" - name: nginx-volume mountPath: {{ .Values.nginx.persistence.mountPath | quote }} + {{- if .Values.nginx.https.enabled }} - name: ssl-certificates mountPath: "{{ .Values.nginx.persistence.mountPath }}/ssl" + {{- end }} resources: {{ toYaml .Values.nginx.resources | indent 10 }} {{- if .Values.nginx.readinessProbe.enabled }} @@ -130,12 +139,10 @@ spec: failureThreshold: {{ .Values.nginx.livenessProbe.failureThreshold }} successThreshold: {{ .Values.nginx.livenessProbe.successThreshold }} {{- end }} - {{- $image := .Values.logger.image.repository }} - {{- $tag := .Values.logger.image.tag }} {{- $mountPath := .Values.nginx.persistence.mountPath }} {{- range .Values.nginx.loggers }} - name: {{ . | replace "_" "-" | replace "." "-" }} - image: '{{ $image }}:{{ $tag }}' + image: {{ include "artifactory-ha.getImageInfoByValue" (list $ "logger") }} command: - tail args: @@ -182,6 +189,7 @@ spec: {{- else }} emptyDir: {} {{- end }} + {{- if .Values.nginx.https.enabled }} - name: ssl-certificates secret: {{- if .Values.nginx.tlsSecretName }} @@ -189,4 +197,5 @@ spec: {{- else }} secretName: {{ template "artifactory-ha.fullname" . }}-nginx-certificate {{- end }} + {{- end }} {{- end }} diff --git a/charts/artifactory-ha/templates/nginx-pvc.yaml b/charts/artifactory-ha/templates/nginx-pvc.yaml index 68a89cea0..0e573f383 100644 --- a/charts/artifactory-ha/templates/nginx-pvc.yaml +++ b/charts/artifactory-ha/templates/nginx-pvc.yaml @@ -15,11 +15,11 @@ spec: resources: requests: storage: {{ .Values.nginx.persistence.size | quote }} -{{- if .Values.nginx.persistence.storageClass }} -{{- if (eq "-" .Values.nginx.persistence.storageClass) }} +{{- if .Values.nginx.persistence.storageClassName }} +{{- if (eq "-" .Values.nginx.persistence.storageClassName) }} storageClassName: "" {{- else }} - storageClassName: "{{ .Values.nginx.persistence.storageClass }}" + storageClassName: "{{ .Values.nginx.persistence.storageClassName }}" {{- end }} {{- end }} {{- end }} diff --git a/charts/artifactory-ha/templates/nginx-service.yaml b/charts/artifactory-ha/templates/nginx-service.yaml index d0e10e248..594717cf9 100644 --- a/charts/artifactory-ha/templates/nginx-service.yaml +++ b/charts/artifactory-ha/templates/nginx-service.yaml @@ -70,7 +70,7 @@ spec: - port: {{ .Values.nginx.ssh.externalPort }} targetPort: {{ .Values.nginx.ssh.internalPort }} protocol: TCP - name: ssh + name: tcp-ssh {{- end }} selector: app: {{ template "artifactory-ha.name" . }} diff --git a/charts/artifactory-ha/values.yaml b/charts/artifactory-ha/values.yaml index f0ff3a1c5..b142d38c6 100644 --- a/charts/artifactory-ha/values.yaml +++ b/charts/artifactory-ha/values.yaml @@ -3,8 +3,30 @@ # Beware when changing values here. You should know what you are doing! # Access the values with {{ .Values.key.subkey }} -# Common -initContainerImage: docker.bintray.io/alpine:3.12 +global: + # imageRegistry: docker.bintray.io + # imagePullSecrets: + # - myRegistryKeySecretName + ## Chart.AppVersion can be overidden using global.versions.artifactory or .Values.artifactory.image.tag + ## Note: Order of preference is 1) global.versions 2) .Values.artifactory.image.tag 3) Chart.AppVersion + ## This applies also for nginx images (.Values.nginx.image.tag) + versions: {} + # artifactory: + # joinKey: + # masterKey: + # joinKeySecretName: + # masterKeySecretName: + # customInitContainersBegin: | + + # customInitContainers: | + + # customVolumes: | + + # customVolumeMounts: | + + # customSidecarContainers: | + +initContainerImage: docker.bintray.io/alpine:3.12.1 installer: type: @@ -13,7 +35,20 @@ installer: installerInfo: '{"productId": "Helm_artifactory-ha/{{ .Chart.Version }}", "features": [ { "featureId": "Platform/{{ default "kubernetes" .Values.installer.platform }}"}]}' # For supporting pulling from private registries -imagePullSecrets: +# imagePullSecrets: +# - myRegistryKeySecretName + +## Artifactory systemYaml override +## This is for advanced usecases where users wants to provide their own systemYaml for configuring artifactory +## Refer: https://www.jfrog.com/confluence/display/JFROG/Artifactory+System+YAML +## Note: This will override existing (default) .Values.artifactory.systemYaml in values.yaml +## Alternatively, systemYaml can be overidden via customInitContainers using external sources like vaults, external repositories etc. Please refer customInitContainer section below for an example. +## Note: Order of preference is 1) customInitContainers 2) systemYamlOverride existingSecret 3) default systemYaml in values.yaml +systemYamlOverride: +## You can use a pre-existing secret by specifying existingSecret + existingSecret: +## The dataKey should be the name of the secret data key created. + dataKey: ## Role Based Access Control ## Ref: https://kubernetes.io/docs/admin/authorization/rbac/ @@ -67,6 +102,8 @@ ingress: # Additional ingress rules additionalRules: [] +## Allows to add custom ingress +customIngress: | networkpolicy: # Allows all ingress and egress @@ -102,7 +139,7 @@ postgresql: image: registry: docker.bintray.io repository: bitnami/postgresql - tag: 10.13.0-debian-10-r38 + tag: 12.5.0-debian-10-r25 postgresqlUsername: artifactory postgresqlPassword: "" postgresqlDatabase: artifactory @@ -156,7 +193,8 @@ database: logger: image: - repository: docker.bintray.io/busybox + registry: docker.bintray.io + repository: busybox tag: 1.31.1 # Artifactory @@ -164,8 +202,9 @@ artifactory: name: artifactory-ha # Note that by default we use appVersion to get image tag/version image: - repository: docker.bintray.io/jfrog/artifactory-pro - # version: + registry: docker.bintray.io + repository: jfrog/artifactory-pro + # tag: pullPolicy: IfNotPresent # Create a priority class for the Artifactory pods or use an existing one @@ -187,6 +226,12 @@ artifactory: maxThreads: 200 extraConfig: 'acceptCount="100"' + # Support for open metrics is only available for Artifactory 7.7.x (appVersions) and above. + # To enable set `.Values.artifactory.openMetrics.enabled` to `true` + # Refer - https://www.jfrog.com/confluence/display/JFROG/Open+Metrics + openMetrics: + enabled: false + # This directory is intended for use with NFS eventual configuration for HA haDataDir: enabled: false @@ -273,13 +318,13 @@ artifactory: ## Add custom init containers execution after predefined init containers customInitContainers: | - # - name: "custom-setup" + # - name: "custom-systemyaml-setup" # image: "{{ .Values.initContainerImage }}" # imagePullPolicy: "{{ .Values.artifactory.image.pullPolicy }}" # command: # - 'sh' # - '-c' - # - 'touch {{ .Values.artifactory.persistence.mountPath }}/example-custom-setup' + # - 'wget -O {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml https:///systemyaml' # volumeMounts: # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" # name: volume @@ -292,8 +337,7 @@ artifactory: # image: "{{ .Values.initContainerImage }}" # imagePullPolicy: "{{ .Values.artifactory.image.pullPolicy }}" # securityContext: - # runAsUser: 0 - # fsGroup: 0 + # allowPrivilegeEscalation: false # command: # - 'sh' # - '-c' @@ -331,6 +375,7 @@ artifactory: # subPath: prehook-start.sh # Add custom persistent volume mounts - Available for the pod + # If skipPrepareContainer is set to true , this will skip the prepare-custom-persistent-volume init container customPersistentPodVolumeClaim: {} # name: # mountPath: @@ -338,6 +383,7 @@ artifactory: # - "-" # size: # storageClassName: + # skipPrepareContainer: false # Add custom persistent volume mounts - Available to the entire namespace customPersistentVolumeClaim: {} @@ -500,6 +546,10 @@ artifactory: driver: "{{ .Values.database.driver }}" {{- end }} artifactory: + {{- if .Values.artifactory.openMetrics }} + metrics: + enabled: {{ .Values.artifactory.openMetrics.enabled }} + {{- end }} {{- if or .Values.artifactory.haDataDir.enabled .Values.artifactory.haBackupDir.enabled }} node: {{- if .Values.artifactory.haDataDir.path }} @@ -515,6 +565,9 @@ artifactory: connector: maxThreads: {{ .Values.artifactory.tomcat.connector.maxThreads }} extraConfig: {{ .Values.artifactory.tomcat.connector.extraConfig }} + frontend: + session: + timeMinutes: {{ .Values.frontend.session.timeoutMinutes | quote }} access: database: maxOpenConnections: {{ .Values.access.database.maxOpenConnections }} @@ -544,7 +597,13 @@ artifactory: externalArtifactoryPort: 8081 internalArtifactoryPort: 8081 uid: 1030 + gid: 1030 terminationGracePeriodSeconds: 30 + + ## By default, the Artifactory StatefulSet is created with a securityContext that sets the `runAsUser` and the `fsGroup` to the `artifactory.uid` value. + ## If you want to disable the securityContext for the Artifactory StatefulSet, set this tag to false + setSecurityContext: true + ## The following settings are to configure the frequency of the liveness and readiness probes livenessProbe: enabled: true @@ -807,6 +866,12 @@ artifactory: {{- with .cloudFrontPrivateKey }} {{ . }} {{- end }} + {{- with .enableSignedUrlRedirect }} + {{ . }} + {{- end }} + {{- with .enablePathStyleAccess }} + {{ . }} + {{- end }} {{- end }} @@ -983,7 +1048,7 @@ artifactory: # "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", # "client_x509_cert_url": "https://www.googleapis.com/robot/v1....." # } - endpoint: storage.googleapis.com + endpoint: commondatastorage.googleapis.com httpsOnly: false # Set a unique bucket name bucketName: "artifactory-ha-gcp" @@ -1011,6 +1076,8 @@ artifactory: cloudFrontDomainName: cloudFrontKeyPairId: cloudFrontPrivateKey: + enableSignedUrlRedirect: false + enablePathStyleAccess: false ## For artifactory.persistence.type aws-s3 ## IMPORTANT: Make sure S3 `endpoint` and `region` match! See https://docs.aws.amazon.com/general/latest/gr/rande.html @@ -1075,6 +1142,22 @@ artifactory: # - hosts: # - artifactory.domain.example # secretName: chart-example-tls-secret + ## When replicator is enabled and want to use tracker feature, trackerIngress.enabled flag should be set to true + ## Please refer - https://www.jfrog.com/confluence/display/JFROG/JFrog+Peer-to-Peer+%28P2P%29+Downloads + trackerIngress: + enabled: false + name: + hosts: [] + annotations: {} + # kubernetes.io/ingress.class: nginx + # nginx.ingress.kubernetes.io/proxy-buffering: "off" + # nginx.ingress.kubernetes.io/configuration-snippet: | + # chunked_transfer_encoding on; + tls: [] + # Secrets must be manually created in the namespace. + # - hosts: + # - artifactory.domain.example + # secretName: chart-example-tls-secret ssh: enabled: false @@ -1095,6 +1178,11 @@ artifactory: ## Set existingClaim to true or false ## If true, you must prepare a PVC with the name e.g `volume-myrelease-artifactory-ha-primary-0` existingClaim: false + + ## IMPORTANT: This value should remain at 1! + replicaCount: 1 + # minAvailable: 1 + ## Resources for the primary node resources: {} # requests: @@ -1189,6 +1277,12 @@ artifactory: type: "" topologyKey: "kubernetes.io/hostname" +frontend: + ## Session settings + session: + ## Time in minutes after which the frontend token will need to be refreshed + timeoutMinutes: '30' + access: ## Enable TLS by changing the tls entry (under the security section) in the access.config.yaml file. ## ref: https://www.jfrog.com/confluence/display/JFROG/Managing+TLS+Certificates#ManagingTLSCertificates @@ -1238,10 +1332,13 @@ nginx: gid: 107 # Note that by default we use appVersion to get image tag/version image: - repository: docker.bintray.io/jfrog/nginx-artifactory-pro - # version: + registry: docker.bintray.io + repository: jfrog/nginx-artifactory-pro + # tag: pullPolicy: IfNotPresent + # Priority Class name to be used in deployment if provided + priorityClassName: # Sidecar containers for tailing Nginx logs loggers: [] @@ -1493,7 +1590,7 @@ filebeat: name: artifactory-filebeat image: repository: "docker.elastic.co/beats/filebeat" - version: 7.5.1 + version: 7.9.2 logstashUrl: "logstash:5044" terminationGracePeriod: 10 @@ -1549,3 +1646,8 @@ filebeat: output: logstash: hosts: ["{{ .Values.filebeat.logstashUrl }}"] + +## Allows to add additional kubernetes resources +## Use --- as a separator between multiple resources +## For an example, refer - https://github.com/jfrog/log-analytics-prometheus/blob/master/artifactory-ha-values.yaml +additionalResources: | diff --git a/index.yaml b/index.yaml index 606e7cda5..f844847fa 100644 --- a/index.yaml +++ b/index.yaml @@ -1,6 +1,35 @@ apiVersion: v1 entries: artifactory-ha: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/release-name: artifactory-ha + apiVersion: v1 + appVersion: 7.12.6 + created: "2021-02-26T18:55:48.762534939Z" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 9.3.4 + description: Universal Repository Manager supporting all major packaging formats, build tools and CI servers. + digest: 6f13240e67c292e0a7229b1e0b1d8389991e10850d629fab7bac34b7f702fa3c + home: https://www.jfrog.com/artifactory/ + icon: https://raw.githubusercontent.com/jfrog/charts/master/stable/artifactory-ha/logo/artifactory-logo.png + keywords: + - artifactory + - jfrog + - devops + maintainers: + - email: installers@jfrog.com + name: Chart Maintainers at JFrog + name: artifactory-ha + sources: + - https://bintray.com/jfrog/product/JFrog-Artifactory-Pro/view + - https://github.com/jfrog/charts + urls: + - assets/artifactory-ha/artifactory-ha-4.7.600.tgz + version: 4.7.600 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/release-name: artifactory-ha @@ -550,4 +579,4 @@ entries: urls: - assets/sysdig/sysdig-1.9.200.tgz version: 1.9.200 -generated: "2021-02-25T22:46:37.810270792Z" +generated: "2021-02-26T18:55:48.743664584Z" diff --git a/sha256sum/artifactory-ha/artifactory-ha.sum b/sha256sum/artifactory-ha/artifactory-ha.sum index 705696e5a..a434e4c79 100644 --- a/sha256sum/artifactory-ha/artifactory-ha.sum +++ b/sha256sum/artifactory-ha/artifactory-ha.sum @@ -1,4 +1,4 @@ -c9d48693a4167c6d483d5b8ff25b52e574d39bb288be470f750e6a8cc832472f packages/artifactory-ha/artifactory-ha.patch +09e6066802fa1d420df13ca6af1c4fd6c90e1497d977c97762175ca3a7ba9226 packages/artifactory-ha/artifactory-ha.patch d12365c0a850cb3a405e105ed2b7858644f27831a46b8dde64ede4eb25bba9c4 packages/artifactory-ha/overlay/app-readme.md -b3242886b886a6273dba8b838dc8f9c52d0fe6a86d195d5291dd13e0e95baf9d packages/artifactory-ha/overlay/questions.yml -df94fbf838108e4178bc3f51be85745dda4c4f577ad842fef65e5b7d78989a0e packages/artifactory-ha/package.yaml +3c42752639460fe63c0b288d2ab8f6cdf87ded1b269ff307028b6c675635e67b packages/artifactory-ha/overlay/questions.yml +65882a62266f8948889c99279658ffcf01fb215a6d303dff9852364d0a3a3d4a packages/artifactory-ha/package.yaml