Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d6528d4
fix: eslint update related fixes
yanksyoon Feb 12, 2026
e7fa704
docs: changelog
yanksyoon Feb 12, 2026
9e52435
Revert "fix: eslint update related fixes"
yanksyoon Feb 12, 2026
184a664
fix: eslint errors
yanksyoon Feb 12, 2026
e90a5a8
fix: update bundler to ESM
yanksyoon Feb 12, 2026
a5709cf
fix: minor syntax issues
yanksyoon Feb 12, 2026
e26340f
chore: build dist
yanksyoon Feb 12, 2026
0fc2d1b
ci: add testing
yanksyoon Feb 12, 2026
1f6a89e
ci: build changes diff
yanksyoon Feb 12, 2026
15b10aa
ci: add copyright
yanksyoon Feb 12, 2026
1c88e06
ci: build w/ matching node version
yanksyoon Feb 12, 2026
3da4407
ci: remove test (no unit tests)
yanksyoon Feb 12, 2026
ccdb62b
docs: update changelog
yanksyoon Feb 12, 2026
78c25ca
chore: replace ncc w/ esbuild
yanksyoon Feb 13, 2026
922d1d8
chore: build
yanksyoon Feb 13, 2026
998e8a7
fix: newline
yanksyoon Feb 13, 2026
eb33e7f
fix: eslint warnings
yanksyoon Feb 13, 2026
4f39fe7
fix: minor issues
yanksyoon Feb 13, 2026
b670f73
ci: fixes
yanksyoon Feb 13, 2026
e01f7a2
Merge branch 'main' into fix/eslint-fixes
yanksyoon Feb 17, 2026
3525c6c
chore: update eslint build files
yanksyoon Feb 17, 2026
416e300
fix: fix archive conversion to avoid corrupt layers in Trivy
yanksyoon Feb 17, 2026
b2531d3
fix: update conditionals in integration_test.yaml for better syntax c…
yanksyoon Feb 17, 2026
bbfe241
Merge branch 'main' into fix/eslint-fixes
yanksyoon Feb 17, 2026
e5e93e6
Revert "fix: update conditionals in integration_test.yaml for better …
yanksyoon Feb 17, 2026
c3bc173
Revert "fix: fix archive conversion to avoid corrupt layers in Trivy"
yanksyoon Feb 17, 2026
d83238b
Merge remote-tracking branch 'origin' into fix/eslint-fixes
yanksyoon Feb 22, 2026
f9b0ad2
ci: debug
yanksyoon Feb 22, 2026
910c875
ci: remove debug
yanksyoon Feb 22, 2026
3979144
feat: aggregate charm artifacts from workflow run
yanksyoon Feb 22, 2026
0b0aa59
chore: build publish workflow
yanksyoon Feb 22, 2026
da425df
chore: merge main
yanksyoon Feb 23, 2026
32da87d
test: multiple artifact charm
yanksyoon Feb 23, 2026
48cb0f9
Merge branch 'main' into feat/publish-multi-artifact-charm
yanksyoon Feb 23, 2026
d5b2167
Merge branch 'main' into feat/publish-multi-artifact-charm
yanksyoon Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/workflow_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
provider: microk8s
with-uv: true
integration-artifact:
strategy:
matrix:
runtime: [amd64, arm64]
uses: ./.github/workflows/integration_test.yaml
secrets: inherit
with:
Expand All @@ -31,6 +34,10 @@ jobs:
upload-image: artifact
microk8s-addons: "dns ingress rbac storage registry"
with-uv: true
builder-runner-label: ${{ matrix.runtime }}
self-hosted-runner: true
self-hosted-runner-arch: ${{ matrix.runtime }}
self-hosted-runner-label: ${{ matrix.runtime }}
integration-self-hosted:
uses: ./.github/workflows/integration_test.yaml
secrets: inherit
Expand Down
3 changes: 3 additions & 0 deletions internal/publish/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ inputs:
github-token:
description: github-token.
required: true
integration-workflow-file:
description: Workflow file path or numeric ID used to discover integration runs (optional)
required: false
plan:
description: operator-workflows plan.
required: true
Expand Down
113 changes: 111 additions & 2 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ class Publish {
private artifact
private workingDir: string
private resourceMapping: { [key: string]: string }
private integrationWorkflowFile: string
private octokit

constructor() {
this.token = core.getInput('github-token')
this.charmhubToken = core.getInput('charmhub-token')
this.workingDir = core.getInput('working-directory')
this.resourceMapping = JSON.parse(core.getInput('resource-mapping'))
this.integrationWorkflowFile = core.getInput('integration-workflow-file')
this.artifact = new DefaultArtifactClient()
this.octokit = github.getOctokit(this.token)
}

async getCharmResources(): Promise<[string[], string[]]> {
Expand Down Expand Up @@ -321,8 +325,22 @@ class Publish {
const {
name: charmName,
dir: charmDir,
files: charms
files: baseCharms
} = await this.getCharms(plan, runId)
// Aggregate charms from all successful integration workflow runs for current commit
const aggregatedCharms = await this.aggregateCharmsAcrossRuns()
// Deduplicate by filename
const seen = new Set<string>()
const finalCharms: string[] = []
for (const f of [...baseCharms, ...aggregatedCharms]) {
const name = path.basename(f)
if (seen.has(name)) {
core.info(`skip duplicate charm: ${name}`)
continue
}
seen.add(name)
finalCharms.push(f)
}
core.endGroup()
if (fileResources.size !== 0) {
core.info(
Expand Down Expand Up @@ -370,7 +388,7 @@ class Publish {
{ env: { ...process.env, CHARMCRAFT_AUTH: this.charmhubToken } }
)
}
core.setOutput('charms', charms.join(','))
core.setOutput('charms', finalCharms.join(','))
core.setOutput('charm-directory', charmDir)
} catch (error) {
// Fail the workflow run if an error occurs
Expand All @@ -380,6 +398,97 @@ class Publish {
}
}
}

private async aggregateCharmsAcrossRuns(): Promise<string[]> {
try {
const owner = github.context.repo.owner
const repo = github.context.repo.repo
// If no workflow is provided, skip aggregation for backwards compatibility
if (!this.integrationWorkflowFile) {
core.info(
'Integration workflow not provided; skipping charm aggregation'
)
return []
}
// GitHub API accepts workflow_id as either numeric ID or workflow file name (basename)
const trimmed = this.integrationWorkflowFile.trim()
const workflowId: number | string = /^[0-9]+$/.test(trimmed)
? Number(trimmed)
: path.basename(trimmed)
// List successful runs of the workflow
const runs = await this.octokit.paginate(
this.octokit.rest.actions.listWorkflowRuns,
{
owner,
repo,
workflow_id: workflowId,
per_page: 100,
status: 'success'
}
)
const matchingRuns = runs.filter(
r => r.head_sha === github.context.sha && r.conclusion === 'success'
)
if (matchingRuns.length === 0) {
core.info('No successful integration runs found to aggregate charms')
return []
}
const aggregated: string[] = []
for (const run of matchingRuns) {
core.info(`Inspecting artifacts from run ${run.id}`)
const artifacts = await this.octokit.paginate(
this.octokit.rest.actions.listWorkflowRunArtifacts,
{ owner, repo, run_id: run.id, per_page: 100 }
)
for (const art of artifacts) {
// Download each artifact and scan for .charm files
const tmp = mkdtemp()
try {
await this.artifact.downloadArtifact(art.id, {
path: tmp,
findBy: {
token: this.token,
repositoryOwner: owner,
repositoryName: repo,
workflowRunId: run.id
}
})
const charms = this.findCharmFiles(tmp)
for (const c of charms) {
aggregated.push(c)
core.info(`Found charm in run ${run.id}: ${path.basename(c)}`)
}
} catch (e) {
core.info(
`Failed downloading artifact ${art.name} from run ${run.id}: ${String(e)}`
)
}
}
}
return aggregated
} catch (e) {
core.info(`Charm aggregation failed: ${String(e)}`)
return []
}
}

private findCharmFiles(root: string): string[] {
const results: string[] = []
const stack: string[] = [root]
while (stack.length) {
const dir = stack.pop() as string
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const ent of entries) {
const p = path.join(dir, ent.name)
if (ent.isDirectory()) {
stack.push(p)
} else if (ent.isFile() && ent.name.endsWith('.charm')) {
results.push(p)
}
}
}
return results
}
}

new Publish().run()
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ type: charm
config:
options: {}
platforms:
ubuntu@22.04:amd64:
amd64:
arm64:
containers:
test:
resource: test-image
Expand Down
Loading