[Boto3 Adv-Cal DAY3]Lightsailインスタンスを対話式で作成するロジックに挑戦してみた

boto3 で楽しむ AWS PythonLife 一人AdventCalendarです。3日目はLightsailインスタンスを対話式作成するロジックに挑戦しました。
2018.12.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

boto3 で楽しむ AWS PythonLife 一人AdventCalendarです。

boto3のドキュメントを通して、サービス別にどういった事が出来るのかを理解したり、管理コンソールを通さずにTerminalだけで完結できるように検証していくことが目的になります。

3日目はLightsailのインスタンスを対話ベースでの作成を試してみました。

目次

boto3を通してLightsailで出来ること

ドキュメントは下記リンク先です。

ざっくりと以下のことができます。

  • インスタンスの操作(作成・削除・スナップショット・StaticIP設定)
  • ディスクの操作(作成・削除・スナップショット)
  • リレーショナル・データベースの操作(作成・削除)

今回の操作

対話ベースでパラメータを指定して、インスタンスを作成します。

  1. blueprint_idの指定
  2. bundle_idの指定
  3. インスタンス名の指定
  4. リージョンの指定
  5. インスタンスの作成

dryrunオプションを指定すると作成は行いません。

% python main.py
Input Profile name [default]>>advent_calendar
Select blueprint
[0] windows_server_2016_2018_10_14
[1] windows_server_2012_2018_10_14
[2] windows_server_2016_sql_2016_express_2018_10_14
[3] amazon_linux_2018_03_0_2
[4] ubuntu_16_04_2
[5] ubuntu_18_04
[6] debian_8_7
[7] debian_9_5
[8] freebsd_11_1
[9] opensuse_42_2
[10] centos_7_1805_01
[11] wordpress_4_9_8
[12] wordpress_multisite_4_9_8
[13] lamp_5_6_37_2
[14] lamp_7_1_20_1
[15] nodejs_10_8_0
[16] joomla_3_8_11
[17] magento_2_2_5
[18] mean_4_0_1
[19] drupal_8_5_6
[20] gitlab_11_1_4_1
[21] redmine_3_4_6
[22] nginx_1_14_0_1
[23] plesk_ubuntu_17_8_11_1
>>3

Select bundle
[0] nano_2_0
[1] micro_2_0
[2] small_2_0
[3] medium_2_0
[4] large_2_0
[5] xlarge_2_0
[6] 2xlarge_2_0
[7] nano_win_2_0
[8] micro_win_2_0
[9] small_win_2_0
[10] medium_win_2_0
[11] large_win_2_0
[12] xlarge_win_2_0
[13] 2xlarge_win_2_0
>>0
Input Instance name >>test

Select region
[0] ap-northeast-1a
[1] ap-northeast-1c
[2] ap-northeast-1d
>>0
test : Started

main.py

import boto3
import subprocess
import argparse

class LightsailWizard:
    _client_name = 'lightsail'
    _session = None

    def __init__(self, profile_name):
        self._session = boto3.Session(profile_name=profile_name)

    @property
    def session(self):
        return self._session

    @property
    def client(self):
        return self.session.client(self.client_name)

    @property
    def client_name(self):
        return self._client_name

    def get_blueprint_ids(self, **kwargs):
        res = self.client.get_blueprints(**kwargs)
        return [blueprint['blueprintId'] for blueprint in res['blueprints']]

    def get_bundle_ids(self, **kwargs):
        res = self.client.get_bundles(**kwargs)
        return [bundle['bundleId'] for bundle in res['bundles']]

    def get_regions(self, **kwargs):
        kwargs.update({'includeAvailabilityZones':True})
        res = self.client.get_regions(**kwargs)
        zone_names = list()
        for region in res['regions']:
            for zone in region['availabilityZones']:
                zone_names.append(zone['zoneName'])
        return zone_names

    def create_instance(self, instance_name, availability_zone, blueprint_id, bundle_id):
        params = {
            'instanceNames': [instance_name],
            'availabilityZone': availability_zone,
            'blueprintId': blueprint_id,
            'bundleId': bundle_id
        }
        lightsail = self.client
        res = lightsail.create_instances(**params)
        for ope in res['operations']:
            print("{name} : {status}".format(name=ope['resourceName'], status=ope['status']))

    @classmethod
    def prompt_instance_name(cls):
        instance_name = None
        while True:
            instance_name = input('Input Instance name >>')
            if instance_name and len(instance_name) != 0:
                break
        return instance_name

    def prompt_availability_zone(self):
        region_name = None
        while True:
            print('\nSelect region')
            region_names = self.get_regions()
            for region_name in region_names:
                print('[{}] {}'.format(region_names.index(region_name), region_name))
            index = input('>> ')
            if len(index) != 0 and int(index) < len(region_names):
                region_name = region_names[int(index)]
                break
        return region_name

    def prompt_blueprint(self):
        blueprint_id = None
        while True:
            print('\nSelect blueprint')
            blueprint_ids = self.get_blueprint_ids()
            for blueprint_id in blueprint_ids:
                print('[{}] {}'.format(blueprint_ids.index(blueprint_id), blueprint_id))
            index = input('>> ')
            if len(index) != 0 and int(index) < len(blueprint_ids):
                blueprint_id = blueprint_ids[int(index)]
                break
        return blueprint_id

    def prompt_bundle(self):
        bundle_id = None
        while True:
            print('\nSelect bundle')
            bundle_ids = self.get_bundle_ids()
            for bundle_id in bundle_ids:
                print('[{}] {}'.format(bundle_ids.index(bundle_id), bundle_id))
            index = input('>> ')
            if len(index) != 0 and int(index) < len(bundle_ids):
                bundle_id = bundle_ids[int(index)]
                break
        return bundle_id

    @staticmethod
    def prompt(dryrun=True):
        if dryrun:
            print("dryrun-mode")
        params = {}
        default_profile_name = 'default'
        profile_name = input('Input Profile name [{}]>> '.format(default_profile_name))
        if len(profile_name) == 0:
            profile_name = default_profile_name
        wizard = LightsailWizard(profile_name)
    
        blueprint_id = wizard.prompt_blueprint()
        bundle_id = wizard.prompt_bundle()
        instance_name = wizard.prompt_instance_name()
        region_name = wizard.prompt_availability_zone()
        if dryrun:
            result_format = "instance_name: {}, region_name: {}, blueprint_id: {}, bundle_id: {}" 
            print(result_format.format(instance_name, region_name, blueprint_id, bundle_id))
            return
        wizard.create_instance(instance_name, region_name, blueprint_id, bundle_id)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--dryrun', action='store_true')
    args = parser.parse_args()
    LightsailWizard.prompt(args.dryrun)

get_region

インスタンス生成時に必要なリージョン名取得はincludeAvailabilityZones=Trueの指定が必要です。

まとめ

Lightsail用のコマンドを通して、お手軽インスタンス生成のロジックを組んでみました。細かいパラメータを指定できるようにすると、色々と対応可能な幅も広がると思います。

なお、今回のロジックはインスタンスを作成するだけで削除しません。作成したインスタンスが必要なければ忘れずに削除しておきましょう。