-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmjsWriteAwsCliScript.m
217 lines (183 loc) · 7.97 KB
/
mjsWriteAwsCliScript.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
function awsCliScriptFile = mjsWriteAwsCliScript(jobScriptFile, varargin)
% Write a shell script that will run the given jobScriptFile, via AWS CLI.
%
% This saves us from having to write lots of AWS CLI and SSH syntax by hand.
%
% AWS CLI is the Amazon Web Services Command Line Interface.
% https://aws.amazon.com/cli/
%
% mjsWriteAwsCliScript = mjsWriteAwsCliScript(jobScriptFile) generates a
% shell script that will cause the given jobScriptFile be executed
% remotely, via ASW CLI and SSH. The general outline is:
% - start an AWS EC2 instance using AWS CLI
% - execute the jobScriptFile via SSH on the instance
% - terminate the instance
%
% mjsWriteAwsCliScript( ... 'awsCliScriptFile', awsCliScriptFile) specify
% the name of the new AWS CLI script that should be generated. The default
% is chosen based on the jobScriptFile.
%
% mjsWriteAwsCliScript( ... 'amiId', amiId) specify id of the Amazon
% Machine Image to use for the new EC2 instance. The AMI should have the
% following already installed:
% - Docker
% - Matlab
% - jq (for parsing JSON, see https://stedolan.github.io/jq/)
%
% mjsWriteAwsCliScript( ... 'instanceType', instanceType) specify the
% instance type to create. For Matlab, use at least t2.small, or at least
% 2GB of memory.
%
% mjsWriteAwsCliScript( ... 'securityGroups', securityGroups) name of
% security groups that allow SSH access from here, as well as access to any
% Matlab license server that's required.
%
% mjsWriteAwsCliScript( ... 'terminate', terminate) specify whether to
% terminate the instance after the job fails or completes. The default is
% true -- do terminate the instance.
%
% mjsWriteAwsCliScript( ... 'iamProfile', iamProfile) configure an "IAM"
% profile for the instance to use. This an optional way to give the
% instance access to other AWS resources, like S3.
%
% scriptFile = mjsWriteAwsCliScript(varargin)
%
% 2016-2017 Brainard Lab, University of Pennsylvania
arguments = mjsIncludeEnvironmentProfile(varargin{:});
parser = inputParser();
parser.KeepUnmatched = true;
parser.StructExpand = true;
parser.addRequired('jobScriptFile', @ischar);
parser.addParameter('awsCliScriptFile', '', @ischar);
parser.addParameter('amiId', '', @ischar);
parser.addParameter('instanceType', 't2.small', @ischar);
parser.addParameter('securityGroups', {'default'}, @iscellstr);
parser.addParameter('terminate', true, @islogical);
parser.addParameter('iamProfile', '', @ischar);
parser.addParameter('identity', '', @ischar);
parser.addParameter('diskGB', [], @isnumeric);
parser.parse(jobScriptFile, arguments);
jobScriptFile = parser.Results.jobScriptFile;
awsCliScriptFile = parser.Results.awsCliScriptFile;
amiId = parser.Results.amiId;
instanceType = parser.Results.instanceType;
securityGroups = parser.Results.securityGroups;
terminate = parser.Results.terminate;
iamProfile = parser.Results.iamProfile;
identity = parser.Results.identity;
diskGB = parser.Results.diskGB;
% default aws cli script name based on job script name
[jobScriptPath, jobScriptBase] = fileparts(jobScriptFile);
if isempty(awsCliScriptFile)
awsCliScriptFile = fullfile(jobScriptPath, [jobScriptBase '-aws-cli.sh']);
end
%% Make sure script dir exists.
scriptDir = fileparts(awsCliScriptFile);
if ~isempty(scriptDir) && 7 ~= exist(scriptDir, 'dir')
mkdir(scriptDir);
end
fid = fopen(awsCliScriptFile, 'w');
if -1 == fid
error('mjsWriteAwsCliScript:fopen', ...
'Could not open file <%s> for writing.', scriptFile);
end
try
fprintf(fid, '#!/bin/sh\n');
fprintf(fid, '## Begin script generated by mjsWriteAwsCliScript.m\n');
fprintf(fid, '# automatically quit after errors\n');
fprintf(fid, 'set -e\n');
fprintf(fid, '\n');
fprintf(fid, 'echo "Hello."\n');
fprintf(fid, 'date\n');
%% Build AWS CLI command to request a new instance.
fprintf(fid, '\n');
fprintf(fid, '# invoke aws ec2 run-instances with lots of options \n');
fprintf(fid, '# and save result in a JSON file \n');
fprintf(fid, 'echo "Requesting <%s> for <%s>"\n', instanceType, amiId);
fprintf(fid, 'aws ec2 run-instances \\\n');
fprintf(fid, ' --image-id %s \\\n', amiId);
fprintf(fid, ' --count 1 \\\n');
fprintf(fid, ' --instance-type %s \\\n', instanceType);
securityGroupArg = sprintf('%s ', securityGroups{:});
fprintf(fid, ' --security-groups %s \\\n', securityGroupArg);
if ~isempty(iamProfile)
fprintf(fid, ' --iam-instance-profile Name=%s \\\n', iamProfile);
end
if ~isempty(identity)
[~, keyName] = fileparts(identity);
fprintf(fid, ' --key-name %s \\\n', keyName);
end
if ~isempty(diskGB)
diskGbArg = sprintf('[{\"DeviceName\":\"/dev/sda1\",\"Ebs\":{\"VolumeSize\":%d,\"DeleteOnTermination\":true,\"VolumeType\":\"gp2\"}}]', ...
diskGB);
fprintf(fid, ' --block-device-mappings %s \\\n', diskGbArg);
end
% redirect results JSON to temp file
instanceDetailsFile = sprintf('/tmp/%s.json', jobScriptBase);
fprintf(fid, ' --output json > "%s"\n', instanceDetailsFile);
%% Add handy tags to the instance.
fprintf(fid, '\n');
fprintf(fid, '# scrape out new instance Id using jq (https://stedolan.github.io/jq/) \n');
fprintf(fid, 'INSTANCE_ID=$(jq -r ".Instances[0].InstanceId" %s)\n', instanceDetailsFile);
fprintf(fid, '\n');
fprintf(fid, '# add tags to help keep track of the new instance \n');
fprintf(fid, 'aws ec2 create-tags \\\n');
fprintf(fid, ' --resources "$INSTANCE_ID" \\\n');
fprintf(fid, ' --tags Key=Name,Value=%s Key=Script,Value=%s \n', jobScriptBase, 'mjsWriteAwsCliScript');
%% Wait for the instance to be ready.
fprintf(fid, '\n');
fprintf(fid, '# wait for the instance to come up\n');
fprintf(fid, 'echo "Waiting for instance <$INSTANCE_ID> to start..."\n');
fprintf(fid, 'aws ec2 wait instance-status-ok \\\n');
fprintf(fid, ' --instance-ids "$INSTANCE_ID" \n');
fprintf(fid, '\n');
fprintf(fid, 'echo "...OK"\n');
fprintf(fid, 'date\n');
%% Update the instance details, now that it's ready.
fprintf(fid, '\n');
fprintf(fid, '# update instance details to get DNS name \n');
fprintf(fid, 'aws ec2 describe-instances \\\n');
fprintf(fid, ' --instance-ids "$INSTANCE_ID" \\\n');
fprintf(fid, ' --output json > "%s"\n', instanceDetailsFile);
fprintf(fid, '\n');
fprintf(fid, 'INSTANCE_DNS_NAME=$(jq -r ".Reservations[0].Instances[0].PublicDnsName" %s)\n', ...
instanceDetailsFile);
%% Insert an ssh script right here inside this aws-cli script!
% this is the fun part!
fprintf(fid, '\n\n');
mjsWriteSshScript(jobScriptFile, ...
arguments, ...
'host', '$INSTANCE_DNS_NAME', ...
'sshScriptFid', fid);
%% Clean up.
if terminate
% terminate the instance
fprintf(fid, '\n');
fprintf(fid, '# terminate the instance after the job is done\n');
fprintf(fid, 'echo "Terminating instance <$INSTANCE_ID>..."\n');
fprintf(fid, 'aws ec2 terminate-instances \\\n');
fprintf(fid, ' --instance-ids "$INSTANCE_ID" \\\n');
fprintf(fid, ' --output json >> "%s"\n', instanceDetailsFile);
% wait for it to be terminated
fprintf(fid, '\n');
fprintf(fid, '# wait for instance to be done terminating\n');
fprintf(fid, 'aws ec2 wait instance-terminated \\\n');
fprintf(fid, ' --instance-ids "$INSTANCE_ID" \n');
fprintf(fid, '\n');
fprintf(fid, 'echo "...OK"\n');
else
% leave instance running, with some info
fprintf(fid, 'echo "Leaving instance <$INSTANCE_ID> running at <$INSTANCE_DNS_NAME>."\n');
end
fprintf(fid, '\n');
fprintf(fid, 'echo "Bye-bye."\n');
fprintf(fid, 'date\n');
fprintf(fid, '\n');
fprintf(fid, '## End script generated by mjsWriteAwsCliScript.m\n');
fprintf(fid, '\n');
fclose(fid);
catch err
fclose(fid);
rethrow(err);
end
system(['chmod +x ' awsCliScriptFile]);