Lambda で EC2 のインスタンスのスナップショットを作成

INSTANCE_ID のスナップショットを作成し、作成したスナップショット数が NUMBER_OF_SNAPSHOTS 数より多い場合は古いスナップショットを削除する。

import boto3
import datetime

INSTANCE_ID = 'xxx'
NUMBER_OF_SNAPSHOTS = 1

def get_instance(ec2_client,instance_id):
    current_instance = None
    for reservation in ec2_client.describe_instances()["Reservations"]:
        for instance in reservation['Instances']:
            if instance['InstanceId'] == instance_id:
                current_instance = instance
    return current_instance

def get_volumd_id(instance):
    return instance['BlockDeviceMappings'][0]['Ebs']['VolumeId']
    
def create_snapshot(ec2_client,instance_id):
    """
    Create a snapshot.
    """
    timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
    instance = get_instance(ec2_client,instance_id)
    snapshot = ec2_client.create_snapshot(
    Description='ec2_development_' +timestamp,
    VolumeId=get_volumd_id(instance)
)
    print('Created a snapshot successully.')
    
def get_old_snapshots_by_volume_id(snapshots,volume_id,number_to_keep_snapshots):
    target_snapshots = [snapshot for snapshot in snapshots if snapshot['VolumeId'] == volume_id]
    sorted_target_snapshots = sorted(target_snapshots, key=lambda x: x['StartTime'],reverse=True)
    if len(sorted_target_snapshots) > number_to_keep_snapshots:
        return sorted_target_snapshots[number_to_keep_snapshots:]
    else:
        return []

    
def delete_old_snapshot(ec2_client,instance_id,number_to_keep_snapshots):
    cnt = 0
    instance = get_instance(ec2_client,instance_id)
    snapshots = ec2_client.describe_snapshots(OwnerIds=["self"])['Snapshots']
    volume_id = get_volumd_id(instance)
    for old_snapshot in get_old_snapshots_by_volume_id(snapshots,volume_id,number_to_keep_snapshots):
        ec2_client.delete_snapshot(SnapshotId=old_snapshot['SnapshotId'])
        cnt += 1
    print(f"Deleted {cnt} old snapshots")


def lambda_handler(event, context):
    ec2_client = boto3.client('ec2')
    try:
        create_snapshot(ec2_client,INSTANCE_ID)
        delete_old_snapshot(ec2_client,INSTANCE_ID,NUMBER_OF_SNAPSHOTS)
        return 'SUCCESS'
    except Exception as e:
        print(e)
        return 'ERROR'

[Django] request.FILESに格納された画像をHTMLに出力する

Django で request.FILES に格納されたファイル画像をHTMLに出力するのにハマったのでメモ。

request.FILES に ‘hoge’ というファイルが格納されているとする。この場合、Python とHTMLのコードはそれぞれ以下のようになる。

views.py

 def render_index(request):
     file_name = 'hoge'
     context = {
     'cached_file':{}
     }
     the_file = request.FILES[file_name]
     the_file.seek(0)
     data = the_file.read()
     encoded = b64encode(data).decode()
     mime = header_icon_file.content_type
     mime = mime + ";" if mime else ";"
     cached = {'src': "data:%sbase64,%s" % (mime, encoded)}
     context['cached_file'][file_name] = cached
     the_file.seek(0)
     return render(request,'index.html',context)

template.html

<img src="{{cached_file.hoge.src}}">

[ Python ] 現在の Git のブランチ名を取得

よく使うのでメモ。

import subprocess

def get_active_branch():
    cmd = "git rev-parse --abbrev-ref HEAD"
    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    proc.wait()
    stdout_data = proc.stdout.read()
    stderr_data = proc.stderr.read()
    active_branch = stdout_data.decode('utf-8').replace('\n','')
    return active_branch

[ Python ] AWS Lambda を使って LightSail のスナップショットを作成

INSTANCE_NAME には LightSail のインスタンス名を入力してください。

import boto3
import datetime

INSTANCE_NAME = 'XXX'

def create_snapshot(lightsail,name):
    """
    スナップショットを作成
    """
    timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
    response = lightsail.create_instance_snapshot(
          instanceSnapshotName = timestamp,
          instanceName = name
    )
    print(f"{INSTANCE_NAME} のスナップショット:{timestamp} を作成しました")
    

def delete_old_snapshot(lightsail,INSTANCE_NAME):
    """
    最も古いスナップショットを削除
    """
    
    all_instance_list_response = lightsail.get_instance_snapshots()
    filtered_instance_list = filter(lambda x: x['fromInstanceName'] == INSTANCE_NAME,all_instance_list_response['instanceSnapshots'])
    sorted_list = sorted(filtered_instance_list, key=lambda x:x['createdAt'])
    
    if len(sorted_list) > 1:
        # 最も古いスナップショットを取得
        old_snapshot = sorted_list[0]   
        old_snapshot_name = old_snapshot['name']
        response = lightsail.delete_instance_snapshot(
                instanceSnapshotName = old_snapshot_name
        )
        print(f"最も古いスナップショット:{old_snapshot_name} を削除しました")
    else:
        print('削除できるスナップショットがありませんでした')
       
   

def lambda_handler(event, context):
    lightsail = boto3.client('lightsail')
    instance = lightsail.get_instance(instanceName=INSTANCE_NAME)
    try:
        create_snapshot(lightsail,INSTANCE_NAME)
        delete_old_snapshot(lightsail,INSTANCE_NAME)
        return 'SUCCESS'
    except Exception as e:
        print(e)
        return 'ERROR'

Vuex で フォームのバリデーションを実装するにはデータバインディングを使わない方がいい

データバインディングに依存したフォームのバリデーションは非同期な処理が入ったり、他のフォームのフィールドにアクセスする必要があると途端にコードが複雑になってしまうのでどうしたものかと試行錯誤した結果、データバインディングを使わなければコードもシンプルかつ低負荷な実装ができるという結論に至った。

フォームに送信するまではバリデーションを行わず、フォームを送信後はリアルタイムにバリデーションを行う例で考えてみる。

.vue 側はこのように書く。


<template>
...
<input type="text" class="form-control"
               :value="form.params.username.value"
               @keyup.stop="update(
                    value: $event.target.value,
                    )”
<span v-if=“form.params.username.error.length > 0”>{{form.params.username.error}}</span>

<button @click=“submit”>submit</button>
...
</template>

<script>

export default {

    methods: {
        update(value){
            this.$store.dispatch(‘updateUserName',value)
        },
        submit(){
            this.$store.dispatch(‘submit’);
        }
    }
}


</script>

上のコードは input タグが文字入力を受け付けるたびに updateUserName というメソッドに input タグの値を引数にして呼び出している。v-model を使用していないのがポイント。

Vuex 側はこのように書く。



state: {
    form: {
        submitted: false,
        params: {
            username: {
                value: ‘’,
                error: ''
            }
        }
    }
}

mutations: {
    validateUserName(state,field){
        if (field.value.trim().length === 0){
            field.error = 'ユーザー名を入力してください’;
        }else{
            field.error = ‘';
        }
    },
    updateUserName(state,payloads){
        // username をセット
        state.params.username.value = payloads.value;
        // submit がまだの時はバリデーションを行わない
        if (!state.submitted){
            return;
        }
        // username のバリデーションを行う
        this.commit(‘validateUserName’,state.params.username);
    },
    submit(state){
        state.submitted = true;
        this.commit(‘validateUserName’,state.form.params.username);
        if (state.form.params.usernname.error === '’){
            // success!
        }else{
            // validation error.
        }
    }
},

actions: {
    udpateUserName(value){
        const payloads = {value};
        if (value.trim().length === 0){
            this.$store.commit(‘updateUserName',payloads);
        }
    },
    submit(){
       this.$store.commit('submit');
    }
}


Vuex 側では 2つの処理を行なっている。

(1) username の値が変化すると, username のバリデーションを行い、username に値をセット。
(2) submit が呼ばれると, submitted フラグを有効にして username のバリデーションを行なっている。

データバインディングを使ったバリデーションよりも処理の流れが細やかに制御することができるし、処理内容も明確になった。