11---
22name : " Release Helm Chart"
3- description : " Action to release a Helm chart to OCI registry"
3+ description : |
4+ Action to release a Helm chart to OCI registry.
5+ Supports umbrella charts: if a chart has local dependencies having version 0.0.0,
6+ the action will update those dependencies version with the given tag, then update the Chart.lock accordingly.
47branding :
58 icon : upload-cloud
69 color : gray-dark
@@ -70,29 +73,41 @@ runs:
7073 const path = require('node:path');
7174
7275 const yqUpdates = {};
76+ const basePath = `${{ inputs.path }}`;
77+ if (!basePath) {
78+ throw new Error(`"path" input is missing`);
79+ }
80+
81+ const tag = `${{ inputs.tag }}`;
82+ if (!tag) {
83+ throw new Error(`"tag" input is missing`);
84+ }
7385
7486 // Chart.yml files
75- const globber = await glob.create(`${{ inputs.path } }/**/Chart.yaml`, {followSymbolicLinks: false})
87+ const globber = await glob.create(`${basePath }/**/Chart.yaml`, {followSymbolicLinks: false})
7688 for await (const chartFile of globber.globGenerator()) {
7789 const filePath = path.relative(`${{ github.workspace }}`, chartFile);
7890 if (!yqUpdates[filePath]) {
7991 yqUpdates[filePath] = [];
8092 }
8193
82- const isRootChart = filePath === `${{ inputs.path } }/Chart.yaml`;
94+ const isRootChart = filePath === `${basePath }/Chart.yaml`;
8395 if (isRootChart) {
8496 // Update name for root chart
8597 yqUpdates[filePath].push(`.name = "${{ github.event.repository.name }}"`);
8698
87- // Update version fields
88- yqUpdates[filePath].push(`.version = "${{ inputs.tag }}"`);
89- yqUpdates[filePath].push(`.appVersion = "${{ inputs.tag }}"`);
99+ // Update dependencies version where repository starts with file://
100+ yqUpdates[filePath].push(`(.dependencies[] | select(.repository == "file://*")).version = "${tag}"`);
90101 }
102+
103+ // Update version fields
104+ yqUpdates[filePath].push(`.version = "${tag}"`);
105+ yqUpdates[filePath].push(`.appVersion = "${tag}"`);
91106 }
92107
93108 // values.yml files
94109 const chartValuesInput = `${{ inputs.values }}`;
95- if(chartValuesInput) {
110+ if (chartValuesInput) {
96111
97112 // Check if is valid Json
98113 let chartValues = null;
@@ -125,7 +140,7 @@ runs:
125140 }
126141
127142 const valueFilePath = chartValue['file'] ? chartValue['file'] : defaultValuesPath;
128- const filePath = `${{ inputs.path } }/${valueFilePath}`;
143+ const filePath = `${basePath }/${valueFilePath}`;
129144
130145 if (!yqUpdates[filePath]) {
131146 yqUpdates[filePath] = [];
@@ -148,6 +163,105 @@ runs:
148163 cmd : |
149164 ${{ steps.chart-values-updates.outputs.yq-command }}
150165
166+ - uses : actions/setup-node@v4
167+
168+ - shell : bash
169+ run : npm install yaml
170+
171+ - name : Rewrite the Chart.lock to match with updated ombrella dependencies if any
172+ 173+ with :
174+ script : |
175+ const fs = require('node:fs');
176+ const path = require('node:path');
177+ const crypto = require('node:crypto');
178+ const yaml = require("yaml");
179+
180+ const chartLockFile = `${{ inputs.path }}/Chart.lock`;
181+ const chartLockFileContent = yaml.parse(fs.readFileSync(chartLockFile, 'utf8'));
182+
183+ // Update ombrella dependencies versions
184+ let hasLocalDependencies = false;
185+ const dependencies = chartLockFileContent.dependencies;
186+
187+ // Check if dependencies are empt
188+ for (const dependency of dependencies) {
189+ const isLocalDependency = dependency.repository.startsWith("file://") && dependency.version === "0.0.0";
190+
191+ // Check if the dependency is a local file
192+ if (isLocalDependency) {
193+ // Update the version to the tag
194+ dependency.version = `${{ inputs.tag }}`;
195+ hasLocalDependencies = true;
196+ }
197+ }
198+
199+ // If no local dependencies, exit
200+ if (!hasLocalDependencies) {
201+ return;
202+ }
203+
204+ // Update generated
205+ chartLockFileContent.generated = new Date().toISOString();
206+
207+ // Update global digest.
208+
209+ // See Helm hashReq function: https://github.com/helm/helm/blob/99c065789ef8c45bade24d4bc2d33432595de956/internal/resolver/resolver.go#L214
210+ function hashReq(req, lock) {
211+ // Sort the dependencies
212+ req = req.map(sortDependencyFields);
213+ lock = lock.map(sortDependencyFields);
214+
215+ const data = JSON.stringify([req, lock]);
216+ const hash = digest(data);
217+ return `sha256:${hash}`;
218+ }
219+
220+ function digest(input) {
221+ const hash = crypto.createHash('sha256');
222+ hash.update(input);
223+ return hash.digest('hex');
224+ }
225+
226+ // Should respect the Helm struct order
227+ // See https://github.com/helm/helm/blob/99c065789ef8c45bade24d4bc2d33432595de956/pkg/chart/v2/dependency.go#L24
228+ function sortDependencyFields(dependency) {
229+ const fieldOrder = [
230+ 'name',
231+ 'version',
232+ 'repository',
233+ 'condition',
234+ 'tags',
235+ 'enabled',
236+ 'import-values',
237+ 'alias',
238+ ];
239+ // Sort the dependency fields
240+ const sortedDependency = {};
241+ for (const field of fieldOrder) {
242+ if (dependency.hasOwnProperty(field)) {
243+ sortedDependency[field] = dependency[field];
244+ }
245+ }
246+
247+ return sortedDependency;
248+ }
249+
250+ const rootChartFile = `${{ inputs.path }}/Chart.yaml`;
251+ const rootChartFileContent = yaml.parse(fs.readFileSync(rootChartFile, 'utf8'));
252+
253+ const req = rootChartFileContent.dependencies;
254+ const lock = dependencies;
255+
256+ const hash = hashReq(req, lock);
257+ chartLockFileContent.digest = hash;
258+
259+ const updatedChartLockFileContent = yaml.stringify(chartLockFileContent);
260+ core.debug(`Updated Chart.lock file content:\n${updatedChartLockFileContent}`);
261+
262+ // Update Chart.lock file
263+ fs.writeFileSync(chartLockFile, updatedChartLockFileContent, 'utf8');
264+
151265 - uses : azure/setup-helm@v4
152266
153267 - shell : bash
@@ -196,4 +310,4 @@ runs:
196310 registry : ${{ inputs.oci-registry }}
197311 registry_username : ${{ inputs.oci-registry-username }}
198312 registry_password : ${{ inputs.oci-registry-password }}
199- update_dependencies : " true"
313+ update_dependencies : " true" # FIXME: Should be false, because updating dependencies during release is risky
0 commit comments