Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: gh nodejs build #76

Merged
merged 3 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:21-alpine
FROM node:22-alpine

ARG APP=app
ARG HOME=/home/node
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ You will need to install one of the following. Either can run the composer using
* [Podman](https://podman.io)
* [Docker](https://www.docker.com)

It is recommended that users running Windows install and run the command using node or use Podman. Docker has a known issue with modifying file permissions correctly on a mounted volumes. The tool needs to set the permission on things like bash scripts. As such, the commands will not run correctly on Windows with Docker.
It is recommended that Windows users install and run the command using Node.js or Podman. Docker has a known issue with correctly modifying file permissions on mounted volumes. Since the tool needs to set permissions for files like bash scripts, commands will not execute properly on Windows when using Docker.

## Run using a local install

You will need to install node and clone this repository. You can checkout a version tag (vx.x.x) to run a specific release.

* [Node 20](https://nodejs.org/en)
* [Node 22](https://nodejs.org/en)

The tool is build using [Yeoman](http://yeoman.io) which is a JavaScript library. You do not need to install Yoeman.

Expand Down Expand Up @@ -73,6 +73,12 @@ The generator, gh-maven-build, generates the CI workflow and NR Broker intention

The generated files will appear in your .github/workflows and .jenkins directories.

### Github Node.js Build: gh-nodejs-build

The generator, gh-nodejs-build, generates the CI workflow and NR Broker intention files for building Node.js in GitHub. The workflow assume that your `package.json` has a `build` command and your build places the files in `./dist`.

The generated files will appear in your .github/workflows and .jenkins directories.

## Command Options

### Skip prompts (--promptless)
Expand Down
2 changes: 1 addition & 1 deletion generators/gh-maven-build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default class extends Generator {
{
type: 'checkbox',
name: 'skip',
message: `Do not prompt for values set in ${BACKSTAGE_FILENAME}:`,
message: `Skip prompts for values found in Backstage file (${BACKSTAGE_FILENAME}):`,
choices: ['yes', 'no'],
default: 'yes',
store: true,
Expand Down
11 changes: 9 additions & 2 deletions generators/gh-maven-build/templates/build-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
broker_jwt: ${{ secrets.<%= brokerJwt %> }}
intention_path: intention.json
quickstart: true
- name: Set intention ID for deployment job
- name: Echo intention ID for deploy job
id: set-intention-id
run: |
echo "intention_id=${INTENTION_ID}" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -163,6 +163,13 @@ jobs:
BUILD_TOKEN: ${{ env.ACTION_TOKEN_BUILD }}
<% } -%>
- name: Close intention
uses: bcgov-nr/action-broker-intention-close@v1
if: ${{ success() && env.INTENTION_TOKEN != '' }}
uses: bcgov-nr/action-broker-intention-close@v3
with:
intention_token: ${{ env.INTENTION_TOKEN }}
- name: Close intention (Failure)
if: ${{ failure() && env.INTENTION_TOKEN != '' }}
uses: bcgov-nr/action-broker-intention-close@v3
with:
intention_token: ${{ env.INTENTION_TOKEN }}
outcome: failure
329 changes: 329 additions & 0 deletions generators/gh-nodejs-build/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
'use strict';
import * as fs from 'fs';
import Generator from 'yeoman-generator';
import yosay from 'yosay';
import chalk from 'chalk';
import { Document, parseDocument } from 'yaml';
import {
BACKSTAGE_FILENAME,
pathToProps,
extractFromYaml,
generateSetAnswerPropPredicate,
writePropToPath,
} from '../util/yaml.js';

/**
* Generate the CI workflow and NR Broker intention files needed for Java/Tomcat Maven builds in GitHub
*/
export default class extends Generator {
constructor(args, opts) {
super(args, opts);

this.option('promptless', {
type: String,
required: false,
description: 'Run headless with no user prompts',
});
}

async initializing() {
const backstagePath = this.destinationPath(BACKSTAGE_FILENAME);
if (fs.existsSync(backstagePath)) {
const backstageYaml = fs.readFileSync(backstagePath, 'utf8');
this.backstageDoc = parseDocument(backstageYaml);
} else {
this.backstageDoc = new Document();
}
}

async prompting() {
const promptless = !!this.options.promptless;
this.answers = extractFromYaml(this.backstageDoc, pathToProps);
console.log(this.answers);

if (!promptless) {
this.log(
yosay(
'Welcome to the GitHub workflow and NR Broker intention file generator!',
),
);

this.log(chalk.bold('Usage'));
this.log('');
this.log(
' ' +
chalk.bold('Project: ') +
chalk.dim(
'Lowercase kebab-case name that uniquely identifies the project',
),
);
this.log(' ' + chalk.dim('Example: super-project'));
this.log(
' ' +
chalk.bold('Service: ') +
chalk.dim(
'Lowercase kebab-case name that uniquely indentifies the service',
),
);
this.log(
' ' +
chalk.dim(
'Should start with project, have an optional descriptor and end with an artifact identifier',
),
);
this.log(
' ' + chalk.dim('Example: super-project-backend-war'),
);
this.log(
' ' +
chalk.bold('Client ID: ') +
chalk.dim(
'The client id of the Broker account to use. Leave blank to use manually set BROKER_JWT secret.',
),
);
this.log(
' ' +
chalk.bold('Artifactory: ') +
chalk.dim('The OCP Artifactory namespace this will be published to'),
);
this.log(
' ' +
chalk.bold('Root: ') +
chalk.dim(
"The path to where your build is located relative to the repository's root",
),
);
this.log(
' ' +
chalk.bold('GitHub Owner with repo path: ') +
chalk.dim(
'The Github owner with repo path (e.g. bcgov-nr/edqa-war) ',
),
);
this.log('');

this.log(chalk.bold('Prompts'));
this.log('');
}

const backstageAnswer = promptless
? { skip: true }
: await this.prompt([
{
type: 'checkbox',
name: 'skip',
message: `Skip prompts for values found in Backstage file (${BACKSTAGE_FILENAME}):`,
choices: ['yes', 'no'],
default: 'yes',
store: true,
},
]);

this.answers = {
...this.answers,
...(await this.prompt(
[
{
type: 'input',
name: 'projectName',
message: 'Project:',
store: true,
},
{
type: 'input',
name: 'serviceName',
message: 'Service:',
store: true,
},
{
type: 'input',
name: 'clientId',
message: 'Client ID:',
default: '',
store: true,
},
{
type: 'input',
name: 'unitTestsPath',
message: 'Path to unit tests (./.github/workflows/test.yaml):',
default: '',
store: true,
},
{
type: 'confirm',
name: 'gitHubPackages',
message: 'Publish to GitHub Packages:',
default: false,
store: true,
},
{
type: 'input',
name: 'gitHubOwnerPack',
message: 'GitHub Owner with repo path (e.g. bcgov-nr/results-war):',
store: true,
when: (answers) => answers.gitHubPackages,
},
{
type: 'input',
name: 'artifactoryProject',
message: 'Artifactory:',
default: 'cc20',
store: true,
when: (answers) => !answers.gitHubPackages,
},
{
type: 'input',
name: 'artifactoryPackageType',
message: 'Artifactory Package Type (maven, ivy, npm):',
default: 'maven',
store: true,
when: (answers) => !answers.gitHubPackages,
},
{
type: 'confirm',
name: 'deployOnPrem',
message: 'Deploy on-prem:',
default: false,
store: true,
},
{
type: 'input',
name: 'playbookPath',
message: 'Playbook path:',
default: 'playbooks',
store: true,
when: (answers) => answers.deployOnPrem,
},
{
type: 'input',
name: 'tomcatContext',
message: 'Tomcat Context (e.g. ext#results):',
store: true,
when: (answers) => answers.deployOnPrem,
},
{
type: 'confirm',
name: 'useAltAppDirName',
message: 'Use alternative webapp directory:',
default: false,
store: true,
when: (answers) => answers.deployOnPrem,
},
{
type: 'input',
name: 'altAppDirName',
message: 'Alternative webapp directory name:',
store: true,
when: (answers) => answers.useAltAppDirName,
},
{
type: 'confirm',
name: 'addWebadeConfig',
message: 'Add Webade configuration:',
default: false,
store: true,
when: (answers) => answers.deployOnPrem,
},
].filter(
generateSetAnswerPropPredicate(
this.answers,
backstageAnswer.skip === 'yes',
),
),
)),
};
}

async configuring() {
// this.config.save();
}

// Generate GitHub workflows and NR Broker intention files
writingWorkflow() {
const brokerJwt = this.answers.clientId.trim()
? `broker-jwt:${this.answers.clientId.trim()}`.replace(
/[^a-zA-Z0-9_]/g,
'_',
)
: 'BROKER_JWT';
this.fs.copyTpl(
this.templatePath('build-release.yaml'),
this.destinationPath('.github/workflows/build-release.yaml'),
{
projectName: this.answers.projectName,
serviceName: this.answers.serviceName,
brokerJwt,
artifactoryProject: this.answers.artifactoryProject,
pomRoot: this.answers.pomRoot,
unitTestsPath: this.answers.unitTestsPath,
gitHubPackages: this.answers.gitHubPackages,
artifactoryPackageType: this.answers.artifactoryPackageType,
gitHubOwnerPack: this.answers.gitHubOwnerPack,
},
);
this.fs.copyTpl(
this.templatePath('build-intention.json'),
this.destinationPath('.github/workflows/build-intention.json'),
{
projectName: this.answers.projectName,
serviceName: this.answers.serviceName,
},
);
this.fs.copyTpl(
this.templatePath('build-intention.sh'),
this.destinationPath('.github/workflows/build-intention.sh'),
);
this.fs.copyTpl(
this.templatePath('check-token.yaml'),
this.destinationPath('.github/workflows/check-token.yaml'),
);
if (this.answers.deployOnPrem) {
this.fs.copyTpl(
this.templatePath('deploy.yaml'),
this.destinationPath('.github/workflows/deploy.yaml'),
{
projectName: this.answers.projectName,
serviceName: this.answers.serviceName,
brokerJwt,
artifactoryProject: this.answers.artifactoryProject,
pomRoot: this.answers.pomRoot,
gitHubPackages: this.answers.gitHubPackages,
artifactoryPackageType: this.answers.artifactoryPackageType,
gitHubOwnerPack: this.answers.gitHubOwnerPack,
},
);
this.fs.copyTpl(
this.templatePath('deployment-intention.json'),
this.destinationPath('.jenkins/deployment-intention.json'),
{
projectName: this.answers.projectName,
serviceName: this.answers.serviceName,
},
);
const playbook_args = [
this.answers.projectName,
this.answers.serviceName,
this.answers.playbookPath,
this.answers.tomcatContext,
this.answers.altAppDirName,
];
const playbook_options = {
addWebadeConfig: this.answers.addWebadeConfig,
altAppDirName: this.answers.altAppDirName,
};
this.composeWith(
'nr-repository-composer:pd-ansible-playbook',
playbook_args,
playbook_options,
);
}
}

writingBackstage() {
writePropToPath(this.backstageDoc, pathToProps, this.answers);
this.fs.write(
this.destinationPath(BACKSTAGE_FILENAME),
this.backstageDoc.toString(),
);
}
}
Loading
Loading