Skip to content

Commit 5a55a9c

Browse files
committed
feat(helm/release-chart): update version for umbrella chart children
Signed-off-by: Emilien Escalle <[email protected]>
1 parent 25f0237 commit 5a55a9c

File tree

1 file changed

+123
-9
lines changed

1 file changed

+123
-9
lines changed

actions/helm/release-chart/action.yml

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
---
22
name: "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.
47
branding:
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+
uses: actions/[email protected]
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

Comments
 (0)