Skip to content

Commit a7b3c84

Browse files
authored
[socket config auto] Add an auto discovery option (#382)
* [socket config auto] Add defaultOrg config option * Auto-discover and ask to update the defaultOrg and enforcedOrgs config keys
1 parent a2db5d2 commit a7b3c84

32 files changed

+491
-73
lines changed

src/commands/audit-log/cmd-audit-log.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
66
import { handleAuditLog } from './handle-audit-log'
77
import constants from '../../constants'
88
import { commonFlags, outputFlags } from '../../flags'
9+
import { getConfigValue } from '../../utils/config'
910
import { meowOrExit } from '../../utils/meow-with-subcommands'
1011
import { getFlagListOutput } from '../../utils/output-formatting'
1112

@@ -73,9 +74,10 @@ async function run(
7374
})
7475

7576
const { json, markdown, page, perPage, type } = cli.flags
76-
7777
const logType = String(type || '')
78-
const [orgSlug = ''] = cli.input
78+
79+
const defaultOrgSlug = getConfigValue('defaultOrg')
80+
const orgSlug = defaultOrgSlug || cli.input[0] || ''
7981

8082
if (!orgSlug) {
8183
// Use exit status of 2 to indicate incorrect usage, generally invalid
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { stripIndents } from 'common-tags'
2+
import colors from 'yoctocolors-cjs'
3+
4+
import { logger } from '@socketsecurity/registry/lib/logger'
5+
6+
import { handleConfigAuto } from './handle-config-auto'
7+
import constants from '../../constants'
8+
import { commonFlags, outputFlags } from '../../flags'
9+
import { supportedConfigKeys } from '../../utils/config'
10+
import { meowOrExit } from '../../utils/meow-with-subcommands'
11+
import { getFlagListOutput } from '../../utils/output-formatting'
12+
13+
import type { LocalConfig } from '../../utils/config'
14+
import type { CliCommandConfig } from '../../utils/meow-with-subcommands'
15+
16+
const { DRY_RUN_BAIL_TEXT } = constants
17+
18+
const config: CliCommandConfig = {
19+
commandName: 'auto',
20+
description: 'Automatically discover and set the correct value config item',
21+
hidden: false,
22+
flags: {
23+
...commonFlags,
24+
...outputFlags
25+
},
26+
help: (command, config) => `
27+
Usage
28+
$ ${command} <org slug>
29+
30+
Options
31+
${getFlagListOutput(config.flags, 6)}
32+
33+
Attempt to automatically discover the correct value for a certain config key.
34+
35+
For certain keys it will request the value from server, for others it will
36+
reset the value to the default. For some keys this has no effect.
37+
38+
Keys:
39+
40+
${Array.from(supportedConfigKeys.entries())
41+
.map(([key, desc]) => ` - ${key} -- ${desc}`)
42+
.join('\n')}
43+
44+
Examples
45+
$ ${command} auto defaultOrg
46+
`
47+
}
48+
49+
export const cmdConfigAuto = {
50+
description: config.description,
51+
hidden: config.hidden,
52+
run
53+
}
54+
55+
async function run(
56+
argv: string[] | readonly string[],
57+
importMeta: ImportMeta,
58+
{ parentName }: { parentName: string }
59+
): Promise<void> {
60+
const cli = meowOrExit({
61+
argv,
62+
config,
63+
importMeta,
64+
parentName
65+
})
66+
67+
const { json, markdown } = cli.flags
68+
const [key = ''] = cli.input
69+
70+
if (!supportedConfigKeys.has(key as keyof LocalConfig) && key !== 'test') {
71+
// Use exit status of 2 to indicate incorrect usage, generally invalid
72+
// options or missing arguments.
73+
// https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
74+
process.exitCode = 2
75+
logger.fail(stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:
76+
77+
- Config key should be the first arg ${!key ? colors.red('(missing!)') : !supportedConfigKeys.has(key as any) ? colors.red('(invalid config key!)') : colors.green('(ok)')}
78+
`)
79+
return
80+
}
81+
82+
if (cli.flags['dryRun']) {
83+
logger.log(DRY_RUN_BAIL_TEXT)
84+
return
85+
}
86+
87+
await handleConfigAuto({
88+
key: key as keyof LocalConfig,
89+
outputKind: json ? 'json' : markdown ? 'markdown' : 'text'
90+
})
91+
}

src/commands/config/cmd-config-get.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ describe('socket config get', async () => {
2929
Keys:
3030
3131
- apiBaseUrl -- Base URL of the API endpoint
32-
- apiToken -- The API token required to access most API endpoints
3332
- apiProxy -- A proxy through which to access the API
33+
- apiToken -- The API token required to access most API endpoints
34+
- defaultOrg -- The default org slug to use when appropriate; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.
3435
- enforcedOrgs -- Orgs in this list have their security policies enforced on this machine
3536
3637
Examples

src/commands/config/cmd-config-list.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ describe('socket config get', async () => {
3030
Keys:
3131
3232
- apiBaseUrl -- Base URL of the API endpoint
33-
- apiToken -- The API token required to access most API endpoints
3433
- apiProxy -- A proxy through which to access the API
34+
- apiToken -- The API token required to access most API endpoints
35+
- defaultOrg -- The default org slug to use when appropriate; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.
3536
- enforcedOrgs -- Orgs in this list have their security policies enforced on this machine
3637
3738
Examples

src/commands/config/cmd-config-set.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ describe('socket config get', async () => {
3434
Keys:
3535
3636
- apiBaseUrl -- Base URL of the API endpoint
37-
- apiToken -- The API token required to access most API endpoints
3837
- apiProxy -- A proxy through which to access the API
38+
- apiToken -- The API token required to access most API endpoints
39+
- defaultOrg -- The default org slug to use when appropriate; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.
3940
- enforcedOrgs -- Orgs in this list have their security policies enforced on this machine
4041
4142
Examples

src/commands/config/cmd-config-unset.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ describe('socket config unset', async () => {
2929
Keys:
3030
3131
- apiBaseUrl -- Base URL of the API endpoint
32-
- apiToken -- The API token required to access most API endpoints
3332
- apiProxy -- A proxy through which to access the API
33+
- apiToken -- The API token required to access most API endpoints
34+
- defaultOrg -- The default org slug to use when appropriate; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.
3435
- enforcedOrgs -- Orgs in this list have their security policies enforced on this machine
3536
3637
Examples

src/commands/config/cmd-config.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('socket config', async () => {
2121
$ socket config <command>
2222
2323
Commands
24+
auto Automatically discover and set the correct value config item
2425
get Get the value of a local CLI config item
2526
list Show all local CLI config items and their values
2627
set Update the value of a local CLI config item

src/commands/config/cmd-config.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { cmdConfigAuto } from './cmd-config-auto'
12
import { cmdConfigGet } from './cmd-config-get'
23
import { cmdConfigList } from './cmd-config-list'
34
import { cmdConfigSet } from './cmd-config-set'
@@ -14,10 +15,11 @@ export const cmdConfig: CliSubcommand = {
1415
async run(argv, importMeta, { parentName }) {
1516
await meowWithSubcommands(
1617
{
17-
unset: cmdConfigUnset,
18+
auto: cmdConfigAuto,
1819
get: cmdConfigGet,
1920
list: cmdConfigList,
20-
set: cmdConfigSet
21+
set: cmdConfigSet,
22+
unset: cmdConfigUnset
2123
},
2224
{
2325
argv,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { handleApiCall } from '../../utils/api'
2+
import { supportedConfigKeys } from '../../utils/config'
3+
import { getDefaultToken, setupSdk } from '../../utils/sdk'
4+
5+
import type { LocalConfig } from '../../utils/config'
6+
7+
export async function discoverConfigValue(
8+
key: string
9+
): Promise<{ success: boolean; value: unknown; message: string }> {
10+
// This will have to be a specific implementation per key because certain
11+
// keys should request information from particular API endpoints while
12+
// others should simply return their default value, like endpoint URL.
13+
14+
if (!supportedConfigKeys.has(key as keyof LocalConfig)) {
15+
return {
16+
success: false,
17+
value: undefined,
18+
message: 'Requested key is not a valid config key.'
19+
}
20+
}
21+
22+
if (key === 'apiBaseUrl') {
23+
// Return the default value
24+
return {
25+
success: false,
26+
value: undefined,
27+
message:
28+
"If you're unsure about the base endpoint URL then simply unset it."
29+
}
30+
}
31+
32+
if (key === 'apiProxy') {
33+
// I don't think we can auto-discover this with any order of reliability..?
34+
return {
35+
success: false,
36+
value: undefined,
37+
message:
38+
'When uncertain, unset this key. Otherwise ask your network administrator.'
39+
}
40+
}
41+
42+
if (key === 'apiToken') {
43+
return {
44+
success: false,
45+
value: undefined,
46+
message:
47+
'You can find/create your API token in your Socket dashboard > settings > API tokens.\nYou should then use `socket login` to login instead of this command.'
48+
}
49+
}
50+
51+
if (key === 'defaultOrg') {
52+
const apiToken = getDefaultToken()
53+
if (!apiToken) {
54+
return {
55+
success: false,
56+
value: undefined,
57+
message:
58+
'No API token set, must have a token to resolve its default org.'
59+
}
60+
}
61+
62+
const org = await getDefaultOrgFromToken()
63+
if (!org?.length) {
64+
return {
65+
success: false,
66+
value: undefined,
67+
message:
68+
'Was unable to determine default org for the current API token.'
69+
}
70+
}
71+
72+
if (Array.isArray(org)) {
73+
return {
74+
success: true,
75+
value: org,
76+
message: 'These are the orgs that the current API token can access.'
77+
}
78+
}
79+
80+
return {
81+
success: true,
82+
value: org,
83+
message: 'This is the org that belongs to the current API token.'
84+
}
85+
}
86+
87+
if (key === 'enforcedOrgs') {
88+
const apiToken = getDefaultToken()
89+
if (!apiToken) {
90+
return {
91+
success: false,
92+
value: undefined,
93+
message:
94+
'No API token set, must have a token to resolve orgs to enforce.'
95+
}
96+
}
97+
98+
const orgs = await getEnforceableOrgsFromToken()
99+
if (!orgs?.length) {
100+
return {
101+
success: false,
102+
value: undefined,
103+
message:
104+
'Was unable to determine any orgs to enforce for the current API token.'
105+
}
106+
}
107+
108+
return {
109+
success: true,
110+
value: orgs,
111+
message: 'These are the orgs whose security policy you can enforce.'
112+
}
113+
}
114+
115+
if (key === 'test') {
116+
return {
117+
success: false,
118+
value: undefined,
119+
message: ''
120+
}
121+
}
122+
123+
// Mostly to please TS, because we're not telling it `key` is keyof LocalConfig
124+
return {
125+
success: false,
126+
value: undefined,
127+
message: 'unreachable?'
128+
}
129+
}
130+
131+
async function getDefaultOrgFromToken(): Promise<
132+
string[] | string | undefined
133+
> {
134+
const sockSdk = await setupSdk()
135+
const result = await handleApiCall(
136+
sockSdk.getOrganizations(),
137+
'looking up organizations'
138+
)
139+
140+
if (result.success) {
141+
const arr = Array.from(Object.values(result.data.organizations)).map(
142+
({ slug }) => slug
143+
)
144+
if (arr.length === 0) {
145+
return undefined
146+
}
147+
if (arr.length === 1) {
148+
return arr[0]
149+
}
150+
return arr
151+
}
152+
153+
return undefined
154+
}
155+
156+
async function getEnforceableOrgsFromToken(): Promise<string[] | undefined> {
157+
const sockSdk = await setupSdk()
158+
const result = await handleApiCall(
159+
sockSdk.getOrganizations(),
160+
'looking up organizations'
161+
)
162+
163+
if (result.success) {
164+
const arr = Array.from(Object.values(result.data.organizations)).map(
165+
({ slug }) => slug
166+
)
167+
if (arr.length === 0) {
168+
return undefined
169+
}
170+
return arr
171+
}
172+
173+
return undefined
174+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { discoverConfigValue } from './discover-config-value'
2+
import { outputConfigAuto } from './output-config-auto'
3+
4+
import type { LocalConfig } from '../../utils/config'
5+
6+
export async function handleConfigAuto({
7+
key,
8+
outputKind
9+
}: {
10+
key: keyof LocalConfig
11+
outputKind: 'json' | 'markdown' | 'text'
12+
}) {
13+
const result = await discoverConfigValue(key)
14+
15+
await outputConfigAuto(key, result, outputKind)
16+
}

0 commit comments

Comments
 (0)