Skip to content

Commit 03d18f6

Browse files
committed
Added GCE provider
1 parent 6ccdafa commit 03d18f6

File tree

4 files changed

+252
-36
lines changed

4 files changed

+252
-36
lines changed

app/server.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
def run_playbook(data):
2323
global task_program
24-
extra_vars = ' '.join(['{0}={1}'.format(key, data[key]) for key in data.keys()])
24+
extra_vars = ' '.join(['{0}={1}'.format(key, data[key])
25+
for key in data.keys()])
2526
task_program = ['ansible-playbook', 'main.yml', '--extra-vars', extra_vars]
2627
return PlaybookCLI(task_program).run()
2728

@@ -101,7 +102,7 @@ async def post_exit(_):
101102

102103

103104
@routes.post('/lightsail_regions')
104-
async def post_exit(request):
105+
async def lightsail_regions(request):
105106
data = await request.json()
106107
client = boto3.client(
107108
'lightsail',
@@ -115,7 +116,7 @@ async def post_exit(request):
115116

116117

117118
@routes.post('/ec2_regions')
118-
async def post_exit(request):
119+
async def ec2_regions(request):
119120
data = await request.json()
120121
client = boto3.client(
121122
'ec2',
@@ -126,16 +127,40 @@ async def post_exit(request):
126127
return web.json_response(response)
127128

128129

130+
@routes.get('/gce_config')
131+
async def check_gce_config(request):
132+
gce_file = join(PROJECT_ROOT, 'configs', 'gce.json')
133+
response = {}
134+
try:
135+
json.loads(open(gce_file, 'r').read())['project_id']
136+
response['status'] = 'ok'
137+
except IOError:
138+
response['status'] = 'not_available'
139+
except ValueError:
140+
response['status'] = 'wrong_format'
141+
142+
return web.json_response(response)
143+
144+
129145
@routes.post('/gce_regions')
130-
async def post_exit(request):
131-
#data = await request.json()
132-
gce_config_file = 'configs/gce.json' # 'data.get('gce_config_file')
133-
project_id = json.loads(open(gce_config_file, 'r').read())['project_id']
146+
async def gce_regions(request):
147+
data = await request.json()
148+
gce_file_name = join(PROJECT_ROOT, 'configs', 'gce.json')
149+
if data.get('project_id'):
150+
# File is missing, save it. We can't get file path from browser :(
151+
with open(gce_file_name, 'w') as f:
152+
f.write(json.dumps(data))
153+
else:
154+
with open(gce_file_name, 'r') as f:
155+
data = json.loads(f.read())
134156

135157
response = AuthorizedSession(
136-
service_account.Credentials.from_service_account_file(gce_config_file).with_scopes(
137-
['https://www.googleapis.com/auth/compute'])).get(
138-
'https://www.googleapis.com/compute/v1/projects/{project_id}/regions'.format(project_id=project_id))
158+
service_account.Credentials.from_service_account_info(
159+
data).with_scopes(
160+
['https://www.googleapis.com/auth/compute'])).get(
161+
'https://www.googleapis.com/compute/v1/projects/{project_id}/regions'.format(
162+
project_id=data['project_id'])
163+
)
139164

140165
return web.json_response(json.loads(response.content))
141166

app/static/provider-gce.vue

Lines changed: 171 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
11
<template>
22
<div>
3+
<div
4+
class="form-group dropzone"
5+
v-if="ui_needs_upload"
6+
v-on:dragover.prevent="dragover_handler"
7+
v-on:dragleave.prevent="dragleave_handler"
8+
v-on:drop.prevent="drop_handler"
9+
v-on:click="show_file_select"
10+
v-bind:class="{
11+
'dropzone--can-drop': ui_can_drop,
12+
'dropzone--error': ui_drop_error,
13+
'dropzone--success': ui_drop_success,
14+
}"
15+
>
16+
<strong>Drag your GCE config file or click here</strong>
17+
<p>
18+
File <code>configs/gce.json</code> was not found. Please create it (<a
19+
href="https://github.com/trailofbits/algo/blob/master/docs/cloud-gce.md"
20+
target="_blank"
21+
rel="noopener noreferrer"
22+
>how?</a
23+
>) or upload.
24+
</p>
25+
<p>After upload it <strong>will be saved</strong> in the configs folder.</p>
26+
<div v-if="ui_drop_error" class="alert alert-warning" role="alert">
27+
<strong>Error:</strong> {{ ui_drop_error }}.
28+
</div>
29+
30+
<div v-if="ui_drop_success" class="alert alert-success" role="alert">
31+
<strong>{{ ui_drop_filename }} loaded successfully</strong>
32+
</div>
33+
</div>
34+
<input type="file" accept=".json,applciation/json" v-on:change="filechange_handler" />
35+
36+
<div class="form-group">
37+
<region-select v-model="region" v-bind:options="ui_region_options" v-bind:loading="ui_loading_check || ui_loading_regions">
38+
<label>Please specify <code>gce.json</code> credentials file to select region</label>
39+
</region-select>
40+
</div>
41+
342
<button
443
class="btn btn-primary"
544
type="button"
@@ -13,51 +52,158 @@
1352

1453
<script>
1554
module.exports = {
16-
data: function() {
55+
data: function () {
1756
return {
57+
drop_error: null,
1858
gce_credentials_file: null,
1959
region: null,
2060
// helper variables
21-
region_options: [],
22-
is_loading: false
61+
ui_can_drop: false,
62+
ui_drop_error: null,
63+
ui_drop_success: null,
64+
ui_drop_filename: null,
65+
ui_needs_upload: null,
66+
ui_loading_regions: false,
67+
ui_loading_check: false,
68+
ui_region_options: []
2369
};
2470
},
71+
created: function() {
72+
this.check_config();
73+
},
2574
computed: {
2675
is_valid() {
2776
return this.gce_credentials_file && this.region;
28-
},
29-
is_region_disabled() {
30-
return !(this.gce_credentials_file) || this.is_loading;
3177
}
3278
},
3379
methods: {
34-
load_regions() {
35-
if (this.gce_credentials_file && this.region_options.length === 0) {
36-
this.is_loading = true;
37-
fetch('/gce_regions', {
38-
method: 'post',
39-
headers: {
40-
'Content-Type': 'application/json'
41-
},
42-
body: JSON.stringify({
43-
gce_credentials_file: this.gce_credentials_file
44-
})
45-
})
80+
show_file_select(e) {
81+
if (e.target.tagName === 'A') {
82+
return;
83+
}
84+
const input = this.$el.querySelector(['input[type=file]']);
85+
const event = new MouseEvent('click', {
86+
'view': window,
87+
'bubbles': true,
88+
'cancelable': true
89+
});
90+
input.dispatchEvent(event);
91+
},
92+
dragover_handler(e) {
93+
this.ui_can_drop = true;
94+
this.ui_drop_success = false;
95+
this.ui_drop_error = false;
96+
this.ui_drop_filename = null;
97+
},
98+
dragleave_handler() {
99+
this.ui_can_drop = false;
100+
},
101+
drop_handler(e) {
102+
try {
103+
const droppedFiles = e.dataTransfer.files;
104+
if (droppedFiles.length !== 1) {
105+
this.ui_drop_error = 'Please upload GCE config as single file';
106+
}
107+
this.read_file(droppedFiles[0]);
108+
} catch (e) {
109+
this.ui_drop_error = 'Unhandled error while trying to read GCE config';
110+
}
111+
},
112+
filechange_handler(e) {
113+
if (e.target.files.length) {
114+
this.read_file(e.target.files[0]);
115+
}
116+
},
117+
read_file(file) {
118+
if (file.type !== 'application/json') {
119+
this.ui_drop_error = 'Incorrect file type';
120+
}
121+
const reader = new FileReader();
122+
reader.onload = e => {
123+
let gce_config_content = null;
124+
try {
125+
gce_config_content = JSON.parse(e.target.result);
126+
this.ui_drop_success = true;
127+
this.ui_drop_filename = file.name;
128+
this.gce_credentials_file = 'configs/gce.json';
129+
} catch (e) {
130+
this.ui_drop_error = 'JSON format error';
131+
}
132+
gce_config_content && this.load_regions(gce_config_content);
133+
}
134+
reader.onerror = e => {
135+
this.ui_drop_error = 'Error while reading file';
136+
}
137+
reader.readAsText(file);
138+
139+
},
140+
check_config() {
141+
this.ui_loading_check = true;
142+
fetch("/gce_config")
46143
.then(r => r.json())
47-
.then(data => {
48-
this.region_options = data;
144+
.then(response => {
145+
if (response.status === 'ok') {
146+
this.gce_credentials_file = 'configs/gce.json';
147+
this.load_regions();
148+
this.ui_needs_upload = false;
149+
} else {
150+
this.ui_needs_upload = true;
151+
}
49152
})
50153
.finally(() => {
51-
this.is_loading = false;
154+
this.ui_loading_check = false;
52155
});
156+
},
157+
load_regions(gce_config_content) {
158+
if (this.gce_credentials_file && this.ui_region_options.length === 0) {
159+
this.ui_loading_regions = true;
160+
fetch("/gce_regions", {
161+
method: "post",
162+
headers: {
163+
"Content-Type": "application/json",
164+
},
165+
body: gce_config_content ? JSON.stringify(gce_config_content) : '{}',
166+
})
167+
.then((r) => r.json())
168+
.then((data) => {
169+
this.ui_region_options = data.items.map(i => ({
170+
value: i.name,
171+
key: i.name
172+
}));
173+
})
174+
.finally(() => {
175+
this.ui_loading_regions = false;
176+
});
53177
}
54178
},
55179
submit() {
56-
this.$emit('submit', {
180+
this.$emit("submit", {
57181
gce_credentials_file: this.gce_credentials_file,
58-
region: this.region
182+
region: this.region,
59183
});
60-
}
61-
}
184+
},
185+
},
186+
components: {
187+
"region-select": window.httpVueLoader("/static/region-select.vue"),
188+
},
62189
};
63190
</script>
191+
<style scoped>
192+
.dropzone {
193+
padding: 2em;
194+
border: 5px dotted #ccc;
195+
cursor: pointer;
196+
}
197+
input[type=file] {
198+
visibility: hidden;
199+
}
200+
.dropzone--can-drop {
201+
border-color: var(--blue);
202+
}
203+
.dropzone--error {
204+
border-color: var(--red);
205+
}
206+
.dropzone--success {
207+
border-color: var(--green);
208+
}
209+
</style>

app/static/provider-setup.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ module.exports = {
5959
components: {
6060
'digitalocean': window.httpVueLoader('/static/provider-do.vue'),
6161
'lightsail': window.httpVueLoader('/static/provider-lightsail.vue'),
62-
'ec2': window.httpVueLoader('/static/provider-ec2.vue')
62+
'ec2': window.httpVueLoader('/static/provider-ec2.vue'),
63+
'gce': window.httpVueLoader('/static/provider-gce.vue')
6364
}
6465
};
6566
</script>

app/static/region-select.vue

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<template>
2+
<div class="form-group">
3+
<label v-if="ui_show_slot"><slot></slot></label>
4+
<label v-if="ui_show_loading">Loading regions...</label>
5+
<label v-if="ui_show_select_prompt"
6+
>What region should the server be located in?</label
7+
>
8+
<select
9+
name="region"
10+
class="form-control"
11+
v-bind:value="value"
12+
v-bind:disabled="ui_disabled"
13+
v-on:input="$emit('input', $event.target.value)"
14+
>
15+
<option value disabled>Select region</option>
16+
<option
17+
v-for="(region, i) in options"
18+
v-bind:key="i"
19+
v-bind:value="region.key"
20+
>{{ region.value }}</option
21+
>
22+
</select>
23+
</div>
24+
</template>
25+
26+
<script>
27+
module.exports = {
28+
props: ["value", "options", "loading"],
29+
computed: {
30+
ui_disabled: function() {
31+
return !this.options.length || this.loading;
32+
},
33+
ui_show_slot: function() {
34+
return !this.loading && !this.options.length
35+
},
36+
ui_show_loading: function() {
37+
return this.loading;
38+
},
39+
ui_show_select_prompt: function() {
40+
return this.options.length > 0;
41+
}
42+
}
43+
};
44+
</script>

0 commit comments

Comments
 (0)