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

Commit

Permalink
Merge pull request #17 from anomaly/issue-13-sync
Browse files Browse the repository at this point in the history
Adds secrets:sync command.
Implements #13
  • Loading branch information
brendon1555 authored Jan 29, 2021
2 parents f646f2f + 9152e6d commit d7e1a17
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 37 deletions.
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

0 comments on commit d7e1a17

Please sign in to comment.