@iexec/iapp-maker 0.0.1-alpha-nightly-79e0105f6025d93833a45abc311c73fb2b5b6c26 → 0.0.1-alpha-nightly-ef579faebe1b4ee041f6da3e79851c51d6dd180e
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +15 -0
- package/package.json +1 -2
- package/src/cmd/deploy.js +0 -1
- package/src/cmd/run.js +1 -2
- package/src/cmd/test.js +1 -1
- package/src/execDocker/docker.js +7 -10
- package/src/utils/dockerhub.js +13 -2
- package/src/utils/initIAppWorkspace.js +3 -0
- package/src/utils/prepareInputFile.js +1 -4
- package/src/utils/sconify.js +53 -23
- package/src/utils/sleep.js +1 -0
- package/templates/js/_.gitignore +9 -0
- package/templates/js/package-lock.json +5 -5
- package/templates/js/package.json +1 -1
package/README.md
CHANGED
@@ -14,6 +14,21 @@ This CLI provides an interface to guide you through different steps:
|
|
14
14
|
(`iapp init` will also propose you to do so)
|
15
15
|
- Docker
|
16
16
|
|
17
|
+
> ℹ️ For MacOS users
|
18
|
+
>
|
19
|
+
> This tool use `docker buildx` to build images for `linux/amd64` platform
|
20
|
+
> compatible with iExec's decentralized workers.
|
21
|
+
>
|
22
|
+
> Make sure your docker builder supports AMD64 architecture:
|
23
|
+
>
|
24
|
+
> ```sh
|
25
|
+
> docker buildx inspect --bootstrap | grep -i platforms
|
26
|
+
> ```
|
27
|
+
>
|
28
|
+
> The output should include `linux/amd64` in the list of supported platforms. If
|
29
|
+
> not update te the latest Docker Desktop version which includes these
|
30
|
+
> requirements.
|
31
|
+
|
17
32
|
## Install
|
18
33
|
|
19
34
|
```sh
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@iexec/iapp-maker",
|
3
|
-
"version": "0.0.1-alpha-nightly-
|
3
|
+
"version": "0.0.1-alpha-nightly-ef579faebe1b4ee041f6da3e79851c51d6dd180e",
|
4
4
|
"description": "A CLI to guide you through the process of building an iExec iApp",
|
5
5
|
"main": "src/index.js",
|
6
6
|
"type": "module",
|
@@ -44,7 +44,6 @@
|
|
44
44
|
"magic-bytes.js": "^1.10.0",
|
45
45
|
"ora": "^8.1.1",
|
46
46
|
"prompts": "^2.4.2",
|
47
|
-
"undici": "^6.21.0",
|
48
47
|
"uuid": "^11.0.3",
|
49
48
|
"yargs": "^17.7.2",
|
50
49
|
"zod": "^3.23.8",
|
package/src/cmd/deploy.js
CHANGED
@@ -89,7 +89,6 @@ export async function deploy() {
|
|
89
89
|
'Transforming your image into a TEE image and deploying on iExec, this may take a few minutes...'
|
90
90
|
);
|
91
91
|
const { sconifiedImage, appContractAddress } = await sconify({
|
92
|
-
sconifyForProd: false,
|
93
92
|
iAppNameToSconify: imageTag,
|
94
93
|
walletAddress,
|
95
94
|
dockerhubAccessToken,
|
package/src/cmd/run.js
CHANGED
@@ -133,8 +133,7 @@ export async function runInDebug({
|
|
133
133
|
tag: SCONE_TAG,
|
134
134
|
});
|
135
135
|
const apporder = await iexec.order.signApporder(apporderTemplate);
|
136
|
-
|
137
|
-
spinner.succeed('AppOrder created and published');
|
136
|
+
spinner.succeed('AppOrder created');
|
138
137
|
|
139
138
|
// Dataset Order
|
140
139
|
let datasetorder;
|
package/src/cmd/test.js
CHANGED
@@ -113,7 +113,7 @@ export async function testApp({
|
|
113
113
|
const imageId = await dockerBuild({
|
114
114
|
isForTest: true,
|
115
115
|
progressCallback: (msg) => {
|
116
|
-
spinner.text = spinner.text +
|
116
|
+
spinner.text = spinner.text + color.comment(msg);
|
117
117
|
},
|
118
118
|
});
|
119
119
|
spinner.succeed(`App docker image built (${imageId})`);
|
package/src/execDocker/docker.js
CHANGED
@@ -24,22 +24,19 @@ export async function dockerBuild({
|
|
24
24
|
src: ['./'],
|
25
25
|
};
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
} else {
|
34
|
-
// AMD64 variant to deploy to iExec stack
|
35
|
-
platform = 'linux/amd64';
|
36
|
-
}
|
27
|
+
// by default force to build amd64 image which is architecture used in iExec workers
|
28
|
+
// this require buildx builder to support 'linux/amd64' (some devices may need QEMU for amd64 architecture emulation)
|
29
|
+
let platform = 'linux/amd64';
|
30
|
+
// for MacOS local testing only build arm64 variant
|
31
|
+
if (osType === 'Darwin' && isForTest) {
|
32
|
+
platform = 'linux/arm64';
|
37
33
|
}
|
38
34
|
|
39
35
|
// Perform the Docker build operation
|
40
36
|
const buildImageStream = await docker.buildImage(buildArgs, {
|
41
37
|
t: tag,
|
42
38
|
platform,
|
39
|
+
pull: true, // docker store does not support multi platform image, this can cause issues when switching build target platform, pulling ensures the right image is used
|
43
40
|
});
|
44
41
|
|
45
42
|
const imageId = await new Promise((resolve, reject) => {
|
package/src/utils/dockerhub.js
CHANGED
@@ -29,6 +29,17 @@ export async function getAuthToken({
|
|
29
29
|
`Fail to get authorization token for scope=${repository}:${action}`
|
30
30
|
);
|
31
31
|
}
|
32
|
-
|
33
|
-
|
32
|
+
return response
|
33
|
+
.json()
|
34
|
+
.catch(() => {
|
35
|
+
throw Error(`Unexpected response from dockerhub auth server`);
|
36
|
+
})
|
37
|
+
.then(({ token }) => {
|
38
|
+
if (!token) {
|
39
|
+
throw Error(
|
40
|
+
`Unexpected response from dockerhub auth server: Missing token`
|
41
|
+
);
|
42
|
+
}
|
43
|
+
return token;
|
44
|
+
});
|
34
45
|
}
|
@@ -91,6 +91,9 @@ async function copyChosenTemplateFiles({
|
|
91
91
|
const files = await fs.readdir(templateDir);
|
92
92
|
await Promise.all(files.map((file) => write(file)));
|
93
93
|
|
94
|
+
// rename _.gitignore (npm does not allow publishing files named .gitignore in a package)
|
95
|
+
await fs.rename('_.gitignore', '.gitignore');
|
96
|
+
|
94
97
|
// transform template: remove unwanted feature code inside " // <<feature>> ... // <</feature>>" tags
|
95
98
|
const code = (await fs.readFile(srcFile)).toString('utf8');
|
96
99
|
let modifiedCode = code;
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
2
2
|
import { join } from 'node:path';
|
3
|
-
import { request } from 'undici';
|
4
3
|
import { TEST_INPUT_DIR } from '../config/config.js';
|
5
4
|
|
6
5
|
// TODO we may want to cache to avoid downloading large input files over and over
|
@@ -19,9 +18,7 @@ export async function prepareInputFile(url) {
|
|
19
18
|
if (name === '.' || name === '..') {
|
20
19
|
throw Error('Invalid computed file name');
|
21
20
|
}
|
22
|
-
await
|
23
|
-
throwOnError: true,
|
24
|
-
}).then(async (response) => {
|
21
|
+
await fetch(url).then(async (response) => {
|
25
22
|
await mkdir(TEST_INPUT_DIR, { recursive: true }); // ensure input dir
|
26
23
|
await writeFile(join(TEST_INPUT_DIR, name), response.body);
|
27
24
|
});
|
package/src/utils/sconify.js
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
import { request } from 'undici';
|
2
1
|
import { addDeploymentData } from './cacheExecutions.js';
|
3
2
|
import { SCONIFY_API_URL } from '../config/config.js';
|
4
3
|
import { getAuthToken } from '../utils/dockerhub.js';
|
4
|
+
import { sleep } from './sleep.js';
|
5
|
+
|
6
|
+
const INITIAL_RETRY_PERIOD = 20 * 1000; // 20s
|
7
|
+
|
8
|
+
class TooManyRequestsError extends Error {}
|
5
9
|
|
6
10
|
export async function sconify({
|
7
|
-
sconifyForProd,
|
8
11
|
iAppNameToSconify,
|
9
12
|
walletAddress,
|
10
13
|
dockerhubAccessToken,
|
11
14
|
dockerhubUsername,
|
15
|
+
tryCount = 0,
|
12
16
|
}) {
|
13
|
-
if (sconifyForProd) {
|
14
|
-
throw Error('This feature is not yet implemented. Coming soon ...');
|
15
|
-
}
|
16
|
-
|
17
17
|
let appContractAddress;
|
18
18
|
let sconifiedImage;
|
19
19
|
try {
|
@@ -26,7 +26,7 @@ export async function sconify({
|
|
26
26
|
dockerhubUsername,
|
27
27
|
});
|
28
28
|
|
29
|
-
const
|
29
|
+
const jsonResponse = await fetch(`${SCONIFY_API_URL}/sconify`, {
|
30
30
|
method: 'POST',
|
31
31
|
headers: {
|
32
32
|
'Content-Type': 'application/json',
|
@@ -37,26 +37,56 @@ export async function sconify({
|
|
37
37
|
dockerhubPushToken: pushToken, // used for pushing sconified image on user repo
|
38
38
|
yourWalletPublicAddress: walletAddress,
|
39
39
|
}),
|
40
|
-
|
41
|
-
|
40
|
+
})
|
41
|
+
.catch(() => {
|
42
|
+
throw Error("Can't reach TEE transformation server!");
|
43
|
+
})
|
44
|
+
.then((res) => {
|
45
|
+
if (res.ok) {
|
46
|
+
return res.json().catch(() => {
|
47
|
+
// failed to parse body
|
48
|
+
throw Error('Unexpected server response');
|
49
|
+
});
|
50
|
+
}
|
51
|
+
if (res.status === 429) {
|
52
|
+
throw new TooManyRequestsError(
|
53
|
+
'TEE transformation server is busy, retry later'
|
54
|
+
);
|
55
|
+
}
|
56
|
+
// try getting error message from json body
|
57
|
+
return res
|
58
|
+
.json()
|
59
|
+
.catch(() => {
|
60
|
+
// failed to parse body
|
61
|
+
throw Error('Unknown server error');
|
62
|
+
})
|
63
|
+
.then(({ error }) => {
|
64
|
+
throw Error(error || 'Unknown server error');
|
65
|
+
});
|
66
|
+
});
|
42
67
|
|
43
68
|
// Extract necessary information
|
44
|
-
|
45
|
-
|
46
|
-
|
69
|
+
if (!jsonResponse.appContractAddress) {
|
70
|
+
throw Error('Unexpected server response: missing appContractAddress');
|
71
|
+
}
|
72
|
+
if (!jsonResponse.sconifiedImage) {
|
73
|
+
throw Error('Unexpected server response: missing sconifiedImage');
|
74
|
+
}
|
75
|
+
appContractAddress = jsonResponse.appContractAddress;
|
76
|
+
sconifiedImage = jsonResponse.sconifiedImage;
|
47
77
|
} catch (err) {
|
48
|
-
|
49
|
-
if (err
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
78
|
+
// retry with exponential backoff
|
79
|
+
if (err instanceof TooManyRequestsError && tryCount < 3) {
|
80
|
+
await sleep(INITIAL_RETRY_PERIOD * Math.pow(2, tryCount));
|
81
|
+
return sconify({
|
82
|
+
iAppNameToSconify,
|
83
|
+
walletAddress,
|
84
|
+
dockerhubAccessToken,
|
85
|
+
dockerhubUsername,
|
86
|
+
tryCount: tryCount + 1,
|
87
|
+
});
|
58
88
|
}
|
59
|
-
throw Error(`Failed to transform your app into a TEE app: ${
|
89
|
+
throw Error(`Failed to transform your app into a TEE app: ${err.message}`);
|
60
90
|
}
|
61
91
|
|
62
92
|
// Add deployment data to deployments.json
|
@@ -0,0 +1 @@
|
|
1
|
+
export const sleep = async (ms) => new Promise((res) => setTimeout(res, ms));
|
@@ -1,13 +1,13 @@
|
|
1
1
|
{
|
2
|
-
"name": "hello-world",
|
3
|
-
"version": "
|
2
|
+
"name": "iexec-hello-world-iapp",
|
3
|
+
"version": "0.0.1",
|
4
4
|
"lockfileVersion": 1,
|
5
5
|
"requires": true,
|
6
6
|
"dependencies": {
|
7
7
|
"@iexec/dataprotector-deserializer": {
|
8
|
-
"version": "0.1.
|
9
|
-
"resolved": "https://registry.npmjs.org/@iexec/dataprotector-deserializer/-/dataprotector-deserializer-0.1.
|
10
|
-
"integrity": "sha512-
|
8
|
+
"version": "0.1.1",
|
9
|
+
"resolved": "https://registry.npmjs.org/@iexec/dataprotector-deserializer/-/dataprotector-deserializer-0.1.1.tgz",
|
10
|
+
"integrity": "sha512-CSz1JWnslm2X3gjL1cx/qqovnmvJSFWDyJMw0ZGvqnYnNatgIqHn+Aky2iO4K0HsArfqmgV3ySIpdPfu/N2M0w==",
|
11
11
|
"requires": {
|
12
12
|
"borsh": "^2.0.0",
|
13
13
|
"jszip": "^3.10.1"
|