diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 4dfe82d45..b6f77d9e6 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # @openfn/cli +## 1.36.1 + +### Patch Changes + +- 68463d8: When running legacy deploy with an openfn.yaml file (ie, github sync), do not generate credentials.yaml by default" + ## 1.36.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 119b43651..00007d2db 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openfn/cli", - "version": "1.36.0", + "version": "1.36.1", "description": "CLI devtools for the OpenFn toolchain", "engines": { "node": ">=18", diff --git a/packages/cli/src/deploy/handler.ts b/packages/cli/src/deploy/handler.ts index ea4d5a344..63bfaae66 100644 --- a/packages/cli/src/deploy/handler.ts +++ b/packages/cli/src/deploy/handler.ts @@ -41,10 +41,10 @@ async function deployHandler( 'openfn.yaml' ); if (!process.env.PREFER_LEGACY_SYNC && (await fileExists(v2ConfigPath))) { - // override endpoint with one from openfn.yaml - const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); - if (config?.project?.endpoint) { - config.endpoint = config.project.endpoint; + // default endpoint to one from openfn.yaml + const v2config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); + if (!config.endpoint && v2config?.project?.endpoint) { + config.endpoint = v2config.project.endpoint; } logger.always( @@ -55,6 +55,7 @@ async function deployHandler( ...options, force: true, endpoint: config.endpoint, + apiKey: config.apiKey ?? undefined, }, logger ); diff --git a/packages/cli/src/projects/checkout.ts b/packages/cli/src/projects/checkout.ts index c81351a85..883feb128 100644 --- a/packages/cli/src/projects/checkout.ts +++ b/packages/cli/src/projects/checkout.ts @@ -19,10 +19,16 @@ import { createProjectCredentials } from './create-credentials'; export type CheckoutOptions = Pick< Opts, - 'command' | 'project' | 'workspace' | 'log' | 'clean' | 'force' + | 'command' + | 'project' + | 'workspace' + | 'log' + | 'clean' + | 'force' + | 'createCredentials' >; -const options = [o.log, po.workspace, po.clean, o.force]; +const options = [o.log, po.workspace, po.clean, o.force, po.creds]; const command: yargs.CommandModule = { command: 'checkout ', @@ -125,8 +131,9 @@ export const handler = async (options: CheckoutOptions, logger?: Logger) => { logger?.warn('WARNING! No content for file', f); } } - - createProjectCredentials(workspacePath, switchProject, logger); + if (options.createCredentials) { + createProjectCredentials(workspacePath, switchProject, logger); + } logger?.success(`Expanded project to ${workspacePath}`); }; diff --git a/packages/cli/src/projects/options.ts b/packages/cli/src/projects/options.ts index d518fa854..ba5ecbfd4 100644 --- a/packages/cli/src/projects/options.ts +++ b/packages/cli/src/projects/options.ts @@ -11,6 +11,7 @@ export type Opts = BaseOpts & { project?: string; format?: 'yaml' | 'json' | 'state'; clean?: boolean; + createCredentials?: boolean; }; // project specific options @@ -39,6 +40,16 @@ export const clean: CLIOption = { }, }; +export const creds: CLIOption = { + name: 'create-credentials', + yargs: { + boolean: true, + default: true, + description: + 'Create a credentials.yaml file and intialize with empty values', + }, +}; + export const dryRun: CLIOption = { name: 'dryRun', yargs: { diff --git a/packages/cli/src/projects/pull.ts b/packages/cli/src/projects/pull.ts index f745dd7cf..5de9bf09c 100644 --- a/packages/cli/src/projects/pull.ts +++ b/packages/cli/src/projects/pull.ts @@ -24,6 +24,7 @@ export type PullOptions = Pick< | 'confirm' | 'snapshots' | 'force' + | 'createCredentials' >; const options = [ @@ -32,6 +33,7 @@ const options = [ o2.alias, o2.env, o2.workspace, + o2.creds, // general options o.apiKey, diff --git a/packages/cli/src/pull/handler.ts b/packages/cli/src/pull/handler.ts index 2fc6dd503..dadfc04a1 100644 --- a/packages/cli/src/pull/handler.ts +++ b/packages/cli/src/pull/handler.ts @@ -27,10 +27,10 @@ async function pullHandler(options: PullOptions, logger: Logger) { 'openfn.yaml' ); if (!process.env.PREFER_LEGACY_SYNC && (await fileExists(v2ConfigPath))) { - // override endpoint with one from openfn.yaml - const config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); - if (config?.project?.endpoint) { - config.endpoint = config.project.endpoint; + // default endpoint to one from openfn.yaml + const v2config = yamlToJson(await fs.readFile(v2ConfigPath, 'utf-8')); + if (!config.endpoint && v2config?.project?.endpoint) { + config.endpoint = v2config.project.endpoint; } logger.always( @@ -42,10 +42,13 @@ async function pullHandler(options: PullOptions, logger: Logger) { project: options.projectId, force: true, endpoint: config.endpoint, + apiKey: config.apiKey ?? undefined, + createCredentials: false, }, logger ); } + if (process.env['OPENFN_API_KEY']) { logger.info('Using OPENFN_API_KEY environment variable'); config.apiKey = process.env['OPENFN_API_KEY']; diff --git a/packages/cli/test/projects/checkout.test.ts b/packages/cli/test/projects/checkout.test.ts index 4479180b1..3a110c5bf 100644 --- a/packages/cli/test/projects/checkout.test.ts +++ b/packages/cli/test/projects/checkout.test.ts @@ -561,7 +561,7 @@ test.serial( triggers: [], edges: [], }, - ], + ], }), }); @@ -580,6 +580,106 @@ test.serial( } ); +test.serial('checkout: creates credentials.yaml', async (t) => { + mock({ + '/ws2/workflows': {}, + '/ws2/openfn.yaml': jsonToYaml({ + project: { id: 'main-project' }, + }), + '/ws2/.projects/main-project@server.yaml': jsonToYaml({ + id: '', + name: 'Main Project', + project_credentials: [ + { + id: 'cred-uuid', + name: 'my-credential', + owner: 'alice', + }, + ], + workflows: [ + { + name: 'My Workflow', + jobs: [ + { + name: 'Run Job', + body: 'fn(s => s)', + adaptor: '@openfn/language-http@latest', + project_credential_id: 'cred-uuid', + }, + ], + triggers: [], + edges: [], + }, + ], + }), + }); + + t.false(fs.existsSync('/ws2/credentials.yaml')); + + await checkoutHandler( + { + command: 'project-checkout', + project: 'main-project', + workspace: '/ws2', + createCredentials: true, + }, + logger + ); + + t.true(fs.existsSync('/ws2/credentials.yaml')); + + const creds = yamlToJson(fs.readFileSync('/ws2/credentials.yaml', 'utf8')); + t.deepEqual(creds, { 'alice|my-credential': {} }); +}); + +test.serial('checkout: do not create credentials.yaml', async (t) => { + mock({ + '/ws2/workflows': {}, + '/ws2/openfn.yaml': jsonToYaml({ + project: { id: 'main-project' }, + }), + '/ws2/.projects/main-project@server.yaml': jsonToYaml({ + id: '', + name: 'Main Project', + project_credentials: [ + { + id: 'cred-uuid', + name: 'my-credential', + owner: 'alice', + }, + ], + workflows: [ + { + name: 'My Workflow', + jobs: [ + { + name: 'Run Job', + body: 'fn(s => s)', + adaptor: '@openfn/language-http@latest', + project_credential_id: 'cred-uuid', + }, + ], + triggers: [], + edges: [], + }, + ], + }), + }); + + t.false(fs.existsSync('/ws2/credentials.yaml')); + + await checkoutHandler( + { + command: 'project-checkout', + project: 'main-project', + workspace: '/ws2', + createCredentials: false, + }, + logger + ); + + t.false(fs.existsSync('/ws2/credentials.yaml')); +}); test.serial( 'checkout: removes workflow directory when workflow is deleted on server', async (t) => { diff --git a/packages/cli/test/pull/handler.test.ts b/packages/cli/test/pull/handler.test.ts index 3ef838dff..472b62371 100644 --- a/packages/cli/test/pull/handler.test.ts +++ b/packages/cli/test/pull/handler.test.ts @@ -1,8 +1,15 @@ import test from 'ava'; import mockfs from 'mock-fs'; +import fs from 'node:fs'; +import { MockAgent, setGlobalDispatcher } from 'undici'; import { createMockLogger } from '@openfn/logger'; + import pullHandler from '../../src/pull/handler'; import { PullOptions } from '../../src/pull/command'; +import { myProject_v1 } from '../projects/fixtures'; + +const ENDPOINT = 'https://app.openfn.org'; +const PROJECT_UUID = 'e16c5f09-f0cb-4ba7-a4c2-73fcb2f29d00'; test.beforeEach(() => { mockfs.restore(); @@ -12,14 +19,31 @@ test.afterEach(() => { mockfs.restore(); }); +let mockAgent = new MockAgent(); +mockAgent.disableNetConnect(); +setGlobalDispatcher(mockAgent); + +test.before(() => { + const mockPool = mockAgent.get(ENDPOINT); + mockPool + .intercept({ + path: `/api/provision/${PROJECT_UUID}?`, + method: 'GET', + }) + .reply(200, { + data: myProject_v1, + }) + .persist(); +}); + const options: PullOptions = { beta: false, command: 'pull', - projectPath: './project.yaml', - configPath: './config.json', - projectId: 'abc-123', + configPath: '/tmp/config.json', + projectId: PROJECT_UUID, confirm: false, snapshots: [], + workspace: '/tmp', // needed in tests to drive other paths }; test.serial( @@ -27,12 +51,30 @@ test.serial( async (t) => { const logger = createMockLogger('', { level: 'debug' }); mockfs({ - ['./config.json']: `{"apiKey": "123"}`, - ['./openfn.yaml']: '', + ['/tmp/config.json']: `{"apiKey": "123", "endpoint": "${ENDPOINT}"}`, + ['/tmp/openfn.yaml']: ` +project: + endpoint: ${ENDPOINT}`, }); - await t.throwsAsync(() => pullHandler(options, logger)); + await pullHandler(options, logger); + + t.true(fs.existsSync('/tmp/.projects/main@app.openfn.org.yaml')); t.truthy(logger._find('always', /Detected openfn.yaml file/i)); } ); + +test.serial('does not create credentials.yaml when redirecting', async (t) => { + const logger = createMockLogger('', { level: 'debug' }); + mockfs({ + ['/tmp/config.json']: `{"apiKey": "123", "endpoint": "${ENDPOINT}"}`, + ['/tmp/openfn.yaml']: ` +project: + endpoint: ${ENDPOINT}`, + }); + + await pullHandler(options, logger); + + t.false(fs.existsSync('/tmp/credentials.yaml')); +});