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

Create issue #14

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7c97305
Added the ability to check for lookups in a chart as well as ensuring…
5herlocked Jan 18, 2024
d369d65
Formatting fixed a little bit
5herlocked Jan 19, 2024
b06eb3a
Merge branch 'main' into refactor
5herlocked Jan 22, 2024
75ba365
Extracted validation into it's own service and made it work for the m…
5herlocked Jan 24, 2024
5679b93
command skeleton
jcabrerizo Jan 19, 2024
1da5538
validating input json with schema and creating issue
jcabrerizo Jan 19, 2024
90459f6
create getRepo params and funcs + expect schema url
jcabrerizo Jan 22, 2024
0f4b92e
better error handling and exit codes
jcabrerizo Jan 22, 2024
5074965
create test positive and negative test from files
jcabrerizo Jan 22, 2024
1552415
remove references to schema stored locally
jcabrerizo Jan 22, 2024
7b735c6
Made rudimentary updates to validation system to ensure it returns co…
5herlocked Jan 24, 2024
106d444
Added separate class for create-issue command
iuliana Jan 23, 2024
a10b44b
service refactored from command
jcabrerizo Jan 23, 2024
0ac140b
refactored types
jcabrerizo Jan 23, 2024
5f415ce
move repo util methods to service/create-issue
jcabrerizo Jan 23, 2024
124a506
encapsulate createIssue service
jcabrerizo Jan 23, 2024
ea75242
moved ServiceResponse to types dir
jcabrerizo Jan 24, 2024
450259b
move create-issues to serviceS dir
jcabrerizo Jan 24, 2024
8b06770
refactor create issue as class
jcabrerizo Jan 24, 2024
8545c07
extracted schemaValidation service
jcabrerizo Jan 24, 2024
6d81d5c
use commandCaller logs and exit func as service logs and exit
jcabrerizo Jan 24, 2024
b0a9ad4
add validation output to the command
jcabrerizo Jan 24, 2024
f1770d8
create issue validates the referred chart
jcabrerizo Jan 24, 2024
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ Sets up the Sleek CLI to work with a given helm chart
```
USAGE
$ aws-sleek-transformer configure [--addonName <value>] [--addonVersion <value>] [--helmUrl <value>]
[--marketplaceId <value>] [--namespace <value>] [--region <value>]
[--marketplaceId <value>] [--namespace <value>] [--region <value>] [--kubeVersion <value>]

FLAGS
--addonName=<value> Name of the addon
--addonVersion=<value> Version of the addon
--helmUrl=<value> Helm URL of the addon
--kubeVersion=<value> Target Kubernetes version of the addon
--marketplaceId=<value> Marketplace AWS Account ID
--namespace=<value> Namespace of the addon
--region=<value> AWS Region
Expand Down
9 changes: 9 additions & 0 deletions aws-sleek-transformer.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
"@inquirer/select": "^1.3.1",
"@oclif/core": "^3",
"@oclif/plugin-help": "^5",
"aws-sleek-transformer": "file:aws-sleek-transformer-0.0.1.tgz",
"@octokit/core": "^5.0.2",
"@types/ajv": "^1.0.0",
"@types/js-yaml": "^4.0.9",
"ajv": "^8.12.0",
"js-yaml": "^4.1.0",
"json-schema": "^0.4.0",
"oclif": "^4.0.3",
"shx": "^0.3.4"
},
Expand Down
42 changes: 42 additions & 0 deletions src/commandOpts/create-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {Args, Flags} from "@oclif/core";

export default class CreateIssueOpt {

static args = {
file: Args.string(
{
description: 'Path to add-on input file',
required: true,
}
),
}

static description = `
This creates a Github Issue on the Sleek repository.

It will validate the input file to match the schema

TODO:
* Run validation before creating the issue
`

static examples = [
'<%= config.bin %> <%= command.id %> filename',
]

static flags = {
dryRun: Flags.boolean({
aliases: ['dry-run', 'dryrun'],
char: 'd',
default: false,
description: "Runs all checks without creating the issue",
}),
file: Flags.string({description: "Path to add-on input file"}),
repo: Flags.string({description:"Github repository name where the issue will be created", hidden:true}),
repoOwner: Flags.string({description:"Github repository owner", hidden:true}),
}

static summary = "Creates a Github Issue based in the input file";

}

91 changes: 51 additions & 40 deletions src/commands/configure.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Flags} from '@oclif/core';
import {confirm, input} from '@inquirer/prompts';
import select from '@inquirer/select';
import {Flags} from '@oclif/core';

import {SleekCommand} from "../sleek-command.js";
import {confirm, input} from '@inquirer/prompts';
import {getAddonKey, getCurrentAddons} from "../utils.js";

export default class Configure extends SleekCommand {
Expand Down Expand Up @@ -39,6 +40,7 @@ export default class Configure extends SleekCommand {
addonName: Flags.string({description: 'Name of the addon'}),
addonVersion: Flags.string({description: 'Version of the addon'}),
helmUrl: Flags.string({description: 'Helm URL of the addon'}),
kubeVersion: Flags.string({description: 'Target Kubernetes version of the addon'}),
marketplaceId: Flags.string({description: 'Marketplace AWS Account ID'}),
namespace: Flags.string({description: 'Namespace of the addon'}),
region: Flags.string({description: 'AWS Region'}),
Expand All @@ -63,51 +65,50 @@ export default class Configure extends SleekCommand {
const addons = getCurrentAddons(currentConf);

const selected = await select({
message: 'Which addon would you like to change the configuration for?',
choices: addons
choices: addons,
message: 'Which addon would you like to change the configuration for?'
});

const addOnKey = getAddonKey(selected.name, selected.version);

const toModify = {
addonName: await input({
message: 'Change the AddOn Name?',
default: selected.name
default: selected.name,
message: 'Change the AddOn Name?'
}),
addonVersion: await input({
message: 'Change the AddOn Version?',
default: selected.version
default: selected.version,
message: 'Change the AddOn Version?'
}),
helmUrl: await input({
default: currentConf[addOnKey].helmUrl,
message: 'Change the Helm URL?',
validate: input => {
return this.isValidUrl(input)
},
default: currentConf[addOnKey].helmUrl
validate: input => this.isValidUrl(input)
}),
kubeVersion: await input({
default: currentConf[addOnKey].kubeVersion,
message: 'Change the Kubernetes Version?'
}),
marketplaceId: await input({
message: 'Change the Marketplace AWS Account ID?',
default: currentConf[addOnKey].accId
default: currentConf[addOnKey].accId,
message: 'Change the Marketplace AWS Account ID?'
}),
namespace: await input({
default: currentConf[addOnKey].namespace,
message: 'Change the Namespace?',
validate: input => {
return this.isValidNamespace(input)
},
default: currentConf[addOnKey].namespace
validate: input => this.isValidNamespace(input)
}),
region: await input({
default: currentConf[addOnKey].region,
message: 'Change the AWS Region?',
validate: input => {
return this.isValidRegion(input)
},
default: currentConf[addOnKey].region
validate: input => this.isValidRegion(input)
}),
};

this.configuration[getAddonKey(toModify.addonName, toModify.addonVersion)] = {
helmUrl: toModify.helmUrl,
accId: toModify.marketplaceId,
helmUrl: toModify.helmUrl,
kubeVersion: toModify.kubeVersion,
namespace: toModify.namespace,
region: toModify.region,
validated: false
Expand All @@ -125,26 +126,22 @@ export default class Configure extends SleekCommand {
addonName: await input({message: 'What is the AddOn Name?'}),
addonVersion: await input({message: 'What is the AddOn Version?'}),
helmUrl: await input({
message: 'What is the Helm URL?', validate: input => {
return this.isValidUrl(input)
}
message: 'What is the Helm URL?', validate: input => this.isValidUrl(input)
}),
kubeVersion: await input({message: 'What is the targeted Kubernetes Version?'}),
marketplaceId: await input({message: 'What is the Marketplace AWS Account ID?'}),
namespace: await input({
message: 'What is the Namespace?', validate: input => {
return this.isValidNamespace(input)
}
message: 'What is the Namespace?', validate: input => this.isValidNamespace(input)
}),
region: await input({
message: 'What is the AWS Region?', validate: input => {
return this.isValidRegion(input)
}
message: 'What is the AWS Region?', validate: input => this.isValidRegion(input)
}),
};

this.configuration[getAddonKey(addonConfig.addonName, addonConfig.addonVersion)] = {
helmUrl: addonConfig.helmUrl,
accId: addonConfig.marketplaceId,
helmUrl: addonConfig.helmUrl,
kubeVersion: addonConfig.kubeVersion,
namespace: addonConfig.namespace,
region: addonConfig.region,
validated: false
Expand All @@ -155,29 +152,29 @@ export default class Configure extends SleekCommand {
return;
}

let addon = {
region: "",
const addon = {
accId: "",
helmUrl: "",
namespace: ""
kubeVersion: "",
namespace: "",
region: ""
};

if (flags.region !== undefined && this.isValidRegion(flags.region)) {
addon["region"] = flags.region;
addon.region = flags.region;
}

if (flags.namespace !== undefined && this.isValidNamespace(flags.namespace)) {
addon["namespace"] = flags.namespace;
addon.namespace = flags.namespace;
}

if (flags.helmUrl !== undefined && this.isValidUrl(flags.helmUrl)) {
addon["helmUrl"] = flags.helmUrl;
addon.helmUrl = flags.helmUrl;
}

if (flags.addonName !== undefined && flags.addonVersion !== undefined) {
if (Object.values(addon).every(value => value !== "")) {
this.configuration[getAddonKey(flags.addonName, flags.addonVersion)] = { ...addon, validated: false };

this.updateConfig();
}
}
Expand Down Expand Up @@ -211,6 +208,20 @@ export default class Configure extends SleekCommand {
return !(namespace[0] === '-' || namespace[namespace.length - 1] === '-');
}

private isValidKubernetesVersion(version: string): boolean {
// Kubernetes versions must:
// start with the letter v
// has 2 periods at most
// not more than 9 characters

const versionRegex = /^v[0-9]+\.[0-9]+(\.[0-9]+)?$/;

if (version.length > 9) {
return false;
}
return versionRegex.test(version);
}

private isValidUrl(input: string): boolean {
try {
new URL(input);
Expand Down
68 changes: 68 additions & 0 deletions src/commands/create-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import fs from "node:fs";

import CreateIssueOpt from "../commandOpts/create-issue.js";
import {SleekCommand} from "../sleek-command.js";
import CreateIssueService from "../services/create-issue.js";
import {IssueData} from "../types/issue.js";
import SchemaValidationService from "../services/schemaValidation.js";
import ChartValidatorService from "../services/validate.js";
import {execSync} from "child_process";


export class CreateIssue extends SleekCommand {
static description = CreateIssueOpt.description;
static summary = CreateIssueOpt.summary;
static examples = CreateIssueOpt.examples;
static args = CreateIssueOpt.args;
static flags = CreateIssueOpt.flags;

async run(): Promise<any> {

const {args, flags} = await this.parse(CreateIssue);
const isDryRun = flags.dryRun;
const filePath = args.file;

this.log(`File to process: ${filePath} ${isDryRun ? '(dry run)' : ''}`)
const fileContents = fs.readFileSync(filePath, 'utf8');
const schemaValidator = new SchemaValidationService(this);
const data = await schemaValidator.validateInputFileSchema(fileContents);
this.log('Schema validation correct') // it exits if not valid

if (isDryRun) return;

const inputDataParsed = data.body as IssueData;
const addonData = inputDataParsed.addon;
const repo= addonData.helmChartUrl.substring(0,addonData.helmChartUrl.lastIndexOf(':'))
const chartTag = addonData.helmChartUrl.lastIndexOf(':') ? `${addonData.helmChartUrl.substring(addonData.helmChartUrl.lastIndexOf(':')+1)}` : ''
const charPath=await this.pullHelmChart(addonData.name, chartTag, repo)
const validatorService = new ChartValidatorService(this, { chart: charPath});
const validatorServiceResp = await validatorService.run();
// todo: if validatorService exits when errors, not need to handle here !success
if(!validatorServiceResp.success){
this.error(validatorServiceResp.error?.input!, validatorServiceResp.error?.options )
}
this.log(`Chart validation successful`)

// create issue base in the file input
const title = `Onboarding ${inputDataParsed.sellerMarketPlaceAlias} ${addonData.name}@${addonData.version}`;
const body = `Issue body:\n\n\`\`\`yaml\n${fileContents}\`\`\`\n`;
const createIssueService = new CreateIssueService(this);
const createIssueResponse = await createIssueService.createIssue(title, body, ['pending'])

this.log(`Issue created: ${createIssueResponse.body?.data.html_url}`)
}

private async pullHelmChart(name:string, chartTag:string, url:string): Promise<string> {
const chartVersionFlag = !! chartTag ? `--version ${chartTag}`:''
const untarLocation = `./unzipped-${name}`;
const pullCmd = `rm -rf ${untarLocation} &&
mkdir ${untarLocation} &&
helm pull oci://${url} ${chartVersionFlag} --untar --untardir ${untarLocation} >/dev/null 2>&1`;
try {
execSync(pullCmd);
} catch (e) {
this.error(`Helm chart pull failed with error ${e}`);
}
return untarLocation;
}
}
21 changes: 9 additions & 12 deletions src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import select from "@inquirer/select";
import {SleekCommand} from "../sleek-command.js";
import {execSync, spawnSync} from "child_process";
import {destructureAddonKey, getAddonKey, getCurrentAddons} from "../utils.js";
import ChartValidatorService from "../services/validate.js";

export default class Validate extends SleekCommand {
static description = `
Expand Down Expand Up @@ -44,7 +45,7 @@ export default class Validate extends SleekCommand {
const currentConf = this.configuration;
const addons = getCurrentAddons(currentConf);

const selected: { name: string, version: string } = await select({
const selected: { name: string, version: string } = await select({
message: 'Which addon would you like to validate the configuration for?',
choices: addons
});
Expand All @@ -55,20 +56,16 @@ export default class Validate extends SleekCommand {
}
const chart = path.resolve(await this.pullHelmChart(addonKey));

// turns out using grep is the best way to do it lmao
// rip all the Windows users
const findCapabilities = spawnSync('grep', ['-Rile', '".Capabilities"', chart], { shell: true });
const findHooks = spawnSync('grep', ['-Rile', '"helm.sh/hook"', chart], { shell: true });
this.log(`Validating chart ${chart}`);

if (findCapabilities.stdout.toString() == "" && findHooks.stdout.toString() == "") {
this.log('No occurrences of .Capabilities or helm.sh/hook found in Helm chart');
const validatorService = new ChartValidatorService(this, { chart: chart});

this.configuration[addonKey].validated = false;
} else {
this.log("Found .Capabilities or helm.sh/hook in helm chart.");

this.configuration[addonKey].validated = true;
const validatorServiceResp = await validatorService.run();
if(!validatorServiceResp.success){
this.error(validatorServiceResp.error?.input!, validatorServiceResp.error?.options )
}
this.log(validatorServiceResp.body)
// do something with the validated service response
}

private async pullHelmChart(addonKey: string): Promise<string> {
Expand Down
Loading