Skip to content

Commit

Permalink
Re-introduce create-dock & destroy-dock that use AWS servicecatalog A…
Browse files Browse the repository at this point in the history
…PI (#61)

* Re-introduce create-dock & destroy-dock that use AWS servicecatalog API

* Clean up directorys on destroy (properly)

* Make create-dock just a little more general, add command line args for:
  * product name
  * product version
  • Loading branch information
rappdw authored Sep 23, 2019
1 parent 6905ea9 commit ed712a8
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 11 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The commands used to operate against these entities are:

| CommandSet / Entity | Creation | Execution | Notebook | Utility |
|----------------------|:------------------------:|:--------------------:|:------------:|:----------------------------:|
| Dock | register-dock <br/> unregister-dock | start-dock <br/> stop-dock | nb-dock | source dock <br/> ls-dock <br/> ssh-dock |
| Dock | create-dock <br/> destroy-dock | start-dock <br/> stop-dock | nb-dock | source dock <br/> ls-dock <br/> ssh-dock |
| Image | build-image | run-image | run-notebook | publish-image <br/> transfer-image |

Possible use cases include:
Expand Down Expand Up @@ -87,9 +87,9 @@ A "dock" is a remote system that you can connect to through `ssh`. You can "dock
any docker commands, including image and notebook cli above will be run against the remote docker server. Once a "dock"
is created, you can dock your terminal by issuing the command `source dock <server IP or moniker>`

`register-dock` is used to add a remote system to the dock list with all its configuration (username, ip and a moniker)
`create-dock` (`register-dock` if provisioning from AWS console) is used to add a remote system to the dock list with all its configuration (username, ip and a moniker)

`unregister-dock` is used to remove the reference to the remote system
`destroy-dock` (`unregister-dock` if provisioned from AWS console) is used to remove the reference to the remote system

`stop-dock` will change the instances state of a remote dock to `stopped`

Expand Down
135 changes: 132 additions & 3 deletions scripts/create-dock
Original file line number Diff line number Diff line change
@@ -1,4 +1,133 @@
#!/usr/bin/env bash
set -e
#!/usr/bin/env python3

echo "create-dock has been deprecated, please use AWS Service Catalog"
from __future__ import print_function
import os
import boto3
import argparse
import getpass
import time
import sys

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("dock", help="Name of dock to create")
parser.add_argument("-i", "--instance_type", help="instance type to launch", default=None)
parser.add_argument("-s", "--subnet", help="subnet ID for instance", default=None)
parser.add_argument("-v", "--volume_size", help="Size of block device (in GBytes)", type=int, default=None)
parser.add_argument("-n", "--product_name", help="Name of the Product to use", default="Basic EC2 Instance")
parser.add_argument("-v", "--product_version", help="Version of the product to use", default="latest")
args = parser.parse_args()

provisioning_artifact_id = None
path_id = None
ip = None
instance_id = None

# first, we need to find the Basic EC2 Instance product
client = boto3.client('servicecatalog')
response = client.search_products(Filters={
'FullTextSearch': [args.product_name]
})
product_id = response['ProductViewSummaries'][0]['ProductId']

# now we need to find the latest version of this product
response = client.describe_product(Id=product_id)
for provisioning_artifact in response['ProvisioningArtifacts']:
if provisioning_artifact['Name'] == args.product_version:
provisioning_artifact_id = provisioning_artifact['Id']

# currently, our products don't have a default path, it would be nice if they were configured
# with default path = "launch", but for now, find the launch path
response = client.list_launch_paths(ProductId=product_id)
for launch_path in response['LaunchPathSummaries']:
for constraint in launch_path['ConstraintSummaries']:
if constraint['Type'] == 'LAUNCH':
path_id = launch_path['Id']

# get the provisioning parameters and plug in any overrides
response = client.describe_provisioning_parameters(ProductId=product_id,
ProvisioningArtifactId=provisioning_artifact_id,
PathId=path_id)

parameters = []
for parameter in response['ProvisioningArtifactParameters']:
key = parameter['ParameterKey']
value = parameter['DefaultValue']
allowed_values = parameter['ParameterConstraints']['AllowedValues']
value_override = None
if key == 'InstanceName':
value_override = args.dock
elif key == 'InstanceType':
value_override = args.instance_type
elif key == 'SubnetID':
value_override = args.subnet
elif key == 'VolumeSize':
value_override = args.volume_size
if value_override and (not allowed_values or value_override in allowed_values):
parameters.append({'Key': key, 'Value':value_override})
# TODO: figure out a way to make this more general, so that as new products are created, we handle those
# parameters without exapnding the command arg list

# now provision product
if (product_id and provisioning_artifact_id and path_id):
response = client.provision_product(ProductId=product_id,
PathId=path_id,
ProvisioningArtifactId=provisioning_artifact_id,
ProvisionedProductName=args.dock,
ProvisioningParameters=parameters,
Tags=[
{
'Key': 'Name',
'Value': args.dock
},
{
'Key': 'business_unit',
'Value': 'Compliance and Digital Risk'
},
{
'Key': 'component',
'Value': 'ec2 instance'
},
{
'Key': 'product',
'Value': 'ML Labs'
},
{
'Key': 'support_level',
'Value': 'dev'
},
{
'Key': 'created_by',
'Value': getpass.getuser()
}
]
)
record_id = response['RecordDetail']['RecordId']
provisioned_product_id = response['RecordDetail']['ProvisionedProductId']
sys.stdout.write('waiting for launch to complete')
while ip is None:
sys.stdout.write('.')
sys.stdout.flush()
time.sleep(2)
response = client.describe_record(Id=record_id)
for output in response['RecordOutputs']:
if output['OutputKey'] == 'InstanceID':
instance_id = output['OutputValue']
elif output['OutputKey'] == 'PrivateIP':
ip = output['OutputValue']
sys.stdout.write('\n')
sys.stdout.flush()

if 'DOCK_USER' in os.environ:
user = os.environ['DOCK_USER']
else:
user = 'ubuntu'
cfg_dir = os.path.join(os.path.join(os.path.expanduser("~"), ".docker"), ip)
os.makedirs(cfg_dir, exist_ok=True)
f = open(os.path.join(cfg_dir, 'connection_config.txt'), 'w')
f.write("DOCK_USER={user}\n".format(user=user))
f.write("DOCK_MONIKER={moniker}\n".format(moniker=args.dock))
f.write("DOCK_HOSTNAME={ip}\n".format(ip=ip))
f.write("DOCK_IP={ip}\n".format(ip=ip))
f.write("DOCK_PROVISIONED_PRODUCT={id}\n".format(id=provisioned_product_id))
f.close()
59 changes: 56 additions & 3 deletions scripts/destroy-dock
Original file line number Diff line number Diff line change
@@ -1,4 +1,57 @@
#!/usr/bin/env bash
set -e
#!/usr/bin/env python3
from __future__ import print_function
import os
import boto3
import argparse
import fnmatch
import sys
import time
import shutil

echo "delete-dock has been deprecated, please use AWS Service Catalog"
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("dock", help="Name of dock to destroy")
args = parser.parse_args()

ip = None
provisioned_product_id = None

cfg_dir = os.path.join(os.path.expanduser("~"), ".docker")
for root, dirs, files in os.walk(cfg_dir):
for file in files:
if fnmatch.fnmatch(file, 'connection_config.txt'):
vars = {}
with open(os.path.join(root, file)) as f:
for line in f:
if line.startswith('#'):
continue
key, value = line.strip().split('=', 1)
vars[key] = value
if vars['DOCK_MONIKER'] == args.dock or vars['DOCK_IP'] == args.dock:
ip = vars['DOCK_IP']
provisioned_product_id = vars['DOCK_PROVISIONED_PRODUCT']

client = boto3.client('servicecatalog')
response = client.terminate_provisioned_product(ProvisionedProductId=provisioned_product_id)

record_id = response['RecordDetail']['RecordId']
errors = None
sys.stdout.write('waiting for terminate to complete')
while True:
sys.stdout.write('.')
sys.stdout.flush()
time.sleep(2)
response = client.describe_record(Id=record_id)
if response['RecordDetail']['Status'] == 'SUCCEEDED':
shutil.rmtree(os.path.join(cfg_dir, ip), ignore_errors=True)
break
elif response['RecordDetail']['Status'] in ['IN_PROGRESS_IN_ERROR', 'FAILED']:
sys.stdout.write('\n')
sys.stdout.flush()
errors = response['RecordDetail']['RecordErrors']
break
sys.stdout.write('\n')
sys.stdout.flush()

if errors:
print(errors)
2 changes: 0 additions & 2 deletions scripts/register-dock
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ echo "DOCK_USER:$DOCK_USER"
echo "MONIKER:$MONIKER"
echo "HOSTNAME:$IP"

### copy CA to client.
mkdir -p ~/.docker/${IP}

DOCKER_CFG_FILE=$(python3 -c "import pkg_resources; print(pkg_resources.resource_filename('dockerutils', 'docker-server-daemon.json'))")
printf "DOCK_USER=$DOCK_USER\nDOCK_MONIKER=$MONIKER\nDOCK_HOSTNAME=$IP\nDOCK_IP=$IP\n" > $HOME/.docker/${IP}/connection_config.txt

0 comments on commit ed712a8

Please sign in to comment.