@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 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-79e0105f6025d93833a45abc311c73fb2b5b6c26",
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
- await iexec.order.publishApporder(apporder);
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 + +color.comment(msg);
116
+ spinner.text = spinner.text + color.comment(msg);
117
117
  },
118
118
  });
119
119
  spinner.succeed(`App docker image built (${imageId})`);
@@ -24,22 +24,19 @@ export async function dockerBuild({
24
24
  src: ['./'],
25
25
  };
26
26
 
27
- let platform;
28
- if (osType === 'Darwin') {
29
- // For MacOS
30
- if (isForTest) {
31
- // ARM64 variant for local testing only
32
- platform = 'linux/arm64';
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) => {
@@ -29,6 +29,17 @@ export async function getAuthToken({
29
29
  `Fail to get authorization token for scope=${repository}:${action}`
30
30
  );
31
31
  }
32
- const { token } = await response.json();
33
- return token;
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 request(url, {
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
  });
@@ -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 { body } = await request(`${SCONIFY_API_URL}/sconify`, {
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
- throwOnError: true,
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
- const json = await body.json();
45
- sconifiedImage = json.sconifiedImage;
46
- appContractAddress = json.appContractAddress;
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
- let reason;
49
- if (err.body) {
50
- reason = err.body;
51
- } else if (
52
- err?.code === 'ECONNREFUSED' ||
53
- err?.code === 'UND_ERR_CONNECT_TIMEOUT'
54
- ) {
55
- reason = "Can't reach TEE transformation server!";
56
- } else {
57
- reason = err.toString();
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: ${reason}`);
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));
@@ -0,0 +1,9 @@
1
+ # iapp config file may contain secrets
2
+ iapp.config.json
3
+
4
+ # dependencies
5
+ node_modules
6
+
7
+ # test directories
8
+ input
9
+ output
@@ -1,13 +1,13 @@
1
1
  {
2
- "name": "hello-world",
3
- "version": "1.0.0",
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.0",
9
- "resolved": "https://registry.npmjs.org/@iexec/dataprotector-deserializer/-/dataprotector-deserializer-0.1.0.tgz",
10
- "integrity": "sha512-qjrcWR286qegASxcHMSZCmt6dKubIHXIqceXqSscq4vzEDHn4nIwHUabDbTLQbvdrF0XGswNeQuYc10vgjZtow==",
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"
@@ -8,7 +8,7 @@
8
8
  "npm": "<7.0.0"
9
9
  },
10
10
  "dependencies": {
11
- "@iexec/dataprotector-deserializer": "^0.1.0",
11
+ "@iexec/dataprotector-deserializer": "^0.1.1",
12
12
  "figlet": "^1.7.0"
13
13
  }
14
14
  }