Skip to content

Commit

Permalink
feat: cleanup namespace command (#11)
Browse files Browse the repository at this point in the history
* upgrade deps

* fast glob update

* rename

* add cleanup step

* add regex option

* fix test typings

* add tests

* Update docker-build.sh
  • Loading branch information
buehler authored and mircostraessle committed Sep 12, 2019
1 parent 66d92ad commit 45fb7f5
Show file tree
Hide file tree
Showing 26 changed files with 3,851 additions and 2,679 deletions.
2 changes: 1 addition & 1 deletion docker-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -e

echo "Create and deploy the various versions for kuby docker image"

kubectl_versions=("1" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8")
kubectl_versions=("1" "1.14" "1.15" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8")
image="smartive/kuby"

echo "Build latest docker image"
Expand Down
5,968 changes: 3,452 additions & 2,516 deletions package-lock.json

Large diffs are not rendered by default.

54 changes: 27 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,44 +38,44 @@
"access": "public"
},
"dependencies": {
"@kubernetes/client-node": "^0.8.2",
"@kubernetes/client-node": "^0.10.2",
"apache-md5": "^1.1.2",
"chalk": "^2.4.2",
"clipboardy": "^2.0.0",
"fast-glob": "^2.2.6",
"find-up": "^3.0.0",
"fs-extra": "^7.0.1",
"clipboardy": "^2.1.0",
"fast-glob": "^3.0.4",
"find-up": "^4.1.0",
"fs-extra": "^8.1.0",
"fuzzy": "^0.1.3",
"got": "^9.6.0",
"inquirer": "^6.3.1",
"inquirer": "^7.0.0",
"inquirer-autocomplete-prompt": "^1.0.1",
"node-machine-id": "^1.1.10",
"node-machine-id": "^1.1.12",
"ora": "^3.4.0",
"semver": "^6.0.0",
"tslib": "^1.9.3",
"semver": "^6.3.0",
"tslib": "^1.10.0",
"yargonaut": "^1.1.4",
"yargs": "^13.2.2"
"yargs": "^13.0.0"
},
"devDependencies": {
"@semantic-release/gitlab": "^3.1.2",
"@semantic-release/npm": "^5.1.4",
"@smartive/tslint-config": "^6.0.0",
"@types/fs-extra": "^5.0.5",
"@types/got": "^9.4.2",
"@types/inquirer": "6.0.0",
"@types/jest": "^24.0.11",
"@semantic-release/gitlab": "^3.1.7",
"@semantic-release/npm": "^5.1.15",
"@smartive/tslint-config": "^7.0.1",
"@types/fs-extra": "^8.0.0",
"@types/got": "^9.6.7",
"@types/inquirer": "6.5.0",
"@types/jest": "^24.0.18",
"@types/node": "^11.13.4",
"@types/semver": "^6.0.0",
"@types/yargs": "^13.0.0",
"@types/semver": "^6.0.2",
"@types/yargs": "^13.0.2",
"del-cli": "^1.1.0",
"jest": "^24.7.1",
"memfs": "^2.15.2",
"mock-fs": "^4.8.0",
"pkg": "^4.3.7",
"semantic-release": "^15.13.3",
"jest": "^24.9.0",
"memfs": "^2.15.5",
"mock-fs": "^4.10.1",
"pkg": "^4.4.0",
"semantic-release": "^15.13.24",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"tsutils": "^3.10.0",
"typescript": "^3.4.3"
"tslint": "^5.20.0",
"tsutils": "^3.17.1",
"typescript": "^3.6.3"
}
}
137 changes: 137 additions & 0 deletions src/commands/cleanup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Arguments, Argv, CommandModule } from 'yargs';
import { RootArguments } from '../../root-arguments';
import { exec } from '../../utils/exec';
import { Logger } from '../../utils/logger';
import { RcFile } from '../../utils/rc-file';
import { simpleConfirm } from '../../utils/simple-confirm';
import { spawn } from '../../utils/spawn';

export type CleanupArguments = RootArguments & {
names: string[];
resources: string[];
dryRun: boolean;
exactMatch?: boolean;
regex?: boolean;
};

class Resource {
public get identifier(): string {
return `${this.type}/${this.name}`;
}

constructor(public readonly type: string, public readonly name: string, public readonly namespace: string) {}
}

function namePattern({ exactMatch, regex }: CleanupArguments): string {
switch (true) {
case exactMatch && !regex:
return 'exact';
case !exactMatch && !regex:
return 'partial';
case !exactMatch && regex:
return 'pattern';
default:
throw new Error('not possible name combo found.');
}
}

function resourceFilter({ exactMatch, regex, names }: CleanupArguments): (resource: Resource) => boolean {
return ({ name }) => {
switch (true) {
case regex:
return names.map(n => new RegExp(n, 'g')).some(exp => exp.test(name));
case !regex && exactMatch:
return names.includes(name);
case !regex && !exactMatch:
return names.some(n => name.includes(n));
default:
return false;
}
};
}

export const cleanupCommand: CommandModule<RootArguments, CleanupArguments> = {
command: 'cleanup <names...>',
describe:
'Cleanup a namespace. Remove (delete) the given resources that contain "name" in it (you can add multiple names).',

builder: (argv: Argv<RootArguments>) =>
(argv
.positional('names', {
type: 'string',
array: true,
description: 'The names of the resources that should be deleted.',
})
.option('resources', {
alias: 'r',
array: true,
string: true,
default: ['ingress', 'service', 'deployment', 'pvc', 'configmap', 'secret', 'certificate'],
description:
'Defines a list of resources that should be cleaned up. If a comma separated list is given, it is split.',
coerce(args: string[]): string[] {
const result = [];
for (const arg of args) {
result.push(...arg.split(','));
}
return result;
},
})
.option('regex', {
boolean: true,
conflicts: 'exact-match',
description: 'Use the inputted names as regex patterns to determine if the resource should be deleted.',
})
.option('dry-run', {
boolean: true,
default: false,
description: "Don't delete the resources, just print what would've been deleted.",
})
.option('exact-match', {
boolean: true,
description: 'Use an exact name match and not a partial match.',
}) as unknown) as Argv<CleanupArguments>,

async handler(args: Arguments<CleanupArguments>): Promise<void> {
const logger = new Logger('cleanup');
logger.debug(
`Delete resources: ${args.resources.join(',')} within the namespace with the ${namePattern(
args,
)} names ${args.names.join(', ')}.`,
);
logger.info('Cleanup resources from the namespace.');

const resources = (await exec(
`kubectl ${RcFile.getKubectlArguments(args, []).join(' ')} get ${args.resources.join(
',',
)} -o jsonpath="{range $.items[*]}{.kind}|{.metadata.name}|{.metadata.namespace}{'\\n'}{end}"`,
))
.split('\n')
.filter(Boolean)
.map(str => str.split('|'))
.map(([type, name, namespace]) => new Resource(type, name, namespace));

const toDelete = resources
.filter(r => !!!args.namespace || args.namespace === r.namespace)
.filter(resourceFilter(args))
.map(r => r.identifier);

if (toDelete.length <= 0) {
logger.info('No items to delete. Return.');
return;
}

if (args.dryRun) {
logger.output(`DRY-RUN - Deletion would include:\n${toDelete.join('\n')}`);
return;
}

if (!args.ci && !(await simpleConfirm(`Do you want to proceed and delete ${toDelete.length} resources?`, false))) {
logger.info('Aborting.');
return;
}

await spawn('kubectl', RcFile.getKubectlArguments(args, ['delete', ...toDelete]));
logger.success('Resources cleaned up.');
},
};
3 changes: 2 additions & 1 deletion src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CommandModule } from 'yargs';

import { applyCommand } from './apply';
import { base64Command } from './base64';
import { cleanupCommand } from './cleanup';
import { contextCommand } from './context';
import { deleteCommand } from './delete';
import { deployCommand } from './deploy';
Expand All @@ -16,6 +16,7 @@ import { versionCommand } from './version';
export const commands: CommandModule<any, any>[] = [
applyCommand,
base64Command,
cleanupCommand,
contextCommand,
deleteCommand,
deployCommand,
Expand Down
5 changes: 2 additions & 3 deletions src/commands/prepare/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { async } from 'fast-glob';
import * as fastGlob from 'fast-glob';
import { emptyDir, outputFile, pathExists, readdir, readFile, stat } from 'fs-extra';
import { posix, sep } from 'path';
import { Arguments, Argv, CommandModule } from 'yargs';

import { RootArguments } from '../../root-arguments';
import { envsubst } from '../../utils/envsubst';
import { ExitCode } from '../../utils/exit-code';
Expand Down Expand Up @@ -67,7 +66,7 @@ export const prepareCommand: CommandModule<RootArguments, PrepareArguments> = {

await emptyDir(args.destinationFolder);

const files = await async<string>(['**/*.{yml,yaml}'], {
const files = await fastGlob(['**/*.{yml,yaml}'], {
cwd: args.sourceFolder,
});
logger.debug(`Found ${files.length} files for processing.`);
Expand Down
11 changes: 5 additions & 6 deletions src/commands/preview-deploy/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { V1Namespace, V1ObjectMeta } from '@kubernetes/client-node';
import { async } from 'fast-glob';
import * as fastGlob from 'fast-glob';
import { readFile } from 'fs-extra';
import { EOL } from 'os';
import { posix } from 'path';
import { Arguments, Argv, CommandModule } from 'yargs';

import { RootArguments } from '../../root-arguments';
import { envsubst } from '../../utils/envsubst';
import { exec } from '../../utils/exec';
Expand Down Expand Up @@ -87,13 +86,13 @@ export const previewDeployCommand: CommandModule<RootArguments, PreviewDeployArg
return;
}

const api = args.kubeConfig ?
KubernetesApi.fromString(args.kubeConfig.isBase64() ? args.kubeConfig.base64Decode() : args.kubeConfig) :
KubernetesApi.fromDefault();
const api = args.kubeConfig
? KubernetesApi.fromString(args.kubeConfig.isBase64() ? args.kubeConfig.base64Decode() : args.kubeConfig)
: KubernetesApi.fromDefault();

await createNamespace(api, name);

const files = await async<string>(['**/*.{yml,yaml}'], {
const files = await fastGlob(['**/*.{yml,yaml}'], {
cwd: args.sourceFolder,
});
logger.debug(`Found ${files.length} files for processing.`);
Expand Down
13 changes: 6 additions & 7 deletions src/commands/secret/docker-registry/create/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { async } from 'fast-glob';
import * as fastGlob from 'fast-glob';
import { pathExists } from 'fs-extra';
import { prompt, Separator } from 'inquirer';
import { EOL } from 'os';
import { parse, posix } from 'path';
import { Arguments, Argv, CommandModule } from 'yargs';

import { RootArguments } from '../../../../root-arguments';
import { Crypto } from '../../../../utils/crypto';
import { exec } from '../../../../utils/exec';
Expand Down Expand Up @@ -120,7 +119,7 @@ export const secretDockerRegistryCreateCommand: CommandModule<RootArguments, Sec
logger.error(`The file ${path} does not exist. Cannot use template.`);
}
} else if (!!!args.from && !args.noInteraction) {
const secretFiles = await async<string>(['./*'], {
const secretFiles = await fastGlob(['./*'], {
cwd: Filepathes.dockerSecretPath,
});
const selected = (await prompt([
Expand Down Expand Up @@ -155,28 +154,28 @@ export const secretDockerRegistryCreateCommand: CommandModule<RootArguments, Sec
{
type: 'input',
name: 'server',
message: `The server url of the registry?`,
message: 'The server url of the registry?',
when: () => !args.noInteraction && !!!args.server,
validate: (input: string) => !!input || 'Please enter a server url.',
},
{
type: 'input',
name: 'user',
message: `The user of the registry?`,
message: 'The user of the registry?',
when: () => !args.noInteraction && !!!args.user,
validate: (input: string) => !!input || 'Please enter a username.',
},
{
type: 'input',
name: 'email',
message: `The email of the registry user?`,
message: 'The email of the registry user?',
when: () => !args.noInteraction && !!!args.email,
validate: (input: string) => !!input || 'Please enter an email address.',
},
{
type: 'password',
name: 'password',
message: `The password of the registry user?`,
message: 'The password of the registry user?',
when: () => !args.noInteraction && !!!args.password,
validate: (input: string) => !!input || 'Please enter a password.',
},
Expand Down
6 changes: 3 additions & 3 deletions src/utils/kubernetes-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Core_v1Api, KubeConfig } from '@kubernetes/client-node';
import { CoreV1Api, KubeConfig } from '@kubernetes/client-node';
import { Context } from '@kubernetes/client-node/dist/config_types';

export class KubernetesApi {
Expand All @@ -9,7 +9,7 @@ export class KubernetesApi {
/**
* Manage elements of the kubernetes core api.
*/
public readonly core: Core_v1Api;
public readonly core: CoreV1Api;

/**
* Manage kube-config.
Expand Down Expand Up @@ -45,7 +45,7 @@ export class KubernetesApi {
this.kubeConfig.setCurrentContext(KubernetesApi.contextOverride);
}

this.core = this.kubeConfig.makeApiClient(Core_v1Api);
this.core = this.kubeConfig.makeApiClient(CoreV1Api);
}

/**
Expand Down
Loading

0 comments on commit 45fb7f5

Please sign in to comment.