summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2023-06-20 15:49:40 +0200
committertv <tv@krebsco.de>2023-06-20 22:08:21 +0200
commitc258706c0313e2014b4df1ca9b282062382b9e32 (patch)
tree1e1512518d5e624fc50e41647ffc9e7c6875fcd3
parentda7133a088b74fd21dde255c24edac7facc58209 (diff)
snatch-instance: init
-rwxr-xr-xbin/snatch-instance354
1 files changed, 354 insertions, 0 deletions
diff --git a/bin/snatch-instance b/bin/snatch-instance
new file mode 100755
index 0000000..f4f051c
--- /dev/null
+++ b/bin/snatch-instance
@@ -0,0 +1,354 @@
+#! /usr/bin/env nix-shell
+#! nix-shell -i bash -p coreutils file gnugrep jq oci-cli openssh terraform
+#
+# usage: snatch-instance [PARAM...]
+#
+# Obtain a compute instance at OCI, and terminate once successful.
+# This program might be useful when Oracle is out of host capacity.
+#
+# For possible values of PARAM search for "Available parameters" in this file.
+
+set -efu
+
+main() {(
+
+ # Available parameters and their default values.
+ # Change parameters via command line using following syntax:
+ # --NAME=VALUE
+ # where NAME is one of param_NAME below.
+ #
+ # Example: --display_name=mycomputer
+ param_apply=true
+ param_availability_domain_list_json=
+ param_display_name=computer
+ param_shape=VM.Standard.A1.Flex
+ param_memory_in_gbs=24
+ param_ocpus=4
+ param_ssh_authorized_key_file=$HOME/.ssh/id_rsa.pub
+ param_workdir=
+ param_compartment_id=
+ param_source_id=
+ param_subnet_id=
+
+ # Set parameters from command line.
+ for arg; do
+ case $arg in
+ --*=*)
+ readonly "param_${arg#--}"
+ ;;
+ *)
+ echo "$0: warning: ignoring unknown argument: $arg" >&2
+ esac
+ done
+
+ #
+ # Compute parameter defaults for each unset parameter.
+ #
+
+ if test -n "$param_apply"; then
+ conf_apply=$param_apply
+ else
+ echo "$0: error: undefined parameter: $param_apply" >&2
+ exit 2
+ fi
+ echo "$0: apply: $conf_apply" >&2
+
+ if test -n "$param_display_name"; then
+ conf_display_name=$param_display_name
+ else
+ echo "$0: error: undefined parameter: $param_display_name" >&2
+ exit 2
+ fi
+ echo "$0: display_name: $conf_display_name" >&2
+
+ if test -n "$param_ssh_authorized_key_file"; then
+ if ! is_ssh_public_key "$param_ssh_authorized_key_file"; then
+ echo "$0: error: not an ssh public key: $param_ssh_authorized_key_file" >&2
+ exit 1
+ fi
+ conf_ssh_authorized_key=$(cat "$param_ssh_authorized_key_file")
+ else
+ echo "$0: error: undefined parameter: $param_ssh_authorized_key_file" >&2
+ exit 2
+ fi
+
+ if test -n "$param_workdir"; then
+ conf_workdir=$param_workdir
+ mkdir -v -p "$conf_workdir"
+ else
+ conf_workdir=$(mktemp -d -t oci.snatch-instance.$$.XXXXXXXX)
+ trap cleanup EXIT
+ cleanup() {
+ rm -v -R "$conf_workdir"
+ }
+ fi
+ echo "$0: workdir: $conf_workdir" >&2
+
+ if test -n "$param_compartment_id"; then
+ conf_compartment_id=$param_compartment_id
+ else
+ conf_compartment_id=$(
+ export response="$(
+ oci iam compartment list \
+ --lifecycle-state=ACTIVE \
+ )"
+ jq -enr '
+ def assert(cond; msg): if cond then . else error(msg) end;
+
+ env.response | fromjson |
+ .data |
+ assert(length == 1; "could not find exactly one compartment") |
+ .[0] |
+ .["compartment-id"]
+ '
+ )
+ fi
+ echo "$0: compartment_id: $conf_compartment_id" >&2
+
+ if test -n "$param_availability_domain_list_json"; then
+ conf_availability_domain_list_json=$param_availability_domain_list_json
+ else
+ conf_availability_domain_list_json=$(
+ export response="$(
+ oci iam availability-domain list \
+ --compartment-id="$conf_compartment_id"
+ )"
+ jq -enr '
+ env.response | fromjson |
+ .data|map(.name)
+ '
+ )
+ export conf_availability_domain_list_json
+ fi
+ echo "$0: availability_domain_list: $(jq -enr '
+ env.conf_availability_domain_list_json | fromjson |
+ join(", ")
+ ')" >&2
+
+ if test -n "$param_shape"; then
+ conf_shape=$param_shape
+ else
+ echo "$0: error: undefined parameter: $param_shape" >&2
+ exit 2
+ fi
+ echo "$0: shape: $conf_shape" >&2
+
+ if test -n "$param_memory_in_gbs"; then
+ conf_memory_in_gbs=$param_memory_in_gbs
+ else
+ echo "$0: error: undefined parameter: $param_memory_in_gbs" >&2
+ exit 2
+ fi
+ echo "$0: memory_in_gbs: $conf_memory_in_gbs" >&2
+
+ if test -n "$param_ocpus"; then
+ conf_ocpus=$param_ocpus
+ else
+ echo "$0: error: undefined parameter: $param_ocpus" >&2
+ exit 2
+ fi
+ echo "$0: ocpus: $conf_ocpus" >&2
+
+ if test -n "$param_source_id"; then
+ conf_source_id=$param_source_id
+ else
+ conf_source_id=$(
+ export response="$(
+ oci compute image list \
+ --all \
+ --lifecycle-state=AVAILABLE \
+ --operating-system='Canonical Ubuntu' \
+ --compartment-id="$conf_compartment_id" \
+ --shape="$conf_shape" \
+ )"
+
+ jq -enr '
+ env.response | fromjson |
+ .data |
+ sort_by(.["display-name"]) |
+ last |
+ .id
+ '
+ )
+ fi
+ echo "$0: source_id: $conf_source_id" >&2
+
+ if test -n "$param_subnet_id"; then
+ subnet_id=$param_subnet_id
+ else
+ conf_subnet_id=$(
+ export response="$(
+ oci network subnet list \
+ --compartment-id="$conf_compartment_id" \
+ --lifecycle-state=AVAILABLE \
+ )"
+ jq -enr '
+ def assert(cond; msg): if cond then . else error(msg) end;
+
+ env.response | fromjson |
+ .data |
+ assert(length == 1; "could not find exactly one compartment") |
+ .[0] |
+ .id
+ '
+ )
+ fi
+ echo "$0: subnet_id: $conf_subnet_id" >&2
+
+ # This tf_config will create a minimal (not applicable) configuration
+ # which is sufficient to initalize terraform plugins.
+ tf_config > "$conf_workdir"/main.tf.json
+
+ terraform -chdir="$conf_workdir" init
+
+ attempt=0
+ until {
+ attempt=$(expr $attempt + 1)
+ availability_domain=$(
+ jq -enr --argjson attempt "$attempt" '
+ env.conf_availability_domain_list_json | fromjson |
+
+ .[($attempt - 1) % length]
+ '
+ )
+ tf_config \
+ --availability_domain="$availability_domain" \
+ --compartment_id="$conf_compartment_id" \
+ --display_name="$conf_display_name" \
+ --memory_in_gbs="$conf_memory_in_gbs" \
+ --ocpus="$conf_ocpus" \
+ --shape="$conf_shape" \
+ --source_id="$conf_source_id" \
+ --ssh_authorized_key="$conf_ssh_authorized_key" \
+ --subnet_id="$conf_subnet_id" \
+ > "$conf_workdir"/main.tf.json
+ terraform -chdir="$conf_workdir" plan -out "$conf_workdir"/main.tfplan
+ if test "$conf_apply" = true; then
+ terraform -chdir="$conf_workdir" apply "$conf_workdir"/main.tfplan
+ else
+ echo "$0: Not not applying terraform plan" >&2
+ fi
+ }; do
+ echo sleeping... >&2
+ sleep 1m
+ done
+ echo "$0: $(date -Is): success on $attempt. attempt" >&2
+)}
+
+is_ssh_public_key() {
+ {
+ file -b "$1" | grep -Fq 'public key' &&
+ ssh-keygen -l -f "$1"
+ } >/dev/null 2>&1
+}
+
+# usage: tf_config [PARAM...]
+# For possible values of PARAM see for "tf_config parameters" below.
+tf_config() {(
+
+ # tf_config parameters.
+ # Change parameters via arguments using following syntax:
+ # --NAME=VALUE
+ # where NAME is one of arg_NAME below.
+ #
+ # Example: --display_name=mycomputer
+ arg_availability_domain=
+ arg_compartment_id=
+ arg_display_name=
+ arg_memory_in_gbs=
+ arg_ocpus=
+ arg_shape=
+ arg_source_id=
+ arg_ssh_authorized_key=
+ arg_subnet_id=
+
+ for arg; do
+ case $arg in
+ --*=*)
+ readonly "arg_${arg#--}"
+ ;;
+ *)
+ echo "$0: warning: ignoring unknown argument: $arg" >&2
+ esac
+ done
+
+ nix-instantiate \
+ --eval --json --strict \
+ --argstr availability_domain "$arg_availability_domain" \
+ --argstr compartment_id "$arg_compartment_id" \
+ --argstr display_name "$arg_display_name" \
+ --argstr memory_in_gbs "$arg_memory_in_gbs" \
+ --argstr ocpus "$arg_ocpus" \
+ --argstr source_id "$arg_source_id" \
+ --argstr shape "$arg_shape" \
+ --argstr ssh_authorized_key "$arg_ssh_authorized_key" \
+ --argstr subnet_id "$arg_subnet_id" \
+ -E \
+ '
+ { availability_domain
+ , compartment_id
+ , display_name
+ , memory_in_gbs
+ , ocpus
+ , shape
+ , source_id
+ , ssh_authorized_key
+ , subnet_id
+ }:
+ {
+ terraform.required_providers.oci = {
+ source = "oracle/oci";
+ };
+ provider.oci = {};
+ resource.oci_core_instance.generated_oci_core_instance = {
+ agent_config = {
+ is_management_disabled = "true";
+ is_monitoring_disabled = "true";
+ plugins_config = [
+ {
+ desired_state = "DISABLED";
+ name = "Vulnerability Scanning";
+ }
+ {
+ desired_state = "DISABLED";
+ name = "Compute Instance Monitoring";
+ }
+ {
+ desired_state = "DISABLED";
+ name = "Bastion";
+ }
+ ];
+ };
+ availability_config = {
+ is_live_migration_preferred = "true";
+ recovery_action = "RESTORE_INSTANCE";
+ };
+ availability_domain = availability_domain;
+ compartment_id = compartment_id;
+ create_vnic_details = {
+ assign_private_dns_record = "true";
+ assign_public_ip = "true";
+ subnet_id = subnet_id;
+ };
+ display_name = display_name;
+ instance_options = {
+ are_legacy_imds_endpoints_disabled = "false";
+ };
+ metadata = {
+ ssh_authorized_keys = ssh_authorized_key;
+ };
+ shape = shape;
+ shape_config = {
+ memory_in_gbs = memory_in_gbs;
+ ocpus = ocpus;
+ };
+ source_details = {
+ source_id = source_id;
+ source_type = "image";
+ };
+ };
+ }
+ '
+)}
+
+main "$@"