@faable/faable 1.2.7 → 1.2.8
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/dist/api/FaableApi.js +39 -21
- package/dist/api/context.js +2 -3
- package/dist/api/strategies/apikey.js +15 -0
- package/dist/api/userdir_config.js +4 -4
- package/dist/commands/configure/index.js +2 -7
- package/dist/commands/deploy/analyze_package.js +6 -8
- package/dist/commands/deploy/build_project.js +17 -0
- package/dist/commands/deploy/{build_docker.js → bundle_docker.js} +17 -5
- package/dist/commands/deploy/cmd.js +7 -2
- package/dist/commands/deploy/deploy_command.js +39 -0
- package/dist/commands/deploy/index.js +2 -36
- package/dist/commands/deploy/upload_tag.js +8 -4
- package/package.json +2 -1
- package/templates/Dockerfile +3 -11
- package/templates/entrypoint.sh +1 -1
- package/dist/api/authenticateOAuthApp.js +0 -35
package/dist/api/FaableApi.js
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
1
2
|
import { prepare_client } from './client.js';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const e = error;
|
|
10
|
-
if (e.isAxiosError) {
|
|
11
|
-
const res = e.response;
|
|
12
|
-
if (res) {
|
|
13
|
-
throw new Error(`API Error ${res.status}: ${res?.data.message}`);
|
|
4
|
+
function handleError(message) {
|
|
5
|
+
return function (target, propertyKey, descriptor) {
|
|
6
|
+
const method = descriptor.value;
|
|
7
|
+
descriptor.value = async function (...args) {
|
|
8
|
+
try {
|
|
9
|
+
return await method.bind(this).apply(target, args);
|
|
14
10
|
}
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
catch (error) {
|
|
12
|
+
const e = error;
|
|
13
|
+
if (e.isAxiosError) {
|
|
14
|
+
const res = e.response;
|
|
15
|
+
if (res) {
|
|
16
|
+
throw new Error(`FaableApi ${e.config.url} ${res.status}: ${res?.data.message}`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
throw new Error(`FaableApi ${e.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
17
23
|
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const paginate = async (res) => {
|
|
28
|
+
const items = (await res).results;
|
|
29
|
+
return items;
|
|
21
30
|
};
|
|
22
|
-
const
|
|
23
|
-
const items = (await
|
|
31
|
+
const data = async (res) => {
|
|
32
|
+
const items = (await res).data;
|
|
24
33
|
return items;
|
|
25
34
|
};
|
|
26
35
|
class FaableApi {
|
|
@@ -32,14 +41,23 @@ class FaableApi {
|
|
|
32
41
|
return new FaableApi(config);
|
|
33
42
|
}
|
|
34
43
|
async list() {
|
|
35
|
-
return paginate(
|
|
44
|
+
return paginate(data(this.client.get(`/app`)));
|
|
36
45
|
}
|
|
37
46
|
async getBySlug(slug) {
|
|
38
|
-
return
|
|
47
|
+
return data(this.client.get(`/app/slug/${slug}`));
|
|
39
48
|
}
|
|
40
49
|
async getRegistry(app_id) {
|
|
41
|
-
return
|
|
50
|
+
return data(this.client.get(`/app/${app_id}/registry`));
|
|
42
51
|
}
|
|
43
52
|
}
|
|
53
|
+
__decorate([
|
|
54
|
+
handleError()
|
|
55
|
+
], FaableApi.prototype, "list", null);
|
|
56
|
+
__decorate([
|
|
57
|
+
handleError()
|
|
58
|
+
], FaableApi.prototype, "getBySlug", null);
|
|
59
|
+
__decorate([
|
|
60
|
+
handleError()
|
|
61
|
+
], FaableApi.prototype, "getRegistry", null);
|
|
44
62
|
|
|
45
63
|
export { FaableApi };
|
package/dist/api/context.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { FaableApi } from './FaableApi.js';
|
|
2
|
-
import {
|
|
2
|
+
import { apikey } from './strategies/apikey.js';
|
|
3
3
|
|
|
4
4
|
const context = async () => {
|
|
5
|
-
const authStrategy = authenticateOAuthApp;
|
|
6
5
|
const { config } = await import('./userdir_config.js');
|
|
7
6
|
return {
|
|
8
|
-
api: FaableApi.create({ authStrategy, auth: config }),
|
|
7
|
+
api: FaableApi.create({ authStrategy: apikey, auth: config }),
|
|
9
8
|
};
|
|
10
9
|
};
|
|
11
10
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const apikey = (config) => {
|
|
2
|
+
const { apikey } = config;
|
|
3
|
+
if (!apikey) {
|
|
4
|
+
throw new Error("Missing apikey.");
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
headers: async () => {
|
|
8
|
+
return {
|
|
9
|
+
Authorization: `Basic ${Buffer.from(`${apikey}:`).toString("base64")}`,
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export { apikey };
|
|
@@ -11,10 +11,10 @@ if (fs.existsSync(credentials_path)) {
|
|
|
11
11
|
// console.log(creds);
|
|
12
12
|
config.clientId = creds.clientId;
|
|
13
13
|
config.clientSecret = creds.clientSecret;
|
|
14
|
+
config.apikey = creds.apikey;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
16
|
+
config.clientId = process.env.FAABLE_CLIENT_ID || config.clientId;
|
|
17
|
+
config.clientSecret = process.env.FAABLE_CLIENT_SECRET || config.clientSecret;
|
|
18
|
+
config.apikey = process.env.FAABLE_APIKEY || config.apikey;
|
|
19
19
|
|
|
20
20
|
export { config };
|
|
@@ -27,13 +27,8 @@ const configure = {
|
|
|
27
27
|
const response = await prompts([
|
|
28
28
|
{
|
|
29
29
|
type: "text",
|
|
30
|
-
name: "
|
|
31
|
-
message: "What is your Faable
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
type: "text",
|
|
35
|
-
name: "clientSecret",
|
|
36
|
-
message: "What is your Faable clientSecret?",
|
|
30
|
+
name: "apikey",
|
|
31
|
+
message: "What is your Faable ApiKey?",
|
|
37
32
|
},
|
|
38
33
|
]);
|
|
39
34
|
await fs.ensureDir(faable_home);
|
|
@@ -2,7 +2,8 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path__default from 'path';
|
|
3
3
|
import { log } from '../../log.js';
|
|
4
4
|
|
|
5
|
-
const analyze_package = async (
|
|
5
|
+
const analyze_package = async (params) => {
|
|
6
|
+
const workdir = params.workdir;
|
|
6
7
|
const package_file = path__default.join(path__default.resolve(workdir), "package.json");
|
|
7
8
|
log.info(`Loading config from package.json...`);
|
|
8
9
|
const pkg = await fs.readJSON(package_file);
|
|
@@ -11,16 +12,13 @@ const analyze_package = async ({ workdir }) => {
|
|
|
11
12
|
throw new Error("Missing start script");
|
|
12
13
|
}
|
|
13
14
|
// Check if build is required to run
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
hasBuild = true;
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
15
|
+
const build_script = params.build_script || "build";
|
|
16
|
+
let build = pkg?.scripts[build_script];
|
|
17
|
+
if (!build) {
|
|
20
18
|
log.info(`No build script found`);
|
|
21
19
|
}
|
|
22
20
|
return {
|
|
23
|
-
|
|
21
|
+
build_script,
|
|
24
22
|
};
|
|
25
23
|
};
|
|
26
24
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { log } from '../../log.js';
|
|
2
|
+
import { cmd } from './cmd.js';
|
|
3
|
+
|
|
4
|
+
const build_project = async (args) => {
|
|
5
|
+
const cwd = args?.cwd || process.cwd();
|
|
6
|
+
const build_script = args?.build_script || "build";
|
|
7
|
+
args.app;
|
|
8
|
+
log.info(`⚡️ Running build script [${build_script}]...`);
|
|
9
|
+
const timeout = 1000 * 60 * 10; // 10 minute timeout
|
|
10
|
+
await cmd("yarn", ["run", build_script], {
|
|
11
|
+
timeout,
|
|
12
|
+
cwd,
|
|
13
|
+
enableOutput: true,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { build_project };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { log } from '../../log.js';
|
|
1
2
|
import { cmd } from './cmd.js';
|
|
2
3
|
import fs from 'fs-extra';
|
|
3
4
|
import Handlebars from 'handlebars';
|
|
@@ -22,15 +23,26 @@ Handlebars.registerHelper("escape", function (variable) {
|
|
|
22
23
|
// Docker template file
|
|
23
24
|
const docker_template = Handlebars.compile(dockerfile);
|
|
24
25
|
const entrypoint_template = Handlebars.compile(entrypoint);
|
|
25
|
-
const
|
|
26
|
-
const {
|
|
26
|
+
const bundle_docker = async (props) => {
|
|
27
|
+
const { app, workdir, template_context } = props;
|
|
27
28
|
const entrypoint_custom = entrypoint_template(template_context);
|
|
28
29
|
const dockerfile = docker_template({
|
|
29
30
|
...template_context,
|
|
30
31
|
entry_script: entrypoint_custom,
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
log.info(`📦 Packaging inside a docker image`);
|
|
34
|
+
const tagname = app.id;
|
|
35
|
+
const timeout = 10 * 60 * 1000; // 10 minute timeout
|
|
36
|
+
const command = [
|
|
37
|
+
"-c",
|
|
38
|
+
`docker build -t ${tagname} ${workdir} -f -<<EOF\n${dockerfile}\nEOF`,
|
|
39
|
+
];
|
|
40
|
+
console.log(command.join(" "));
|
|
41
|
+
await cmd("/bin/bash", command, { timeout, enableOutput: true });
|
|
42
|
+
log.info(`⚙️ Image ready [tag:${tagname}]`);
|
|
43
|
+
return {
|
|
44
|
+
tagname,
|
|
45
|
+
};
|
|
34
46
|
};
|
|
35
47
|
|
|
36
|
-
export {
|
|
48
|
+
export { bundle_docker };
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { spawn } from 'promisify-child-process';
|
|
2
2
|
|
|
3
|
-
const cmd = async (cmd, args, params
|
|
4
|
-
|
|
3
|
+
const cmd = async (cmd, args, params) => {
|
|
4
|
+
// Defaults
|
|
5
|
+
const enableOutput = params?.enableOutput || false;
|
|
6
|
+
const timeout = params?.timeout;
|
|
7
|
+
const cwd = params?.cwd;
|
|
5
8
|
const child = spawn(cmd, args, {
|
|
6
9
|
encoding: "utf8",
|
|
7
10
|
stdio: enableOutput ? "inherit" : "pipe",
|
|
11
|
+
timeout,
|
|
12
|
+
cwd,
|
|
8
13
|
});
|
|
9
14
|
const out_data = [];
|
|
10
15
|
child.stderr?.on("data", (data) => {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { log } from '../../log.js';
|
|
2
|
+
import { bundle_docker } from './bundle_docker.js';
|
|
3
|
+
import { upload_tag } from './upload_tag.js';
|
|
4
|
+
import { check_environment } from './check_environment.js';
|
|
5
|
+
import { analyze_package } from './analyze_package.js';
|
|
6
|
+
import { context } from '../../api/context.js';
|
|
7
|
+
import { build_project } from './build_project.js';
|
|
8
|
+
|
|
9
|
+
const deploy_command = async (args) => {
|
|
10
|
+
const app_slug = args.app_slug;
|
|
11
|
+
const workdir = args.workdir || process.cwd();
|
|
12
|
+
if (!app_slug) {
|
|
13
|
+
throw new Error("Missing app name");
|
|
14
|
+
}
|
|
15
|
+
// Check if we can build docker images
|
|
16
|
+
await check_environment();
|
|
17
|
+
// Get registry data from api.faable.com
|
|
18
|
+
const { api } = await context();
|
|
19
|
+
const app = await api.getBySlug(app_slug);
|
|
20
|
+
log.info(`🚀 Preparing to build ${app.name} [${app.id}]`);
|
|
21
|
+
// Analyze package.json to check if build is needed
|
|
22
|
+
const { build_script } = await analyze_package({ workdir });
|
|
23
|
+
if (build_script) {
|
|
24
|
+
await build_project({ app, build_script });
|
|
25
|
+
}
|
|
26
|
+
// Bundle project inside a docker image
|
|
27
|
+
const { tagname } = await bundle_docker({
|
|
28
|
+
app,
|
|
29
|
+
workdir,
|
|
30
|
+
template_context: {
|
|
31
|
+
from: "node:18.12.0-slim",
|
|
32
|
+
start_script: "start",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
// Upload to Faable registry
|
|
36
|
+
await upload_tag({ app, api, tagname });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { deploy_command };
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { log } from '../../log.js';
|
|
2
|
-
import { build_docker } from './build_docker.js';
|
|
3
|
-
import { upload_tag } from './upload_tag.js';
|
|
4
1
|
import fs from 'fs-extra';
|
|
5
|
-
import {
|
|
6
|
-
import { analyze_package } from './analyze_package.js';
|
|
7
|
-
import { context } from '../../api/context.js';
|
|
2
|
+
import { deploy_command } from './deploy_command.js';
|
|
8
3
|
|
|
9
4
|
const deploy = {
|
|
10
5
|
command: "deploy [app_name]",
|
|
@@ -23,39 +18,10 @@ const deploy = {
|
|
|
23
18
|
alias: "w",
|
|
24
19
|
type: "string",
|
|
25
20
|
description: "Working directory",
|
|
26
|
-
default: process.cwd(),
|
|
27
21
|
})
|
|
28
22
|
.showHelpOnFail(false);
|
|
29
23
|
},
|
|
30
|
-
handler: async (args) =>
|
|
31
|
-
const { app_name, workdir } = args;
|
|
32
|
-
if (!app_name) {
|
|
33
|
-
throw new Error("Missing app_name");
|
|
34
|
-
}
|
|
35
|
-
// Check environment is ready
|
|
36
|
-
await check_environment();
|
|
37
|
-
// Get registry data from api.faable.com
|
|
38
|
-
const { api } = await context();
|
|
39
|
-
const app = await api.getBySlug(app_name);
|
|
40
|
-
log.info(`⚡️ Building app "${app.name}"`);
|
|
41
|
-
// Analyze package.json
|
|
42
|
-
const { hasBuild } = await analyze_package({ workdir });
|
|
43
|
-
// Build docker image
|
|
44
|
-
await build_docker({
|
|
45
|
-
app_name,
|
|
46
|
-
workdir,
|
|
47
|
-
template_context: {
|
|
48
|
-
from: "node:18.12.0-slim",
|
|
49
|
-
build_script: hasBuild && "build",
|
|
50
|
-
start_script: "start",
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
log.info(`⚙️ Image ready. Uploading...`);
|
|
54
|
-
// Upload using api.faable.com data
|
|
55
|
-
const registry = await api.getRegistry(app.id);
|
|
56
|
-
await upload_tag({ registry });
|
|
57
|
-
log.info(`✅ Upload completed.`);
|
|
58
|
-
},
|
|
24
|
+
handler: async (args) => deploy_command(args),
|
|
59
25
|
};
|
|
60
26
|
|
|
61
27
|
export { deploy };
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import { log } from '../../log.js';
|
|
1
2
|
import { cmd } from './cmd.js';
|
|
2
3
|
|
|
3
|
-
const upload_tag = async (
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const upload_tag = async (args) => {
|
|
5
|
+
const { api, app, tagname } = args;
|
|
6
|
+
log.info(`Uploading ${tagname}`);
|
|
7
|
+
const registry = await api.getRegistry(app.id);
|
|
6
8
|
// Registry login
|
|
9
|
+
const { user, password, hostname, image } = registry;
|
|
7
10
|
const docker_login_cmd = `echo "${password}" | docker login --username ${user} --password-stdin ${hostname}`;
|
|
8
11
|
await cmd("/bin/bash", ["-c", docker_login_cmd]);
|
|
9
12
|
// Tag image for production
|
|
10
13
|
const image_tag = `${hostname}/${image}`;
|
|
11
|
-
await cmd("docker", ["tag",
|
|
14
|
+
await cmd("docker", ["tag", tagname, image_tag]);
|
|
12
15
|
// Upload the image to faable registry
|
|
13
16
|
await cmd("docker", ["push", image_tag]);
|
|
17
|
+
log.info(`✅ Upload completed.`);
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export { upload_tag };
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"pino-pretty": "^9.4.0",
|
|
10
10
|
"promisify-child-process": "^4.1.1",
|
|
11
11
|
"prompts": "^2.4.2",
|
|
12
|
+
"tslib": "^2.4.1",
|
|
12
13
|
"yargs": "^17.6.2"
|
|
13
14
|
},
|
|
14
15
|
"bin": {
|
|
@@ -25,5 +26,5 @@
|
|
|
25
26
|
"access": "public"
|
|
26
27
|
},
|
|
27
28
|
"homepage": "https://github.com/faablecloud/faable#readme",
|
|
28
|
-
"version": "1.2.
|
|
29
|
+
"version": "1.2.8"
|
|
29
30
|
}
|
package/templates/Dockerfile
CHANGED
|
@@ -7,17 +7,9 @@ WORKDIR /faable/app
|
|
|
7
7
|
# Environment variables for runtime
|
|
8
8
|
ENV PORT=80
|
|
9
9
|
ENV NODE_ENV=production
|
|
10
|
+
ENV START_SCRIPT=start
|
|
10
11
|
|
|
11
12
|
# Copy Usercode
|
|
12
|
-
COPY
|
|
13
|
+
COPY . .
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Build the project if requested
|
|
17
|
-
{{#if build_script}}
|
|
18
|
-
RUN echo "Running build command: {{build_script}}"
|
|
19
|
-
RUN yarn run {{build_script}}
|
|
20
|
-
{{/if}}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
CMD ["/bin/sh", "/faable/app/entrypoint.sh","{{start_script}}"]
|
|
15
|
+
CMD ["/bin/sh", "./entrypoint.sh"]
|
package/templates/entrypoint.sh
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
|
|
3
|
-
const authenticateOAuthApp = (creds) => {
|
|
4
|
-
const { clientId, clientSecret } = creds;
|
|
5
|
-
if (!clientId || !clientSecret) {
|
|
6
|
-
throw new Error("Missing credentials. Run faable configure first.");
|
|
7
|
-
}
|
|
8
|
-
const client = axios.create({ baseURL: "https://api-auth.app.faable.com" });
|
|
9
|
-
let cache_token;
|
|
10
|
-
const fetch_token = async () => {
|
|
11
|
-
// Send client credentials in POST body. Can also be sent as basic auth header.
|
|
12
|
-
const res = await client.post("/oauth/token", {
|
|
13
|
-
client_id: clientId,
|
|
14
|
-
client_secret: clientSecret,
|
|
15
|
-
grant_type: "client_credentials",
|
|
16
|
-
});
|
|
17
|
-
//console.log(`Fetched Token!`);
|
|
18
|
-
// Cache the token
|
|
19
|
-
cache_token = res.data;
|
|
20
|
-
return cache_token;
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
headers: async () => {
|
|
24
|
-
// TODO: Check token is not expired
|
|
25
|
-
const { token_type, access_token } = cache_token
|
|
26
|
-
? cache_token
|
|
27
|
-
: await fetch_token();
|
|
28
|
-
return {
|
|
29
|
-
Authorization: `${token_type} ${access_token}`,
|
|
30
|
-
};
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export { authenticateOAuthApp };
|