Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Adds secrets:sync command. #17

Merged
merged 4 commits into from
Jan 29, 2021
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
14 changes: 11 additions & 3 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
{
"extends": ["oclif", "oclif-typescript"],
"extends": [
"oclif",
"oclif-typescript"
],
"rules": {
"@typescript-eslint/member-delimiter-style": {
"multiline": {
"delimiter": "none",
"requireLast": false
}
}
},
"indent": [
{
"SwitchCase": 1
}
]
}
}
}
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"bracketSpacing": false
"bracketSpacing": false,
"arrowParens": "avoid"
}
37 changes: 30 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ $ npm install -g @anomalyhq/github-secrets-cli
$ ghs COMMAND
running command...
$ ghs (-v|--version|version)
@anomalyhq/github-secrets-cli/1.1.0 darwin-x64 node-v12.10.0
@anomalyhq/github-secrets-cli/1.2.0 darwin-x64 node-v14.15.4
$ ghs --help [COMMAND]
USAGE
$ ghs COMMAND
Expand All @@ -40,6 +40,7 @@ USAGE
* [`ghs secrets:get`](#ghs-secretsget)
* [`ghs secrets:remove`](#ghs-secretsremove)
* [`ghs secrets:set`](#ghs-secretsset)
* [`ghs secrets:sync FILE`](#ghs-secretssync-file)

## `ghs config:get`

Expand All @@ -50,7 +51,7 @@ USAGE
$ ghs config:get
```

_See code: [src/commands/config/get.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.1.0/src/commands/config/get.ts)_
_See code: [src/commands/config/get.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.2.0/src/commands/config/get.ts)_

## `ghs config:set`

Expand All @@ -66,7 +67,7 @@ OPTIONS
-t, --personalAccessToken=personalAccessToken Your GitHub Personal Access Token.
```

_See code: [src/commands/config/set.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.1.0/src/commands/config/set.ts)_
_See code: [src/commands/config/set.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.2.0/src/commands/config/set.ts)_

## `ghs help [COMMAND]`

Expand All @@ -83,7 +84,7 @@ OPTIONS
--all see all commands in CLI
```

_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.0/src/commands/help.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.1/src/commands/help.ts)_

## `ghs secrets:get`

Expand All @@ -100,7 +101,7 @@ OPTIONS
-t, --personalAccessToken=personalAccessToken Your GitHub Personal Access Token.
```

_See code: [src/commands/secrets/get.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.1.0/src/commands/secrets/get.ts)_
_See code: [src/commands/secrets/get.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.2.0/src/commands/secrets/get.ts)_

## `ghs secrets:remove`

Expand All @@ -119,7 +120,7 @@ OPTIONS
-y, --autoYes Skips user confirmation.
```

_See code: [src/commands/secrets/remove.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.1.0/src/commands/secrets/remove.ts)_
_See code: [src/commands/secrets/remove.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.2.0/src/commands/secrets/remove.ts)_

## `ghs secrets:set`

Expand All @@ -140,7 +141,29 @@ OPTIONS
-t, --personalAccessToken=personalAccessToken Your GitHub Personal Access Token.
```

_See code: [src/commands/secrets/set.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.1.0/src/commands/secrets/set.ts)_
_See code: [src/commands/secrets/set.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.2.0/src/commands/secrets/set.ts)_

## `ghs secrets:sync FILE`

Add/Update multiple secrets from one file.

```
USAGE
$ ghs secrets:sync FILE

ARGUMENTS
FILE Path to the file to read from.

OPTIONS
-b, --base64 base64 the values before encoding.
-f, --format=env|json|yaml (required) File format to parse secrets from.
-h, --help show CLI help
-o, --org=org Organisation the repo belongs to.
-r, --repo=repo Name of the repo.
-t, --personalAccessToken=personalAccessToken Your GitHub Personal Access Token.
```

_See code: [src/commands/secrets/sync.ts](https://github.com/anomaly/github-secrets-cli/blob/v1.2.0/src/commands/secrets/sync.ts)_
<!-- commandsstop -->


Expand Down
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@anomalyhq/github-secrets-cli",
"description": "Github secrets manager",
"version": "1.1.0",
"version": "1.2.0",
"author": "Brendon Lees @brendon1555",
"bin": {
"ghs": "./bin/run"
Expand All @@ -11,10 +11,13 @@
"@oclif/command": "^1",
"@oclif/config": "^1",
"@oclif/errors": "^1",
"@oclif/plugin-help": "^3",
"@octokit/request": "^5.4.5",
"@oclif/plugin-help": "^3.2.1",
"@octokit/request": "^5.4.13",
"@types/js-yaml": "^4.0.0",
"cli-ux": "^5.5.0",
"fs-extra": "^9.0.1",
"dotenv": "^8.2.0",
"fs-extra": "^9.1.0",
"js-yaml": "^4.0.0",
"node-emoji": "^1.10.0",
"tslib": "^1",
"tweetsodium": "^0.0.5"
Expand All @@ -23,9 +26,9 @@
"@oclif/dev-cli": "^1",
"@oclif/test": "^1",
"@types/chai": "^4",
"@types/fs-extra": "^9.0.1",
"@types/fs-extra": "^9.0.6",
"@types/mocha": "^5",
"@types/node": "^14.0.20",
"@types/node": "^14.14.22",
"@types/node-emoji": "^1.8.1",
"chai": "^4",
"eslint": "^5.13",
Expand Down
5 changes: 2 additions & 3 deletions src/commands/secrets/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,9 @@ export default class SecretsSet extends Command {
key_id: token.key_id,
}
)
this.log('secret updated')
this.log(`Updated secret: ${flags.secret}`)
} catch (error) {
this.error(error)
this.log('unable to update secret')
this.error(`Unable to update secret: ${flags.secret} \n${error}`)
}
} catch (error) {
this.error(new CLIError(error), {exit: 1})
Expand Down
148 changes: 148 additions & 0 deletions src/commands/secrets/sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {Command, flags} from '@oclif/command'
import {CLIError} from '@oclif/errors'
import dotenv from 'dotenv'
import {request} from '@octokit/request'
import fs from 'fs-extra'
import sodium from 'tweetsodium'
import {configuration} from '../../utils/config'
import yaml from 'js-yaml'

type Env = {[env: string]: string}

export default class SecretsSync extends Command {
static description = 'Add/Update multiple secrets from one file.'

static flags = {
help: flags.help({char: 'h'}),
personalAccessToken: flags.string({
char: 't',
description: 'Your GitHub Personal Access Token.',
required: false,
}),
org: flags.string({
char: 'o',
description: 'Organisation the repo belongs to.',
required: false,
}),
repo: flags.string({
char: 'r',
description: 'Name of the repo.',
required: false,
}),
base64: flags.boolean({
char: 'b',
description: 'base64 the values before encoding.',
required: false,
default: false,
}),
format: flags.string({
char: 'f',
description: 'File format to parse secrets from.',
required: true,
options: ['env', 'json', 'yaml'],
}),
}

static args = [
{
name: 'file',
required: true,
description: 'Path to the file to read from.',
},
]

async run() {
const {args, flags} = this.parse(SecretsSync)

try {
if (!args.file) {
this.error(new CLIError('Please provide a file'))
}
const messageString = fs.readFileSync(args.file, 'utf-8')

let output: Env = {}

switch (flags.format) {
case 'env':
output = dotenv.parse(messageString, {debug: true})
// Check that the parser could read anything valid from the file
if (
Object.keys(output).length === 0 ||
output.constructor !== Object
) {
this.error('File is either empty or not a valid .env format')
}
break
case 'json':
try {
output = JSON.parse(messageString)
// Check that the parser could read anything valid from the file
} catch (error) {
this.error('File is either empty or not a valid json format')
}
break
case 'yaml':
output = yaml.load(messageString) as Env
// Check that the parser could read anything valid from the file
if (
Object.keys(output).length === 0 ||
output?.constructor !== Object
) {
this.error('File is either empty or not a valid yaml format')
}
break
}

const conf = await configuration(this)

const requestWithAuth = request.defaults({
headers: {
authorization: `token ${
flags.personalAccessToken ?? conf.personalAccessToken
}`,
},
})

const {data: token} = await requestWithAuth(
'GET /repos/{owner}/{repo}/actions/secrets/public-key',
{
owner: flags.org ?? conf.org,
repo: flags.repo ?? conf.repo,
}
)

Object.keys(output).forEach(async key => {
let value = output[key]
if (flags.base64) {
value = Buffer.from(value).toString('base64')
}

const messageBytes = Buffer.from(value)

const keyBytes = Buffer.from(token.key, 'base64')

const encryptedBytes = sodium.seal(messageBytes, keyBytes)

const encrypted = Buffer.from(encryptedBytes).toString('base64')

try {
await requestWithAuth(
'PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}',
{
owner: flags.org ?? conf.org,
repo: flags.repo ?? conf.repo,
secret_name: key,
encrypted_value: encrypted,
key_id: token.key_id,
}
)
this.log(`Updated secret: ${key}`)
} catch (error) {
this.error(`Unable to update secret: ${key} \n${error}`)
}
})
} catch (error) {
this.error(new CLIError(error), {exit: 1})
}
}
}
17 changes: 17 additions & 0 deletions test/commands/secrets/sync.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {expect, test} from '@oclif/test'

describe('secrets:sync', () => {
test
.stdout()
.command(['secrets:sync'])
.it('runs hello', ctx => {
expect(ctx.stdout).to.contain('hello world')
})

test
.stdout()
.command(['secrets:sync', '--name', 'jeff'])
.it('runs hello --name jeff', ctx => {
expect(ctx.stdout).to.contain('hello jeff')
})
})
Loading