Skip to content

Commit c5aa8e7

Browse files
committed
start refactoring deploy
1 parent 9f63e8f commit c5aa8e7

File tree

6 files changed

+182
-209
lines changed

6 files changed

+182
-209
lines changed

packages/cli/src/deploy/beta.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

packages/cli/src/deploy/handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@openfn/deploy';
88
import type { Logger } from '../util/logger';
99
import { DeployOptions } from './command';
10-
import * as beta from './beta';
10+
import * as beta from '../projects/deploy';
1111

1212
export type DeployFn = typeof deploy;
1313

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// beta v2 version of CLI deploy
2+
3+
/**
4+
* New plan for great glory
5+
*
6+
* - from('fs') does NOT take project file into account
7+
* - deploy must first fetch (and ensure no conflcits)
8+
* - deploy must then load the project from disk
9+
* - deploy must then merge into that project
10+
* - then call provisioner
11+
* - finally write to disk
12+
*
13+
*
14+
* PLUS: diff summary (changed workflows and steps)
15+
* PLUS: confirm
16+
* PLUS: dry run
17+
*
18+
*
19+
*
20+
* One possible probllem for deploy
21+
*
22+
* The idea is we fetch the latest server version,
23+
* write that to disk, merge our changes, and push
24+
*
25+
* But what if our project file is ahead of the server? A fetch
26+
* will conflict and we don't want to throw.
27+
*
28+
* The project may be ahead because: a), we checked out another branch
29+
* and stashed changes, b) we can ran some kind of reconcilation/merge,
30+
* c) we did a manual export (take my fs and write it to the project)
31+
*
32+
* So basically when fetching, we need to check for divergence in history.
33+
* When fetching, for each workflow, we need to decide whether to keep or reject the
34+
* server version based on the history.
35+
*
36+
*
37+
*
38+
* This is super complex and we're getting into merge territory
39+
* First priority is: if there's a problem (that's a super difficult thing!) warn the user
40+
* Second priority is: help the user resolve it
41+
*
42+
*
43+
* The local project files are giving me a headache. But we should be strict and say:
44+
* the project is ALWAYS a representation of the remote. It is invalid for that project
45+
* to represent the local system
46+
*
47+
* So this idea that I can "save" the local to the project file is wrong
48+
* The idea thatwhen I checkout, I "stash" to a project file is wrong
49+
*
50+
* I should be able to export a project to any arbitrary file, yes
51+
* And when checking out and there are conflicts, I should be able to create a duplicate
52+
* file to save my changes without git.
53+
* I think that means checkout errors (it detects changes will be lost), but you have the option to
54+
* stash a temporary local project to be checkedout later
55+
*
56+
*
57+
* This clarify and strictness will I think really help
58+
*
59+
* So: the local project is NEVER ahead of the server
60+
* (but what if the user edited it and it is? I think the system igores it and that's just a force push)
61+
*/
62+
63+
import Project from '@openfn/project';
64+
import { DeployConfig, deployProject } from '@openfn/deploy';
65+
import type { Logger } from '../util/logger';
66+
import { Opts } from '../options';
67+
import { loadAppAuthConfig } from './util';
68+
69+
export type DeployOptionsBeta = Required<
70+
Pick<
71+
Opts,
72+
| 'beta'
73+
| 'command'
74+
| 'log'
75+
| 'logJson'
76+
| 'apiKey'
77+
| 'endpoint'
78+
| 'path'
79+
| 'workspace'
80+
>
81+
>;
82+
83+
export async function handler(options: DeployOptionsBeta, logger: Logger) {
84+
const config = loadAppAuthConfig(options, logger);
85+
86+
// First step, fetch the latest version and write
87+
// this may throw!
88+
try {
89+
await fetch(options, logger);
90+
} catch (e) {
91+
// If fetch failed because of compatiblity, what do we do?
92+
//
93+
// Basically we failed to write to the local project file
94+
// If -f is true, do we:
95+
// a) force-fetch the latest project
96+
// b) or force merge into the old project, and then force push?
97+
//
98+
// The file system may be in a real mess if fs, project and app are all diverged!
99+
// So I think we:
100+
// Log an error: the server has diverged from your local copy
101+
// Run fetch to resolve the conflict (it'll throw too!)
102+
// Pass -f to ignore your local project and pull the latest app changes
103+
// (changes shouldn't be lost here, because its the file system that's kind)
104+
//
105+
// Actually, the FS is king here.
106+
//
107+
// What if:
108+
// Locally I've changed workflow A
109+
// Remove has changed workflow B
110+
// I basically want to keep my workflow A changes and keep the workflow B changes
111+
// But if we force, we'll force our local workflow into the project, overriding it
112+
// Gods its complicated
113+
// What I think you actually want to do is:
114+
// force pull the remote version
115+
// merge only your changed workflows onto the remote
116+
// but merge doesn't work like that
117+
// So either I need merge to be able to merge the fs with a project (sort of like an expand-and-merge)
118+
// Or deploy should accept a list of workflows (only apply these workflows)
119+
// The problem with the deploy is that the local fs will still be out of date
120+
//
121+
// What about openfn project reconcile
122+
// This will fetch the remote project
123+
// check it out into your fs
124+
// any changed workflows you'll be promoted to:
125+
// - keep local
126+
// - keep app
127+
// - keep both
128+
// if keep both, two folders will be created. The user must manually merge
129+
// this leaves you with a file system that can either be merged, deployed or exported
130+
}
131+
132+
// TMP use options.path to set the directory for now
133+
// We'll need to manage this a bit better
134+
const project = await Project.from('fs', { root: options.workspace || '.' });
135+
// TODO: work out if there's any diff
136+
137+
// generate state for the provisioner
138+
const state = project.serialize('state', { format: 'json' });
139+
140+
logger.debug('Converted local project to app state:');
141+
logger.debug(JSON.stringify(state, null, 2));
142+
143+
// TODO not totally sold on endpoint handling right now
144+
config.endpoint ??= project.openfn?.endpoint!;
145+
146+
logger.info('Sending project to app...');
147+
148+
// TODO do I really want to use this deploy function? Is it suitable?
149+
await deployProject(config as DeployConfig, state);
150+
151+
logger.success('Updated project at', config.endpoint);
152+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import test from 'ava';
2+
3+
// what will deploy tests look like?
4+
5+
// deploy a project for the first time (this doesn't work though?)
6+
7+
// deploy a change to a project
8+
9+
// deploy a change to a project but fetch latest first
10+
11+
// throw when trying to deploy to a diverged remote project
12+
13+
// force deploy an incompatible project
14+
15+
// don't post the final version if dry-run is set
16+
17+
// TODO diff + confirm

packages/project/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ A single Project can be Checked Out to disk at a time, meaning its source workfl
88

99
A Workspace is a set of related Projects , including a Project and its associated Sandboxes, or a Project deployed to apps in multiple web domains
1010

11+
## Structure and Artifects
12+
13+
openfn.yaml
14+
15+
project file
16+
17+
sort of a mix of project.yaml, state.json and config.json
18+
19+
This is strictly a representation of a server-side project, it's like the last-sync-state. CLI-only or offline projects do not have one.
20+
21+
It's also a portable representation of the project
22+
1123
### Serializing and Parsing
1224

1325
The main idea of Projects is that a Project represents a set of OpenFn workflows defined in any format and present a standard JS-friendly interface to manipulate and reason about them.

0 commit comments

Comments
 (0)