Skip to content

Commit

Permalink
Merge pull request #61 from atomist/workspace-create
Browse files Browse the repository at this point in the history
Add provider and workspace create commands
  • Loading branch information
cdupuis committed Jan 15, 2019
2 parents 6e18664 + 150c063 commit 3fb59ca
Show file tree
Hide file tree
Showing 8 changed files with 1,069 additions and 218 deletions.
36 changes: 36 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ import { gqlFetch } from "./lib/gqlFetch";
import { install } from "./lib/install";
import { kube } from "./lib/kube";
import * as print from "./lib/print";
import * as provider from "./lib/provider";
import { start } from "./lib/start";
import { version } from "./lib/version";
import * as workspace from "./lib/workspace";

process.env.SUPPRESS_NO_CONFIG_WARNING = "true";
if (!isEmbeddedSdmCommand(process.argv)) {
Expand Down Expand Up @@ -81,6 +83,40 @@ function setupYargs(yargBuilder: yb.YargBuilder): void {
workspaceId: argv["workspace-id"],
})),
});
yargBuilder.withSubcommand({
command: "workspace create",
describe: "Create a new workspace",
parameters: [{
parameterName: "api-key",
describe: "Atomist API key",
type: "string",
}, {
parameterName: "workspace-name",
describe: "Workspace name",
type: "string",
}],
handler: argv => cliCommand(() => workspace.create({
apiKey: argv["api-key"],
workspaceName: argv["workspace-name"],
})),
});
yargBuilder.withSubcommand({
command: "provider create",
describe: "Create a new provider",
parameters: [{
parameterName: "api-key",
describe: "Atomist API key",
type: "string",
}, {
parameterName: "workspace-id",
describe: "Atomist workspace ID",
type: "string",
}],
handler: argv => cliCommand(() => provider.create({
apiKey: argv["api-key"],
workspaceId: argv["workspace-id"],
})),
});
yargBuilder.withSubcommand({
command: "execute <name>",
describe: "Run a command",
Expand Down
39 changes: 24 additions & 15 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {
Configuration,
QueryNoCacheOptions,
toStringArray,
} from "@atomist/automation-client";
import {
defaultConfiguration,
Expand Down Expand Up @@ -162,7 +163,7 @@ export async function config(opts: ConfigOptions): Promise<number> {
userCfg.workspaceIds = workspaceIds;
await writeUserConfig(userCfg);

print.log(`Successfully wrote configuration: ${chalk.green(cfgPath)}`);
print.log(`Successfully wrote configuration ${chalk.green(cfgPath)}`);
return 0;
}

Expand All @@ -178,15 +179,16 @@ async function createApiKey(cfg: Configuration): Promise<string> {
name: "apiKey",
transformer: maskString,
message: `Enter your ${chalk.cyan("api key")} from ${chalk.yellow("https://app.atomist.com/apikeys")}
or hit ${chalk.cyan("<ENTER>")} to select an authentication provider to login with Atomist`,
or hit ${chalk.cyan("<ENTER>")} to select an authentication provider
to login with Atomist:`,
},
];

let answers = await inquirer.prompt(questions);
if (!answers.apiKey) {
const providers = await axios.get(`${cfg.endpoints.auth}/providers`);

print.log(`Select one of the following authentication providers available to login with Atomist:`);
print.log(`Select one of the following authentication providers\navailable to login with Atomist:`);

questions = [
{
Expand Down Expand Up @@ -272,7 +274,7 @@ async function createApiKey(cfg: Configuration): Promise<string> {
* @param apiKey
* @param cfg
*/
async function validateApiKey(apiKey: string, cfg: Configuration): Promise<void> {
export async function validateApiKey(apiKey: string, cfg: Configuration): Promise<void> {
const spinner = createSpinner("Validating API key");
const graphClient = new ApolloGraphClient(
cfg.endpoints.graphql.replace("/team", ""),
Expand Down Expand Up @@ -304,7 +306,9 @@ async function validateApiKey(apiKey: string, cfg: Configuration): Promise<void>
* @param apiKey
* @param cfg
*/
async function configureWorkspaces(apiKey: string, cfg: Configuration): Promise<string[]> {
export async function configureWorkspaces(apiKey: string,
cfg: Configuration,
multiple: boolean = true): Promise<string[]> {
const graphClient = new ApolloGraphClient(
cfg.endpoints.graphql.replace("/team", ""),
{ Authorization: `Bearer ${apiKey}` });
Expand All @@ -319,13 +323,17 @@ async function configureWorkspaces(apiKey: string, cfg: Configuration): Promise<
return [];
}

print.log(`Select one or more workspaces to connect to:`);
if (multiple) {
print.log(`Select one or more workspaces:`);
} else {
print.log(`Select a workspace:`);
}

const questions: inquirer.Question[] = [
{
type: "checkbox",
type: multiple ? "checkbox" : "list",
name: "workspaceIds",
message: "Workspace IDs",
message: "Workspaces",
choices: workspaces.sort((p1, p2) => p1.team.name.localeCompare(p2.team.name))
.map(p => ({
name: `${p.team.id} - ${p.team.name}`,
Expand All @@ -337,7 +345,7 @@ async function configureWorkspaces(apiKey: string, cfg: Configuration): Promise<
];

const answers: any = await inquirer.prompt(questions);
return answers.workspaceIds || [];
return toStringArray(answers.workspaceIds) || [];
}

export function createSpinner(text: string): any {
Expand All @@ -350,12 +358,13 @@ export function createSpinner(text: string): any {
}

function nonce(length: number = 40): string {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
const crypto = require("crypto");
return crypto
.randomBytes(Math.ceil((length * 3) / 4))
.toString("base64") // convert to base64 format
.slice(0, length) // return required number of characters
.replace(/\+/g, "0") // replace '+' with '0'
.replace(/\//g, "0"); // replace '/' with '0'
}

/**
Expand Down
146 changes: 146 additions & 0 deletions lib/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Configuration } from "@atomist/automation-client";
import {
defaultConfiguration,
mergeConfigs,
writeUserConfig,
} from "@atomist/automation-client/lib/configuration";
import chalk from "chalk";
import * as inquirer from "inquirer";
import * as _ from "lodash";
import { resolveUserConfig } from "./cliConfig";
import {
configureWorkspaces,
validateApiKey,
} from "./config";
import * as print from "./print";
import { createGitHubCom } from "./provider/github";

type ProviderTypes = Record<string, {
label: string;
create: (workspaceId: string,
apiKey: string,
cfg: Configuration) => Promise<{ code: number, configuration: Partial<Configuration> }>;
}>;

/*const UnsupportedProvider = async (workspaceId: string,
apiKey: string,
cfg: Configuration) => {
print.error("SCM provider not supported yet!");
return {
code: 1,
configuration: cfg,
};
};*/

const Providers: ProviderTypes = {
github_com: {
label: "GitHub.com",
create: createGitHubCom,
},
/*ghe: {
label: "GitHub Enterprise",
create: UnsupportedProvider,
},
gitlab: {
label: "GitLab",
create: UnsupportedProvider,
},
gitlab_com: {
label: "GitLab.com",
create: UnsupportedProvider,
},
bitbucket: {
label: "BitBucket",
create: UnsupportedProvider,
},*/
};

/**
* Command-line options and arguments for provider create
*/
export interface CreateOptions {
/** Atomist API key */
apiKey?: string;

/** Atomist workspace id */
workspaceId?: string;
}

/**
* Create a new SCM provider
* @param opts
*/
export async function create(opts: CreateOptions): Promise<number> {
const userCfg = resolveUserConfig();
const defaultCfg = defaultConfiguration();
const cfg = mergeConfigs(defaultCfg, userCfg);

const apiKey = opts.apiKey || cfg.apiKey;

if (!apiKey) {
print.error(`No API key found. Run ${chalk.cyan("atomist config")} to obtain a key`);
return 1;
}

try {
// Validate api key
await validateApiKey(apiKey, cfg);
} catch (e) {
print.error(`Failed to validate API key: ${e.message}`);
return 1;
}

let workspaceId = opts.workspaceId;

try {
if (!workspaceId) {
workspaceId = (await configureWorkspaces(apiKey, cfg, false))[0];
}
} catch (e) {
print.error(`Failed to load list of workspaces: ${e.message}`);
return 1;
}

print.log("Select an SCM provider to add to your workspace:");
const questions: inquirer.Question[] = [
{
type: "list",
name: "provider",
message: "SCM provider",
choices: _.map(Providers, (v, k) => ({ name: v.label, value: k })),
},
];

const answers = await inquirer.prompt(questions);
try {
const result = await Providers[answers.provider].create(workspaceId, apiKey, cfg);
const newCfg = {
...userCfg,
...result.configuration,
};
await writeUserConfig(newCfg);
print.log(`Successfully created SCM provider ${chalk.cyan(Providers[answers.provider].label)}`);
return result.code;
} catch (e) {
print.error(`Failed to create SCM provider: ${e.message}`);
return 1;
}

return 0;
}
Loading

0 comments on commit 3fb59ca

Please sign in to comment.