Skip to content
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
6 changes: 6 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
9 changes: 5 additions & 4 deletions packages/cli/src/deploy/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -55,6 +55,7 @@ async function deployHandler(
...options,
force: true,
endpoint: config.endpoint,
apiKey: config.apiKey ?? undefined,
},
logger
);
Expand Down
15 changes: 11 additions & 4 deletions packages/cli/src/projects/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <project>',
Expand Down Expand Up @@ -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}`);
};
11 changes: 11 additions & 0 deletions packages/cli/src/projects/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type Opts = BaseOpts & {
project?: string;
format?: 'yaml' | 'json' | 'state';
clean?: boolean;
createCredentials?: boolean;
};

// project specific options
Expand Down Expand Up @@ -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: {
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/projects/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type PullOptions = Pick<
| 'confirm'
| 'snapshots'
| 'force'
| 'createCredentials'
>;

const options = [
Expand All @@ -32,6 +33,7 @@ const options = [
o2.alias,
o2.env,
o2.workspace,
o2.creds,

// general options
o.apiKey,
Expand Down
11 changes: 7 additions & 4 deletions packages/cli/src/pull/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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'];
Expand Down
102 changes: 101 additions & 1 deletion packages/cli/test/projects/checkout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ test.serial(
triggers: [],
edges: [],
},
],
],
}),
});

Expand All @@ -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: '<uuid:main>',
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: '<uuid:main>',
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) => {
Expand Down
54 changes: 48 additions & 6 deletions packages/cli/test/pull/handler.test.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -12,27 +19,62 @@ 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(
'redirects to beta handler when openfn.yaml exists in cwd',
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'));
});