mirror of
https://github.com/trailofbits/algo.git
synced 2025-07-14 17:52:51 +02:00
Added GCE provider
This commit is contained in:
parent
149e7dd019
commit
97d3fabde2
4 changed files with 252 additions and 36 deletions
|
@ -21,7 +21,8 @@ task_program = ''
|
|||
|
||||
def run_playbook(data):
|
||||
global task_program
|
||||
extra_vars = ' '.join(['{0}={1}'.format(key, data[key]) for key in data.keys()])
|
||||
extra_vars = ' '.join(['{0}={1}'.format(key, data[key])
|
||||
for key in data.keys()])
|
||||
task_program = ['ansible-playbook', 'main.yml', '--extra-vars', extra_vars]
|
||||
return PlaybookCLI(task_program).run()
|
||||
|
||||
|
@ -101,7 +102,7 @@ async def post_exit(_):
|
|||
|
||||
|
||||
@routes.post('/lightsail_regions')
|
||||
async def post_exit(request):
|
||||
async def lightsail_regions(request):
|
||||
data = await request.json()
|
||||
client = boto3.client(
|
||||
'lightsail',
|
||||
|
@ -115,7 +116,7 @@ async def post_exit(request):
|
|||
|
||||
|
||||
@routes.post('/ec2_regions')
|
||||
async def post_exit(request):
|
||||
async def ec2_regions(request):
|
||||
data = await request.json()
|
||||
client = boto3.client(
|
||||
'ec2',
|
||||
|
@ -126,16 +127,40 @@ async def post_exit(request):
|
|||
return web.json_response(response)
|
||||
|
||||
|
||||
@routes.get('/gce_config')
|
||||
async def check_gce_config(request):
|
||||
gce_file = join(PROJECT_ROOT, 'configs', 'gce.json')
|
||||
response = {}
|
||||
try:
|
||||
json.loads(open(gce_file, 'r').read())['project_id']
|
||||
response['status'] = 'ok'
|
||||
except IOError:
|
||||
response['status'] = 'not_available'
|
||||
except ValueError:
|
||||
response['status'] = 'wrong_format'
|
||||
|
||||
return web.json_response(response)
|
||||
|
||||
|
||||
@routes.post('/gce_regions')
|
||||
async def post_exit(request):
|
||||
#data = await request.json()
|
||||
gce_config_file = 'configs/gce.json' # 'data.get('gce_config_file')
|
||||
project_id = json.loads(open(gce_config_file, 'r').read())['project_id']
|
||||
async def gce_regions(request):
|
||||
data = await request.json()
|
||||
gce_file_name = join(PROJECT_ROOT, 'configs', 'gce.json')
|
||||
if data.get('project_id'):
|
||||
# File is missing, save it. We can't get file path from browser :(
|
||||
with open(gce_file_name, 'w') as f:
|
||||
f.write(json.dumps(data))
|
||||
else:
|
||||
with open(gce_file_name, 'r') as f:
|
||||
data = json.loads(f.read())
|
||||
|
||||
response = AuthorizedSession(
|
||||
service_account.Credentials.from_service_account_file(gce_config_file).with_scopes(
|
||||
['https://www.googleapis.com/auth/compute'])).get(
|
||||
'https://www.googleapis.com/compute/v1/projects/{project_id}/regions'.format(project_id=project_id))
|
||||
service_account.Credentials.from_service_account_info(
|
||||
data).with_scopes(
|
||||
['https://www.googleapis.com/auth/compute'])).get(
|
||||
'https://www.googleapis.com/compute/v1/projects/{project_id}/regions'.format(
|
||||
project_id=data['project_id'])
|
||||
)
|
||||
|
||||
return web.json_response(json.loads(response.content))
|
||||
|
||||
|
|
|
@ -1,5 +1,44 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="form-group dropzone"
|
||||
v-if="ui_needs_upload"
|
||||
v-on:dragover.prevent="dragover_handler"
|
||||
v-on:dragleave.prevent="dragleave_handler"
|
||||
v-on:drop.prevent="drop_handler"
|
||||
v-on:click="show_file_select"
|
||||
v-bind:class="{
|
||||
'dropzone--can-drop': ui_can_drop,
|
||||
'dropzone--error': ui_drop_error,
|
||||
'dropzone--success': ui_drop_success,
|
||||
}"
|
||||
>
|
||||
<strong>Drag your GCE config file or click here</strong>
|
||||
<p>
|
||||
File <code>configs/gce.json</code> was not found. Please create it (<a
|
||||
href="https://github.com/trailofbits/algo/blob/master/docs/cloud-gce.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>how?</a
|
||||
>) or upload.
|
||||
</p>
|
||||
<p>After upload it <strong>will be saved</strong> in the configs folder.</p>
|
||||
<div v-if="ui_drop_error" class="alert alert-warning" role="alert">
|
||||
<strong>Error:</strong> {{ ui_drop_error }}.
|
||||
</div>
|
||||
|
||||
<div v-if="ui_drop_success" class="alert alert-success" role="alert">
|
||||
<strong>{{ ui_drop_filename }} loaded successfully</strong>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" accept=".json,applciation/json" v-on:change="filechange_handler" />
|
||||
|
||||
<div class="form-group">
|
||||
<region-select v-model="region" v-bind:options="ui_region_options" v-bind:loading="ui_loading_check || ui_loading_regions">
|
||||
<label>Please specify <code>gce.json</code> credentials file to select region</label>
|
||||
</region-select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
|
@ -13,51 +52,158 @@
|
|||
|
||||
<script>
|
||||
module.exports = {
|
||||
data: function() {
|
||||
data: function () {
|
||||
return {
|
||||
drop_error: null,
|
||||
gce_credentials_file: null,
|
||||
region: null,
|
||||
// helper variables
|
||||
region_options: [],
|
||||
is_loading: false
|
||||
ui_can_drop: false,
|
||||
ui_drop_error: null,
|
||||
ui_drop_success: null,
|
||||
ui_drop_filename: null,
|
||||
ui_needs_upload: null,
|
||||
ui_loading_regions: false,
|
||||
ui_loading_check: false,
|
||||
ui_region_options: []
|
||||
};
|
||||
},
|
||||
created: function() {
|
||||
this.check_config();
|
||||
},
|
||||
computed: {
|
||||
is_valid() {
|
||||
return this.gce_credentials_file && this.region;
|
||||
},
|
||||
is_region_disabled() {
|
||||
return !(this.gce_credentials_file) || this.is_loading;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
load_regions() {
|
||||
if (this.gce_credentials_file && this.region_options.length === 0) {
|
||||
this.is_loading = true;
|
||||
fetch('/gce_regions', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
gce_credentials_file: this.gce_credentials_file
|
||||
})
|
||||
})
|
||||
show_file_select(e) {
|
||||
if (e.target.tagName === 'A') {
|
||||
return;
|
||||
}
|
||||
const input = this.$el.querySelector(['input[type=file]']);
|
||||
const event = new MouseEvent('click', {
|
||||
'view': window,
|
||||
'bubbles': true,
|
||||
'cancelable': true
|
||||
});
|
||||
input.dispatchEvent(event);
|
||||
},
|
||||
dragover_handler(e) {
|
||||
this.ui_can_drop = true;
|
||||
this.ui_drop_success = false;
|
||||
this.ui_drop_error = false;
|
||||
this.ui_drop_filename = null;
|
||||
},
|
||||
dragleave_handler() {
|
||||
this.ui_can_drop = false;
|
||||
},
|
||||
drop_handler(e) {
|
||||
try {
|
||||
const droppedFiles = e.dataTransfer.files;
|
||||
if (droppedFiles.length !== 1) {
|
||||
this.ui_drop_error = 'Please upload GCE config as single file';
|
||||
}
|
||||
this.read_file(droppedFiles[0]);
|
||||
} catch (e) {
|
||||
this.ui_drop_error = 'Unhandled error while trying to read GCE config';
|
||||
}
|
||||
},
|
||||
filechange_handler(e) {
|
||||
if (e.target.files.length) {
|
||||
this.read_file(e.target.files[0]);
|
||||
}
|
||||
},
|
||||
read_file(file) {
|
||||
if (file.type !== 'application/json') {
|
||||
this.ui_drop_error = 'Incorrect file type';
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
let gce_config_content = null;
|
||||
try {
|
||||
gce_config_content = JSON.parse(e.target.result);
|
||||
this.ui_drop_success = true;
|
||||
this.ui_drop_filename = file.name;
|
||||
this.gce_credentials_file = 'configs/gce.json';
|
||||
} catch (e) {
|
||||
this.ui_drop_error = 'JSON format error';
|
||||
}
|
||||
gce_config_content && this.load_regions(gce_config_content);
|
||||
}
|
||||
reader.onerror = e => {
|
||||
this.ui_drop_error = 'Error while reading file';
|
||||
}
|
||||
reader.readAsText(file);
|
||||
|
||||
},
|
||||
check_config() {
|
||||
this.ui_loading_check = true;
|
||||
fetch("/gce_config")
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
this.region_options = data;
|
||||
.then(response => {
|
||||
if (response.status === 'ok') {
|
||||
this.gce_credentials_file = 'configs/gce.json';
|
||||
this.load_regions();
|
||||
this.ui_needs_upload = false;
|
||||
} else {
|
||||
this.ui_needs_upload = true;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.is_loading = false;
|
||||
this.ui_loading_check = false;
|
||||
});
|
||||
},
|
||||
load_regions(gce_config_content) {
|
||||
if (this.gce_credentials_file && this.ui_region_options.length === 0) {
|
||||
this.ui_loading_regions = true;
|
||||
fetch("/gce_regions", {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: gce_config_content ? JSON.stringify(gce_config_content) : '{}',
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
this.ui_region_options = data.items.map(i => ({
|
||||
value: i.name,
|
||||
key: i.name
|
||||
}));
|
||||
})
|
||||
.finally(() => {
|
||||
this.ui_loading_regions = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
submit() {
|
||||
this.$emit('submit', {
|
||||
this.$emit("submit", {
|
||||
gce_credentials_file: this.gce_credentials_file,
|
||||
region: this.region
|
||||
region: this.region,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
"region-select": window.httpVueLoader("/static/region-select.vue"),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.dropzone {
|
||||
padding: 2em;
|
||||
border: 5px dotted #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=file] {
|
||||
visibility: hidden;
|
||||
}
|
||||
.dropzone--can-drop {
|
||||
border-color: var(--blue);
|
||||
}
|
||||
.dropzone--error {
|
||||
border-color: var(--red);
|
||||
}
|
||||
.dropzone--success {
|
||||
border-color: var(--green);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -59,7 +59,8 @@ module.exports = {
|
|||
components: {
|
||||
'digitalocean': window.httpVueLoader('/static/provider-do.vue'),
|
||||
'lightsail': window.httpVueLoader('/static/provider-lightsail.vue'),
|
||||
'ec2': window.httpVueLoader('/static/provider-ec2.vue')
|
||||
'ec2': window.httpVueLoader('/static/provider-ec2.vue'),
|
||||
'gce': window.httpVueLoader('/static/provider-gce.vue')
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
44
app/static/region-select.vue
Normal file
44
app/static/region-select.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div class="form-group">
|
||||
<label v-if="ui_show_slot"><slot></slot></label>
|
||||
<label v-if="ui_show_loading">Loading regions...</label>
|
||||
<label v-if="ui_show_select_prompt"
|
||||
>What region should the server be located in?</label
|
||||
>
|
||||
<select
|
||||
name="region"
|
||||
class="form-control"
|
||||
v-bind:value="value"
|
||||
v-bind:disabled="ui_disabled"
|
||||
v-on:input="$emit('input', $event.target.value)"
|
||||
>
|
||||
<option value disabled>Select region</option>
|
||||
<option
|
||||
v-for="(region, i) in options"
|
||||
v-bind:key="i"
|
||||
v-bind:value="region.key"
|
||||
>{{ region.value }}</option
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
props: ["value", "options", "loading"],
|
||||
computed: {
|
||||
ui_disabled: function() {
|
||||
return !this.options.length || this.loading;
|
||||
},
|
||||
ui_show_slot: function() {
|
||||
return !this.loading && !this.options.length
|
||||
},
|
||||
ui_show_loading: function() {
|
||||
return this.loading;
|
||||
},
|
||||
ui_show_select_prompt: function() {
|
||||
return this.options.length > 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
Loading…
Add table
Reference in a new issue