@alwaysai/device-agent 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/config.d.ts +0 -1
- package/lib/application-control/config.d.ts.map +1 -1
- package/lib/application-control/config.js +15 -29
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts +7 -3
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +71 -35
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.d.ts +2 -0
- package/lib/application-control/environment-variables.test.d.ts.map +1 -0
- package/lib/application-control/environment-variables.test.js +163 -0
- package/lib/application-control/environment-variables.test.js.map +1 -0
- package/lib/application-control/index.d.ts +3 -3
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +1 -3
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/models.d.ts +0 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +12 -26
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +3 -0
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +19 -1
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +2 -2
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +6 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +201 -151
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +3 -0
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +23 -7
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.d.ts +2 -0
- package/lib/cloud-connection/live-updates-handler.test.d.ts.map +1 -0
- package/lib/cloud-connection/live-updates-handler.test.js +57 -0
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.d.ts +11 -3
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +22 -7
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +313 -228
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/shadow.js +1 -1
- package/lib/cloud-connection/shadow.js.map +1 -1
- package/lib/environment.d.ts +1 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +2 -1
- package/lib/environment.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +3 -1
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts +1 -1
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +32 -5
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +4 -1
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +6 -1
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/shadow.d.ts +7 -0
- package/lib/subcommands/app/shadow.d.ts.map +1 -0
- package/lib/subcommands/app/shadow.js +48 -0
- package/lib/subcommands/app/shadow.js.map +1 -0
- package/lib/subcommands/app/version.js +2 -2
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/util/cloud-mode-ready.d.ts +2 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -0
- package/lib/util/cloud-mode-ready.js +22 -0
- package/lib/util/cloud-mode-ready.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +2 -2
- package/src/application-control/config.ts +30 -31
- package/src/application-control/environment-variables.test.ts +171 -0
- package/src/application-control/environment-variables.ts +102 -43
- package/src/application-control/index.ts +3 -9
- package/src/application-control/models.ts +14 -29
- package/src/application-control/status.ts +20 -0
- package/src/application-control/utils.ts +4 -2
- package/src/cloud-connection/device-agent-cloud-connection.ts +220 -155
- package/src/cloud-connection/live-updates-handler.test.ts +68 -0
- package/src/cloud-connection/live-updates-handler.ts +30 -7
- package/src/cloud-connection/shadow-handler.test.ts +329 -239
- package/src/cloud-connection/shadow-handler.ts +38 -12
- package/src/cloud-connection/shadow.ts +1 -1
- package/src/environment.ts +2 -0
- package/src/infrastructure/agent-config.ts +1 -1
- package/src/subcommands/app/env-vars.ts +38 -8
- package/src/subcommands/app/index.ts +4 -1
- package/src/subcommands/app/models.ts +10 -1
- package/src/subcommands/app/shadow.ts +48 -0
- package/src/subcommands/app/version.ts +2 -2
- package/src/util/cloud-mode-ready.ts +23 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
2
|
+
import { readDockerCompose, writeDockerCompose } from './config';
|
|
3
|
+
import { getAllEnvs, setEnv } from './environment-variables';
|
|
4
|
+
import { isAppStarted, restartApp } from './status';
|
|
5
|
+
import { buildApp, requireAppReady } from './utils';
|
|
6
|
+
|
|
7
|
+
jest.mock('./config');
|
|
8
|
+
|
|
9
|
+
jest.mock('./utils');
|
|
10
|
+
jest.mocked(requireAppReady).mockResolvedValue();
|
|
11
|
+
jest.mocked(buildApp).mockResolvedValue();
|
|
12
|
+
|
|
13
|
+
jest.mock('./status');
|
|
14
|
+
jest.mocked(isAppStarted).mockResolvedValue(true);
|
|
15
|
+
jest.mocked(restartApp).mockResolvedValue();
|
|
16
|
+
|
|
17
|
+
jest.mock('../infrastructure/agent-config');
|
|
18
|
+
const mockSetAppInstalling = jest.fn().mockResolvedValue({});
|
|
19
|
+
const mockSetAppInstalled = jest.fn().mockResolvedValue({});
|
|
20
|
+
const mockGetAppVersion = jest.fn().mockResolvedValue('test-version');
|
|
21
|
+
jest.mocked(AgentConfigFile as jest.Mock).mockReturnValue({
|
|
22
|
+
setAppInstalling: mockSetAppInstalling,
|
|
23
|
+
setAppInstalled: mockSetAppInstalled,
|
|
24
|
+
getAppVersion: mockGetAppVersion
|
|
25
|
+
});
|
|
26
|
+
const projectId1 = 'test-project';
|
|
27
|
+
|
|
28
|
+
describe('Test environment variable get and set', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
describe('Test getAllEnvs()', () => {
|
|
33
|
+
test('get all envs with one service and no envs', async () => {
|
|
34
|
+
const compose = {
|
|
35
|
+
services: {
|
|
36
|
+
alwaysai: {}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
jest.mocked(readDockerCompose).mockResolvedValue(compose);
|
|
40
|
+
|
|
41
|
+
const envVars = await getAllEnvs({ projectId: projectId1 });
|
|
42
|
+
expect(jest.mocked(readDockerCompose)).toBeCalledWith({
|
|
43
|
+
projectId: projectId1
|
|
44
|
+
});
|
|
45
|
+
expect(envVars).toEqual({
|
|
46
|
+
alwaysai: {}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// TODO: Add test with env file
|
|
50
|
+
test('get all envs with one service and two envs', async () => {
|
|
51
|
+
const compose = {
|
|
52
|
+
services: {
|
|
53
|
+
alwaysai: {
|
|
54
|
+
environment: ['TEST=1', 'TEST2=2']
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
jest.mocked(readDockerCompose).mockResolvedValue(compose);
|
|
59
|
+
|
|
60
|
+
const envVars = await getAllEnvs({ projectId: projectId1 });
|
|
61
|
+
expect(jest.mocked(readDockerCompose)).toBeCalledWith({
|
|
62
|
+
projectId: projectId1
|
|
63
|
+
});
|
|
64
|
+
expect(envVars).toEqual({
|
|
65
|
+
alwaysai: { TEST: '1', TEST2: '2' }
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
test('get all envs with two services and envs', async () => {
|
|
69
|
+
const compose = {
|
|
70
|
+
services: {
|
|
71
|
+
alwaysai: {
|
|
72
|
+
environment: ['TEST=3', 'TEST2=4']
|
|
73
|
+
},
|
|
74
|
+
edgeiq: {
|
|
75
|
+
environment: ['TEST3=5', 'TEST4=6']
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
jest.mocked(readDockerCompose).mockResolvedValue(compose);
|
|
80
|
+
|
|
81
|
+
const envVars = await getAllEnvs({ projectId: projectId1 });
|
|
82
|
+
expect(jest.mocked(readDockerCompose)).toBeCalledWith({
|
|
83
|
+
projectId: projectId1
|
|
84
|
+
});
|
|
85
|
+
expect(envVars).toEqual({
|
|
86
|
+
alwaysai: { TEST: '3', TEST2: '4' },
|
|
87
|
+
edgeiq: { TEST3: '5', TEST4: '6' }
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('Test setEnv()', () => {
|
|
92
|
+
test('Set one env', async () => {
|
|
93
|
+
const compose = {
|
|
94
|
+
services: {
|
|
95
|
+
alwaysai: {}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
jest.mocked(readDockerCompose).mockResolvedValue(compose);
|
|
99
|
+
|
|
100
|
+
const envVars = { alwaysai: { TEST: '1' } };
|
|
101
|
+
await setEnv({ projectId: projectId1, envVars });
|
|
102
|
+
expect(jest.mocked(readDockerCompose)).toBeCalledWith({
|
|
103
|
+
projectId: projectId1
|
|
104
|
+
});
|
|
105
|
+
expect(jest.mocked(writeDockerCompose)).toBeCalledWith({
|
|
106
|
+
projectId: projectId1,
|
|
107
|
+
dockerCompose: {
|
|
108
|
+
services: {
|
|
109
|
+
alwaysai: {
|
|
110
|
+
environment: ['TEST=1']
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
test('Update one env', async () => {
|
|
117
|
+
const environment = ['TEST1=1', 'TEST2=2'];
|
|
118
|
+
const compose = {
|
|
119
|
+
services: {
|
|
120
|
+
alwaysai: { environment }
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
jest.mocked(readDockerCompose).mockResolvedValue(compose);
|
|
124
|
+
|
|
125
|
+
const envVars = { alwaysai: { TEST1: '2' } };
|
|
126
|
+
await setEnv({ projectId: projectId1, envVars });
|
|
127
|
+
expect(jest.mocked(readDockerCompose)).toBeCalledWith({
|
|
128
|
+
projectId: projectId1
|
|
129
|
+
});
|
|
130
|
+
expect(jest.mocked(writeDockerCompose)).toBeCalledWith({
|
|
131
|
+
projectId: projectId1,
|
|
132
|
+
dockerCompose: {
|
|
133
|
+
services: {
|
|
134
|
+
alwaysai: {
|
|
135
|
+
environment: ['TEST1=1', 'TEST2=2', 'TEST1=2']
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
test('Update one env with two services', async () => {
|
|
142
|
+
const environment = ['TEST1=3', 'TEST2=4'];
|
|
143
|
+
const compose = {
|
|
144
|
+
services: {
|
|
145
|
+
alwaysai: { environment },
|
|
146
|
+
other: { environment: ['OTHER_TEST=1'] }
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
jest.mocked(readDockerCompose).mockResolvedValue(compose);
|
|
150
|
+
|
|
151
|
+
const envVars = { alwaysai: { TEST1: '2' } };
|
|
152
|
+
await setEnv({ projectId: projectId1, envVars });
|
|
153
|
+
expect(jest.mocked(readDockerCompose)).toBeCalledWith({
|
|
154
|
+
projectId: projectId1
|
|
155
|
+
});
|
|
156
|
+
expect(jest.mocked(writeDockerCompose)).toBeCalledWith({
|
|
157
|
+
projectId: projectId1,
|
|
158
|
+
dockerCompose: {
|
|
159
|
+
services: {
|
|
160
|
+
alwaysai: {
|
|
161
|
+
environment: ['TEST1=3', 'TEST2=4', 'TEST1=2']
|
|
162
|
+
},
|
|
163
|
+
other: {
|
|
164
|
+
environment: ['OTHER_TEST=1']
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -1,72 +1,131 @@
|
|
|
1
1
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
2
|
-
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
3
2
|
import { readDockerCompose, writeDockerCompose } from './config';
|
|
4
|
-
import { getAppDir } from './utils';
|
|
3
|
+
import { buildApp, getAppDir, requireAppReady } from './utils';
|
|
4
|
+
import { logger } from '../util/logger';
|
|
5
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
6
|
+
import { isAppStarted, restartApp } from './status';
|
|
7
|
+
|
|
8
|
+
export interface EnvVars {
|
|
9
|
+
[service: string]: {
|
|
10
|
+
[name: string]: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function setEnv(props: { projectId: string; envVars: EnvVars }) {
|
|
15
|
+
const { projectId, envVars } = props;
|
|
16
|
+
await requireAppReady({ projectId });
|
|
17
|
+
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
18
|
+
projectId
|
|
19
|
+
});
|
|
20
|
+
await AgentConfigFile().setAppInstalling({
|
|
21
|
+
projectId,
|
|
22
|
+
version: appReleaseHash
|
|
23
|
+
});
|
|
5
24
|
|
|
6
|
-
export async function setEnv(props: {
|
|
7
|
-
projectId: string;
|
|
8
|
-
vars: string[];
|
|
9
|
-
service?: string;
|
|
10
|
-
}) {
|
|
11
|
-
const { projectId, vars } = props;
|
|
12
|
-
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
13
|
-
throw new Error(`App ${projectId} is not ready!`);
|
|
14
|
-
}
|
|
15
25
|
const composeParsed = await readDockerCompose({ projectId });
|
|
16
|
-
if ('services' in composeParsed) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
if (!('services' in composeParsed)) {
|
|
27
|
+
throw new Error(`Docker compose file for ${projectId} has no services!`);
|
|
28
|
+
}
|
|
29
|
+
const services: string[] = Object.keys(envVars);
|
|
30
|
+
for (const s of services) {
|
|
31
|
+
if (!Object.keys(composeParsed['services']).includes(s)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Service ${s} not found in ${JSON.stringify(
|
|
34
|
+
composeParsed['services'],
|
|
35
|
+
null,
|
|
36
|
+
2
|
|
37
|
+
)}`
|
|
38
|
+
);
|
|
24
39
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
const envVarList: string[] = [];
|
|
41
|
+
for (const envVar of Object.keys(envVars[s])) {
|
|
42
|
+
envVarList.push(`${envVar}=${envVars[s][envVar]}`);
|
|
43
|
+
}
|
|
44
|
+
const service = composeParsed['services'][s];
|
|
45
|
+
// The environment field overrides the env files, so by appending to
|
|
46
|
+
// the environment list we can assure the value will be used over
|
|
47
|
+
// the env_file.
|
|
48
|
+
|
|
49
|
+
// NOTE: We are only appending the new values to the end of the
|
|
50
|
+
// environment variable list, which will override but not replace
|
|
51
|
+
// previous instances of the same environment variable.
|
|
52
|
+
if ('environment' in service) {
|
|
53
|
+
const environment: string[] = service['environment'];
|
|
54
|
+
composeParsed['services'][s]['environment'] =
|
|
55
|
+
environment.concat(envVarList);
|
|
56
|
+
} else {
|
|
57
|
+
composeParsed['services'][s]['environment'] = envVarList;
|
|
35
58
|
}
|
|
36
59
|
}
|
|
37
60
|
await writeDockerCompose({ projectId, dockerCompose: composeParsed });
|
|
61
|
+
|
|
62
|
+
const appDir = getAppDir(projectId);
|
|
63
|
+
await buildApp({ appDir });
|
|
64
|
+
|
|
65
|
+
await AgentConfigFile().setAppInstalled({
|
|
66
|
+
projectId,
|
|
67
|
+
version: appReleaseHash
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (await isAppStarted({ projectId })) {
|
|
71
|
+
await restartApp({ projectId });
|
|
72
|
+
}
|
|
73
|
+
logger.info(
|
|
74
|
+
`Updated environment variables for ${projectId}: ${JSON.stringify(
|
|
75
|
+
envVars,
|
|
76
|
+
null,
|
|
77
|
+
2
|
|
78
|
+
)}`
|
|
79
|
+
);
|
|
38
80
|
}
|
|
39
81
|
|
|
40
|
-
|
|
82
|
+
async function convertStringEnvsToKeyVal(stringEnvs: string[]) {
|
|
83
|
+
const envVars = {};
|
|
84
|
+
stringEnvs.forEach((env: string) => {
|
|
85
|
+
const keyVal = env.split('=');
|
|
86
|
+
envVars[keyVal[0]] = keyVal[1];
|
|
87
|
+
});
|
|
88
|
+
return envVars;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function getAllEnvs(props: {
|
|
92
|
+
projectId: string;
|
|
93
|
+
}): Promise<EnvVars> {
|
|
41
94
|
const { projectId } = props;
|
|
42
|
-
|
|
43
|
-
throw new Error(`App ${projectId} is not ready!`);
|
|
44
|
-
}
|
|
95
|
+
await requireAppReady({ projectId });
|
|
45
96
|
const appDir = getAppDir(projectId);
|
|
46
97
|
const spawner = JsSpawner({ path: appDir });
|
|
47
|
-
const envVars = {};
|
|
98
|
+
const envVars: EnvVars = {};
|
|
48
99
|
const composeParsed = await readDockerCompose({ projectId });
|
|
49
100
|
if ('services' in composeParsed) {
|
|
50
101
|
const services = Object.keys(composeParsed['services']);
|
|
51
102
|
for (const s of services) {
|
|
52
|
-
envVars[s] =
|
|
103
|
+
envVars[s] = {};
|
|
53
104
|
const service = composeParsed['services'][s];
|
|
105
|
+
// Read env_file first, since it is lowest on the hierarchy
|
|
54
106
|
if ('env_file' in service) {
|
|
55
107
|
const envFiles: string[] = service['env_file'];
|
|
56
108
|
for (const ef of envFiles) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
if (!(await spawner.exists(ef))) {
|
|
110
|
+
logger.error(
|
|
111
|
+
`Skipping env file ${ef} for service ${s}: not found!`
|
|
112
|
+
);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
let envFileLines = (await spawner.readFile(ef)).split('\n');
|
|
116
|
+
// Filter out empty lines and comment lines
|
|
117
|
+
envFileLines = envFileLines.filter((v) => {
|
|
118
|
+
return v !== '' && !v.includes('#');
|
|
119
|
+
});
|
|
120
|
+
const newEnvVars = await convertStringEnvsToKeyVal(envFileLines);
|
|
121
|
+
envVars[s] = { ...envVars[s], ...newEnvVars };
|
|
60
122
|
}
|
|
61
123
|
}
|
|
62
124
|
if ('environment' in service) {
|
|
63
125
|
const environment: string[] = service['environment'];
|
|
64
|
-
|
|
126
|
+
const newEnvVars = await convertStringEnvsToKeyVal(environment);
|
|
127
|
+
envVars[s] = { ...envVars[s], ...newEnvVars };
|
|
65
128
|
}
|
|
66
|
-
// Filter out empty lines and comment lines
|
|
67
|
-
envVars[s] = envVars[s].filter((v) => {
|
|
68
|
-
return v !== '' && !v.includes('#');
|
|
69
|
-
});
|
|
70
129
|
}
|
|
71
130
|
}
|
|
72
131
|
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readAppCfgFile,
|
|
3
|
-
updateAppCfg,
|
|
4
|
-
readDockerCompose,
|
|
5
|
-
writeDockerCompose
|
|
6
|
-
} from './config';
|
|
1
|
+
import { readAppCfgFile, updateAppCfg } from './config';
|
|
7
2
|
import { installApp, uninstallApp } from './install';
|
|
8
3
|
import { rollbackApp } from './backup';
|
|
9
4
|
import {
|
|
@@ -14,13 +9,11 @@ import {
|
|
|
14
9
|
restartApp
|
|
15
10
|
} from './status';
|
|
16
11
|
import { ModelDetails } from './types';
|
|
17
|
-
import { getAllEnvs, setEnv } from './environment-variables';
|
|
12
|
+
import { EnvVars, getAllEnvs, setEnv } from './environment-variables';
|
|
18
13
|
|
|
19
14
|
export {
|
|
20
15
|
readAppCfgFile,
|
|
21
16
|
updateAppCfg,
|
|
22
|
-
readDockerCompose,
|
|
23
|
-
writeDockerCompose,
|
|
24
17
|
installApp,
|
|
25
18
|
uninstallApp,
|
|
26
19
|
rollbackApp,
|
|
@@ -30,6 +23,7 @@ export {
|
|
|
30
23
|
stopApp,
|
|
31
24
|
restartApp,
|
|
32
25
|
ModelDetails,
|
|
26
|
+
EnvVars,
|
|
33
27
|
getAllEnvs,
|
|
34
28
|
setEnv
|
|
35
29
|
};
|
|
@@ -13,8 +13,7 @@ import { join, dirname } from 'path';
|
|
|
13
13
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
14
14
|
import { copyDir } from '../util/copy-dir';
|
|
15
15
|
import { runInDir } from '../util/run-in-dir';
|
|
16
|
-
import { restartApp } from './status';
|
|
17
|
-
|
|
16
|
+
import { isAppStarted, restartApp } from './status';
|
|
18
17
|
import { ModelDetails } from './types';
|
|
19
18
|
import {
|
|
20
19
|
buildApp,
|
|
@@ -24,7 +23,7 @@ import {
|
|
|
24
23
|
} from './utils';
|
|
25
24
|
import { MODEL_JSON_FILE_NAME } from 'alwaysai/lib/core/model';
|
|
26
25
|
import { APP_MODELS_DIRECTORY_NAME } from 'alwaysai/lib/constants';
|
|
27
|
-
import {
|
|
26
|
+
import { writeAppCfgFile } from './config';
|
|
28
27
|
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
29
28
|
|
|
30
29
|
export async function getAppModels(props: { projectId: string }) {
|
|
@@ -158,27 +157,21 @@ export async function installModelsWithPresignedURLs(
|
|
|
158
157
|
export async function updateModelsWithPresignedUrls(props: {
|
|
159
158
|
projectId: string;
|
|
160
159
|
modelInstallPayloads: ModelInstallPayload[];
|
|
161
|
-
appReleaseHash: string;
|
|
162
160
|
newAppCfg: AppConfig;
|
|
163
161
|
}) {
|
|
164
|
-
const { projectId, modelInstallPayloads,
|
|
162
|
+
const { projectId, modelInstallPayloads, newAppCfg } = props;
|
|
165
163
|
logger.info(`Installing models for ${projectId}`);
|
|
166
164
|
const spawner = JsSpawner();
|
|
167
165
|
const appDir = getAppDir(projectId);
|
|
168
166
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
});
|
|
178
|
-
} else {
|
|
179
|
-
throw new Error('Application is not installed!');
|
|
180
|
-
}
|
|
181
|
-
const ogAppCfg = await readAppCfgFile({ projectId });
|
|
167
|
+
await requireAppReady({ projectId });
|
|
168
|
+
const appReleaseHash = await AgentConfigFile().getAppVersion({
|
|
169
|
+
projectId
|
|
170
|
+
});
|
|
171
|
+
await AgentConfigFile().setAppInstalling({
|
|
172
|
+
projectId,
|
|
173
|
+
version: appReleaseHash
|
|
174
|
+
});
|
|
182
175
|
|
|
183
176
|
const ogDir = path.join(appDir, APP_MODELS_DIRECTORY_NAME);
|
|
184
177
|
// Copy all current models to restore dir in case of failure
|
|
@@ -206,19 +199,11 @@ export async function updateModelsWithPresignedUrls(props: {
|
|
|
206
199
|
version: appReleaseHash
|
|
207
200
|
});
|
|
208
201
|
|
|
209
|
-
await
|
|
202
|
+
if (await isAppStarted({ projectId })) {
|
|
203
|
+
await restartApp({ projectId });
|
|
204
|
+
}
|
|
210
205
|
|
|
211
206
|
logger.info(`Models installed for project ${projectId}`);
|
|
212
|
-
/* Leave error handling to higher level so errors are sent to cloud
|
|
213
|
-
} catch (e) {
|
|
214
|
-
logger.error(
|
|
215
|
-
'Error updating app models from presigned URL, restoring models.',
|
|
216
|
-
e.message
|
|
217
|
-
);
|
|
218
|
-
await spawner.rimraf(ogDir);
|
|
219
|
-
await copyDir({ srcPath: restoreDir, destPath: ogDir });
|
|
220
|
-
await writeAppCfgFile({ projectId, appCfg: ogAppCfg });
|
|
221
|
-
*/
|
|
222
207
|
} finally {
|
|
223
208
|
await spawner.rimraf(tmpDir);
|
|
224
209
|
await spawner.rimraf(restoreDir);
|
|
@@ -26,6 +26,8 @@ export async function getAppStatus(props: {
|
|
|
26
26
|
};
|
|
27
27
|
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
28
28
|
// App is being installed or updated
|
|
29
|
+
// FIXME: We may be able to get the service status even
|
|
30
|
+
// though an update is in progress
|
|
29
31
|
return { appDetails, services: [] };
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -95,6 +97,24 @@ export async function getAppStatus(props: {
|
|
|
95
97
|
return { appDetails, services };
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
export async function isAppStarted(props: {
|
|
101
|
+
projectId: string;
|
|
102
|
+
}): Promise<boolean> {
|
|
103
|
+
const { projectId } = props;
|
|
104
|
+
const appStatus = await getAppStatus({ projectId });
|
|
105
|
+
if (appStatus.services.length === 0) {
|
|
106
|
+
// Services list will be empty if app has not yet been started.
|
|
107
|
+
// NOTE: This depends on internal handling in getAppStatus()
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
for (const service of appStatus.services) {
|
|
111
|
+
if (service.state === keyMirrors.appState.stopped) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
98
118
|
export async function getAppLogs(props: {
|
|
99
119
|
projectId: string;
|
|
100
120
|
services?: string[];
|
|
@@ -28,7 +28,7 @@ export async function requireAppInstalled(props: { projectId: string }) {
|
|
|
28
28
|
const { projectId } = props;
|
|
29
29
|
// Ensure an app that is being installed or updated is not modified
|
|
30
30
|
if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
|
|
31
|
-
throw new Error(
|
|
31
|
+
throw new Error(`Application ${projectId} is not installed`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -36,7 +36,9 @@ export async function requireAppReady(props: { projectId: string }) {
|
|
|
36
36
|
const { projectId } = props;
|
|
37
37
|
await requireAppInstalled({ projectId });
|
|
38
38
|
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
39
|
-
throw new Error(
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Application ${projectId} is not done installing or updating`
|
|
41
|
+
);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|