Skip to content

Commit f9a251a

Browse files
committed
feat(vms): added ssm code and more tests + changes on backend
1 parent 5e8a76f commit f9a251a

File tree

11 files changed

+1091
-163
lines changed

11 files changed

+1091
-163
lines changed

packages/actions/.env.default

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ AWS_ACCESS_KEY_ID="YOUR-AWS-ACCESS-KEY-ID"
4444
AWS_SECRET_ACCESS_KEY="YOUR-AWS-SECRET-ACCESS-KEY"
4545
# The AWS region where your buckets are located.
4646
AWS_REGION="YOUR-AWS-REGION"
47-
# The AWS AMI ID (default to Ubuntu 22.04 LTS in us-east-1).
48-
AWS_AMI_ID="ami-007855ac798b5175e"
47+
# The AWS AMI ID (default to Ubuntu 20.04 LTS in us-east-1).
48+
AWS_AMI_ID="ami-01e8572ea33b21308"
4949
# The EC2 instance role to access S3
5050
AWS_ROLE_ARN="YOUR-AWS-ROLE-ARN"
5151
# The SSH key to access the EC2 instance

packages/actions/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
},
6464
"devDependencies": {
6565
"@aws-sdk/client-ec2": "^3.316.0",
66+
"@aws-sdk/client-ssm": "^3.357.0",
6667
"@ethersproject/providers": "^5.7.2",
6768
"@nomicfoundation/hardhat-chai-matchers": "^1.0.6",
6869
"@nomiclabs/hardhat-ethers": "^2.2.3",

packages/actions/src/helpers/ec2.ts

+122-42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TerminateInstancesCommand,
88
DescribeInstancesCommand
99
} from "@aws-sdk/client-ec2"
10+
import { GetCommandInvocationCommand, SSMClient, SendCommandCommand, SendCommandCommandInput } from "@aws-sdk/client-ssm"
1011
import { P0tionEC2Instance } from "../types"
1112
import dotenv from "dotenv"
1213
dotenv.config()
@@ -52,6 +53,24 @@ export const createEC2Client = async (): Promise<EC2Client> => {
5253
return ec2
5354
}
5455

56+
/**
57+
* Create an SSM client object
58+
* @returns <Promise<SSMClient>> an SSM client
59+
*/
60+
export const createSSMClient = async (): Promise<SSMClient> => {
61+
const { accessKeyId, secretAccessKey, region } = getAWSVariables()
62+
63+
const ssm: SSMClient = new SSMClient({
64+
credentials: {
65+
accessKeyId: accessKeyId,
66+
secretAccessKey: secretAccessKey
67+
},
68+
region: region
69+
})
70+
71+
return ssm
72+
}
73+
5574
/**
5675
* Generate the command to be run by the EC2 instance
5776
* @param r1csPath <string> path to r1cs file
@@ -63,23 +82,24 @@ export const generateVMCommand = (
6382
r1csPath: string,
6483
zKeyPath: string,
6584
ptauPath: string,
66-
verificationTranscriptPath: string
6785
): string[] => {
6886
const command = [
6987
"#!/usr/bin/env bash",
7088
"sudo apt update",
71-
"sudo apt install awscli -y", // install cli
89+
"sudo apt install awscli -y", // install aws cli
7290
"curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash", // install nvm
91+
"source ~/.bashrc",
7392
"nvm install 16",
7493
"nvm use 16",
94+
"npm install -g yarn",
7595
"npm install -g snarkjs",
96+
`aws s3 cp s3://${r1csPath} /var/tmp/circuit.r1cs`,
97+
`aws s3 cp s3://${zKeyPath} /var/tmp/genesisZkey.zkey`,
98+
`aws s3 cp s3://${ptauPath} /var/tmp/ptau.ptau`,
99+
"npm install -g p0tion-api",
100+
"p0tion-api /var/tmp/circuit.r1cs /var/tmp/genesisZkey.zkey /var/tmp/ptau.ptau",
76101
]
77-
command.push(`aws s3 cp s3://${r1csPath} /tmp/circuit.r1cs`)
78-
command.push(`aws s3 cp s3://${zKeyPath} /tmp/zkey.zkey`)
79-
command.push(`aws s3 cp s3://${ptauPath} /tmp/ptau.ptau`)
80-
command.push("snarkjs zkey verify circuit.r1cs ptau.ptau zkey.zkey > /tmp/verify.txt")
81-
command.push(`aws s3 cp /tmp/verify.txt s3://${verificationTranscriptPath}`)
82-
102+
83103
return command
84104
}
85105

@@ -169,27 +189,26 @@ const instancesTypes = {
169189
/**
170190
* Creates a new EC2 instance
171191
* @param ec2 <EC2Client> the EC2 client to talk to AWS
192+
* @param commands <string[]> the commands to be run on the EC2 instance
193+
* @param instanceType <string> the type of instance to be created
194+
* @param amiId <string> the AMI ID to be used
195+
* @param keyName <string> the name of the key to be used
196+
* @param roleArn <string> the ARN of the role to be used
172197
* @returns <Promise<P0tionEC2Instance>> the instance that was created
173198
*/
174-
export const createEC2Instance = async (ec2: EC2Client): Promise<P0tionEC2Instance> => {
175-
const { amiId, keyName, roleArn } = getAWSVariables()
176-
177-
// @note Test only
178-
const commands = [
179-
"#!/usr/bin/env bash",
180-
"sudo apt update",
181-
"sudo apt install awscli -y",
182-
"touch /tmp/test.txt",
183-
"echo 'hello world' > /tmp/test.txt",
184-
"aws s3 cp /tmp/test.txt s3://p0tion-test-bucket/test.txt",
185-
"npm install -g p0tion-api",
186-
"p0tion-api ",
187-
]
199+
export const createEC2Instance = async (
200+
ec2: EC2Client,
201+
commands: string[],
202+
instanceType: string,
203+
amiId: string,
204+
keyName: string,
205+
roleArn: string
206+
): Promise<P0tionEC2Instance> => {
188207

189208
// create the params
190209
const params = {
191210
ImageId: amiId,
192-
InstanceType: "t2.micro", // to be determined programmatically
211+
InstanceType: instanceType, // to be determined programmatically
193212
MaxCount: 1,
194213
MinCount: 1,
195214
KeyName: keyName,
@@ -202,22 +221,27 @@ export const createEC2Instance = async (ec2: EC2Client): Promise<P0tionEC2Instan
202221
}
203222

204223
// create command
205-
const command = new RunInstancesCommand(params)
206-
const response = await ec2.send(command)
207-
208-
if (response.$metadata.httpStatusCode !== 200) {
209-
throw new Error("Could not create a new EC2 instance")
210-
}
211-
212-
const instance: P0tionEC2Instance = {
213-
InstanceId: response.Instances![0].InstanceId!,
214-
ImageId: response.Instances![0].ImageId!,
215-
InstanceType: response.Instances![0].InstanceType!,
216-
KeyName: response.Instances![0].KeyName!,
217-
LaunchTime: response.Instances![0].LaunchTime!.toISOString()
224+
try {
225+
const command = new RunInstancesCommand(params)
226+
const response = await ec2.send(command)
227+
228+
if (response.$metadata.httpStatusCode !== 200) {
229+
throw new Error("Could not create a new EC2 instance")
230+
}
231+
232+
const instance: P0tionEC2Instance = {
233+
InstanceId: response.Instances![0].InstanceId!,
234+
ImageId: response.Instances![0].ImageId!,
235+
InstanceType: response.Instances![0].InstanceType!,
236+
KeyName: response.Instances![0].KeyName!,
237+
LaunchTime: response.Instances![0].LaunchTime!.toISOString()
238+
}
239+
240+
return instance
241+
} catch (error: any) {
242+
console.log("[*] Debug", error)
243+
throw new Error("Could not deploy a new EC2 instance")
218244
}
219-
220-
return instance
221245
}
222246

223247
/**
@@ -232,10 +256,9 @@ export const checkEC2Status = async (ec2Client: EC2Client, instanceId: string):
232256
})
233257

234258
const response = await ec2Client.send(command)
235-
if (response.$metadata.httpStatusCode !== 200) {
259+
if (response.$metadata.httpStatusCode !== 200)
236260
throw new Error("Could not get the status of the EC2 instance")
237-
}
238-
261+
239262
return response.InstanceStatuses![0].InstanceState!.Name === "running"
240263
}
241264

@@ -281,7 +304,6 @@ export const startEC2Instance = async (ec2: EC2Client, instanceId: string) => {
281304
* Stops an EC2 instance
282305
* @param ec2 <EC2Client> the EC2 client to talk to AWS
283306
* @param instanceId <string> the id of the instance to stop
284-
* @returns
285307
*/
286308
export const stopEC2Instance = async (ec2: EC2Client, instanceId: string) => {
287309
const command = new StopInstancesCommand({
@@ -313,3 +335,61 @@ export const terminateEC2Instance = async (ec2: EC2Client, instanceId: string) =
313335
throw new Error("Could not terminate the EC2 instance")
314336
}
315337
}
338+
339+
/**
340+
* Run a command on a VM using SSM
341+
* @param ssmClient <SSMClient> the SSM client to talk to AWS
342+
* @param instanceId <string> the id of the instance to run the command on
343+
* @param commands <string[]> the commands to run
344+
* @return <Promise<any>> the command id
345+
*/
346+
export const runCommandOnEC2 = async (
347+
ssmClient: SSMClient,
348+
instanceId: string,
349+
commands: string[]
350+
): Promise<any> => {
351+
// the params for the command
352+
const params: SendCommandCommandInput = {
353+
DocumentName: "AWS-RunShellScript",
354+
InstanceIds: [instanceId],
355+
Parameters: {
356+
"commands": commands
357+
},
358+
TimeoutSeconds: 1200
359+
}
360+
361+
try {
362+
const response = await ssmClient.send(new SendCommandCommand(params))
363+
if (response.$metadata.httpStatusCode !== 200) {
364+
throw new Error("Could not run the command on the EC2 instance")
365+
}
366+
return response.Command!.CommandId
367+
} catch (error: any) {
368+
throw new Error("Could not run the command on the EC2 instance")
369+
}
370+
}
371+
372+
/**
373+
* Retrieve the output of a SSM command
374+
* @param ssmClient <SSMClient> the SSM client to talk to AWS
375+
* @param commandId <string> The id of the command to retrieve the output of
376+
* @param instanceId <string> The id of the instance to retrieve the output of
377+
* @return <Promise<any>> The output of the command
378+
*/
379+
export const retrieveCommandOutput = async (
380+
ssmClient: SSMClient,
381+
commandId: string,
382+
instanceId: string
383+
): Promise<any> => {
384+
const command = new GetCommandInvocationCommand({
385+
CommandId: commandId,
386+
InstanceId: instanceId
387+
})
388+
389+
try {
390+
const output = await ssmClient.send(command)
391+
return output.StandardOutputContent
392+
} catch (error: any) {
393+
throw new Error("Could not retrieve the output of the command")
394+
}
395+
}

packages/actions/src/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,8 @@ export {
140140
stopEC2Instance,
141141
startEC2Instance,
142142
checkEC2Status,
143-
getEC2Ip
143+
getEC2Ip,
144+
generateVMCommand,
145+
runCommandOnEC2,
146+
retrieveCommandOutput
144147
} from "./helpers/ec2"

0 commit comments

Comments
 (0)