From c258706c0313e2014b4df1ca9b282062382b9e32 Mon Sep 17 00:00:00 2001 From: tv Date: Tue, 20 Jun 2023 15:49:40 +0200 Subject: snatch-instance: init --- bin/snatch-instance | 354 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100755 bin/snatch-instance (limited to 'bin/snatch-instance') 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 "$@" -- cgit v1.2.3