Skip to content

Commit

Permalink
plan a charm file resource built by script called 'build-resource.sh'
Browse files Browse the repository at this point in the history
  • Loading branch information
addyess committed Oct 24, 2024
1 parent 5ffd7ba commit 37b68c5
Show file tree
Hide file tree
Showing 23 changed files with 15,763 additions and 12,201 deletions.
5,309 changes: 2,747 additions & 2,562 deletions dist/build/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/build/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/build/sourcemap-register.js

Large diffs are not rendered by default.

4,779 changes: 2,455 additions & 2,324 deletions dist/plan-integration/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plan-integration/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plan-integration/sourcemap-register.js

Large diffs are not rendered by default.

5,967 changes: 3,668 additions & 2,299 deletions dist/plan-scan/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plan-scan/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plan-scan/sourcemap-register.js

Large diffs are not rendered by default.

6,514 changes: 3,987 additions & 2,527 deletions dist/plan/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plan/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/plan/sourcemap-register.js

Large diffs are not rendered by default.

5,115 changes: 2,655 additions & 2,460 deletions dist/publish/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/publish/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/publish/sourcemap-register.js

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ interface BuildDockerImageParams {
token: string
}

async function buildFileResource(plan: BuildPlan): Promise<void> {
core.startGroup(`Build resource {plan.name}`)
await exec.exec('./build-resource.sh', [plan.name, plan.source_file], {
cwd: plan.source_directory
})
core.endGroup()
const resourceFiles = await (
await glob.create(path.join(plan.source_directory, plan.source_file))
).glob()
const artifact = new DefaultArtifactClient()
const manifestFile = path.join(plan.source_directory, 'manifest.json')
fs.writeFileSync(
manifestFile,
JSON.stringify(
{ name: plan.name, files: resourceFiles.map(f => path.basename(f)) },
null,
2
)
)
await artifact.uploadArtifact(
plan.output,
[...resourceFiles, manifestFile],
plan.source_directory
)
}

async function buildDockerImage({
plan,
user,
Expand Down Expand Up @@ -366,6 +392,8 @@ export async function run(): Promise<void> {
user: github.context.actor,
token: core.getInput('github-token')
})
case 'file':
await buildFileResource(plan)
}
} catch (error) {
// Fail the workflow run if an error occurs
Expand Down
9 changes: 8 additions & 1 deletion src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ export interface Plan {
}

export interface BuildPlan {
type: 'charm' | 'rock' | 'docker-image'
type: 'charm' | 'rock' | 'docker-image' | 'file'
name: string
source_file: string
source_directory: string
output_type: 'file' | 'registry'
output: string
}

export interface CharmResource {
type: 'file' | 'oci-image'
description?: string
filename?: string
'upstream-source'?: string
}
9 changes: 5 additions & 4 deletions src/plan-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@ export async function run(): Promise<void> {
const manifest = JSON.parse(
fs.readFileSync(path.join(tmp, 'manifest.json'), { encoding: 'utf-8' })
) as object
if (build.type === 'charm') {
if (build.type === 'charm' || build.type === 'file') {
// @ts-ignore
for (const file of manifest.files) {
fs.renameSync(
path.join(tmp, file),
path.join(plan.working_directory, file)
)
args.push(`--charm-file=./${file}`)
let argName: string =
build.type === 'charm' ? 'charm-file' : 'charm-resource'
args.push(`--${argName}=${plan.working_directory}/${file}`)
}
}
if (build.type === 'rock' || build.type == 'docker-image') {
} else if (build.type === 'rock' || build.type == 'docker-image') {
// @ts-ignore
const name = manifest.name as string
if ('files' in manifest) {
Expand Down
86 changes: 84 additions & 2 deletions src/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as path from 'path'
import * as yaml from 'js-yaml'
import * as fs from 'fs'
import * as github from '@actions/github'
import { Plan, BuildPlan } from './model'
import { Plan, BuildPlan, CharmResource } from './model'
import { DefaultArtifactClient } from '@actions/artifact'
import * as os from 'os'

Expand All @@ -34,6 +34,7 @@ async function planBuildCharm(
workingDir: string,
id: string
): Promise<BuildPlan[]> {
core.info(`Planning charms: ${workingDir}`)
const allCharmcraftFiles = await (
await glob.create(path.join(workingDir, '**', 'charmcraft.yaml'))
).glob()
Expand Down Expand Up @@ -82,6 +83,7 @@ async function planBuildRock(
id: string,
outputType: 'file' | 'registry'
): Promise<BuildPlan[]> {
core.info(`Planning rock image resources: ${workingDir}`)
const rockcraftFiles = await (
await glob.create(path.join(workingDir, '**', 'rockcraft.yaml'))
).glob()
Expand All @@ -108,6 +110,7 @@ async function planBuildDockerImage(
id: string,
outputType: 'file' | 'registry'
): Promise<BuildPlan[]> {
core.info(`Planning docker image resources: ${workingDir}`)
const dockerFiles = await (
await glob.create(path.join(workingDir, '**', '*.Dockerfile'))
).glob()
Expand All @@ -127,6 +130,84 @@ async function planBuildDockerImage(
})
}

async function planBuildFileResource(
workingDir: string,
id: string
): Promise<BuildPlan[]> {
core.info(`Planning build file resources: ${workingDir}`)
const allCharmcraftFiles = await (
await glob.create(path.join(workingDir, '**', 'charmcraft.yaml'))
).glob()
const charmcraftFiles = allCharmcraftFiles.filter(
file =>
!path.normalize(path.relative(workingDir, file)).startsWith('tests/')
)
return charmcraftFiles.flatMap((charmcraftFile: string) => {
const file = path.join(
workingDir,
path.relative(workingDir, charmcraftFile)
)
const charmcraft = yaml.load(
fs.readFileSync(charmcraftFile, { encoding: 'utf-8' })
) as object

let charmName: string
if ('name' in charmcraft) {
charmName = charmcraft['name'] as string
} else {
const metadataFile = path.join(
path.dirname(charmcraftFile),
'metadata.yaml'
)
const metadata = yaml.load(
fs.readFileSync(metadataFile, { encoding: 'utf-8' })
) as object
if (!('name' in metadata)) {
throw new Error(`unknown charm name (${workingDir})`)
}
charmName = metadata['name'] as string
}

let resources: Map<string, CharmResource> = new Map()
if ('resources' in charmcraft) {
resources = charmcraft['resources'] as Map<string, CharmResource>
} else {
const metadataFile = path.join(
path.dirname(charmcraftFile),
'metadata.yaml'
)
const metadata = yaml.load(
fs.readFileSync(metadataFile, { encoding: 'utf-8' })
) as object
if ('resources' in metadata) {
resources = metadata['resources'] as Map<string, CharmResource>
}
}
core.info(
`Processing: charm: ${charmName} resources: ${JSON.stringify(resources)}`
)

return Object.entries(resources).reduce(
(acc, [resourceName, resource]: [string, CharmResource]) => {
if (resource.type === 'file' && resource.filename) {
acc.push({
type: 'file',
name: resourceName,
source_file: resource.filename,
source_directory: path.dirname(file),
output_type: 'file',
output: sanitizeArtifactName(
`${id}__build__output__file__${charmName}__${resourceName}`
)
})
}
return acc
},
[] as BuildPlan[]
)
})
}

async function planBuild(
workingDir: string,
id: string,
Expand All @@ -135,7 +216,8 @@ async function planBuild(
return [
...(await planBuildCharm(workingDir, id)),
...(await planBuildRock(workingDir, id, imageOutputType)),
...(await planBuildDockerImage(workingDir, id, imageOutputType))
...(await planBuildDockerImage(workingDir, id, imageOutputType)),
...(await planBuildFileResource(workingDir, id))
]
}

Expand Down
99 changes: 88 additions & 11 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ class Publish {
throw new Error(`can't find plan artifact for workflow run ${runId}`)
}

async getImageResources(): Promise<string[]> {
async getCharmResources(): Promise<[string[], string[]]> {
interface Metadata {
resources?: {
[name: string]: {
type: string
filename?: string
'upstream-source'?: string
}
}
Expand Down Expand Up @@ -185,15 +186,69 @@ class Publish {
}
const resources = metadata.resources
if (resources === undefined) {
return []
return [[], []]
}
return Object.keys(resources).filter(
let images = Object.keys(resources).filter(
k => resources[k].type === 'oci-image' && !resources[k]['upstream-source']
)
let files = Object.keys(resources).filter(k => resources[k].type === 'file')
return [images, files]
}

async getFiles(): Promise<Map<string, string>> {
const [, resources] = await this.getCharmResources()
core.info(`required resources: ${resources}`)
const upload: Map<string, string> = new Map()
if (resources.length === 0) {
return upload
}
const runId = await this.findWorkflowRunId()
const plan = await this.getPlan(runId)
for (const build of plan.build) {
if (build.type === 'file') {
const resourceName = this.resourceMapping[build.name]
if (!resources.includes(resourceName)) {
core.info(`skip uploading file: ${build.name}`)
continue
}
const tmp = this.mkdtemp()
const artifact = await this.artifact.getArtifact(build.output, {
findBy: {
token: this.token,
repositoryOwner: github.context.repo.owner,
repositoryName: github.context.repo.repo,
workflowRunId: runId
}
})
await this.artifact.downloadArtifact(artifact.artifact.id, {
path: tmp,
findBy: {
token: this.token,
repositoryOwner: github.context.repo.owner,
repositoryName: github.context.repo.repo,
workflowRunId: runId
}
})
const manifest = JSON.parse(
fs.readFileSync(path.join(tmp, 'manifest.json'), {
encoding: 'utf-8'
})
)
const files = manifest.files as string[]
if (files.length !== 1) {
throw new Error(
`file resource ${build.name} contain multiple candidates: ${files}`
)
}
const file = files[0]
upload.set(resourceName, file)
}
}
return upload
}

async getImages(): Promise<Map<string, string>> {
const resources = await this.getImageResources()
const [resources] = await this.getCharmResources()
core.info(`required resources: ${resources}`)
const upload: Map<string, string> = new Map()
if (resources.length === 0) {
Expand All @@ -203,7 +258,7 @@ class Publish {
const plan = await this.getPlan(runId)
let dockerLogin = false
for (const build of plan.build) {
if (build.type === 'charm') {
if (build.type === 'charm' || build.type === 'file') {
continue
}
const resourceName = this.resourceMapping.hasOwnProperty(build.name)
Expand Down Expand Up @@ -346,7 +401,8 @@ class Publish {
async run() {
try {
core.startGroup('retrieve image info')
const images = await this.getImages()
const imageResources = await this.getImages()
const fileResources = await this.getFiles()
core.endGroup()
core.startGroup('retrieve charm info')
const {
Expand All @@ -355,15 +411,36 @@ class Publish {
files: charms
} = await this.getCharms()
core.endGroup()
core.info(
`start uploading image resources: ${JSON.stringify(Object.fromEntries([...images]))}`
)
for (const resource of images.keys()) {
if (fileResources.size !== 0) {
core.info(
`start uploading file resources: ${JSON.stringify(Object.fromEntries([...fileResources]))}`
)
}
for (const [resource, filePath] of fileResources) {
core.info(`upload resource ${resource}`)
await exec.exec(
'charmcraft',
[
'upload-resource',
charmName,
resource,
`--filepath=${filePath}`,
'--verbosity=brief'
],
{ env: { CHARMCRAFT_AUTH: this.charmhubToken } }
)
}
if (imageResources.size !== 0) {
core.info(
`start uploading image resources: ${JSON.stringify(Object.fromEntries([...imageResources]))}`
)
}
for (const [resource, image] of imageResources) {
core.info(`upload resource ${resource}`)
const imageId = (
await exec.getExecOutput('docker', [
'images',
images.get(resource) as string,
image,
'--format',
'{{.ID}}'
])
Expand Down
20 changes: 20 additions & 0 deletions tests/workflows/integration/test-upload-charm/build-resource.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

set -eu

function build_resource() {
local RESOURCE=$1
local RESOURCE_OUTPUT=$2

case $RESOURCE in
test-file)
touch "$RESOURCE_OUTPUT"
;;
*)
echo "Unsupported resource: $RESOURCE"
exit 1
;;
esac
}

build_resource "$1" "$2"
4 changes: 4 additions & 0 deletions tests/workflows/integration/test-upload-charm/metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ resources:
test-image:
type: oci-image
description: Test image
test-file:
type: file
description: Test file
filename: test-file.txt
Loading

0 comments on commit 37b68c5

Please sign in to comment.