Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active February 7, 2018 22:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smoser/5806147 to your computer and use it in GitHub Desktop.
Save smoser/5806147 to your computer and use it in GitHub Desktop.
simplified "run ubuntu instance on azure" wrapper around azure cmdline tools (azure-ubuntu)

az-ubuntu: easily run Ubuntu on azure using azure-cli

This just makes running az from the azure-cli pypi package possibly more friendly.

Setup

First install azure-cli

Do so with pip:

$ pip install azure-cli

Or snap:

$ snap install azure-cli

At this point you should have az command in your PATH.

Then login

$ az login
To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code FPQPKYW2R to authenticate.

Now you should be set.

Usage

At this point you have to create a resource group before you can use az-ubuntu. It will create one for you.

Create the resource group in a region. Pick one.

# get the regions available.
$ az account list-locations > locations.json
$ python3 -c 'import sys, json; print("\n".join(sorted([k["name"] for k in json.loads(sys.stdin.read())])))' < locations.json
australiaeast
..
eastus
eastus2
..
westus2


$ az group create --location=eastus2 --name=mygroup1

Now use az-ubuntu for creating the instance.

$ az-ubuntu --name=my-x1 -g mygroup1 xenial

To add user-data:

$ az-ubuntu --name=my-x1 -g mygroup1 xenial --user-data-file=my.userdata

To destroy all remnants of the instance, do:

$ az group delete --yes --name=mygroup1
#!/bin/bash
declare -A IMAGES
# pt_optargs created from:
#
# $ az vm create --help |
# sed -n -e '/Arguments/,/Examples/!d' \
# -e 's/.*\(--[a-z-]*\) \(-[a-zA-Z]\| \).*/\2 \1/p' |
# sort -k2
DEFAULT_LOCATION="westus2"
VERBOSITY=0
TEMP_D=""
pt_optargs=(
--admin-password
--admin-username
--asgs
--assign-identity
--attach-data-disks
--attach-os-disk
--authentication-type
--availability-set
--custom-data
--data-disk-caching
--data-disk-sizes-gb
--debug
--generate-ssh-keys
# --image
--license-type
--nics
--no-wait
--nsg
--nsg-rule
--os-disk-name
--os-disk-size-gb
--os-type
--private-ip-address
--public-ip-address
--public-ip-address-allocation
--public-ip-address-dns-name
--query
--role
--scope
--secrets
# --size
--ssh-dest-key-path
# --ssh-key-value
--storage-account
--storage-container-name
--storage-sku
--subnet
--subnet-address-prefix
--tags
--use-unmanaged-disk
--validate
--verbose
--vnet-address-prefix
--vnet-name
-h --help
#-l --location
#-n --name
-o --output
#-g --resource-group
-z --zone
)
## you can list all Canonical images with
## az vm image list --all --publisher=Canonical
## see LP: #1701062 for request for stream data.
# az vm create -n smz1 -g smgroup1 \
# --image=Canonical:UbuntuServer:17.04-DAILY:latest \
# --ssh-key-value="$(cat ~/.ssh/id_rsa.pub)" \
# --admin-username=smoser
## locations can be read via: az account list-locations
# az account list-locations |
# python3 -c 'import sys, json; print("\n".join(sorted([k["name"] for k in json.loads(sys.stdin.read())])))'
# defaults
image="xenial"
key_pubf="$HOME/.ssh/id_rsa.pub"
image="xenial"
size="Standard_DS1_v2"
location=$DEFAULT_LOCATION
user=$(id -un)
name_prefix="${user}"
error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
cleanup() {
[ -n "${TEMP_D}" ] || return
[ ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
get_temp_d() {
if [ -z "${TEMP_D}" ]; then
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || return
trap cleanup EXIT
fi
_RET=${TEMP_D}
}
render_userdata_tmpl() {
local udata="$1" tmpd="" now=""
_RET="$udata"
[ -n "$udata" ] || return 0
grep -q "LAUNCH_SECONDS" "$udata" || return 0
get_temp_d && tmpd="$_RET" || return 1
now=$(date "+%s") || return 1
_RET="$tmpd/ud-rendered"
sed "s/LAUNCH_SECONDS/$now/" "$udata" > "$_RET"
}
# search list of 'key:value[whitespace]'
inargs() {
local needle="$1" c=""
shift;
for c in "$@"; do
[ "$c" = "$needle" ] && return 0
done
return 1
}
kvget() {
local key="$1" tok=""
shift
for tok in "$@"; do
[ "${tok#${key}:}" != "${tok}" ] && _RET=${tok#${key}:} && return 0
done
return 1
}
kvgetk() {
local val="$1" tok=""
shift
for tok in "$@"; do
[ "${tok%:${val}}" != "${tok}" ] && _RET=${tok%:${val}} && return 0
done
return 1
}
populate_images() {
# trusty and precise got point in their names (14.04.1-LTS) as opposed
# to xenial 16.04-LTC
local ipre="Canonical:UbuntuServer:" ipost=":latest"
local line="" data="" cleaned=""
data=$(ubuntu-distro-info --supported --fullname) ||
fail "Must install distro-info"
# data is like
# Ubuntu 14.04 LTS "Trusty Tahr"
# Ubuntu 17.10 "Artful Aardvark"
# turn that into '14.04,trusty,lts' '17.10,artful,'
cleaned=$(echo "$data" | awk '
{gsub(/"/,"",$0);
lts=""; if(NF == 5) lts="lts";
printf("%s,%s,%s\n", $2, tolower($(NF-1)), lts)}')
local tok="" lts="" codename="" ver="" point=""
local oifs="$IFS"
for tok in $cleaned; do
IFS=","; set -- $tok; IFS="$oifs"
ver=$1; codename=$2; lts="$3"
case "$codename" in
precise) pver="$ver.5";;
trusty) pver="$ver.5";;
*) pver="$ver";;
esac
IMAGES[$codename]="${ipre}$pver-DAILY${lts:+-LTS}${ipost}"
IMAGES[$codename-released]="${ipre}$pver${lts:+-LTS}${ipost}"
done
}
Usage() {
cat <<EOF
Usage: ${0##*/} [-n name] [image|flavor] [image|flavor|[.|auto]]
Give a image a flavor and a name and boot an instance.
required option is '-g' or '--group'
if the group does not exist,
it will be created in default location $DEFAULT_LOCATION
options:
--size S specify size
--image I specify image
-n | --name N specify name
--key K use ssh key in K (must be .pem file)
--password P set default user's password to 'P'
--dry-run only report what would be done
--user-data-file F specify user-data read from file 'F'
--custom-data F alias for user-data-file
-v | --verbose be more verbose
--user U initial user named 'U' (default: $(id -un))
if root, use 'ubuntu'
EOF
local i
echo " images:"
for i in "${!IMAGES[@]}"; do
# if they gave -v --help, give more help
printf " %-18s %s\n" "$i" "${IMAGES[$i]}"
done | sort
echo
cat <<EOF
Examples:
* launch trusty instance in US East 1 size Basic_A0 (default location/size)
azure-ubuntu trusty --name=foo
* launch larger vivid instance in "Brazil South"
azure-ubuntu vivid --name=vivid-foo Basic_A4 --user-data=my-userdata
EOF
}
bad_Usage() {
Usage 1>&2;
error "$@"
exit 1
}
if [ "$user" = "root" ]; then
name_prefix="$(hostname)"
name_prefix=${name_prefix%%.*}
user="ubuntu"
error "using default name_prefix from hostname ($name_prefix)"
error "and '$user' for user"
fi
populate_images || fail "failed to populate images variable"
name=""
pt=()
dry=false
udarg=""
group=""
def_userdata="$HOME/data/my-userdata-azure"
[ -f "$def_userdata" ] && udarg="$def_userdata"
while [ $# -ne 0 ]; do
cur="$1"
next="$2"
if [ -n "${IMAGES[$cur]}" ]; then
image_id=${IMAGES[$cur]};
rel=${cur%%-*};
shift; continue;
fi
if inargs "$cur" "${pt_optargs[@]}"; then
pt[${#pt[@]}]="$cur";
pt[${#pt[@]}]="$next";
shift 2 || bad_Usage "Expected argument for $cur";
debug 1 "passing through: $cur $next";
continue
fi
case "$cur" in
-h|--help) Usage; exit 0;;
--image) image_id=${next}; shift;;
--image=*) image_id=${cur#*=};;
--size) size=${next}; shift;;
--size=*) size=${cur#*=};;
--key=*) key_pubf=${cur#*=};;
--key) key_pubf=$next; shift;;
--ssh-key-value=*) key_pubf=""; pt[${#pt[@]}]="$cur";;
--ssh-key-value) key_pubf=""; pt[${#pt[@]}]="$cur=$next"; shift;;
--dry-run) dry=true;;
--user-data-file|--custom-data)
udarg=$next; shift;;
--user-data-file=*|--custom-data=*)
udarg=${cur#--user-data-file=}
[ "${udarg#[~]/}" != "$udarg" ] && udarg="$HOME/${udarg#[~]/}";;
-v|--verbose)
VERBOSITY=$(($VERBOSITY+1));
pt[${#pt[@]}]=$cur;;
.|auto|${name_prefix}*)
[ -z "$name" ] ||
bad_Usage "name already set to '$name'. confused by '$cur'." 1>&2;
name="$cur"
[ "$name" = "." ] && name="auto"
;;
--user=*) user="${cur#*=}";;
--user) user="$next"; shift;;
-n|--name) name="$next"; shift;;
-g|--group|--resource-group) group=$next; shift;;
--group=*|--resource-group=*) group=${cur#*=}; shift;;
-l|--location) location=$next; shift;;
--location=*) location=${cur#*=};;
--name=*) name="${cur#*=}";;
--*=*|-[a-z]*)
pt[${#pt[@]}]=$cur
debug 1 "passing through: $cur";;
--*)
pt[${#pt[@]}]=$cur;
debug 1 "passing through: $cur";;
*) bad_Usage "confused by argument '$cur'";;
esac
shift
done
[ -n "$name" ] ||
bad_Usage "must give a name. use '.' or 'auto' for generated";
[ -n "$group" ] ||
bad_Usage "must provide group '--resource-group' or '-g'"
[ -z "$image_id" ] && { image_id=${IMAGES[$image]}; rel=${image%%-*}; }
if [ -n "$key_pubf" ]; then
[ -f "$key_pubf" ] || fail "--key argument ($key_pubf) is not a file"
pt[${#pt[@]}]="--ssh-key-value=@${key_pubf}"
fi
if [ "$name" = "auto" ]; then
daterel=$(date --utc "+%m%d${rel:0:1}")
dnsname="${name_prefix}${daterel}"
else
dnsname="$name"
fi
if [ -n "$udarg" -a "$udarg" != "none" ]; then
[ -f "$udarg" ] ||
{ error "userdata '$udarg': not a file"; exit 1; }
render_userdata_tmpl "$udarg" ||
{ error "failed to render $udarg"; exiit 1; }
pt[${#pt[@]}]="--custom-data=$_RET"
fi
# az vm create -n smz1 -g smgroup1 \
# --image=Canonical:UbuntuServer:17.04-DAILY:latest \
# --ssh-key-value="$(cat ~/.ssh/id_rsa.pub)" \
# --admin-username=smoser
if ! $dry; then
out=$(az group show "--name=$group") ||
{ error "failed to show group '$group'"; exit 1; }
if [ -z "$out" ]; then
error "creating group $group in location $location"
az group create "--name=$group" "--location=$location"
fi
fi
cmd=(
az vm create "--resource-group=$group" "--name=$dnsname"
"--image=${image_id}"
"--admin-username=$user"
"${pt[@]}" )
echo "${cmd[*]}"
#error "flavor=${flavor} image=${image} location=${location}"
pcmd=""
for c in "${cmd[@]}"; do
[ "${c#* }" = "$c" ] && pcmd="$pcmd $c" || pcmd="$pcmd \"$c\""
done
error "${pcmd# }"
sdate="$(date -R)"
stime=$(date "+%s")
$dry && exit 0
"${cmd[@]}"
ret=$?
[ $ret -eq 0 ] || fail "command failed [$ret]. maybe check azure_error"
etime=$(date "+%s")
error "Launched in $(($etime-$stime)) seconds [started at $sdate]"
error "ssh $user@$dnsname.cloudapp.net"
#!/bin/bash
# the .pem file is a key created by following:
# kn="id_rsa-${USER}-azure"
# kf="$HOME/.ssh/$kn"
# ssh-keygen -f "$kf" -C "$kn"
# openssl req -key $kf -new -x509 -days 999 > $kf.pem
# chmod 600 $kf.pem
# openssl x509 -outform der -in $kf.pem -out $kf.cer
# pt_optargs created from:
# $ azure vm create --help |
# sed -n 's/.*\(-.\), \(--[a-z-]*\) \(<[a-z|-]*>\).*/\1 \2/p' |
# sort -k 2
pt_optargs=(
-a --affinity-group
-A --availability-set
-u --blob-url
-d --custom-data
-l --location
-t --ssh-cert
-b --subnet-names
-s --subscription
-w --virtual-network-name
-n --vm-name
-z --vm-size
)
TEMP_D=""
INVALIDATE_CACHE=${AZURE_INVALIDATE_CACHE:-0}
CANONICAL_ID="b39f27a8b8c64d52b05eac6a62ebad85"
VERBOSITY=0
REL_VER_MAP=$(ubuntu-distro-info --supported --fullname 2>/dev/null |
sed -e 's,Ubuntu ,,; s, LTS,,; s,",,g' -e '/Lucid/d' |
awk '{print $2 ":" $1}' | tr '[A-Z]' '[a-z]' | tr '\n' ' ')
[ -n "$REL_VER_MAP" ] ||
REL_VER_MAP="precise:12.04 trusty:14.04 utopic:14.10 vivid:15.04"
SSTREAM_PAIRS=(
"released|http://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:azure.sjson"
"daily|http://cloud-images.ubuntu.com/daily/streams/v1/com.ubuntu.cloud:daily:azure.sjson"
)
# defaults
user=$(id -un)
key_pem="$HOME/.ssh/id_rsa-${user}-azure@$(hostname).pem"
image="trusty-released"
flavor="Basic_A0"
location="us-east"
password="${AZURE_PASSWORD}"
name_prefix="${user}"
error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
cache_valid() {
local file="$1" date="$2" tf="/tmp/ts.$$" ret=""
[ -n "$file" -a -e "$file" ] || return 1
touch --date "${date}" "$tf"
[ "$file" -nt "$tf" ]
ret=$?
rm -f "$tf"
return $ret
}
cleanup() {
[ -n "${TEMP_D}" ] || return
[ ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
get_temp_d() {
if [ -z "${TEMP_D}" ]; then
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") || return
trap cleanup EXIT
fi
_RET=${TEMP_D}
}
render_userdata_tmpl() {
local udata="$1" tmpd="" now=""
_RET="$udata"
[ -n "$udata" ] || return 0
grep -q "LAUNCH_SECONDS" "$udata" || return 0
get_temp_d && tmpd="$_RET" || return 1
now=$(date "+%s") || return 1
_RET="$tmpd/ud-rendered"
sed "s/LAUNCH_SECONDS/$now/" "$udata" > "$_RET"
}
get_images_sstream() {
local cache_d="$1" out_f="$2" pair="" name="" url="" tmpf=""
local keyring="/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg"
local tmpf="$out_f.$$"
for pair in "${SSTREAM_PAIRS[@]}"; do
name="${pair%%|*}"
url="${pair#*|}"
sstream-mirror "--keyring=$keyring" "$url" "$cache_d/$name" &&
sstream-query "--keyring=$keyring" '--output-format=%(id)s' \
"$cache_d/$name/streams/v1/"*.sjson "crsn=East US" ||
{ error "failed query $pair"; rm -f "$tmp_f"; return 1; }
done > "$tmpf"
mv "$tmpf" "$out_f"
}
parse_image_list() {
# this scrapes a list of azure formated images
# and returns a space delimited list of label:image_id
local fname="$1"
local rel="" n="" tok="" label=""
for tok in $REL_VER_MAP; do
rel=${tok%:*}
n=${tok#*:}
for label in "" "daily"; do
if [ "$label" = "daily" ]; then
grepstr="DAILY.*$n"
alias="${rel}-daily"
else
grepstr="Ubuntu-$n"
alias="${rel}"
fi
img=$(grep "$grepstr" "$fname" | sort | tail -n 1 | awk '{print $1}')
[ -n "$img" ] || continue
if [ "$label" = "daily" ]; then
add="${rel}-daily:$img ${rel}:$img"
else
add="${rel}-released:$img"
fi
imgs="${imgs:+${imgs} }$add"
done
done
_RET="${imgs}"
}
get_images() {
local cache_d="$HOME/.cache/${0##*/}"
local flist="$cache_d/images.list" method=""
[ -d "$cache_d" ] || mkdir -p "${cache_d}" ||
{ error "failed make ${cache_dir}"; return 1; }
if [ "${INVALIDATE_CACHE:-0}" != "0" ] ||
! cache_valid "$flist" "now - $((60*60*24)) seconds"; then
method="get_images_vmlist"
command -v sstream-sync >/dev/null 2>&1 && method="get_images_sstream"
error "updating image-cache in $flist via $method"
"$method" "$cache_d" "$flist" ||
{ error "updating cache via $method failed"; return 1; }
fi
parse_image_list "$flist"
IMAGES=$_RET
return
}
get_images_vmlist() {
local cache_d="$1" out_f="$2" tmpf="" imgs=""
tmpf="$out_f.$$"
azure vm image list > "$tmpf" || { rm -f "$tmpf" ; return 1; }
sed -i -e '/^data:/!d' -e "/${CANONICAL_ID}__/!d" -e "s/^data:[ ]*//" "$tmpf"
grep "${CANONICAL_ID}" "$tmpf" ||
{ error "id '$CANONICAL_ID' not found"; return 1; }
mv "$tmpf" "$out_f"
}
# from azure vm location list
LOCATIONS=(
"ap-east:East Asia"
"ap-southeast:Southeast Asia"
"br-south:Brazil South"
"eu-north:North Europe"
"eu-west:West Europe"
"ja-east:Japan East"
"ja-west:Japan West"
"us-central:Central US"
"us-east-1:East US"
"us-east-2:East US 2"
"us-east:East US"
"us-northcentral:North Central US"
"us-southcentral:South Central US"
"us-west:West US"
)
declare -A FLAVOR_ALIASES FLAVOR_DATA
FLAVOR_ALIASES=(
[A0]=ExtraSmall [A1]=Small [A2]=Medium [A3]=Large [A4]=ExtraLarge
)
## These are cores/ram/disk/price
## from http://azure.microsoft.com/en-us/pricing/details/virtual-machines/
## Prices are in West US updated 2016-03-01.
## Get a list of api names by specifing a bad one in cli.
FLAVOR_DATA=(
# General purpose compute: Basic tier
[Basic_A0]='1/0.75/20/$0.018'
[Basic_A1]='1/1.75/40/$0.047'
[Basic_A2]='2/3.5/60/$0.094'
[Basic_A3]='4/7/120/$0.188'
[Basic_A4]='8/14/240/$0.376'
# General purpose compute: Standard tier
[ExtraSmall]='1/0.75/20/$0.02'
[Small]='1/1.75/70/$0.06'
[Medium]='2/3.5/135/$0.12'
[Large]='4/7/285/$0.24'
[ExtraLarge]='8/14/605/$0.48'
[A5]='2/14/135/$0.25'
[A6]='2/28/285/$0.50'
[A7]='2/15/605/$1.00'
# Optimized compute
[Standard_D1]='1/3.5/50/$0.077'
[Standard_D2]='2/7/100/$0.154'
[Standard_D3]='4/14/200/$0.308'
[Standard_D4]='8/28/400/$0.616'
[Standard_D11]='2/14/100/$0.195'
[Standard_D12]='4/28/200/$0.390'
[Standard_D13]='8/56/400/$0.780'
[Standard_D14]='16/112/800/$1.542'
# Optimized compute
[Standard_D1_v2]='1/3.5/50/$0.073'
[Standard_D2_v2]='2/7/100/$0.146'
[Standard_D3_v2]='4/14/200/$0.293'
[Standard_D4_v2]='8/28/400/$0.585'
[Standard_D5_v2]='16/56/800/$1.17'
[Standard_D11_v2]='2/14/100/$0.185'
[Standard_D12_v2]='4/28/200/$0.371'
[Standard_D13_v2]='8/56/400/$0.741'
[Standard_D14_v2]='16/112/800/$1.482'
# Performance optimized compute
[Standard_G1]='2/28/412/$0.61'
[Standard_G2]='4/56/825/$1.22'
[Standard_G3]='8/112/1,650/$2.44'
[Standard_G4]='16/224/3,300/$4.88'
[Standard_G5]='32/448/6,600/$8.69'
# Network Optimized
[A8]='8/56/382/$0.975'
[A9]='16/112/382/$1.95'
[A10]='8/56/382/0.78'
[A11]='16/112/382/1.56'
)
get_images || fail "failed to get image lists"
IMAGES=${_RET}
# search list of 'key:value[whitespace]'
inargs() {
local needle="$1" c=""
shift;
for c in "$@"; do
[ "$c" = "$needle" ] && return 0
done
return 1
}
kvget() {
local key="$1" tok=""
shift
for tok in "$@"; do
[ "${tok#${key}:}" != "${tok}" ] && _RET=${tok#${key}:} && return 0
done
return 1
}
kvgetk() {
local val="$1" tok=""
shift
for tok in "$@"; do
[ "${tok%:${val}}" != "${tok}" ] && _RET=${tok%:${val}} && return 0
done
return 1
}
Usage() {
cat <<EOF
Usage: ${0##*/} [-n name] [image|flavor|location] [image|flavor|[.|auto]]
Give a image a flavor and a name and boot an instance.
options:
--flavor F specify flavor
--image I specify image
-n | --name N specify name
--key K use ssh key in K (must be .pem file)
--password P set default user's password to 'P'
--dry-run only report what would be done
--user-data-file F specify user-data read from file 'F'
--custom-data F alias for user-data-file
-v | --verbose be more verbose
--user U initial user named 'U' (default: $(id -un))
EOF
local i
echo " images:"
for i in $IMAGES; do
# if they gave -v --help, give more help
if [ "$VERBOSITY" -ge 1 ]; then
echo " ${i%:*} [${i##*__}]"
else
echo " ${i%:*}"
fi
done
echo " flavors:"
local flavors=$(for i in "${!FLAVOR_DATA[@]}"; do echo "$i"; done | sort)
for i in ${flavors}; do
printf "%7s%-18s%s\n" "" "$i" "${FLAVOR_DATA[$i]}"
done
echo " locations:"
for i in "${LOCATIONS[@]}"; do
echo " ${i/:/: }"
done
echo
cat <<EOF
Examples:
* launch trusty instance in US East 1 size Basic_A0 (default location/size)
azure-ubuntu trusty --name=foo
* launch larger vivid instance in "Brazil South"
azure-ubuntu vivid --name=vivid-foo Basic_A4 --user-data=my-userdata
EOF
}
bad_Usage() {
Usage 1>&2;
error "$@"
exit 1
}
name=""
image_id=""
flavor_id=""
location_id=""
pt=()
dry=false
udarg=""
def_userdata="$HOME/data/my-userdata-azure"
[ -f "$def_userdata" ] && udarg="$def_userdata"
while [ $# -ne 0 ]; do
cur="$1"
next="$2"
if kvget "$cur" $IMAGES; then
image_id=$_RET; rel=$cur; shift; continue;
fi
if [ -n "${FLAVOR_ALIASES[$cur]}" ]; then
flavor_id=${FLAVOR_ALIASES[$cur]}; shift; continue;
fi
if inargs "$cur" "${!FLAVOR_DATA[@]}"; then
flavor_id=$cur; shift; continue;
fi
if kvget "$cur" "${LOCATIONS[@]}"; then
location_id="$_RET"; shift; continue
fi
if inargs "$cur" "${pt_optargs[@]}"; then
pt[${#pt[@]}]="$cur";
pt[${#pt[@]}]="$next";
shift 2 || bad_Usage "Expected argument for $cur";
debug 1 "passing through: $cur $next";
continue
fi
case "$1" in
-h|--help) Usage; exit 0;;
--flavor)
[ -n "${FLAVOR_ALIASES[$next]}" ] && next="${FLAVOR_ALIASES[$next]}"
inargs "$next" "${!FLAVOR_DATA[@]}" ||
fail "bad argument for flavor: $next"
flavor_id=$next;
shift;;
--flavor=*)
next="${cur#*=}"
[ -n "${FLAVOR_ALIASES[$next]}" ] && next="${FLAVOR_ALIASES[$next]}"
inargs "$next" "${!FLAVOR_DATA[@]}" ||
fail "bad argument for flavor: $next"
flavor_id=$next;;
--image) image_id=${next}; shift;;
--image=*) image_id=${cur#*=};;
--key=*) key_pem=${cur#*=};;
--key) key_pem=$next; shift;;
--password=*) password=${cur#*=};;
--password) password=$next; shift;;
--dry-run) dry=true;;
--user-data-file|--custom-data)
udarg=$next; shift;;
--user-data-file=*|--custom-data=*)
udarg=${cur#--user-data-file=}
[ "${udarg#[~]/}" != "$udarg" ] && udarg="$HOME/${udarg#[~]/}";;
-v|--verbose)
VERBOSITY=$(($VERBOSITY+1));
pt[${#pt[@]}]=$cur;;
.|auto|${name_prefix}*)
[ -z "$name" ] ||
bad_Usage "name already set to '$name'. confused by '$cur'." 1>&2;
name="$cur"
[ "$name" = "." ] && name="auto"
;;
--user=*) user="${cur#*=}";;
--user) user="$next"; shift;;
-n|--name) name="$next"; shift;;
--name=*) name="${cur#*=}";;
--*=*|-[a-z]*)
pt[${#pt[@]}]=$cur
debug 1 "passing through: $cur";;
--*)
pt[${#pt[@]}]=$cur;
debug 1 "passing through: $cur";;
*) bad_Usage "confused by argument '$cur'";;
esac
shift
done
[ -n "$name" ] ||
bad_Usage "must give a name. use '.' or 'auto' for generated";
[ -z "$image_id" ] && { kvget $image $IMAGES; image_id=$_RET; rel=$image; }
[ -z "$flavor_id" ] && { flavor_id=$flavor; }
[ -z "$location_id" ] &&
{ kvget $location "${LOCATIONS[@]}"; location_id=$_RET; }
[ -z "$key_pem" -o -f "$key_pem" ] ||
fail "--key-pem argument ($key_pem) is not a file"
[ -n "$password" -o -n "$key_pem" ] ||
fail "must provide --password (AZURE_PASSWORD) or --key-pem"
if [ "$name" = "auto" ]; then
daterel=$(date --utc "+%m%d${rel:0:1}")
dnsname="${name_prefix}${daterel}"
else
dnsname="$name"
fi
no_pass="--no-ssh-password"
[ -n "$password" ] && no_pass=""
kvgetk "$image_id" $IMAGES && image="$_RET" || image="${image_id}"
kvgetk "$location_id" "${LOCATIONS[@]}" &&
location="$_RET" || location="${location_id}"
if [ -n "$udarg" -a "$udarg" != "none" ]; then
[ -f "$udarg" ] ||
{ error "userdata '$udarg': not a file"; return 1; }
render_userdata_tmpl "$udarg" ||
{ error "failed to render $udarg"; return 1; }
pt[${#pt[@]}]="--custom-data=$_RET"
fi
cmd=( azure vm create
"--vm-size=$flavor_id" "--vm-name=$dnsname" "--location=${location_id}"
${key_pem:+"--ssh-cert=${key_pem}"} $no_pass --ssh=22
"${pt[@]}"
"$dnsname" "$image_id" "$user" ${password:+"${password}"} )
error "flavor=${flavor} image=${image} location=${location}"
pcmd=""
for c in "${cmd[@]}"; do
[ "${c#* }" = "$c" ] && pcmd="$pcmd $c" || pcmd="$pcmd \"$c\""
done
error "${pcmd# }"
sdate="$(date -R)"
stime=$(date "+%s")
$dry && exit 0
"${cmd[@]}"
ret=$?
[ $ret -eq 0 ] || fail "command failed [$ret]. maybe check azure_error"
etime=$(date "+%s")
error "Launched in $(($etime-$stime)) seconds [started at $sdate]"
error "ssh $user@$dnsname.cloudapp.net"
# vi: ts=4 expandtab
#cloud-config
#
# This is my default user-data on azure.
# az-ubuntu will 'preprocess' the seconds value here, so that when
# you log in you can see how long from launch start to functional system.
sm_misc:
- &write_launch_info |
f=/usr/local/bin/launch-info
cat > "$f" <<"ENDLAUNCH"
#!/bin/sh
launch_sec="LAUNCH_SECONDS"
if [ -n "$1" ]; then
launch=$(date -R --date="$1")
launch_sec=$(date --date="$launch" +%s)
else
launch=$(date -R --date="@$launch_sec")
fi
ran="$(date -R)"
ran_sec=$(date --date="$ran" +%s)
read up idle < /proc/uptime
up_sec=${up%.*} # drop milliseconds
kboot=$(date -R --date="$ran - $up_sec seconds")
kboot_sec=$(date --date="$kboot" +%s)
echo "uptime: $up seconds"
echo "you launched me at: $launch"
echo "it is now : $ran"
echo "kernel booted : $kboot"
echo "launch to kernel boot: $((${kboot_sec}-${launch_sec})) seconds"
echo "launch to user-data : $((${ran_sec}-${launch_sec})) seconds"
ENDLAUNCH
chmod 755 "$f"
- &write_as_def_user |
p="/usr/local/bin/as-def-user"
ln -sf "${p##*/}" "${p}sh" # link as-def-usersh
cat > "$p" <<"END_DEF_USER"
#!/bin/sh -f
defuser=ubuntu
for u in smoser ubuntu azuser dhc-user; do
$(id -u $u >/dev/null 2>&1) && defuser=$u && break
done
if [ "${0%usersh}" != "$0" ]; then
set -- sh -c "$@"
fi
exec sudo -Hu $defuser -- "$@"
END_DEF_USER
chmod 755 "$p"
- &user_setup |
set -x; exec > ~/user_setup.log 2>&1
echo "starting at $(date -R)"
grep "cloud-init.*running" /var/log/cloud-init.log
launch-info
echo "set -o vi" >> ~/.bashrc
echo "export EDITOR=vi" >> ~/.profile
mkdir ~/bin
chmod 755 ~/bin
byobu-ctrl-a screen
cat > ~/.vimrc <<"EOF"
set modeline modelines=3
set incsearch hlsearch
EOF
- &apt_go_fast |
url="https://gist.githubusercontent.com/smoser/5823699/raw/5d47ca91d9583389733bab48cab5dbbd511aae04/apt-go-fast"
wget --tries=1 --timeout=10 "$url" -O - | sh
bootcmd:
- [sh, -c, *write_launch_info]
- launch-info | tee /run/launch-info.txt
- [sh, -c, *write_as_def_user]
- [sh, -c, *apt_go_fast]
manage_etc_hosts: localhost
ssh_import_id: [smoser]
runcmd:
- [ as-def-usersh, *user_setup ]
snappy:
ssh_enabled: true

azure-ubuntu: easily run Ubuntu on azure using azure-cli

This is just a simpler way to run Ubuntu using the azure-cli.

Note: It has not been updated to run with 'arm' mode. It only works with 'asm' mode. To change the client to 'asm', do:

azure config set mode asm

Basically:

azure-ubuntu xenial --name=xenial-foo Basic_A4 --user-data=my-userdata
or
azure-ubuntu xenial myvm-xenial

Instead of:

azure vm create --vm-size=Basic_A0 --vm-name=myvm-xenial \
   "--location=East US" \
   --ssh-cert=$HOME/.ssh/id_rsa-smoser-azure@brickies.pem \
   --no-ssh-password --ssh=22 \
   --custom-data=my-userdata \
   myvm-xenial \
   b39f2...__Ubuntu_DAILY_BUILD-xenial-16_04-LTS-amd64-server-20161029-en-us-30GB smoser

Getting azure cli tools on Ubuntu 14.04 or later

To get the azure-cli tools on Ubuntu 14.04 or later, do:

sudo apt-get install npm nodejs-legacy
# for azure-ubuntu then
sudo apt-get install simplestreams ubuntu-cloudimage-keyring
mkdir azure-cli
cd azure-cli
npm install azure-cli
ln -s "$PWD/node_modules/azure-cli/bin/azure" ~/bin

Then, you have to import your accounts using:

azure account import

More information on this process is available in Azure Documentation.

An alternative to installing nodejs-legacy is fixing your path or programs installed by npm to use nodejs rather than node. See debian bug 614907 for more info.

failures

  • If you fail with 'Creating a new storage account', then: azure storage account create "--location=South Central US"
    --disable-geoReplication yourrandomname
  • Ubuntu does not have data for arm mode bug 1701062

ARM mode (Azure Resource Manager) command

In arm mode you can do something like (more info)

$ az group create --location=westus2 smgroup1
$ az vm create -n smz1 -g smgroup1 \
    --image=Canonical:UbuntuServer:17.04-DAILY:latest \
    --ssh-key-value="$(cat ~/.ssh/id_rsa.pub)" \
    --admin-username=smoser
mkdir -p ~/bin
export PATH=$HOME/bin:$PATH
pkgs="npm nodejs-legacy"
pkgs="$pkg simplestreams ubuntu-cloudimage-keyring"
sudo apt-get update
sudo apt-get install --quite --assume-yes $pkgs
mkdir azure
( cd azure-cli
npm install azure-cli
ln -sf "$PWD/node_modules/azure-cli/bin/azure" ~/bin
)
git clone https://gist.github.com/5806147.git azubuntu
ln -sf azubuntu/azure-ubuntu ~/bin
cat <<EOF
azure-cli should be set up now. But you still need to:
azure account download # gives a url to open in browser
azure account import account.publishsettings # file downloaded in browser
azure-ubuntu eu-west xenial --name="foobar"
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment