Skip to content

Commit

Permalink
Feature/eng 949 secrets envvars management commands for the cli (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
uditdc authored Aug 14, 2023
1 parent 2fc8829 commit 6caa82b
Show file tree
Hide file tree
Showing 8 changed files with 803 additions and 416 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"chalk": "^2.4.2",
"cli-table3": "^0.6.3",
"compare-versions": "^5.0.1",
"dayjs": "^1.11.9",
"fastify": "^4.8.1",
"follow-redirects": "^1.15.1",
"form-data": "^4.0.0",
Expand Down
99 changes: 99 additions & 0 deletions src/commands/function/env/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Argv } from 'yargs'
import { run as runList } from './list'
import { run as runSet } from './set'
import { run as runUnset } from './unset'
import { logger } from '../../../lib/logger'

export function functionEnvCli(yargs: Argv) {
yargs.usage('bls function env [subcommand]')

yargs.command(
['list'],
'Lists all environment variables for a function',
(yargs) => {
return yargs
.option('target', {
alias: 't',
description: 'The name of the function (Defaults to the working directory)',
type: 'string',
default: undefined
})
.group(['target'], 'Options:')
},
(argv) => {
runList({ target: argv.target })
}
)

yargs.command(
'set',
'Set environment variables for a function',
(yargs) => {
return yargs
.usage('set NAME=VALUE ...')
.strict(false)
.parserConfiguration({ 'unknown-options-as-args': true })
.option('target', {
alias: 't',
description: 'The name of the function (Defaults to the working directory)',
type: 'string',
default: undefined
})
.group(['target'], 'Options:')
},
(argv) => {
const vars = argv._.filter(
(f: string | number) =>
typeof f === 'string' &&
['function', 'functions', 'env', 'set'].indexOf(f) === -1 &&
/^[A-Za-z]{2,}=.+$/.test(f)
) as string[]

if (vars.length > 0) {
const envVars = vars.reduce(
(dest, item) => ({ ...dest, [item.split('=')[0]]: item.split('=')[1] }),
{}
)

runSet({ target: argv.target, envVars })
} else {
logger.log('Skipping: Nothing to update.')
}
}
)

yargs.command(
'unset',
'Unset environment variables for a function',
(yargs) => {
return yargs
.usage('unset NAME NAME2 ...')
.strict(false)
.parserConfiguration({ 'unknown-options-as-args': true })
.positional('target', {
alias: 't',
description: 'The name of the function (Defaults to the working directory)',
type: 'string',
default: undefined
})
.group(['target'], 'Options:')
},
(argv) => {
const envVarKeys = argv._.filter(
(f: string | number) =>
typeof f === 'string' &&
['function', 'functions', 'env', 'unset'].indexOf(f) === -1 &&
/^[A-Za-z]+$/.test(f)
) as string[]

if (envVarKeys.length > 0) {
const envVars = envVarKeys.reduce((dest, key) => ({ ...dest, [key]: null }), {})
runUnset({ target: argv.target, envVars })
} else {
logger.log('Skipping: Nothing to update.')
}
}
)

return yargs
}
81 changes: 81 additions & 0 deletions src/commands/function/env/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Chalk from 'chalk'
import Table from 'cli-table3'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'

import { parseBlsConfig } from '../../../lib/blsConfig'
import { gatewayRequest } from '../../../lib/gateway'
import { logger } from '../../../lib/logger'
import { normalizeFunctionName } from '../../../lib/strings'

dayjs.extend(relativeTime)

interface EnvSetCommandOptions {
target: string | undefined
}

/**
* Entry function for bls function env list
*
* @param options
* @returns
*/
export const run = async (options: EnvSetCommandOptions) => {
try {
if (options.target) {
await listEnvVars({ name: options.target })
} else {
const { name: configName } = parseBlsConfig()
await listEnvVars({ name: configName })
}
} catch (error: any) {
logger.error('Failed to list environment variables.', error.message)
return
}
}

const listEnvVars = async ({ name: functionName }: { name: string }) => {
logger.log(Chalk.yellow(`Listing environment variables for ${functionName} ...`))
logger.log('')

try {
let matchingFunction = null
let internalFunctionId = null

const { data } = await gatewayRequest('[GET] /functions')
const functions = data.docs ? data.docs : []

// Sort all matching functions by name and select the last matching function
// TODO: Ensure all functions have unique names under a user's scope
const matchingFunctions = functions.filter(
(f: any) => normalizeFunctionName(f.functionName) === normalizeFunctionName(functionName)
)

if (matchingFunctions && matchingFunctions.length > 0) {
matchingFunction = matchingFunctions[matchingFunctions.length - 1]
internalFunctionId = matchingFunction._id
}

// If a function does not exist, request the user to deploy that function first
if (!matchingFunction) {
throw new Error('Function not found.')
}

const { data: fn } = await gatewayRequest('[GET] /functions/{id}', { id: internalFunctionId })

var table = new Table({
wordWrap: true,
wrapOnWordBoundary: false,
head: ['Name', 'Last Updated'],
colWidths: [25]
})

fn.envVars.map((envVar: any) =>
table.push([envVar.name, envVar.updatedAt ? dayjs().to(envVar.updatedAt) : 'Unknown'])
)

logger.log(table.toString())
} catch (error) {
logger.error('Failed to list environment variables.')
}
}
79 changes: 79 additions & 0 deletions src/commands/function/env/set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Chalk from 'chalk'

import { parseBlsConfig } from '../../../lib/blsConfig'
import { gatewayRequest } from '../../../lib/gateway'
import { logger } from '../../../lib/logger'
import { normalizeFunctionName } from '../../../lib/strings'

interface EnvSetCommandOptions {
target: string | undefined
envVars: {
[key: string]: string
}
}

/**
* Entry function for bls function env set
*
* @param options
* @returns
*/
export const run = async (options: EnvSetCommandOptions) => {
try {
if (options.target) {
await setEnvVars({ name: options.target, envVars: options.envVars })
} else {
const { name: configName } = parseBlsConfig()
await setEnvVars({ name: configName, envVars: options.envVars })
}
} catch (error: any) {
logger.error('Failed to set environment variables.', error.message)
return
}
}

const setEnvVars = async ({
name: functionName,
envVars
}: {
name: string
envVars: {
[key: string]: string
}
}) => {
logger.log(Chalk.yellow(`Setting environment variables for ${functionName} ...`))

try {
let matchingFunction = null
let internalFunctionId = null

const { data } = await gatewayRequest('[GET] /functions')
const functions = data.docs ? data.docs : []

// Sort all matching functions by name and select the last matching function
// TODO: Ensure all functions have unique names under a user's scope
const matchingFunctions = functions.filter(
(f: any) => normalizeFunctionName(f.functionName) === normalizeFunctionName(functionName)
)

if (matchingFunctions && matchingFunctions.length > 0) {
matchingFunction = matchingFunctions[matchingFunctions.length - 1]
internalFunctionId = matchingFunction._id
}

// If a function does not exist, request the user to deploy that function first
if (!matchingFunction) {
throw new Error('Function not found.')
}

await gatewayRequest('[PATCH] /functions/{id}/env-vars', {
id: internalFunctionId,
envVars
})

logger.log(Chalk.green(`Successfully updated function ${functionName}!`))
} catch (error: any) {
logger.error('Failed to set environment variables.', error.message)
return
}
}
78 changes: 78 additions & 0 deletions src/commands/function/env/unset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Chalk from 'chalk'

import { parseBlsConfig } from '../../../lib/blsConfig'
import { logger } from '../../../lib/logger'
import { gatewayRequest } from '../../../lib/gateway'
import { normalizeFunctionName } from '../../../lib/strings'

interface EnvUnsetCommandOptions {
target: string | undefined
envVars: {
[key: string]: null
}
}

/**
* Entry function for bls function env unset
*
* @param options
* @returns
*/
export const run = async (options: EnvUnsetCommandOptions) => {
try {
if (options.target) {
await unsetEnvVars({ name: options.target, envVars: options.envVars })
} else {
const { name: configName } = parseBlsConfig()
await unsetEnvVars({ name: configName, envVars: options.envVars })
}
} catch (error: any) {
logger.error('Failed to unset environment variables.', error.message)
return
}
}

const unsetEnvVars = async ({
name: functionName,
envVars
}: {
name: string
envVars: {
[key: string]: null
}
}) => {
logger.log(Chalk.yellow(`Unsetting environment variables for ${functionName} ...`))

try {
let matchingFunction = null
let internalFunctionId = null

const { data } = await gatewayRequest('[GET] /functions')
const functions = data.docs ? data.docs : []

// Sort all matching functions by name and select the last matching function
// TODO: Ensure all functions have unique names under a user's scope
const matchingFunctions = functions.filter(
(f: any) => normalizeFunctionName(f.functionName) === normalizeFunctionName(functionName)
)

if (matchingFunctions && matchingFunctions.length > 0) {
matchingFunction = matchingFunctions[matchingFunctions.length - 1]
internalFunctionId = matchingFunction._id
}

// If a function does not exist, request the user to deploy that function first
if (!matchingFunction) {
throw new Error('Function not found.')
}

await gatewayRequest('[PATCH] /functions/{id}/env-vars', {
id: internalFunctionId,
envVars
})

logger.log(Chalk.green(`Successfully updated function ${functionName}!`))
} catch (error) {
logger.error('Failed to unset environment variables.')
}
}
Loading

0 comments on commit 6caa82b

Please sign in to comment.