From c344213d1a3455730d4c90412e1a9f0378185b3a Mon Sep 17 00:00:00 2001 From: Jo Date: Tue, 5 Nov 2024 18:46:59 +0100 Subject: [PATCH] feat: handle semver dependencies (#515) --- .../dependencies/download-helm-chart.js | 38 ++- packages/helm-tree/package.json | 1 + ...pendencies-with-not-fixed-version.dev.yaml | 251 ++++++++++++++++++ .../Chart.yaml | 7 + .../values.yaml | 5 + yarn.lock | 10 + 6 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 packages/kontinuous/tests/__snapshots__/helm-dependencies-with-not-fixed-version.dev.yaml create mode 100644 packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/Chart.yaml create mode 100644 packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/values.yaml diff --git a/packages/helm-tree/dependencies/download-helm-chart.js b/packages/helm-tree/dependencies/download-helm-chart.js index d0625d3986..06ab6ddbc5 100644 --- a/packages/helm-tree/dependencies/download-helm-chart.js +++ b/packages/helm-tree/dependencies/download-helm-chart.js @@ -1,11 +1,21 @@ const fs = require("fs-extra") - +const semver = require("semver") const axios = require("~common/utils/axios-retry") const yaml = require("~common/utils/yaml") const downloadFile = require("~common/utils/download-file") const slug = require("~common/utils/slug") const handleAxiosError = require("~common/utils/handle-axios-error") +function satisfiesVersion(availableVersion, requiredVersion) { + // If requiredVersion is an exact version (no special characters), do a direct comparison + if (/^\d+\.\d+\.\d+$/.test(requiredVersion)) { + return availableVersion === requiredVersion + } + + // For all other cases, use semver.satisfies + return semver.satisfies(availableVersion, requiredVersion) +} + module.exports = async ({ dependency, target, cachePath, logger }) => { const { repository, version } = dependency const localArchive = `${target}/charts/${dependency.name}-${version}.tgz` @@ -31,13 +41,29 @@ module.exports = async ({ dependency, target, cachePath, logger }) => { const repo = yaml.load(repositoryIndex.data) const { entries } = repo const entryVersions = entries[dependency.name] - const versionEntry = entryVersions.find( - (entry) => entry.version.toString() === dependency.version.toString() + + // Find all versions that satisfy the constraint + const satisfyingVersions = entryVersions.filter((entry) => + satisfiesVersion(entry.version, dependency.version) ) - if (!versionEntry) { - throw new Error(`version ${version} not found for ${dependency.name}`) + + if (satisfyingVersions.length === 0) { + throw new Error( + `No matching version found for ${dependency.name}@${version}` + ) + } + + // Sort the satisfying versions in descending order + satisfyingVersions.sort((a, b) => semver.rcompare(a.version, b.version)) + + // Select the newest version that satisfies the constraint + const versionEntry = satisfyingVersions[0] + + let url = versionEntry.urls[0] + // Check if the URL is relative and add the repository as prefix if needed + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = `${repository.replace(/\/$/, "")}/${url.replace(/^\//, "")}` } - const url = versionEntry.urls[0] logger.debug(`⬇️ downloading chart ${url}`) await downloadFile(url, zfile, logger) } diff --git a/packages/helm-tree/package.json b/packages/helm-tree/package.json index a74d01424b..c5f9b746d8 100644 --- a/packages/helm-tree/package.json +++ b/packages/helm-tree/package.json @@ -9,6 +9,7 @@ "dependencies": { "decompress": "^4.2.1", "fs-extra": "^11.1.1", + "semver": "^7.6.3", "~common": "workspace:^" } } diff --git a/packages/kontinuous/tests/__snapshots__/helm-dependencies-with-not-fixed-version.dev.yaml b/packages/kontinuous/tests/__snapshots__/helm-dependencies-with-not-fixed-version.dev.yaml new file mode 100644 index 0000000000..515d8126a5 --- /dev/null +++ b/packages/kontinuous/tests/__snapshots__/helm-dependencies-with-not-fixed-version.dev.yaml @@ -0,0 +1,251 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`test build manifests with snapshots helm-dependencies-with-not-fixed-version.dev 1`] = ` +"apiVersion: v1 +kind: ServiceAccount +metadata: + name: release-name-postgresql + namespace: default + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgresql + app.kubernetes.io/version: 16.1.0 + helm.sh/chart: postgresql-13.4.4 + annotations: + kontinuous/chartPath: project.postgresql + kontinuous/source: project/charts/postgresql/templates/serviceaccount.yaml +automountServiceAccountToken: false +--- +apiVersion: v1 +kind: Secret +metadata: + name: release-name-postgresql + namespace: default + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgresql + app.kubernetes.io/version: 16.1.0 + helm.sh/chart: postgresql-13.4.4 + annotations: + kontinuous/chartPath: project.postgresql + kontinuous/source: project/charts/postgresql/templates/secrets.yaml +type: Opaque +data: + postgres-password: MTIzNA== +--- +apiVersion: v1 +kind: Service +metadata: + name: release-name-postgresql-hl + namespace: default + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgresql + app.kubernetes.io/version: 16.1.0 + helm.sh/chart: postgresql-13.4.4 + app.kubernetes.io/component: primary + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: \\"true\\" + kontinuous/chartPath: project.postgresql + kontinuous/source: project/charts/postgresql/templates/primary/svc-headless.yaml +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: tcp-postgresql + port: 5432 + targetPort: tcp-postgresql + selector: + app.kubernetes.io/instance: release-name + app.kubernetes.io/name: postgresql + app.kubernetes.io/component: primary +--- +apiVersion: v1 +kind: Service +metadata: + name: release-name-postgresql + namespace: default + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgresql + app.kubernetes.io/version: 16.1.0 + helm.sh/chart: postgresql-13.4.4 + app.kubernetes.io/component: primary + annotations: + kontinuous/chartPath: project.postgresql + kontinuous/source: project/charts/postgresql/templates/primary/svc.yaml +spec: + type: ClusterIP + sessionAffinity: None + ports: + - name: tcp-postgresql + port: 5432 + targetPort: tcp-postgresql + selector: + app.kubernetes.io/instance: release-name + app.kubernetes.io/name: postgresql + app.kubernetes.io/component: primary +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: release-name-postgresql + namespace: default + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgresql + app.kubernetes.io/version: 16.1.0 + helm.sh/chart: postgresql-13.4.4 + app.kubernetes.io/component: primary + annotations: + kontinuous/chartPath: project.postgresql + kontinuous/source: project/charts/postgresql/templates/primary/statefulset.yaml +spec: + replicas: 1 + serviceName: release-name-postgresql-hl + updateStrategy: + rollingUpdate: {} + type: RollingUpdate + selector: + matchLabels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/name: postgresql + app.kubernetes.io/component: primary + template: + metadata: + name: release-name-postgresql + labels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgresql + app.kubernetes.io/version: 16.1.0 + helm.sh/chart: postgresql-13.4.4 + app.kubernetes.io/component: primary + spec: + serviceAccountName: release-name-postgresql + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + app.kubernetes.io/instance: release-name + app.kubernetes.io/name: postgresql + app.kubernetes.io/component: primary + topologyKey: kubernetes.io/hostname + weight: 1 + securityContext: + fsGroup: 1001 + fsGroupChangePolicy: Always + supplementalGroups: [] + sysctls: [] + hostNetwork: false + hostIPC: false + containers: + - name: postgresql + image: docker.io/bitnami/postgresql:16.1.0-debian-11-r25 + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 1001 + seccompProfile: + type: RuntimeDefault + env: + - name: BITNAMI_DEBUG + value: \\"false\\" + - name: POSTGRESQL_PORT_NUMBER + value: \\"5432\\" + - name: POSTGRESQL_VOLUME_DIR + value: /bitnami/postgresql + - name: PGDATA + value: /bitnami/postgresql/data + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: release-name-postgresql + key: postgres-password + - name: POSTGRESQL_ENABLE_LDAP + value: \\"no\\" + - name: POSTGRESQL_ENABLE_TLS + value: \\"no\\" + - name: POSTGRESQL_LOG_HOSTNAME + value: \\"false\\" + - name: POSTGRESQL_LOG_CONNECTIONS + value: \\"false\\" + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: \\"false\\" + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: \\"off\\" + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: error + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: pgaudit + ports: + - name: tcp-postgresql + containerPort: 5432 + livenessProbe: + failureThreshold: 6 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + exec: + command: + - /bin/sh + - -c + - exec pg_isready -U \\"postgres\\" -h 127.0.0.1 -p 5432 + readinessProbe: + failureThreshold: 6 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + exec: + command: + - /bin/sh + - -c + - -e + - > + exec pg_isready -U \\"postgres\\" -h 127.0.0.1 -p 5432 + + [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ] + resources: + limits: {} + requests: + cpu: 250m + memory: 256Mi + volumeMounts: + - name: dshm + mountPath: /dev/shm + - name: data + mountPath: /bitnami/postgresql + volumes: + - name: dshm + emptyDir: + medium: Memory + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi +" +`; diff --git a/packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/Chart.yaml b/packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/Chart.yaml new file mode 100644 index 0000000000..acf232ce68 --- /dev/null +++ b/packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: postgresql +version: 0.0.0 +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: ^13.0.0 \ No newline at end of file diff --git a/packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/values.yaml b/packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/values.yaml new file mode 100644 index 0000000000..60224d5b86 --- /dev/null +++ b/packages/kontinuous/tests/samples/helm-dependencies-with-not-fixed-version/values.yaml @@ -0,0 +1,5 @@ +global: + postgresql: + auth: + password: "1234" + postgresPassword: "1234" \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3f48b7185e..bcb3f32882 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9305,6 +9305,7 @@ __metadata: dependencies: decompress: "npm:^4.2.1" fs-extra: "npm:^11.1.1" + semver: "npm:^7.6.3" ~common: "workspace:^" languageName: unknown linkType: soft @@ -14092,6 +14093,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.3": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 783216b1ad06776dda65c8d24f7e447fd7d129c83f0d2e795b2791d234e2b41b631f5520f8baa5e6e0f1624ffa56f363a20c5f309958cdba8d648cb3dff5d808 + languageName: node + linkType: hard + "semver@npm:~7.0.0": version: 7.0.0 resolution: "semver@npm:7.0.0"