From 718cf56b2b84c01e13d46cf7ff8a81e38de2563b Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Mon, 26 Sep 2022 20:04:16 -0300 Subject: [PATCH] WIP: Add option to use balena deploy as command Change-type: minor --- README.md | 41 +++++++++++++++++---------------- action.yml | 10 +++++--- src/action.ts | 4 +++- src/balena-utils.ts | 56 ++++++++++++++++++++++++++++++++++----------- src/main.ts | 1 + src/types.ts | 1 + 6 files changed, 76 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e0dbdea6..120191d7 100644 --- a/README.md +++ b/README.md @@ -32,19 +32,20 @@ jobs: Inputs are provided using the `with:` section of your workflow YML file. -| key | Description | Required | Default | -| --- | --- | --- | --- | -| balena_token | API key to balenaCloud | true | | -| fleet | The slug of the fleet (eg: `my_org/sample_fleet`) for which the release is for | true | | -| environment | Domain of API hosting your fleets | false | balena-cloud.com | -| cache | If a release matching the commit already exists do not build again | false | true | -| versionbot | Tells action to use Versionbot branch for versioning | false | false | -| create_tag | Create a tag on the git commit with the final release version | false | false | -| source | Specify a source directory (for `Dockerfile.template` or `docker-compose.yml`) | false | root directory | -| layer_cache | Use cached layers of previously built images for this project | false | true | -| registry_secrets | JSON string containing image registry credentials used to pull base images | false | | - -`balena_token` and other tokens needs to be stored in GitHub as an [encrypted secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) that GitHub Actions can access. +| key | Description | Required | Default | +| ---------------- | ------------------------------------------------------------------------------ | -------- | ---------------- | +| balena_token | API key to balenaCloud | true | | +| fleet | The slug of the fleet (eg: `my_org/sample_fleet`) for which the release is for | true | | +| environment | Domain of API hosting your fleets | false | balena-cloud.com | +| cache | If a release matching the commit already exists do not build again | false | true | +| versionbot | Tells action to use Versionbot branch for versioning | false | false | +| create_tag | Create a tag on the git commit with the final release version | false | false | +| source | Specify a source directory (for `Dockerfile.template` or `docker-compose.yml`) | false | root directory | +| layer_cache | Use cached layers of previously built images for this project | false | true | +| registry_secrets | JSON string containing image registry credentials used to pull base images | false | | +| use_deploy | Use `balena deploy` instead of `balena push` for creating the release | false | | + +`balena_token` and other tokens needs to be stored in GitHub as an [encrypted secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) that GitHub Actions can access. `environment` can be used to specify a custom domain for the backend that will build and deploy your release. If for example you want to deploy to staging environment, you would set it to `balena-staging.com` or if you run your own instance of balenaCloud such as openBalena then specify your domain here. @@ -57,18 +58,18 @@ Inputs are provided using the `with:` section of your workflow YML file. "username": "${{ secrets.REGISTRY_USER }}", "password": "${{ secrets.REGISTRY_PASS }}" } - } + } ``` ## Outputs -| key | Description | Nullable | -| --- | --- | --- | -| release_id | ID of the release built | true | -| version | Version of the release built | true | +| key | Description | Nullable | +| ---------- | ---------------------------- | -------- | +| release_id | ID of the release built | true | +| version | Version of the release built | true | The `release_id` output could be null because the action might just finalize previously built releases. - + ## Workflows This action is leveraging the `is_final` trait of a release to enable you to develop releases in a way that make it easier to test. @@ -96,7 +97,7 @@ on: ### Additional comments about workflows -If you need to build a release for multiple fleets across several environments (balena-cloud.com, balena-staging.com, etc) you can create multiple workflow files for each environment and use a matrix to pass a list of fleet names into 1 job. See how Balena's Supervisor does this with the [staging deployment workflow](https://github.com/balena-os/balena-supervisor/blob/caf3c1fd5867c127346058742cfa4864e9072313/.github/workflows/staging-balena-ci.yml). +If you need to build a release for multiple fleets across several environments (balena-cloud.com, balena-staging.com, etc) you can create multiple workflow files for each environment and use a matrix to pass a list of fleet names into 1 job. See how Balena's Supervisor does this with the [staging deployment workflow](https://github.com/balena-os/balena-supervisor/blob/caf3c1fd5867c127346058742cfa4864e9072313/.github/workflows/staging-balena-ci.yml). ## Development diff --git a/action.yml b/action.yml index 623b555f..14a35313 100644 --- a/action.yml +++ b/action.yml @@ -2,7 +2,7 @@ name: 'Deploy to Balena' description: 'Automate release builds and deployments' branding: - icon: 'code' + icon: 'code' color: 'blue' inputs: balena_token: @@ -46,11 +46,15 @@ inputs: layer_cache: description: 'Use cached layers of previously built images for this project' required: false - default: true + default: true registry_secrets: description: 'Image registry credentrials (Balena secrets.json)' required: false - default: '' + default: '' + use_deploy: + description: 'Use `balena deploy` instead of `balena push` for creating the new release.' + required: false + default: false outputs: release_id: description: 'ID of the release built' diff --git a/src/action.ts b/src/action.ts index 2286302a..f1e6645a 100644 --- a/src/action.ts +++ b/src/action.ts @@ -124,9 +124,11 @@ export async function run( }; } + const operation = inputs.useDeploy ? balena.deploy : balena.push; + // Finally send source to builders try { - releaseId = await balena.push(inputs.fleet, inputs.source, inputs.cache, { + releaseId = await operation(inputs.fleet, inputs.source, inputs.cache, { ...buildOptions, noCache: inputs.layerCache === false, }); diff --git a/src/balena-utils.ts b/src/balena-utils.ts index 91f1d907..e9f7e8a4 100644 --- a/src/balena-utils.ts +++ b/src/balena-utils.ts @@ -40,10 +40,11 @@ export async function init(endpoint: string, token: string) { await sdk.auth.loginWithToken(token); } -export async function push( +async function pushOrDeploy( fleet: string, source: string, useCache: boolean, + useDeploy = false, options: Partial, ): Promise { if (process.env.GITHUB_ACTIONS === 'false') { @@ -77,12 +78,13 @@ export async function push( } } - const pushOpt = [ - 'push', + const cliOpts = [ + useDeploy ? 'deploy' : 'push', fleet, '--source', source, '--release-tag', + ...(useDeploy ? ['--projectName', fleet] : []), ...Object.entries(buildOpt.tags).flatMap(([key, value]) => [ TagKeyMap[key as keyof typeof TagKeyMap], typeof value === 'string' && value.includes(' ') @@ -92,25 +94,25 @@ export async function push( ]; if (buildOpt.draft) { - pushOpt.push('--draft'); + cliOpts.push('--draft'); } if (buildOpt.noCache) { - pushOpt.push('--nocache'); + cliOpts.push('--nocache'); } let releaseId: string | null = null; return new Promise((resolve, reject) => { - core.debug(`balena ${pushOpt.join(' ')}`); + core.debug(`balena ${cliOpts.join(' ')}`); - const buildProcess = spawn('balena', pushOpt, { + const cliProcess = spawn('balena', cliOpts, { stdio: 'pipe', }); - buildProcess.stdout.setEncoding('utf8'); + cliProcess.stdout.setEncoding('utf8'); - buildProcess.stdout.on('data', (data: Buffer) => { + cliProcess.stdout.on('data', (data: Buffer) => { const msg = stripAnsi(data.toString()); // Ignore logging messages if they are progress bar lines since they don't display correctly if (!isProgressBar(msg) && !isEmptyCharacter(msg)) { @@ -122,19 +124,19 @@ export async function push( } }); - buildProcess.stderr.on('data', (data: Buffer) => { + cliProcess.stderr.on('data', (data: Buffer) => { core.error(stripAnsi(data.toString())); }); process.on('SIGTERM', () => { - buildProcess.kill('SIGINT'); + cliProcess.kill('SIGINT'); }); process.on('SIGINT', () => { - buildProcess.kill('SIGINT'); + cliProcess.kill('SIGINT'); }); - buildProcess.on('exit', (code: number) => { + cliProcess.on('exit', (code: number) => { if (code !== 0) { return reject('Build process returned non-0 exit code'); } @@ -148,6 +150,34 @@ export async function push( }); } +export async function push( + fleet: string, + source: string, + useCache: boolean, + options: Partial, +): Promise { + return pushOrDeploy(fleet, source, useCache, false, options); +} + +export async function deploy( + fleet: string, + source: string, + useCache: boolean, + options: Partial, +): Promise { + // TODO: Parse docker compose + // for services with `build`, look for a `_:` image or a `_:latest` + // image, where `` is the architecture deduced from the fleet. It should look for both + // balena architecture ids and docker architecture ids: e.g. `armv7hf` and `armv7` are valid tags + + // TODO: use dockerode to look for the images in the local context, if they don't exist + // throw an error + + // TODO: If different than `latest`, pass the `--tag` argument to deploy + + return pushOrDeploy(fleet, source, useCache, true, options); +} + export async function getReleaseByTags( fleet: string, tags: Tags, diff --git a/src/main.ts b/src/main.ts index abd0fa09..674cac4b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,6 +24,7 @@ const inputs: Inputs = { source: join(WORKSPACE, core.getInput('source', { required: false })), githubToken: core.getInput('github_token', { required: false }), layerCache: core.getBooleanInput('layer_cache', { required: false }), + useDeploy: core.getBooleanInput('use_deploy', { required: false }), }; (async () => { diff --git a/src/types.ts b/src/types.ts index 5f389358..eedd8226 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,7 @@ export type Inputs = { versionbot: boolean; createTag: boolean; layerCache: boolean; + useDeploy: boolean; }; export type RepoContext = {