AWS CLIのEmacs用ユーティリティ aws.el を作る

AWS CLIのEmacs用ユーティリティを作る事にした。このライブラリはAWS CLIをスケルトンファイルを用いて、Emacsから実行する機能を提供する。ここではこのライブラリが提供する各機能を説明する。

変数

aws-cli-command

AWS CLIとして実行するコマンドを設定する。通常は aws を用いる。例えばAWSのダミーサーバー群であるlocalstackなどに対して実行する場合 --endpoint-url を指定しなければならない。そのように値を変更することで実行するコマンドをカスタマイズできる。

(setq aws-cli-command "aws --endpoint-url http://localhost:4566")

aws-cli-current-profile

AWS CLIを実行する際に使用するプロフィールを設定する。何も設定しなければデフォルトのプロフィールを使用する。AWS CLIのコマンドを実行する直前に、一時的にAWS_PROFILE環境変数をprocess-environmentに追加し実行するために、この値を利用する。

コマンド

コマンドも関数だろという指摘が来そうだが、違いはM-xで呼び出し可能になっているかどうかという点だ。このセクションではコマンドとして提供している関数を説明する。

aws-cli-apply-profile-to-global ()

aws-cli-current-profile]AWS_PROFILE環境変数に設定する。通常aws-cli-current-profileは一時的に設定されるが、このコマンドにより現在のEmacs全体の環境変数としてAWS_PROFILEを設定する。

aws-cli-execute-skelton (subcmd)

現在のバッファの訪れているファイルを使い、AWS CLIを実行する。どのようなサブコマンドを指定するかは、コマンド呼び出し時に入力を受け付ける。そのコマンドの --cli-input-json オプションとして、現在のバッファの訪れているファイルを引数として渡す。そのため実行時の現在のバッファはファイルを訪れている必要がある。バッファの内容は --generate-cli-skelton で生成したJSONファイルを元に値を変更したものを想定している。

aws-cli-help (subcmd)

AWS CLIのhelpを表示する。AWS CLIは多くのサブコマンドを提供している。どのサブコマンドに対して行うかは、コマンド呼び出し時に入力を受け付ける。

aws-cli-print-skeleton (subcmd)

指定したAWS CLIのサブコマンドに対し --generate-cli-skeleton を実行し、出力をバッファに表示する。

この出力は通常JSON形式であり、それを用いてAWS CLIのコマンドを実行するためのパラメータを指定できる。この出力をファイルに保存しaws-cli-execute-skeltonを用いることで、実行パラメータをファイルとして表現し実行できる。実行パラメータをファイルにすることで、フォーマッターを適応しやすくなったり、Gitで管理しやすくなるといったメリットを享受できる。

aws-cli-switch-cli (command)

aws-cli-commandを変更する。

aws-cli-switch-profile (profile-name)

aws-cli-current-profile変更する。

関数

提供している関数の内、コマンドではないものを説明する。

aws-cli–process-environment ()

AWS CLIをサブプロセスとして実行する時に使用する環境変数の一覧を返す。現在のprocess-environmentにaws-cli-current-profileをAWS_PROFILEとして追加したリストを返す。

パッケージ

このライブラリはMelpaなどのリポジトリに登録していない。そのためインストールするためには、Githubから直接このファイルを取得し、package-install-from-fileなどを用いてインストールする必要がある。

実装

[[./aws.el][aws.el]

;;; aws.el --- AWS CLI for Emacs. -*- lexical-binding: t -*-

;; Copyright (C) 2022 TakesxiSximada

;; Author: TakesxiSximada <[email protected]>
;; Maintainer: TakesxiSximada <[email protected]>
;; Version: 1
;; Package-Version: 20221203.0000
;; Package-Requires: ((emacs "27.1"))
;; Date: 2022-12-03

;; This file is not part of GNU Emacs.

;; aws-cli.el is free software: you can redistribute it and/or
;; modify it under the terms of the GNU Affero General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.

;; aws-cli.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Affero General Public License for more details.

;; You should have received a copy of the GNU Affero General Public
;; License along with this program.  If not, see
;; <https://www.gnu.org/licenses/>.

;;; Code:

;;; Customization
(require 'simple)

(defcustom aws-cli-command "aws" "")

(defcustom aws-cli-executable "aws"
  "AWS CLI Command"
  :group 'aws
  :type 'file)

(defcustom aws-cli-current-profile nil "")
(defcustom aws-cli-current-region nil "")

;;;###autoload
(defun aws-cli-switch-profile (profile-name)
  (interactive "s:AWS_PROFILE: ")
  (setq aws-cli-current-profile profile-name)
  (message (format "Change aws profile configuration: %s"
		   aws-cli-current-profile)))

;;;###autoload
(defun aws-cli-switch-region (region-name)
  (interactive "s:AWS_REGION: ")
  (setq aws-cli-current-region region-name)
  (message (format "Change aws region configuration: %s"
		   aws-cli-current-region)))

;;;###autoload
(defun aws-cli-switch-cli (command)
  (interactive "s:AWS CLI COMMAND: ")
  (setq aws-cli-command command)
  (message (format "Change aws cli base command: %s"
		   aws-cli-command)))

;;;###autoload
(defun aws-cli-apply-profile-to-global ()
  (interactive)
  (when aws-cli-current-profile
    (setenv "AWS_PROFILE" aws-cli-current-profile)
    (message (format "Change `AWS_PROFILE` environment variable: %s"
		     aws-cli-current-profile))))

(defun aws-cli--process-environment ()
  (if aws-cli-current-profile
      (append `(
		,(format "AWS_PROFILE=%s" aws-cli-current-profile)
		,(format "AWS_REGION=%s" aws-cli-current-region)
		) process-environment)
    process-environment))

;;;###autoload
(defun aws-cli-help (subcmd)
  (interactive "s[aws] ")
  (let ((process-environment (aws-cli--process-environment)))
    (async-shell-command (format "%s %s help" aws-cli-command subcmd))))

;;;###autoload
(defun aws-cli-print-skeleton (subcmd)
  (interactive "s[aws] ")
  (let ((process-environment (aws-cli--process-environment)))
    (async-shell-command (format "%s %s --generate-cli-skeleton" aws-cli-command subcmd))))

;;;###autoload
(defun aws-cli-execute-skelton (subcmd)
  (interactive "s[aws] ")
  (let ((process-environment (aws-cli--process-environment)))
    (async-shell-command
     (format "%s %s --cli-input-json file://%s"
	     aws-cli-command
	     subcmd
	     buffer-file-name))))

;;;###autoload
(defun aws-cli-shell ()
  (interactive)
  (let ((process-environment (aws-cli--process-environment)))
    (vterm)))

(defcustom aws-ecs-current-cluster-name ""
  "Name of ECS Cluster"
  :group 'aws
  :type 'string)

(defcustom aws-ecs-execute-command-container ""
  "Container name"
  :group 'aws
  :type 'string)

(defcustom aws-ecs-execute-command-line "/bin/bash"
  "Commandline"
  :group 'aws
  :type 'string)

(defcustom aws-ecs-exec-buffer-name "*AWS ECS Exec*"
  "Buffer name of AWS ECS Exec"
  :group 'aws
  :type 'string)

;;;###autoload
(defun aws-ecs-execute-command (task-id container command)
  (interactive (list
		(read-from-minibuffer "Task ID: " )
		(read-from-minibuffer "Container: " nil nil nil nil aws-ecs-execute-command-container)
		(read-from-minibuffer "Command: " nil nil nil nil aws-ecs-execute-command-line)))

  (let ((shell-command-buffer-name-async aws-ecs-exec-buffer-name)
	(async-shell-command-buffer 'confirm-new-buffer))
    (async-shell-command
     (s-join " "
	     `(,aws-cli-executable
	       "ecs"
	       "execute-command"
	       "--interactive"
	       "--profile" ,aws-cli-current-profile
	       "--cluster" ,aws-ecs-current-cluster-name
	       "--container" ,container
	       "--task" ,task-id
	       "--command" ,command)))))

(defcustom aws-region nil
  "aws region"
  :type 'string
  :group 'aws)

;;;###autoload
(defcustom aws-ecr-current-repository-name nil
  "Target AWS ECR Repository Name"
  :type 'string
  :group 'aws)

;;;###autoload
(defun aws-ecr-describe-image-tags ()
  (interactive)
  (let ((process-environment
	 (aws-cli--process-environment)))
    (async-shell-command
     (s-join " "
	     `("aws"
	       "ecr"
	       "describe-images"
	       "--repository-name" ,aws-ecr-current-repository-name
	       "|"
	       "jq"
	       "-r"
	       "'.imageDetails[].imageTags[]?'")))))

;;;###autoload
(defcustom aws-ecs-current-cluster-name nil
  "Target AWS ECS Cluster Name"
  :type 'string
  :group 'aws)

;;;###autoload
(defcustom aws-ecs-current-service-name nil
  "Target AWS ECS Service Name"
  :type 'string
  :group 'aws)

;;;###autoload
(defun aws-ecs-list-tasks ()
  (interactive)
  (let ((process-environment
	 (aws-cli--process-environment)))
    (compile
     (s-join " "
	     `("aws"
	       "ecs"
	       "list-tasks"
	       "--cluster" ,aws-ecs-current-cluster-name
	       "--service-name" ,aws-ecs-current-service-name)))))

;;;###autoload
(defun aws-browse-rds ()
  "RDSを表示する"
  (interactive)
  (browse-url (format
	       "https://%s.console.aws.amazon.com/rds/home?region=%s"
	       aws-region aws-region)))

(provide 'aws)
;;; aws.el ends here