mirror of
https://github.com/trailofbits/algo.git
synced 2025-08-10 23:03:03 +02:00
Merge branch 'master' into update-docs
This commit is contained in:
commit
5ab17fae59
11 changed files with 702 additions and 173 deletions
9
docs/cloud-scaleway.md
Normal file
9
docs/cloud-scaleway.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
### Configuration file
|
||||
|
||||
Algo requires an API key from your Scaleway account to create a server.
|
||||
The API key is generated by going to your Scaleway credentials at [https://console.scaleway.com/account/credentials](https://console.scaleway.com/account/credentials), and then selecting "Generate new token" on the right side of the box labeled "API Tokens".
|
||||
|
||||
Enter this token when Algo prompts you for the `auth token`.
|
||||
This information will be pass as the `algo_scaleway_token` variable when asked for in the Algo prompt.
|
||||
|
||||
Your organization ID is also on this page: https://console.scaleway.com/account/credentials
|
|
@ -230,7 +230,6 @@ Possible options can be gathered via cli `aws lightsail get-regions`
|
|||
Required variables:
|
||||
|
||||
- [scaleway_token](https://www.scaleway.com/docs/generate-an-api-token/)
|
||||
- [scaleway_org](https://cloud.scaleway.com/#/billing)
|
||||
- region
|
||||
|
||||
Possible regions:
|
||||
|
|
|
@ -18,7 +18,7 @@ First of all, check [this](https://github.com/trailofbits/algo#features) and ens
|
|||
* [Windows: The value of parameter linuxConfiguration.ssh.publicKeys.keyData is invalid](#windows-the-value-of-parameter-linuxconfigurationsshpublickeyskeydata-is-invalid)
|
||||
* [Docker: Failed to connect to the host via ssh](#docker-failed-to-connect-to-the-host-via-ssh)
|
||||
* [Wireguard: Unable to find 'configs/...' in expected paths](#wireguard-unable-to-find-configs-in-expected-paths)
|
||||
* [Ubuntu Error: "unable to write 'random state" when generating CA password](#ubuntu-error-unable-to-write-random-state-when-generating-ca-password")
|
||||
* [Ubuntu Error: "unable to write 'random state'" when generating CA password](#ubuntu-error-unable-to-write-random-state-when-generating-ca-password)
|
||||
* [Connection Problems](#connection-problems)
|
||||
* [I'm blocked or get CAPTCHAs when I access certain websites](#im-blocked-or-get-captchas-when-i-access-certain-websites)
|
||||
* [I want to change the list of trusted Wifi networks on my Apple device](#i-want-to-change-the-list-of-trusted-wifi-networks-on-my-apple-device)
|
||||
|
@ -153,7 +153,9 @@ You need to reset the permissions on your `.ssh` directory. Run `chmod 700 /home
|
|||
|
||||
### The region you want is not available
|
||||
|
||||
You want to install Algo to a specific region in a cloud provider, but that region is not available in the list given by the installer. In that case, you should [file an issue](https://github.com/trailofbits/algo/issues/new). Cloud providers add new regions on a regular basis and we don't always keep up. File an issue and give us information about what region is missing and we'll add it.
|
||||
Algo downloads the regions from the supported cloud providers (other than Microsoft Azure) listed in the first menu using APIs. If the region you want isn't available, the cloud provider has probably taken it offline for some reason. You should investigate further with your cloud provider.
|
||||
|
||||
If there's a specific region you want to install to in Microsoft Azure that isn't available, you should [file an issue](https://github.com/trailofbits/algo/issues/new), give us information about what region is missing, and we'll add it.
|
||||
|
||||
### AWS: SSH permission denied with an ECDSA key
|
||||
|
||||
|
@ -269,7 +271,7 @@ sudo rm -rf /etc/wireguard/*.lock
|
|||
```
|
||||
Then immediately re-run `./algo`.
|
||||
|
||||
### Ubuntu Error: "unable to write 'random state" when generating CA password
|
||||
### Ubuntu Error: "unable to write 'random state'" when generating CA password
|
||||
|
||||
When running Algo, you received an error like this:
|
||||
|
||||
|
|
18
input.yml
18
input.yml
|
@ -26,7 +26,7 @@
|
|||
|
||||
tasks:
|
||||
- block:
|
||||
- name: Region prompt
|
||||
- name: Cloud prompt
|
||||
pause:
|
||||
prompt: |
|
||||
What provider would you like to use?
|
||||
|
@ -122,11 +122,11 @@
|
|||
{{ _server | regex_replace('(?!\.)(\W|_)', '-') }}
|
||||
algo_ondemand_cellular: >-
|
||||
{% if ondemand_cellular is defined %}{{ ondemand_cellular | bool }}
|
||||
{%- elif _ondemand_cellular.user_input %}{{ booleans_map[_ondemand_cellular.user_input] | default(defaults['ondemand_cellular']) }}
|
||||
{%- elif _ondemand_cellular.user_input is defined %}{{ booleans_map[_ondemand_cellular.user_input] | default(defaults['ondemand_cellular']) }}
|
||||
{%- else %}false{% endif %}
|
||||
algo_ondemand_wifi: >-
|
||||
{% if ondemand_wifi is defined %}{{ ondemand_wifi | bool }}
|
||||
{%- elif _ondemand_wifi.user_input %}{{ booleans_map[_ondemand_wifi.user_input] | default(defaults['ondemand_wifi']) }}
|
||||
{%- elif _ondemand_wifi.user_input is defined %}{{ booleans_map[_ondemand_wifi.user_input] | default(defaults['ondemand_wifi']) }}
|
||||
{%- else %}false{% endif %}
|
||||
algo_ondemand_wifi_exclude: >-
|
||||
{% if ondemand_wifi_exclude is defined %}{{ ondemand_wifi_exclude | b64encode }}
|
||||
|
@ -135,19 +135,19 @@
|
|||
{%- else %}{{ '_null' | b64encode }}{% endif %}
|
||||
algo_local_dns: >-
|
||||
{% if local_dns is defined %}{{ local_dns | bool }}
|
||||
{%- elif _local_dns.user_input %}{{ booleans_map[_local_dns.user_input] | default(defaults['local_dns']) }}
|
||||
{%- elif _local_dns.user_input is defined %}{{ booleans_map[_local_dns.user_input] | default(defaults['local_dns']) }}
|
||||
{%- else %}false{% endif %}
|
||||
algo_ssh_tunneling: >-
|
||||
{% if ssh_tunneling is defined %}{{ ssh_tunneling | bool }}
|
||||
{%- elif _ssh_tunneling.user_input %}{{ booleans_map[_ssh_tunneling.user_input] | default(defaults['ssh_tunneling']) }}
|
||||
{%- elif _ssh_tunneling.user_input is defined %}{{ booleans_map[_ssh_tunneling.user_input] | default(defaults['ssh_tunneling']) }}
|
||||
{%- else %}false{% endif %}
|
||||
algo_windows: >-
|
||||
{% if windows is defined %}{{ windows | bool }}
|
||||
{%- elif _windows.user_input %}{{ booleans_map[_windows.user_input] | default(defaults['windows']) }}
|
||||
{%- elif _windows.user_input is defined %}{{ booleans_map[_windows.user_input] | default(defaults['windows']) }}
|
||||
{%- else %}false{% endif %}
|
||||
algo_store_cakey: >-
|
||||
{% if store_cakey is defined %}{{ store_cakey | bool }}
|
||||
{%- elif _store_cakey.user_input %}{{ booleans_map[_store_cakey.user_input] | default(defaults['store_cakey']) }}
|
||||
{%- else %}false{% endif %}
|
||||
{% if ipsec_enabled %}{%- if store_cakey is defined %}{{ store_cakey | bool }}
|
||||
{%- elif _store_cakey.user_input is defined %}{{ booleans_map[_store_cakey.user_input] | default(defaults['store_cakey']) }}
|
||||
{%- else %}false{% endif %}{% endif %}
|
||||
rescue:
|
||||
- include_tasks: playbooks/rescue.yml
|
||||
|
|
619
library/scaleway_compute.py
Normal file
619
library/scaleway_compute.py
Normal file
|
@ -0,0 +1,619 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Scaleway Compute management module
|
||||
#
|
||||
# Copyright (C) 2018 Online SAS.
|
||||
# https://www.scaleway.com
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: scaleway_compute
|
||||
short_description: Scaleway compute management module
|
||||
version_added: "2.6"
|
||||
author: Remy Leone (@sieben)
|
||||
description:
|
||||
- "This module manages compute instances on Scaleway."
|
||||
extends_documentation_fragment: scaleway
|
||||
|
||||
options:
|
||||
|
||||
enable_ipv6:
|
||||
description:
|
||||
- Enable public IPv6 connectivity on the instance
|
||||
default: false
|
||||
type: bool
|
||||
|
||||
boot_type:
|
||||
description:
|
||||
- Boot method
|
||||
default: bootscript
|
||||
choices:
|
||||
- bootscript
|
||||
- local
|
||||
|
||||
image:
|
||||
description:
|
||||
- Image identifier used to start the instance with
|
||||
required: true
|
||||
|
||||
name:
|
||||
description:
|
||||
- Name of the instance
|
||||
|
||||
organization:
|
||||
description:
|
||||
- Organization identifier
|
||||
required: true
|
||||
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the instance.
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
- running
|
||||
- restarted
|
||||
- stopped
|
||||
|
||||
tags:
|
||||
description:
|
||||
- List of tags to apply to the instance (5 max)
|
||||
required: false
|
||||
default: []
|
||||
|
||||
region:
|
||||
description:
|
||||
- Scaleway compute zone
|
||||
required: true
|
||||
choices:
|
||||
- ams1
|
||||
- EMEA-NL-EVS
|
||||
- par1
|
||||
- EMEA-FR-PAR1
|
||||
|
||||
commercial_type:
|
||||
description:
|
||||
- Commercial name of the compute node
|
||||
required: true
|
||||
choices:
|
||||
- ARM64-2GB
|
||||
- ARM64-4GB
|
||||
- ARM64-8GB
|
||||
- ARM64-16GB
|
||||
- ARM64-32GB
|
||||
- ARM64-64GB
|
||||
- ARM64-128GB
|
||||
- C1
|
||||
- C2S
|
||||
- C2M
|
||||
- C2L
|
||||
- START1-XS
|
||||
- START1-S
|
||||
- START1-M
|
||||
- START1-L
|
||||
- X64-15GB
|
||||
- X64-30GB
|
||||
- X64-60GB
|
||||
- X64-120GB
|
||||
|
||||
wait:
|
||||
description:
|
||||
- Wait for the instance to reach its desired state before returning.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
wait_timeout:
|
||||
description:
|
||||
- Time to wait for the server to reach the expected state
|
||||
required: false
|
||||
default: 300
|
||||
|
||||
wait_sleep_time:
|
||||
description:
|
||||
- Time to wait before every attempt to check the state of the server
|
||||
required: false
|
||||
default: 3
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a server
|
||||
scaleway_compute:
|
||||
name: foobar
|
||||
state: present
|
||||
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
|
||||
organization: 951df375-e094-4d26-97c1-ba548eeb9c42
|
||||
region: ams1
|
||||
commercial_type: VC1S
|
||||
tags:
|
||||
- test
|
||||
- www
|
||||
|
||||
- name: Destroy it right after
|
||||
scaleway_compute:
|
||||
name: foobar
|
||||
state: absent
|
||||
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
|
||||
organization: 951df375-e094-4d26-97c1-ba548eeb9c42
|
||||
region: ams1
|
||||
commercial_type: VC1S
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six.moves.urllib.parse import quote as urlquote
|
||||
from ansible.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway
|
||||
|
||||
SCALEWAY_COMMERCIAL_TYPES = [
|
||||
|
||||
# Virtual ARM64 compute instance
|
||||
'ARM64-2GB',
|
||||
'ARM64-4GB',
|
||||
'ARM64-8GB',
|
||||
'ARM64-16GB',
|
||||
'ARM64-32GB',
|
||||
'ARM64-64GB',
|
||||
'ARM64-128GB',
|
||||
|
||||
# Baremetal
|
||||
'C1', # ARM64 (4 cores) - 2GB
|
||||
'C2S', # X86-64 (4 cores) - 8GB
|
||||
'C2M', # X86-64 (8 cores) - 16GB
|
||||
'C2L', # x86-64 (8 cores) - 32 GB
|
||||
|
||||
# Virtual X86-64 compute instance
|
||||
'START1-XS', # Starter X86-64 (1 core) - 1GB - 25 GB NVMe
|
||||
'START1-S', # Starter X86-64 (2 cores) - 2GB - 50 GB NVMe
|
||||
'START1-M', # Starter X86-64 (4 cores) - 4GB - 100 GB NVMe
|
||||
'START1-L', # Starter X86-64 (8 cores) - 8GB - 200 GB NVMe
|
||||
'X64-15GB',
|
||||
'X64-30GB',
|
||||
'X64-60GB',
|
||||
'X64-120GB',
|
||||
]
|
||||
|
||||
SCALEWAY_SERVER_STATES = (
|
||||
'stopped',
|
||||
'stopping',
|
||||
'starting',
|
||||
'running',
|
||||
'locked'
|
||||
)
|
||||
|
||||
SCALEWAY_TRANSITIONS_STATES = (
|
||||
"stopping",
|
||||
"starting",
|
||||
"pending"
|
||||
)
|
||||
|
||||
|
||||
def fetch_state(compute_api, server):
|
||||
compute_api.module.debug("fetch_state of server: %s" % server["id"])
|
||||
response = compute_api.get(path="servers/%s" % server["id"])
|
||||
|
||||
if response.status_code == 404:
|
||||
return "absent"
|
||||
|
||||
if not response.ok:
|
||||
msg = 'Error during state fetching: (%s) %s' % (response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
try:
|
||||
compute_api.module.debug("Server %s in state: %s" % (server["id"], response.json["server"]["state"]))
|
||||
return response.json["server"]["state"]
|
||||
except KeyError:
|
||||
compute_api.module.fail_json(msg="Could not fetch state in %s" % response.json)
|
||||
|
||||
|
||||
def wait_to_complete_state_transition(compute_api, server):
|
||||
wait = compute_api.module.params["wait"]
|
||||
if not wait:
|
||||
return
|
||||
wait_timeout = compute_api.module.params["wait_timeout"]
|
||||
wait_sleep_time = compute_api.module.params["wait_sleep_time"]
|
||||
|
||||
start = datetime.datetime.utcnow()
|
||||
end = start + datetime.timedelta(seconds=wait_timeout)
|
||||
while datetime.datetime.utcnow() < end:
|
||||
compute_api.module.debug("We are going to wait for the server to finish its transition")
|
||||
if fetch_state(compute_api, server) not in SCALEWAY_TRANSITIONS_STATES:
|
||||
compute_api.module.debug("It seems that the server is not in transition anymore.")
|
||||
compute_api.module.debug("Server in state: %s" % fetch_state(compute_api, server))
|
||||
break
|
||||
time.sleep(wait_sleep_time)
|
||||
else:
|
||||
compute_api.module.fail_json(msg="Server takes too long to finish its transition")
|
||||
|
||||
|
||||
def create_server(compute_api, server):
|
||||
compute_api.module.debug("Starting a create_server")
|
||||
target_server = None
|
||||
response = compute_api.post(path="servers",
|
||||
data={"enable_ipv6": server["enable_ipv6"],
|
||||
"boot_type": server["boot_type"],
|
||||
"tags": server["tags"],
|
||||
"commercial_type": server["commercial_type"],
|
||||
"image": server["image"],
|
||||
"name": server["name"],
|
||||
"organization": server["organization"]})
|
||||
|
||||
if not response.ok:
|
||||
msg = 'Error during server creation: (%s) %s' % (response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
try:
|
||||
target_server = response.json["server"]
|
||||
except KeyError:
|
||||
compute_api.module.fail_json(msg="Error in getting the server information from: %s" % response.json)
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
|
||||
return target_server
|
||||
|
||||
|
||||
def restart_server(compute_api, server):
|
||||
return perform_action(compute_api=compute_api, server=server, action="reboot")
|
||||
|
||||
|
||||
def stop_server(compute_api, server):
|
||||
return perform_action(compute_api=compute_api, server=server, action="poweroff")
|
||||
|
||||
|
||||
def start_server(compute_api, server):
|
||||
return perform_action(compute_api=compute_api, server=server, action="poweron")
|
||||
|
||||
|
||||
def perform_action(compute_api, server, action):
|
||||
response = compute_api.post(path="servers/%s/action" % server["id"],
|
||||
data={"action": action})
|
||||
if not response.ok:
|
||||
msg = 'Error during server %s: (%s) %s' % (action, response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=server)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def remove_server(compute_api, server):
|
||||
compute_api.module.debug("Starting remove server strategy")
|
||||
response = compute_api.delete(path="servers/%s" % server["id"])
|
||||
if not response.ok:
|
||||
msg = 'Error during server deletion: (%s) %s' % (response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=server)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def present_strategy(compute_api, wished_server):
|
||||
compute_api.module.debug("Starting present strategy")
|
||||
changed = False
|
||||
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
||||
|
||||
if not query_results:
|
||||
changed = True
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "A server would be created."}
|
||||
|
||||
target_server = create_server(compute_api=compute_api, server=wished_server)
|
||||
else:
|
||||
target_server = query_results[0]
|
||||
|
||||
if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server,
|
||||
wished_server=wished_server):
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "Server %s attributes would be changed." % target_server["id"]}
|
||||
|
||||
server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server)
|
||||
|
||||
return changed, target_server
|
||||
|
||||
|
||||
def absent_strategy(compute_api, wished_server):
|
||||
compute_api.module.debug("Starting absent strategy")
|
||||
changed = False
|
||||
target_server = None
|
||||
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
||||
|
||||
if not query_results:
|
||||
return changed, {"status": "Server already absent."}
|
||||
else:
|
||||
target_server = query_results[0]
|
||||
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "Server %s would be made absent." % target_server["id"]}
|
||||
|
||||
# A server MUST be stopped to be deleted.
|
||||
while not fetch_state(compute_api=compute_api, server=target_server) == "stopped":
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
response = stop_server(compute_api=compute_api, server=target_server)
|
||||
|
||||
if not response.ok:
|
||||
err_msg = 'Error while stopping a server before removing it [{0}: {1}]'.format(response.status_code,
|
||||
response.json)
|
||||
compute_api.module.fail_json(msg=err_msg)
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
|
||||
response = remove_server(compute_api=compute_api, server=target_server)
|
||||
|
||||
if not response.ok:
|
||||
err_msg = 'Error while removing server [{0}: {1}]'.format(response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=err_msg)
|
||||
|
||||
return changed, {"status": "Server %s deleted" % target_server["id"]}
|
||||
|
||||
|
||||
def running_strategy(compute_api, wished_server):
|
||||
compute_api.module.debug("Starting running strategy")
|
||||
changed = False
|
||||
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
||||
|
||||
if not query_results:
|
||||
changed = True
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "A server would be created before being run."}
|
||||
|
||||
target_server = create_server(compute_api=compute_api, server=wished_server)
|
||||
else:
|
||||
target_server = query_results[0]
|
||||
|
||||
if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server,
|
||||
wished_server=wished_server):
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "Server %s attributes would be changed before running it." % target_server["id"]}
|
||||
|
||||
server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server)
|
||||
|
||||
current_state = fetch_state(compute_api=compute_api, server=target_server)
|
||||
if current_state not in ("running", "starting"):
|
||||
compute_api.module.debug("running_strategy: Server in state: %s" % current_state)
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "Server %s attributes would be changed." % target_server["id"]}
|
||||
|
||||
response = start_server(compute_api=compute_api, server=target_server)
|
||||
if not response.ok:
|
||||
msg = 'Error while running server [{0}: {1}]'.format(response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
return changed, target_server
|
||||
|
||||
|
||||
def stop_strategy(compute_api, wished_server):
|
||||
compute_api.module.debug("Starting stop strategy")
|
||||
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
||||
|
||||
changed = False
|
||||
|
||||
if not query_results:
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "A server would be created before being stopped."}
|
||||
|
||||
target_server = create_server(compute_api=compute_api, server=wished_server)
|
||||
changed = True
|
||||
else:
|
||||
target_server = query_results[0]
|
||||
|
||||
compute_api.module.debug("stop_strategy: Servers are found.")
|
||||
|
||||
if server_attributes_should_be_changed(compute_api=compute_api, target_server=target_server,
|
||||
wished_server=wished_server):
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {
|
||||
"status": "Server %s attributes would be changed before stopping it." % target_server["id"]}
|
||||
|
||||
server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server)
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
|
||||
current_state = fetch_state(compute_api=compute_api, server=target_server)
|
||||
if current_state not in ("stopped",):
|
||||
compute_api.module.debug("stop_strategy: Server in state: %s" % current_state)
|
||||
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "Server %s would be stopped." % target_server["id"]}
|
||||
|
||||
response = stop_server(compute_api=compute_api, server=target_server)
|
||||
compute_api.module.debug(response.json)
|
||||
compute_api.module.debug(response.ok)
|
||||
|
||||
if not response.ok:
|
||||
msg = 'Error while stopping server [{0}: {1}]'.format(response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
return changed, target_server
|
||||
|
||||
|
||||
def restart_strategy(compute_api, wished_server):
|
||||
compute_api.module.debug("Starting restart strategy")
|
||||
changed = False
|
||||
query_results = find(compute_api=compute_api, wished_server=wished_server, per_page=1)
|
||||
|
||||
if not query_results:
|
||||
changed = True
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "A server would be created before being rebooted."}
|
||||
|
||||
target_server = create_server(compute_api=compute_api, server=wished_server)
|
||||
else:
|
||||
target_server = query_results[0]
|
||||
|
||||
if server_attributes_should_be_changed(compute_api=compute_api,
|
||||
target_server=target_server,
|
||||
wished_server=wished_server):
|
||||
changed = True
|
||||
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {
|
||||
"status": "Server %s attributes would be changed before rebooting it." % target_server["id"]}
|
||||
|
||||
server_change_attributes(compute_api=compute_api, target_server=target_server, wished_server=wished_server)
|
||||
|
||||
changed = True
|
||||
if compute_api.module.check_mode:
|
||||
return changed, {"status": "Server %s would be rebooted." % target_server["id"]}
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
|
||||
if fetch_state(compute_api=compute_api, server=target_server) in ("running",):
|
||||
response = restart_server(compute_api=compute_api, server=target_server)
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
if not response.ok:
|
||||
msg = 'Error while restarting server that was running [{0}: {1}].'.format(response.status_code,
|
||||
response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
if fetch_state(compute_api=compute_api, server=target_server) in ("stopped",):
|
||||
response = restart_server(compute_api=compute_api, server=target_server)
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
if not response.ok:
|
||||
msg = 'Error while restarting server that was stopped [{0}: {1}].'.format(response.status_code,
|
||||
response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
return changed, target_server
|
||||
|
||||
|
||||
state_strategy = {
|
||||
"present": present_strategy,
|
||||
"restarted": restart_strategy,
|
||||
"stopped": stop_strategy,
|
||||
"running": running_strategy,
|
||||
"absent": absent_strategy
|
||||
}
|
||||
|
||||
|
||||
def find(compute_api, wished_server, per_page=1):
|
||||
compute_api.module.debug("Getting inside find")
|
||||
# Only the name attribute is accepted in the Compute query API
|
||||
url = 'servers?name=%s&per_page=%d' % (urlquote(wished_server["name"]), per_page)
|
||||
response = compute_api.get(url)
|
||||
|
||||
if not response.ok:
|
||||
msg = 'Error during server search: (%s) %s' % (response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
search_results = response.json["servers"]
|
||||
|
||||
return search_results
|
||||
|
||||
|
||||
PATCH_MUTABLE_SERVER_ATTRIBUTES = (
|
||||
"ipv6",
|
||||
"tags",
|
||||
"name",
|
||||
"dynamic_ip_required",
|
||||
)
|
||||
|
||||
|
||||
def server_attributes_should_be_changed(compute_api, target_server, wished_server):
|
||||
compute_api.module.debug("Checking if server attributes should be changed")
|
||||
compute_api.module.debug("Current Server: %s" % target_server)
|
||||
compute_api.module.debug("Wished Server: %s" % wished_server)
|
||||
debug_dict = dict((x, (target_server[x], wished_server[x]))
|
||||
for x in PATCH_MUTABLE_SERVER_ATTRIBUTES
|
||||
if x in target_server and x in wished_server)
|
||||
compute_api.module.debug("Debug dict %s" % debug_dict)
|
||||
|
||||
try:
|
||||
return any([target_server[x] != wished_server[x]
|
||||
for x in PATCH_MUTABLE_SERVER_ATTRIBUTES
|
||||
if x in target_server and x in wished_server])
|
||||
except AttributeError:
|
||||
compute_api.module.fail_json(msg="Error while checking if attributes should be changed")
|
||||
|
||||
|
||||
def server_change_attributes(compute_api, target_server, wished_server):
|
||||
compute_api.module.debug("Starting patching server attributes")
|
||||
patch_payload = dict((x, wished_server[x])
|
||||
for x in PATCH_MUTABLE_SERVER_ATTRIBUTES
|
||||
if x in wished_server and x in target_server)
|
||||
response = compute_api.patch(path="servers/%s" % target_server["id"],
|
||||
data=patch_payload)
|
||||
if not response.ok:
|
||||
msg = 'Error during server attributes patching: (%s) %s' % (response.status_code, response.json)
|
||||
compute_api.module.fail_json(msg=msg)
|
||||
|
||||
wait_to_complete_state_transition(compute_api=compute_api, server=target_server)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def core(module):
|
||||
region = module.params["region"]
|
||||
wished_server = {
|
||||
"state": module.params["state"],
|
||||
"image": module.params["image"],
|
||||
"name": module.params["name"],
|
||||
"commercial_type": module.params["commercial_type"],
|
||||
"enable_ipv6": module.params["enable_ipv6"],
|
||||
"boot_type": module.params["boot_type"],
|
||||
"tags": module.params["tags"],
|
||||
"organization": module.params["organization"]
|
||||
}
|
||||
module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"]
|
||||
|
||||
compute_api = Scaleway(module=module)
|
||||
|
||||
changed, summary = state_strategy[wished_server["state"]](compute_api=compute_api, wished_server=wished_server)
|
||||
module.exit_json(changed=changed, msg=summary)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = scaleway_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
image=dict(required=True),
|
||||
name=dict(),
|
||||
region=dict(required=True, choices=SCALEWAY_LOCATION.keys()),
|
||||
commercial_type=dict(required=True, choices=SCALEWAY_COMMERCIAL_TYPES),
|
||||
enable_ipv6=dict(default=False, type="bool"),
|
||||
boot_type=dict(default="bootscript"),
|
||||
state=dict(choices=state_strategy.keys(), default='present'),
|
||||
tags=dict(type="list", default=[]),
|
||||
organization=dict(required=True),
|
||||
wait=dict(type="bool", default=False),
|
||||
wait_timeout=dict(type="int", default=300),
|
||||
wait_sleep_time=dict(type="int", default=3),
|
||||
))
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
core(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -4,12 +4,14 @@
|
|||
shell: >
|
||||
./algo-showenv.sh \
|
||||
'algo_provider "{{ algo_provider }}"' \
|
||||
{% if ipsec_enabled %}
|
||||
'algo_ondemand_cellular "{{ algo_ondemand_cellular }}"' \
|
||||
'algo_ondemand_wifi "{{ algo_ondemand_wifi }}"' \
|
||||
'algo_ondemand_wifi_exclude "{{ algo_ondemand_wifi_exclude }}"' \
|
||||
'algo_windows "{{ algo_windows }}"' \
|
||||
{% endif %}
|
||||
'algo_local_dns "{{ algo_local_dns }}"' \
|
||||
'algo_ssh_tunneling "{{ algo_ssh_tunneling }}"' \
|
||||
'algo_windows "{{ algo_windows }}"' \
|
||||
'wireguard_enabled "{{ wireguard_enabled }}"' \
|
||||
'dns_encryption "{{ dns_encryption }}"' \
|
||||
> /dev/tty
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
- name: Set image id as a fact
|
||||
set_fact:
|
||||
image_id: "{{ item.id }}"
|
||||
no_log: true
|
||||
when:
|
||||
- cloud_providers.scaleway.image == item.name
|
||||
- cloud_providers.scaleway.arch == item.arch
|
||||
- server_disk_size == item.root_volume.size
|
||||
with_items: "{{ outer_item['json']['images'] }}"
|
|
@ -1,133 +1,46 @@
|
|||
- name: Include prompts
|
||||
import_tasks: prompts.yml
|
||||
|
||||
- name: Set disk size
|
||||
set_fact:
|
||||
server_disk_size: 50000000000
|
||||
|
||||
- name: Check server size
|
||||
set_fact:
|
||||
server_disk_size: 25000000000
|
||||
when: cloud_providers.scaleway.size == "START1-XS"
|
||||
|
||||
- name: Check if server exists
|
||||
uri:
|
||||
url: "https://cp-{{ algo_region }}.scaleway.com/servers"
|
||||
method: GET
|
||||
headers:
|
||||
Content-Type: 'application/json'
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
status_code: 200
|
||||
register: scaleway_servers
|
||||
|
||||
- name: Set server id as a fact
|
||||
set_fact:
|
||||
server_id: "{{ item.id }}"
|
||||
no_log: true
|
||||
when: algo_server_name == item.name
|
||||
with_items: "{{ scaleway_servers.json.servers }}"
|
||||
|
||||
- name: Create a server if it doesn't exist
|
||||
block:
|
||||
- name: Get the organization id
|
||||
uri:
|
||||
url: https://account.cloud.online.net/organizations
|
||||
method: GET
|
||||
headers:
|
||||
Content-Type: 'application/json'
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
status_code: 200
|
||||
register: scaleway_organizations
|
||||
|
||||
- name: Set organization id as a fact
|
||||
set_fact:
|
||||
organization_id: "{{ item.id }}"
|
||||
no_log: true
|
||||
when: algo_scaleway_org == item.name
|
||||
with_items: "{{ scaleway_organizations.json.organizations }}"
|
||||
|
||||
- name: Get total count of images
|
||||
uri:
|
||||
url: "https://cp-{{ algo_region }}.scaleway.com/images"
|
||||
method: GET
|
||||
headers:
|
||||
Content-Type: 'application/json'
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
status_code: 200
|
||||
register: scaleway_pages
|
||||
- block:
|
||||
- name: Gather Scaleway organizations facts
|
||||
scaleway_organization_facts:
|
||||
|
||||
- name: Get images
|
||||
uri:
|
||||
url: "https://cp-{{ algo_region }}.scaleway.com/images?per_page=100&page={{ item }}"
|
||||
method: GET
|
||||
headers:
|
||||
Content-Type: 'application/json'
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
status_code: 200
|
||||
register: scaleway_images
|
||||
with_sequence: start=1 end={{ ((scaleway_pages.x_total_count|int / 100)| round )|int }}
|
||||
scaleway_image_facts:
|
||||
region: "{{ algo_region }}"
|
||||
|
||||
- name: Set image id as a fact
|
||||
include_tasks: image_facts.yml
|
||||
with_items: "{{ scaleway_images['results'] }}"
|
||||
loop_control:
|
||||
loop_var: outer_item
|
||||
- name: Set cloud specific facts
|
||||
set_fact:
|
||||
organization_id: "{{ scaleway_organization_facts[0]['id'] }}"
|
||||
images: >-
|
||||
[{% for i in scaleway_image_facts -%}
|
||||
{% if i.name == cloud_providers.scaleway.image and
|
||||
i.arch == cloud_providers.scaleway.arch -%}
|
||||
'{{ i.id }}'{% if not loop.last %},{% endif %}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}]
|
||||
|
||||
- name: Create a server
|
||||
uri:
|
||||
url: "https://cp-{{ algo_region }}.scaleway.com/servers/"
|
||||
method: POST
|
||||
headers:
|
||||
Content-Type: 'application/json'
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
body:
|
||||
organization: "{{ organization_id }}"
|
||||
name: "{{ algo_server_name }}"
|
||||
image: "{{ image_id }}"
|
||||
commercial_type: "{{ cloud_providers.scaleway.size }}"
|
||||
enable_ipv6: true
|
||||
boot_type: local
|
||||
tags:
|
||||
- Environment:Algo
|
||||
- AUTHORIZED_KEY={{ lookup('file', SSH_keys.public)|regex_replace(' ', '_') }}
|
||||
status_code: 201
|
||||
body_format: json
|
||||
scaleway_compute:
|
||||
name: "{{ algo_server_name }}"
|
||||
enable_ipv6: true
|
||||
boot_type: local
|
||||
state: running
|
||||
image: "{{ images[0] }}"
|
||||
organization: "{{ organization_id }}"
|
||||
region: "{{ algo_region }}"
|
||||
commercial_type: "{{ cloud_providers.scaleway.size }}"
|
||||
wait: true
|
||||
tags:
|
||||
- Environment:Algo
|
||||
- AUTHORIZED_KEY={{ lookup('file', SSH_keys.public)|regex_replace(' ', '_') }}
|
||||
register: algo_instance
|
||||
|
||||
- name: Set server id as a fact
|
||||
set_fact:
|
||||
server_id: "{{ algo_instance.json.server.id }}"
|
||||
when: server_id is not defined
|
||||
|
||||
- name: Power on the server
|
||||
uri:
|
||||
url: https://cp-{{ algo_region }}.scaleway.com/servers/{{ server_id }}/action
|
||||
method: POST
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
body:
|
||||
action: poweron
|
||||
status_code: 202
|
||||
body_format: json
|
||||
ignore_errors: true
|
||||
no_log: true
|
||||
|
||||
- name: Wait for the server to become running
|
||||
uri:
|
||||
url: "https://cp-{{ algo_region }}.scaleway.com/servers/{{ server_id }}"
|
||||
method: GET
|
||||
headers:
|
||||
Content-Type: 'application/json'
|
||||
X-Auth-Token: "{{ algo_scaleway_token }}"
|
||||
status_code: 200
|
||||
until:
|
||||
- algo_instance.json.server.state is defined
|
||||
- algo_instance.json.server.state == "running"
|
||||
retries: 20
|
||||
delay: 30
|
||||
register: algo_instance
|
||||
until: algo_instance.msg.public_ip
|
||||
retries: 3
|
||||
delay: 3
|
||||
environment:
|
||||
SCW_TOKEN: "{{ algo_scaleway_token }}"
|
||||
|
||||
- set_fact:
|
||||
cloud_instance_ip: "{{ algo_instance['json']['server']['public_ip']['address'] }}"
|
||||
cloud_instance_ip: "{{ algo_instance.msg.public_ip.address }}"
|
||||
ansible_ssh_user: root
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
---
|
||||
- pause:
|
||||
prompt: |
|
||||
Enter your auth token (https://www.scaleway.com/docs/generate-an-api-token/)
|
||||
Enter your auth token (https://trailofbits.github.io/algo/cloud-scaleway.html)
|
||||
echo: false
|
||||
register: _scaleway_token
|
||||
when: scaleway_token is undefined
|
||||
|
||||
- pause:
|
||||
prompt: |
|
||||
Enter your organization name (https://cloud.scaleway.com/#/billing)
|
||||
register: _scaleway_org
|
||||
when: scaleway_org is undefined
|
||||
when:
|
||||
- scaleway_token is undefined
|
||||
- lookup('env','SCW_TOKEN')|length <= 0
|
||||
|
||||
- pause:
|
||||
prompt: |
|
||||
|
@ -26,8 +22,7 @@
|
|||
|
||||
- name: Set scaleway facts
|
||||
set_fact:
|
||||
algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) }}"
|
||||
algo_scaleway_org: "{{ scaleway_org | default(_scaleway_org.user_input|default(omit)) }}"
|
||||
algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) | default(lookup('env','SCW_TOKEN'), true) }}"
|
||||
algo_region: >-
|
||||
{% if region is defined %}{{ region }}
|
||||
{%- elif _algo_region.user_input %}{{ scaleway_regions[_algo_region.user_input | int -1 ]['alias'] }}
|
||||
|
|
|
@ -13,21 +13,21 @@
|
|||
{%- elif _algo_server.user_input %}{{ _algo_server.user_input }}
|
||||
{%- else %}localhost{% endif %}
|
||||
|
||||
- pause:
|
||||
prompt: |
|
||||
What user should we use to login on the server? (note: passwordless login required, or ignore if you're deploying to localhost)
|
||||
[root]
|
||||
register: _algo_ssh_user
|
||||
when:
|
||||
- ssh_user is undefined
|
||||
- cloud_instance_ip != "localhost"
|
||||
- block:
|
||||
- pause:
|
||||
prompt: |
|
||||
What user should we use to login on the server? (note: passwordless login required, or ignore if you're deploying to localhost)
|
||||
[root]
|
||||
register: _algo_ssh_user
|
||||
when: ssh_user is undefined
|
||||
|
||||
- name: Set the facts
|
||||
set_fact:
|
||||
ansible_ssh_user: >-
|
||||
{% if ssh_user is defined %}{{ ssh_user }}
|
||||
{%- elif _algo_ssh_user.user_input %}{{ _algo_ssh_user.user_input }}
|
||||
{%- else %}root{% endif %}
|
||||
- name: Set the facts
|
||||
set_fact:
|
||||
ansible_ssh_user: >-
|
||||
{% if ssh_user is defined %}{{ ssh_user }}
|
||||
{%- elif _algo_ssh_user.user_input %}{{ _algo_ssh_user.user_input }}
|
||||
{%- else %}root{% endif %}
|
||||
when: cloud_instance_ip != "localhost"
|
||||
|
||||
- pause:
|
||||
prompt: |
|
||||
|
|
|
@ -40,6 +40,10 @@
|
|||
{%- elif _ca_password.user_input %}{{ _ca_password.user_input }}
|
||||
{%- else %}omit{% endif %}
|
||||
|
||||
- name: Local pre-tasks
|
||||
import_tasks: playbooks/cloud-pre.yml
|
||||
become: false
|
||||
|
||||
- name: Add the server to the vpn-host group
|
||||
add_host:
|
||||
name: "{{ algo_server }}"
|
||||
|
@ -61,10 +65,6 @@
|
|||
|
||||
tasks:
|
||||
- block:
|
||||
- name: Local pre-tasks
|
||||
import_tasks: playbooks/cloud-pre.yml
|
||||
become: false
|
||||
|
||||
- import_role:
|
||||
name: common
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue