のぴぴのメモ

クラウドやLinuxとかの技術メモ

(JavaScript)配列の文字列のいずれかを含むケースをチェックする方法

結論

pattern.some( pt => element.includes(pt) ) )でチェックする。

  • patternが検査に適用する特定のパターンの文字列を登録した配列
  • elementがチェックしたい文字列

サンプルコード

const pattern = ['cat','dog','mouse'];
const input = ['cat','house','dog','dog-dayo','do'];

input.forEach( (element) => {
    console.log(
        "input:", 
        element, 
        "pattern.some( pt => element.includes(pt) result:", 
        pattern.some( pt => element.includes(pt) ) 
    );
});

実行結果

完全一致するdog以外に、patternの文字列を含むdog-dayotrueで判定してくれる。

input: cat pattern.some( pt => element.includes(pt) result: true
input: house pattern.some( pt => element.includes(pt) result: false
input: dog pattern.some( pt => element.includes(pt) result: true
input: dog-dayo pattern.some( pt => element.includes(pt) result: true
input: do pattern.some( pt => element.includes(pt) result: false

検証詳細

Array.prototype.includes()を利用した場合。

Array.prototype.includes()は要素の文字列と完全一致した、catdogのみ、trueとなる。

サンプルコード
const pattern = ['cat','dog','mouse'];
const input = ['cat','house','dog','dog-dayo','do'];

input.forEach( (element) => {
    console.log(
        "input:",
        element,
        "pattern.includes() result:",
        pattern.includes(element)
    );
});
実行結果

完全一致するcatdogしか、trueにならない。
これは、Array.prototype.includes()はそれぞれの要素に対して、SameValueZeroアルゴリズムで評価しているかららしい。

$ node test1.js

input: cat pattern.includes() result: true
input: house pattern.includes() result: false
input: dog pattern.includes() result: true
input: dog-dayo pattern.includes() result: false
input: do pattern.includes() result: false

String.prototype.includes()を利用した場合。

Array.prototype.some()は、配列を順番に関数にかけてチェックしtrueが一つでも出たらtrueとするメソッド。 それにArray.prototype.includes()で対象の文字列が含まれるかをチェックすることで、やりたいことを実現する。

サンプルコード
const pattern = ['cat','dog','mouse'];
const input = ['cat','house','dog','dog-dayo','do'];

input.forEach( (element) => {
    console.log(
        "input:",
        element,
        "pattern.some( pt => element.includes(pt) result:",
        pattern.some( pt => element.includes(pt) )
    );
});
実行結果

Array.prototype.includes()のケースに加えて、dog-dayotrueと判断される。
これは、String.prototype.includes()が、1 つの文字列を別の文字列の中に見出すことができるかどうかを判断しているかららしい。(Array.prototype.includes())と挙動が異なる。

$ node test2.js

input: cat pattern.some( pt => element.includes(pt) result: true
input: house pattern.some( pt => element.includes(pt) result: false
input: dog pattern.some( pt => element.includes(pt) result: true
input: dog-dayo pattern.some( pt => element.includes(pt) result: true
input: do pattern.some( pt => element.includes(pt) result: false

TypeScriptを初めて触った時のメモ(その2: TypeScript言語編)

はじめに

この記事は、AWS CDKを覚えるにあたり、その前にAWS CDKを使ったIaCで最も使われるプログラミング言語であるTypeScriptの基礎を押さえないと行けないなと思い、触った時のメモである。

この記事では、TypeScriptとその前提となるJavaScritpの言語としてのメモを記載する。

TypeScriptを始めるまでの初期セットアップについては以下の記事にまとめている。
nopipi.hatenablog.com

おことわり

結局以下のページの解説を見ながらペコペコ叩いただけなので、このブログを見るよりこちらのページを見た方が有益かと思う。最初は分量が少ない「とほほ」から読んで、なんとなくわかったところから公式のドキュメントなどを読んだ。

あと以下のOREILLYの本も役に立った。
www.oreilly.co.jp

JavaScriptの仕様

JavaScriptの基本仕様

  • 大文字と小文字を区別する
  • 文はセミコロンで区切られる
  • 冒頭に"use strict";を入れると厳格な実行モードで安全でない仕様が無効化されるstrict modeで実行される
  • 実行ファイルの冒頭から実行される(main(){}のように必ずこの関数から実行するという仕様はない)
    • 一番最初に呼び出すファイル(エントリーポイント)は、index.jsにすることが多い

JavaScriptサンプル: index.js

"use strict";

// `name`という名前の変数を宣言
const name = "azu";

// `NAME`という名前の変数を宣言
const NAME = "azu";

console.log("name=",name)
console.log("NAME=",NAME)

(JavaScript仕様)コメント

  • 一行コメント
// 以降から行末までが一行コメント
    • 複数行コメント
/* ここに
コメントを
書きます
*/
let a: number = 1;

式と演算子

以下を参照
developer.mozilla.org

変数

(JavaScript仕様)変数・定数宣言

  • 変数の宣言
    • let : ES2015(ES6) から使える、varの後継。範囲が局所化できネストされたブロックで同一変数名で宣言しても上書きされない。
    • var: 以前からある変数宣言方式。ネストされたブロック内で同一変数名で宣言されると上書きされる。
var a = 3;
let b = 3;
{
  var a = 5;
  let b = 5;
}
console.log("a = "+a);    // 5 (ブロック内で上書きされてしまう)
console.log("b = "+b);    // 3 (ブロック内で上書きされない)
  • 定数の宣言
    • const : 定数を宣言する
const a = 1;

a = 2; <<= 定数には代入できないのでエラーになる(tscコンパイル時に"error TS2588: Cannot assign to 'aaa' because it is a constant."となる)

console.log("a = "+a); 

(TypeScript固有)変数の型指定

JavaScript動的型付け言語であることが特徴であるが、大規模開発を行う際は「動的型付け」であることが意図しない挙動に繋がりバグが見つけられにくいというデメリットがある。TypeScriptでは明示的に型宣言を行う(ゆるい)静的型付け宣言であることが特徴である。これにより潜在的なバグの存在を減らすことができる。

型の明示方法(型アノテーション)
  • 単一の型を指定する方法
let a: number = 1;
let b: string = "hello";
  • 複数の型を指定する方法 : | で複数の方を指定する
function add(a: number | undefined, b: number | undefined): number {
  if (a === undefined || b === undefined) {
    throw new Error("引数が不正です");
  }

  return a + b;
}

let bb: undefined;
console.log(add(1, 2));
console.log(add(1, bb));
型の種類

TypeScriptのデータ型は、結局のところJavaScriptで扱うデータ型に準ずる。
JavaScriptで扱うデータ型は大きく、プリミティブ型とオブジェクト型の2つに分類できる。
プリミティブ型には8種類のタイプがある。

  • プリミティブ型: 数値や文字列など単一のデータを格納するデータ型
    • よく使うデータ型
      • string : 文字列("Hello, world")
      • number : 数値(43)
      • boolean : 真偽(true, false)
    • 任意の型
      • any : どのような型の入力も許容する型(JavaScriptの型と一緒)
    • nullと未定義
      • null : ヌル(null pointer)
      • undefined : 初期化をしておらず未定義
    • あまり使わない型
      • bigint : ES2020で追加。 number型で扱えない非常に大きな整数に使用する型
      • symbol : よくわからない
let a: number = 1;
let b: string = "hello";
let c: boolean = true;
let d: any = 1;
let e: null = null;
let f: undefined = undefined;
// let g: bigint = 10n;
let h: symbol = Symbol("key");

console.log("type a:", typeof a);
console.log("type b:", typeof b);
console.log("type c:", typeof c);
console.log("type d:", typeof d);
console.log("type e:", typeof e);
console.log("type f:", typeof f);
// console.log("type g:", typeof g);
console.log("type h:", typeof h);
  • オブジェクト型: プリミティブ型以外のデータ型
    • Array型
    • Dictonary型
    • オブジェクト
    • などなど
let array: number[] = [1, 2, 3];
let dictionary: { [key: string]: string } = {
  "name": "Taro Yamada",
  "age": "20",
  "address": "Tokyo"
};

console.log("type array:", typeof array);
console.log("type dictionary:", typeof dictionary);
リテラル型(特定の文字列や数値などのデータのみ許可する型)

限られた文字や数値などのデータのみデータの投入を許可する特殊な型。
例えば、EC2インスタンスタイプの数値など、特定の文字列の入力のみ許可する変数を定義&宣言するのに利用する。

let StrInstanceSize: "m5.large" | "m5.xlarge" | "m5.2xlarge";

StrInstanceSize = "m5.large";
console.log(StrInstanceSize);

StrInstanceSize = "t2.xlarge"; // t2.xlargeは定義されてないためErrorになる

console.log(StrInstanceSize);

オブジェクト

(JavaScript&TypeScript)配列(Array)

Arrayの詳細な説明は以下を参照

基本的なArrayの操作
//配列の宣言
let year: number[] = [2019, 2020, 2021, 2022, 2023];
let word: string[] = ['hello', 'world'];
let arr: (number | string)[] = [1, 'a'];

// 配列の参照
console.log(year);     //全ての要素を表示
console.log(year[0]);  //1番目の要素を表示
console.log(year[1]);  //2番目の要素を表示

// 配列の操作
word.push('!');     //末尾に追加
word.unshift('Hi'); //先頭に追加

console.log(word.pop());   //末尾を削除
console.log(word.shift()); //先頭を削除
高度なArrayの操作
  • filter: 条件に合致する要素を一つ以上抽出し新しいArrayを作成する
const words: string[] = ["spray", "elite", "exuberant", "destruction", "present"];

const result: string[] = words.filter((word) => word.length > 6);

console.log(result);
// Expected output: Array ["exuberant", "destruction", "present"]
  • find: 先頭から検索し条件に合致する要素を一つ抽出する
const array1: number[] = [5, 12, 8, 130, 44];

const found: number | undefined = array1.find((element) => element > 10);

console.log(found);
// Expected output: 12
  • map: 全ての要素に対して定義したメソッドの処理を行い新しい配列を生成する
const array1: number[] = [1, 4, 9, 16];

// Pass a function to map
const map1: number[] = array1.map((x) => x * 2);

console.log(map1);
// Expected output: Array [2, 8, 18, 32]

(JavaScript&TypeScript)連想配列(=>はJavaScriptにない。オブジェクトで実現)

JavaScriptには連想配列の実装はない。
Objectを利用して、連想配列のような利用を実現する。
ということで、詳しくは次の「オブジェクト」を参照。

(JavaScript&TypeScript)オブジェクト

オブジェクトの基本的(定義&宣言/参照/更新)
// オブジェクトの定義と宣言
let poetletter: { [key: string]: string } = {
    name: "Taro Yamada",
    age: "20",
    address: "Tokyo"
};

// オブジェクトの参照
console.log(poetletter);
console.log(poetletter.name); // Taro Yamada
console.log(poetletter.age); // 20
console.log(poetletter["address"]); // <==こういう書き方もできる

// オブジェクトの操作
poetletter.name = "Hanako Yamada";
console.log(poetletter.name); // Hanako Yamada
  • 宣言時に複数の型タイプを指定することもできる
// オブジェクトの型ていぎ
let poetletter2: { [key: string]: string | number } = {
    name: "Taro Yamada",
    age: 20,
    address: "Tokyo"
};  // <==ageの型がnumberになっている
(TypeScript)オブジェクトの定義(type, interface)
// typeを使って型を事前に定義する
type StrPoetLetter = {
    name: string;
    age: number;
    address: string;
};

// オブジェクトの宣言
let poetletter: StrPoetLetter = {
    name: "Taro Yamada",
    age: 20,
    address: "Tokyo"
};

console.log(poetletter);
  • interfaceを使った定義
// interfaceを使って型を事前に定義する
interface StrPoetLetter {
    name: string;
    age: number;
    address: string;
}

// オブジェクトの宣言
let poetletter: StrPoetLetter = {
    name: "Taro Yamada",
    age: 20,
    address: "Tokyo"
};

console.log(poetletter);
(TypeScript)ネストする複雑なオブジェクトの定義
  • interfaceを使った定義
// 配列にするオブジェクトの型を宣言
interface StrInternal {
    fuga: string;
    nuga: string | undefined;
}

// オブジェクトの型を宣言
interface StrPoetLetter {
    name: string;
    age: number;
    address: string;
    hoge: StrInternal[];
}

// オブジェクトの宣言
let poetletter: StrPoetLetter = {
    name: "Taro Yamada",
    age: 20,
    address: "Tokyo",
    hoge: [
        {
            fuga: "fugafuga",
            nuga: undefined
        },
        {
            fuga: "fugafuga2",
            nuga: "nuganuga2"
        }
    ]
};

// オブジェクトの構成要素を表示
console.dir(poetletter);

typeの場合は、interfaceの部分を以下のように=を入れればOK

interface StrInternal = {
(TypeScript)型エイリアス(type)とinterfaceの違い
  • 継承同名の宣言時にマージされるなど、拡張性が高いのはinterface
  • 拡張性は低いがシンプルなのが型エイリアス(type)

この辺の記事が参考になる。

(Javascript)オブジェクト構造の表示方法
console.dir(オブジェクト);
継承

割愛

(JavaScript)JSON処理

JSONの処理にはJSONオブジェクトを利用する。

  • JSON to Object: JSON.parse()を利用
  • Object to: JSONJSON.stringify()を利用


オブジェクトの説明は以下を参照。

JSON to Object
const json = '{"result":true, "count":42, "data":[{"foo": "hoge1", "bar": "hage1},{"foo": "hoge2","bar": "hage2"}]}';

const obj = JSON.parse(json);

console.dir(obj);
Object to JSON
const obj = {
    result: true,
    count: 42,
    data: [
        {
            foo: "hoge1",
            bar: "hage1"
        },
        {
            foo: "hoge2",
            bar: "hage2"
        }
    ]
};

console.log(JSON.stringify(obj, null, 4));

関数

JavaScriptの関数仕様

JavaScriptの関数の基本

JavaScriptの関数は、

  • function XXX(){ }の形式
  • 関数呼び出し時に引数を省略した場合は、undefinedになる
function add(a, b) {
    console.log("type of a: "+typeof(a)+"   a="+a)
    console.log("type of b: "+typeof(b)+"   b="+b)
    return a + b;
}

// aとb両方指定した場合
console.log(add(1, 2));

// aだけ指定した場合 ->bはundefined,リターンはNaN(Not-A-Number)
console.log(add(1));

// bだけを明示的に指定した場合 ->aはundefined,リターンはNaN(Not-A-Number)
console.log(add(b=2));
JavaScriptの関数の引数の渡し方(参照渡しと値渡し)

javascriptには、参照渡しと値渡しの使い分けはない

  • プリミティブ型 : 値渡し(call by value)
  • オブジェクト型:参照渡し(call by reference)

(TypeScript)関数のパラメータ

関数パラメータの基本的な考え方

TypeScriptでは、

  • 関数を呼び出す時に、関数で定義した全てのパラメータを指定するのが基本
    • なので関数呼び出し時に、TypeScriptではJavaScriptでは許容されるadd(a=1, b=2)という書き方は許容されない
  • パラメータは、型アノテーションを行うのが基本(型アノテーションを省略した場合はanyになる)
  • 関数の戻り値の型アノテーションは、function xxx(): 型アノテーション {}と書く。
function add(a: number, b: number): number {
    console.log("type of a: " + typeof (a))
    console.log("type of b: " + typeof (b))

    return a + b;

}

console.log(add(1, 2));
console.log(add(1));  // エラーになる
console.log(add(a=1, b=2)); //エラーになる
オプションパラメーター

引数の :型アノテーションの前に?をつけると呼び出し時に引数を省略できるオプションパラメーターとなる
ただし以下の注意点がある。

  • 必須パラメーターの後ろにオプションパラメーターを指定する必要がある。
    • 必須パラメータの前にオプションパラメーターは指定できない
  • 指定を省略した場合、オプションパラメーターにはundefinedが設定される。
    • undefinedが設定されるのはjavascriptの仕様によるもの
  • オプションパラメーターを2つ以上定義することも可能であるが、基本はオプションパラメーターは1つにするのが望ましい
    • 呼び出し時に省略できるのは最後のパラメーターのみのため

注意点があるので、
次に説明するデフォルトパラメーターを基本的に利用する方が良いと思う。

function add(a: number, b?: number, c?: number): number {
    console.log("type of a: " + typeof (a))
    console.log("type of b: " + typeof (b))
    console.log("type of c: " + typeof (c))

    if (b && c) {
        return a + b + c;
    } if (b) {
        return a + b;
    } if (c) {
        return a + c;
    } else {
        return a;
    }
}

console.log(add(1, 2, 3));
console.log(add(1, 2)); // 3番目の引数cは省略可能(その場合、c=undefinedになる
console.log(add(1, undefined, 3)); // 2番目の引数を省略する場合はundefinedを渡す必要がある
console.log(add(1)); 
  • エラーになるパターン(必須パラメータの前にオプションパラメーターを指定)
function add2(a?: number, b: number): number { //エラーが発生する: error TS1016: A required parameter cannot follow an optional parameter.
    return a + b;
}
デフォルトパラメーター

パラメータ記載時にb = 9とイコールでデフォルト値を指定することで、パラメータ省略時のデフォルト値が自動的に入る。

デフォルトパラメータを利用した場合、デフォルト値で定義した型から類推しパラメーターの型になり、この関数ではその型固定となる。
そのため、パラメーター省略時を考慮した型チェック(undefinedかどうかの確認)を行う必要がないのがメリットとなる。

function add(a: number, b: number = 9): number {
    return a + b;
}

console.log(add(1, 2)); // 3
console.log(add(1));    // 10
console.log(add(1, undefined));    // 10
レストパラメーター

割愛

(TypeScript)関数の戻り値

関数戻り時の暗示的/明示的な型アノテーション

関数戻り時の型指定のポイント

  • TypeScriptは、関数内のreturnから関数戻り値の型を類推する。
  • そのため、通常は明示的な型アノテーションはしなくても良い(O.REILLYの本によると)
  • ただ再起呼出や大規模開発の場合は、明示的に指定するのが良い、らしい

所感として、安全のためには明示的に関数の戻り値を指定した方が良いような気がする。

function add(a: number, b: number) {
    console.log("type of a: " + typeof (a))
    console.log("type of b: " + typeof (b))

    return a + b;
}
function add(a: number, b: number): number {
    console.log("type of a: " + typeof (a))
    console.log("type of b: " + typeof (b))

    return a + b;
}

(JavaScript)関数宣言と関数式とアロー関数式

最初にJavaScriptの関数の仕様を整理する。

  • 関数宣言 : function 関数名{...}で定義する一般的な関数の宣言方法
  • 関数式: 一回しか利用しないような関数をより簡易的に書く方法
  • アロー関数式: 関数名を付けない無名関数を、より簡略化して書く方法。ES2015(ES6)でJavaScriptの仕様として採用されたもの。

関数宣言を簡略化してよりコードをシンプルにするために、関数式やアロー関数式があると理解している。
アロー関数は、Arrayの節でwords.filter((word) => word.length > 6);でちらっと書いているが、ここではアロー関数を使うことで何が便利になるかをarray.filter()を例として説明する。

関数宣言の利用例

文字列が6文字より大きかどうかを判定する関数checkLengthを作り、その関数をarray.filter()に渡している。
words.filter(checkLength)という書き方が一見不自然に見えるが、こちらのwords.filterの仕様を見ると、以下の通りである。

  • words.filter(checkLength)の構文仕様は以下の通り
    • filter(callbackFn, thisArg)
      • callbackFn : 真偽を返す関数を指定する。filter関数は、指定した関数に対して以下の引数を渡して実行する
        • (a)element:処理中の要素のデータ
        • (b)index:処理中要素のarrayの中のindex情報
        • (c)array: filter() が呼び出された配列の情報

つまり、真偽を判断する関数の関数ポインタ的なものをfilter関数に渡して、filter関数はその関数ポインタを利用し関数に所定の引数を渡して実行ということになる。

この書き方だと、(1)関数を外出しに書く必要があり冗長、(2)書き方が関数ポインタのような使い方と直感的に分かりずらい、というデメリットがある。(個人的な所感)

function checkLength(word) {
    return word.length > 6;
}

const words = ["spray", "elite", "exuberant", "destruction", "present"];
const result = words.filter(checkLength);

console.log(result);
関数式の利用例

先ほどの関数宣言での書き方を関数式で書き直した例。

コードの量自体は変わらないが、関数を変数的に扱うような書き方になったので、関数ポインタ的な利用が直感的にわかりやすい書き方に感じる。
可読性向上の目的が大きい書き方かと思う。

const checkLength = function(word) {
    return word.length > 6;
}

const words = ["spray", "elite", "exuberant", "destruction", "present"];
const result = words.filter(checkLength);

console.log(result);
アロー関数式の利用例

要は、上記のcheckLength関数をより簡略な書き方にして、filter()の中に押し込んだ書き方。

const words = ["spray", "elite", "exuberant", "destruction", "present"];
const result = words.filter(word => word.length > 6);

console.log(result);

アロー関数は正式には以下の書き方をする

(引数1, 引数N) => {}

以下のような簡略方式も可能である。

() => 式

引数 => 

(引数) => 

(引数1, 引数N) => 式

クラス(別途記載)

別途記載します。

モジュール(import、export、require)

JavaScriptのスコープ

JavaScriptで定義した変数・関数などは、そのままの状態では該当のJavaScriptファイル(.js)の中だけのスコープにとどまる。
別の.jsファイルからそれらを参照したい場合は、参照先のjsファイルの中で該当の変数なり関数をexportしたものを、利用したいjsファイルでrequireまたはimportで取り込む必要がある。

エラーになる例
  • util.js
function add(a, b) {
    return a + b;
}
  • index.js
console.log(add(3));  // add関数が見つからずエラーとなる
CommonJS形式でのexport, import
  • util.js
exports.add = add;
function add(a, b) {
    return a + b;
}
  • index.js
const lib_1 = require("./lib");
console.log((0, lib_1.add)(1, 2)); // 3

JavaScriptモジュールの記載方式

モジュール間のexport, importは、JavaScriptベースで、(a)JavaScriptの古い仕様、(b)node.jsの仕様(CommonJS)、(c) ES2015(ES6) の仕様、が混在していて正直よくわからない。
具体的内容をきちんと理解したい人は以下のリンク先を参照。

(JavaScript)CommonJSの書き方
  • util.js
exports.increment = (i) => i + 1;
  • index.js
const { increment } = require("./util");
 
console.log(util.increment(3));
(JavaScript)ES6の書き方
  • util.js
export const increment = (i) => i + 1;
  • index.js
import { increment } from "./util";
 
console.log(increment(3));

または以下の書き方

import * as util from "./util";
 
console.log(util.increment(3));

TypeScriptでの書き方

よくわからない&悩ましいですが、
私が使おうとしているのはAWS CDKでのTypeScript利用で、AWS CDKのサンプルではES6モードで書いているようなので、ES6で書こうと思う。
(TypeScriptからJavaScriptコンパイルする時に、tscコマンドがいい感じにcommonJSに書き直しているようにも見える)

TypeScriptを初めて触った時のメモ(その1: 初期セットアップ編)

はじめに

この記事は、AWS CDKを覚えるにあたり、その前にAWS CDKを使ったIaCで最も使われるプログラミング言語であるTypeScriptの基礎を押さえないと行けないなと思い、触った時のメモである。

TypeScriptを動かすまででも結構な量になったので、この記事ではTypeScriptを動かすまでの初期セットアップとして、node.js・npm/npx・tsc周りまでの話を記載する。TypeScriptの言語そのものは別途記載する。

TypeScriptとは?

wikipediaのTypeScriptの説明の冒頭から拝借

TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースプログラミング言語である。TypeScriptはJavaScriptに対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセット(既存のものを全て含んだ上でより機能が拡張されている上位互換となるモノ)となっている。

TypeScriptとJavaScriptとnode.jsの関係性は?

最初はここから理解がふわっとしていたが、調べた内容を図にするとこんな感じ。
こちらのblog記事で別途まとめているのでよければ参照頂きたく。

メモ

以下、やったことと調べたことをつらつら書く。

TypeScript開発&実行環境のセットアップ

node.jsのセットアップ

まず、前提としてJavaScriptの実行環境としてnode.jsをセットアップする。
基本は、以下のnode.js公式ページの手順に従ってセットアップする。

インストール例(最新バージョンでの実行コマンドは上記リンク先を参照)

# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"

# Download and install Node.js:
nvm install 22

# Verify the Node.js version:
node -v # Should print "v22.14.0".
nvm current # Should print "v22.14.0".

# Verify npm version:
npm -v # Should print "10.9.2".

ここで、node.jsnvmnpmの3つのツールがセットアップされる。この3つが何者かを一言でまとめると以下の通り。

  • node.js : JavaScriptの実行環境の一つ
  • nvm : node.jsのバージョン管理ツール
    • node.jsそのものの管理用のコマンド。最新バージョンや指定したバージョンのnode.jsをインストールしたり、複数のバージョンのnode.jsをインストールしている時に、使いたいバージョンのnode.jsを切り替えたりするのに使うコマンド。
  • npm : node.jsのパッケージ管理ツール
    • node.jsで動かすJavaScriptで作られたさまざまなパッケージを管理するためのツール。TypeScriptもnpmコマンドでこの後インストールする。

ちなみに、nvmnpmは最初に実行するシェルでインストールし、node.js は、3つ目のnvm install 22でインストールされる。

開発用のフォルダを準備する

任意のディレクトリを用意する。ここでは、TypeScriptPracticeというディレクトリを利用する前提とする。
GitHubなどで空のリポジトリを作成してgit coneしても良い。

mkdir TypeScriptPractice && cd TypeScriptPractice && pwd
npmパッケージ管理の初期化

開発するディレクトリでnpmの初期化を行う

npm init --yes

そもそもなぜnpmの初期化を行うのか?
npmのパッケージインストール方法は以下の2種類がある。そして通常は、プロジェクト間でnpmパッケージが干渉し合わないようにするために、ローカルモードでパッケージをインストールするのが推奨ということらしい。このローカルモードでインストールする際に、「このディレクトリから独立した領域ですよ」というふうにするためにnpmの初期化を行うようである。

npmのインストールモード

  1. ローカルモード :
    • npm initによる初期化を実行したディレクトリでのみ有効なパッケージとしてインストールされるモード
    • npm initを実行したディレクトリ直下に作成される、./node_modulesディレクトリにパッケージをインストールする。
    • npm installのデフォルトのモード。
  2. グローバルモード :
    • npm initによる初期化に関係なく、実行可能なglobalとしてインストールするモード
    • ~/.nvm/versions/node/vXX.XX.X/lib/node_modulesディレクトリにパッケージがインストールされる
    • 環境変数PATHにも追加されるため、ユーザーはディレクトリを意識することなくインストールしたパッケージを実行できる。
    • グローバルモードでインストールする場合は、npm install -g パッケージ名と、オプションを明示的に指定する必要がある。
TypeScriptのセットアップ
  • typescript, @types/node, ts-nodeの3つのモジュールのインストール
npm install --save-dev typescript @types/node@22 ts-node
  • TypeScriptの動作テスト

以下のコマンドを打って、tscコマンドのバージョンが表記されればTypeScriptのインストールは成功である。

npx tsc --version

インストールするパッケージの説明

パッケージ名 説明 備考
typescript Node.jsをTypeScriptで実装するための静的型付け言語の基本的なライブラリ
@types/node@node.jsバージョン TypeScriptのためのNode.jsの型情報を提供するライブラリ
ts-node TypeScriptファイルをリアルタイムでJavaScriptコンパイルし、利用者はコンパイルを意識せずnode.js上でTypeScriptを直接実行ように見せるためのツール 最初はts-nodeを使わず、tscでTypsScriptからJavaScriptコンパイルしてからnodeコマンドでJavaScriptを実行してみるとありがたみがわかる

npm installの--save-devオプションについて
開発だけでなく本番環境でも使うパッケージなのか、開発環境だけで良いパッケージなのかを指定する。違いはpackage.jsonDependenciesに登録されるかdevDependenciesに登録されるかが異なる。

オプション(short) オプション(long) 説明 インストール時
-D --save-dev 開発環境のみで利用するパッケージの場合に指定する。この場合package.jsonには、devDependenciesのブロックにパッケージが登録される npm installdevDependenciesDependenciesの両方がインストールされる
-P --save-prod 開発環境でも利用するパッケージの場合に指定する。この場合package.jsonには、Dependenciesのブロックにパッケージが登録される npm install --productionDependenciesのみインストールされる

xpnコマンドについて

xpnは、ローカルモードでインストールしたまたはリモートのパッケージを実行するためのコマンド。
例えばTypeScriptのコードをコンパイルするtscコマンドについて、グローバルモードでインストールした場合はPATHが設定されているためプロンプトでtscと打てば実行できるが、ローカルモードでインストールした場所にはPATHがないため、そのままでは実行できない。そのため、xpnコマンドを利用して、パッケージを実行する。 npm execと同様の動きになる。

tscの初期化

TypeScriptコンパイラtscの設定ファイルであるtsconfig.jsonを生成する。
この設定ファイルを編集することでコンパイラの動作をチューニングすることができるが、まずはデフォルトのままで利用する。

npx tsc --init

TypeScriptをとりあえず動かしてみた

前提のTypeScritp環境のセットアップ
# npmの初期化
npm init --yes

# パッケージのインストール
npm install --save-dev typescript @types/node@22

# tscの初期化
npx tsc --init
とりあえずTypeScriptを動かしてみる

こちらのblogの内容を参考に動かしてみた。

console.log("Hello! Node.js × TypeScript");
npx tsc --build
node ./ts_practice/sample1.js
  • 生成された ./ts_practice/sample1.jsファイルを削除する
npx tsc --build --clean
  • ts-nodeで実行してみる
npx ts-node ./ts_practice/sample1.ts
  • ポイント
    • tscコマンド、nodeコマンドを実行せず、ts-nodeコマンド一つでTypeScriptをnode.jsで実行可能
    • この場合はJavaScriptコンパイルされた.jsファイルの書き込みは発生しない
node.jsのhttpモジュールを使って簡易httpサーバを動かしてみる

引き続き、こちらのblogの内容を参考に動かしてみた。

import * as http from "http";

const port = 8989; // ポート番号

// httpサーバーを設定する
const server = http.createServer(
  (request, response) => {
    // サーバーにリクエストがあった時に実行される関数
    response.end("Hello! Node.js with TypeScript");
  }
);
// サーバーを起動してリクエストを待ち受け状態にする
server.listen(port);
// ログを出力する
console.log(`http://localhost:${port} へアクセスください`);
  • 実行してみる
npx ts-node ./ts_practice/sample2.ts
  • 別のターミナルを開いてアクセスしてみる
curl http://localhost:8989

npmでのTypeScriptプロジェクト管理

TypeScriptからJavaScriptへのコンパイル(ビルド)や、実行をnpmのpackage.jsonscriptsとして登録することで、npmコマンドを介して簡単に実行することができる。

  • package.jsonscriptsブロックに以下のように記載する
  "scripts": {
    "build": "tsc --build",
    "clean": "tsc --build --clean",
    "start": "node ./ts_practice/sample2.js"
  },
  • 実行してみる
# TypeScriptのビルド
npm run build

# sample2.jsの実行
npm run start

# ビルドしたファイルのクリア
npm run clean

どんなふうに記載すると良いのかは、node_module配下にインストールした有名なパッケージのpackage.jsonを眺めると参考になると思う。

TypeScript, JavaScript, node.jsの関係を図で整理してみた

今更ですが、TypeScript・JavaScript・node.jsの違いをよく分かってなかったので、自分の理解のために図にしてみました。

特徴

TypeScriptとは

JavaScriptとは

  • 言わずと知れた、WWWの中核要素の一つであるプログラミング言語仕様
  • WebブラウザNetscape Navigatorに起源を持ち、1997年に国際的な標準化団体であるEcmaインターナショナルがECMAScriptとして言語の仕様を標準化している
  • 言語の使用規定なので、実際に動作する環境はブラウザなどで仕様に準拠した実行エンジンを実装してJavaScriptを動作させることになる。

node.js

  • JavaScriptの実行環境の一つ
  • googleが開発・メンテナンスをしているV8 javascript engineをコアに非同期処理やファイルアクセスなどの機能拡張をした実行環境
  • 簡単なJavaScriptのプログラミングで、単独でhttpサーバーとして動作させることも可能
  • 公式ページ: node.js

GitHub ロール(Read,Triage,Write,Maintain,Admin)の権限一覧をスプレットシートで一覧化してみた

レポジトリのユーザー/チームに付与する権限設定(下記画面)には、Read/Triage/Write/MaintainAdminの5つのロールがあります。


それぞれのロールにどのような権限が付与されているかは、以下のGitHubのドキュメントに記載されてます。

ただ行が多すぎてドキュメント見ただけだとよくわからないので、スプレットシートに落としてフィルタリングしたりしやすくしました。
以下のリンクからEXCELファイル形式(GoogleスプレットのEXCEL形式での公開)で取得できます。


作り方

GitHubのマニュアルのページのhtmlから該当のtable部分を抜き出して新しいhtmlファイルを作成して、Googleスプレットシートにインポートしただけです。

  1. Organizationのリポジトリロール - GitHub Docsのページを開く
  2. ブラウザでソースコードを開きリポジトリアクションを検索する。4箇所ヒットするが、一つ目にチェックされたところが該当の表のはずである。
  3. 一行に<table> から </table>があるので、その行をまるっとコピーする。
  4. テキストエディタで新しいページを開き以下の内容を記載する
<html><body>

コピーした<table>から</table>までを貼り付け

</body></html>
  1. レ点とXはSVGになっているので、文字列に置換する。
    1. <svg version=1.1 から始まり</svg>>を探す。
    2. NoYesの2つがあるので、それぞれsvgの一連の文字列を、NoやYesに一気に置換する。
  2. htmlファイルを保存する
  3. Googleスプレットシートで保存したhtmlファイルをインポートする

結局どのロールを指定するか?

5名未満の小規模の開発チームでのプロジェクトで、あまりガチガチに役割を分離せず柔軟に運用するけど、締めるところは締めるという場合はこんな感じかと妄想。

ロール 割り当て先 ざっくりした役割
Admin そのプロジェクトの責任者&副責任者 レポジトリのSettingでの設定変更
Write チームメンバー 開発全般&PullReqの承認も含む
Read チーム以外のオブザーバー 監査やレビューなど

GitHub CLI - ghコマンド - を使ってみた

はじめに

この記事は、GitHub CLIをセットアップして使ってみたメモです。

ghコマンドとは?

ghコマンドは、GitHubの管理を行うためのCLIコマンドです。ghコマンドを使うことで、リポジトリの作成/変更/削除やユーザー管理などGitHubの各種管理をコマンドラインにて実行することが可能になります。
詳しくは、下記の公式ドキュメントを参照ください。
docs.github.com

セットアップ

こちらの [GitHub CLI リポジトリ](https://github.com/cli/cli#installation) のドキュメントに説明がありますが、ここではMacOSにセットアップした時のメモを記載します。

インストール

brewのセットアップ

ここでは、brewを利用してインストールしています。brewが入っていない場合は先にbrewをセットアップします。
brewのインストールは以下のコマンドで実行します。詳しくは[brewのページ](https://brew.sh/ja/)を参照してください。

  • brewのインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  • 実行パスの設定のため、以下内容を~/.zshrcに追加
# For brew command
eval "$(/opt/homebrew/bin/brew shellenv)"
  • 実行確認
gh --version
ghコマンドのインストール
brew install gh

GitHubへのログイン認証

github.comに認証してログインします。

  • 下記コマンドでgithub.comへのブラウザを利用したweb認証を開始します。
gh auth login --git-protocol https --hostname github.com --web
  • 下記メッセージが表示されるのでyで進めます
? Authenticate Git with your GitHub credentials? Yes
  • ghコマンドでEnterキーを押すとブラウザ画面が出てくる
  • githubユーザーを確認して進め、ワンタイムコード入力画面が出たらghコマンドで表示されているXXXX-XXXXのコードを入力し進める
  • Passkeys設定が求められるのでクリックして進める
  • ghコマンドが進み、ログイン情報が表示されれば成功

ghコマンドの使い方

ここではいくつかの利用例を提示します。
詳しくは、下記のマニュアルを参照してください。
cli.github.com

リポジトリ関連操作

# 自分が所有するリポジトリの表示
gh repo list

# 指定したOrganizationが所有するリポジトリの表示(XXXXがOrganization名)
gh repo XXXX

# リポジトリの作成
gh repo create <Organization名>/<リポジトリ名>

Secrets/Variables関連操作

# secretsの一覧表示
gh secret list --env ENVIRONMENT_NAME

# secretsの設定
gh secret set NAME --body VALUE --env ENVIRONMENT_NAME

# secretsの削除
gh secret delete NAME  --env ENVIRONMENT_NAME

# variableの一覧表示
gh variable list --env ENVIRONMENT_NAME

# variableの設定
gh variable set NAME --body VALUE --env ENVIRONMENT_NAME

# variableの削除
gh variable delete NAME  --env ENVIRONMENT_NAME

Macの開発環境を整備する

はじめに

この記事は、自分自身のmacの開発環境をセットアップするための備忘録です。

セットアップ手順

前提

初期設定で以下の設定が完了していること。

  • appleアカウントにログインしデータの同期ができていること

ブラウザ

Macコマンドラインデベロッパーツール

appleが提供している、gitコマンドpipコマンドなど開発に必要なツールのパッケージをインストールする

  • command + spaceで検索ウィンドウを出し、terminalで検索しターミナルを起動する
  • xcode-select --installコマンドを実行する
  • 別ウィンドウでコマンドラインデベロッパーツールが必要です。ツールを今すぐインストールしますか?と聞かれるので、インストールでインストールする。

その他ツールのインストール

Visual Studio Codeセットアップの備忘録

はじめに

この記事は、自分自身のmac bookでVisual Studio Codeを利用した開発環境のセットアップのための備忘録です。
私は、主にpythonAWS CloudFormation、Terraformを利用しているので、その辺りの開発のためのセットアップ手順になります。

前提

このセットアップの前提です

VSCodeのインストール

  • 下記リンク先からパッケージ(私はMac)を選択してダウンロードする

code.visualstudio.com

  • ダウンロードしたパッケージのファイルをアプリケーションに移動する

VSCodeの日本語化

拡張機能 Japanese Language Pack for Visual Studio Codeをインストールし再起動して反映する

  • 左のExtensions Marketplaceを選択し、Japaneseで検索する
  • installボタンからインストールする
  • 拡張のインストール完了後に、VSCodeを再起動するとメニューが日本語表示に変更される


Settings Syncの有効化

説明

VSCodeの設定のバックアップと複数端末でのVSCode設定の共有を行う機能です。
同期のために、GitHubアカウントまたはMicrosoftのアカウントが必要です。
ここではGitHubアカウントを利用します。

手順

  • 左下の設定アイコン(歯車アイコン)のメニューからバックアップと同期の設定を選択
  • 上部中央のpannelに出ているサインインボタンからGitHubでサインインを選びサインインする

拡張機能

拡張機能のセットアップ

VSCodeで、以下の拡張機能をインストールする。

Category Extension Name 提供元 説明
common YAML Red Hat YAML支援機能
Code Spell Checker Street Side Software スペルチェッカー
Markdown Preview Enhanced Markdown Preview Enhanced ported to vscode Markdownのプレビュー。スタイルを編集したかも(要確認)
Markdown Preview Github Styling Matt Bierner MarkdownのプレビューをGitHubスタイルで表示。いらないのかも?
Draw.io Integration Henning Dieterichs VSC内でDraw.ioを編集する拡張
python Python Microsoft Python拡張機能
Pylance Microsoft Python拡張機能の言語サーバ
IaC HashiCorp Terraform HashiCorp TerraformのHLCのSyntaxハイライトとautocompletion機能
CloudFormation Linter kddejong CloudFormationテンプレートの解析ツール
Container Docker Microsoft Docker開発支援

CloudFormation Linterの前提コマンドセットアップ

CloudFormation Linterは、前提としてcfn-lintをインストールする必要があるため、以下の手順でインストールする。

pip3 install cfn-lint
  • 以下のコマンドで実行確認
cfn-lint --version
  • (オプション)もし実行できない場合は~/.zshrcに以下の内容を追記してpython3のパスを追加する
# Add Path
PATH="${PATH}:/Users/n/Library/Python/3.9/bin"

Terraform ExtensionのFormattingのための前提セットアップ

Terraform ExtensionでFormattingを行うためには、ローカルに`terraform`コマンドが必要そうです。そのため以下リンク先からterraformをインストールします。
developer.hashicorp.com

設定

自動フォーマット

保存時のフォーマッタの自動実行設定

  • 設定
    • 右下の設定(歯車アイコン)から設定を選び、以下のキーワードでフィルタリングしてチェック(true)にする。
  • 設定:Format On Save: true

terraformの自動フォーマット実行

terraform編集中のファイル保存時のフォーマッタ(terraform fmtコマンド)を実行する設定。

  • 設定
    • shift + command + pでコマンドパレットを開く
    • "open user setting"と入力し、>open user settings (JSON)を実行する
    • 開かれたsettings.jsonに以下のJSONブロックを追加する
"[terraform]": {
  "editor.defaultFormatter": "hashicorp.terraform",
  "editor.formatOnSave": true,
  "editor.formatOnSaveMode": "file"
},
"[terraform-vars]": {
  "editor.defaultFormatter": "hashicorp.terraform",
  "editor.formatOnSave": true,
  "editor.formatOnSaveMode": "file"
}

CloudFormationの構文チェック用のカスタム設定追加

下記設置を追加します

  1. CloudFormation仕様の追加
  2. CloudFormationのカスタムタグ定義の追加
設定(1) CloudFormation仕様の追加

VSCodeの設定ファイル(Macの場合: $HOME/Library/Application Support/Code/User/settings.json)*1に、CloudFormationのリソース仕様を追加します。この設定でをすると、拡張子がcf.yamlのファイル、cfn/、cloudformation配下のYAMLファイルがCloudFormation用のYAMLとして指定したソース仕様にしたがってチェックされます。

  1. コマンドパレット(⇧⌘P)を開いて >open user settings (JSON)を実行する
  2. 下記定義を追加する
    "yaml.schemas": {
        "https://d33vqc0rt9ld30.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json": [
            "*.cf.yaml",
            "*.cf.yml",
            "cfn/*.yaml",
            "cfn/*.yml",
            "cloudformation/*.yaml",
            "cloudformation/*.yml"
        ]
    },
設定(2) CloudFormationのカスタムタグ定義の追加

デフォルトのままだと!Sub, !RefなどのCloudFormation固有のスキーマYAMLの構文エラーとなるため、これら固有スキーマを追加します。内容はこちらのIssueAndyJPhillipsさんのコメント内容を流用しています。

    "yaml.customTags": [
        "!And sequence",
        "!Equals sequence",
        "!If sequence",
        "!Not sequence",
        "!Or sequence",
        "!Base64",
        "!Cidr sequence",
        "!FindInMap sequence",
        "!GetAtt",
        "!GetAZs",
        "!ImportValue",
        "!Join sequence",
        "!Select sequence",
        "!Split sequence",
        "!Sub",
        "!Ref"
    ]
(参考)setting.json全体の内容

設定(1)&(2)をすると、既存の設定に、下記設定が追加された形になります。

{
    <既存の設定>
    "yaml.schemas": {
        "https://d33vqc0rt9ld30.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json": [
            "*.cf.yaml",
            "*.cf.yml",
            "cfn/*.yaml",
            "cfn/*.yml",
            "cloudformation/*.yaml",
            "cloudformation/*.yml"
        ]
    },
    "yaml.customTags": [
        "!And sequence",
        "!Equals sequence",
        "!If sequence",
        "!Not sequence",
        "!Or sequence",
        "!Base64",
        "!Cidr sequence",
        "!FindInMap sequence",
        "!GetAtt",
        "!GetAZs",
        "!ImportValue",
        "!Join sequence",
        "!Select sequence",
        "!Split sequence",
        "!Sub",
        "!Ref"
    ]
}

MacOSでDocker Desktopの代替えとしてLima+docker環境を作ってみた

はじめに

MacOSでdockerを使おうとすると、第一候補は Docker Desktopになりますが、一定規模以上の企業で利用する場合は有料サブスクリプションが必要になります。*1

WindowsならWLS2でLinuxの仮想環境を作ってその上で、linuxのdocker(Docker Desktopでないので有料サブスクリプション不要)を動かすということができます。一方MacOSには標準でWLS2相当のものがないので、OSSLimaを使ってLinux仮想環境を作って、その上でlinuxのdockerを動かします。

ここではそのセットアップ時の手順を備忘録として記載します。

Limaとは

Mac上でLinuxの仮想環境のVMを作ることができるOSSです。
LimaはGitHubにホストされており、URLは以下になります。

以下のような特徴があります

  • brewコマンドでインストールが楽
  • intel on intelARM on intelARM on ARMintel on ARMのどれでも可能
  • Linuxとして、Alpine、Arch LinuxDebianFedora、Ubunts(default)などさまざまなイメージが利用可能

セットアップ手順

前提のセットアップ

すでにセットアップされている場合はスキップしてください。

brewコマンドのインストール

macOS用のパッケージマネージャーbrewをセットアップします。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

セットアップ手順やその他brew情報は、下記のbrewのページを参照ください。

Limaのインストールとdockerインスタンスの作成

Limaのインストール
brew install lima
dockerが入ったLinux仮想環境の起動

Limaには多数のVMテンプレートがあり、それらテンプレートを利用して簡単に目的の仮想環境をセットアップすることができます。dockerの環境もLimaのテンプレートにあるため、ここではそのテンプレートを利用してdocker環境を起動する手順をまとめました。

limactl start --name=default template://docker
(参考)dockerが入ったLinux仮想環境の停止
limactl stop
(参考)dockerが入ったLinux仮想環境の2回目以降の起動
limactl start

dockerコマンドのセットアップ

dockerコマンドのインストール
brew install docker
dockerコマンドからLimaの仮想環境上のdockerデーモンへの接続

ここで記載した手順は、Limaのdocker環境を起動した時のメッセージの最後に表示されています。
lima-defaultという名前のコンテキストを作成して、切り替える方法になります。

  • dockerコンテキストの作成
docker context create lima-default --docker "host=unix:///Users/nobuyuf/.lima/default/sock/docker.sock"
  • dockerで作成したコンテキストへの切り替え
docker context use lima-default
  • テスト
docker run hello-world

下記メッセージが表示されればOKです。

Hello from Docker!
This message shows that your installation appears to be working correctly.

docker起動スクリプト

上記手順で作成した環境をベースに、Limaのdocker仮想環境を起動しdockerのコンテキストを変更する一括のシェルスクリプトです。

  • start-docker.sh
#!env bash

# start docker instance via Lima
limactl start

# change docker context
docker context use lima-default

補足

補足1 Limaの操作

Limaで稼働している仮想環境の一覧
limactl list
固有のインスタンス名をつけた仮想環境の新規作成と起動

fedora仮想環境をfedoravmというインスタンス名で起動する

limactl start --name=fedoravm template://fedora
Limaの仮想環境の停止

fedoravmというインスタンス名の環境を停止する。

limactl stop fedoravm
既存の仮想環境の起動

fedoravmという作成済みインスタンスを起動する。

limactl start fedoravm
起動中の仮想環境にインタラクティブなシェルで入る
limactl shell fedoravm
Limaの起動可能なテンプレート一覧参照
limactl start --list-templates

補足2 dockerのコンテキスト操作

コンテキスト一覧表示
docker context list
コンテキストの切り替え
docker context use <コンテキスト名>
コンテキストの削除
docker context rm <コンテキスト名>

*1:. Commercial use of Docker Desktop at a company of more than 250 employees OR more than $10 million in annual revenue requires a paid subscription (Pro, Team, or Business) to use Docker Desktop. Pricing | Docker

zshでコマンドライン実行で#コメントを使えるようにする設定

以下のコマンドでzshのプロファイルにコメントを有効化する設定を追加する。
設定後再ログインすれば、コメントがエラーにならないはず。

cat >> ~/.zshrc << EOL

# enable comment on command line mode
setopt interactivecomments
EOL

Jira Service Management Rest APIでカスタム フィールドのオプションを変更したメモ

選択リスト型のカスタム フィールドの選択できる値のリスト更新を Rest APIで実行したメモです。

手順メモ

事前設定

ID="JiraユーザのID"
PASSWD="API トークン"
PROJECT_URL="プロジェクトのURLのxxxx.atlassian.netのxxxx部分"

カスタムフィールドのオプション(リストの選択値一覧)情報の取得

事前設定
FIELD_NAME='AwsSupportServices'
PROJECT_NAME='aws portal'
ISSUE_TYPE_NAME='AWS Support'
プロジェクトのID取得
PROJECT_ID=$(
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request GET \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/project/search" | \
    jq -r '.values[] | select ( .name == "'"${PROJECT_NAME}"'" ) | .id' )
echo ${PROJECT_ID}
課題タイプのID取得
ISSUE_TYPE_ID=$(
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request GET \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/issuetype" | \
    jq -r '.[] | select( .name == "'"${ISSUE_TYPE_NAME}"'" ) | .id' )
echo ${ISSUE_TYPE_ID}
カスタム フィールドのID取得
CUSTOME_FIELD_ID=$(
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request GET \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/search?maxResults=1&expand=names" | \
     jq -r '.names | to_entries | .[] | select( .value == "'"${FIELD_NAME}"'" ) | .key' )
echo $CUSTOME_FIELD_ID
カスタム フィールドの コンテキスト情報の取得

アトラシアンのドキュメントによると、「カスタム フィールド コンテキストは、同じフィールドの異なる設定です。プロジェクトに対して既定値が異なる複数のカスタム フィールドを作成する代わりに、コンテキストを使用して同じ結果を得られます。」とのことです。
同じカスタムフィールドでもプロジェクトごとにリストから選択できる値を変えることなどができるようです。

BODY_DATA='{
  "mappings": [
    {
      "issueTypeId": "'"${ISSUE_TYPE_ID}"'",
      "projectId": "'"${PROJECT_ID}"'"
    }
  ]
}'

CONTEXT_ID=$( \
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request POST \
        --header 'Content-Type: application/json' \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/mapping" \
        --data "${BODY_DATA}" \
    | jq -r '.values[0].contextId' )
echo $CONTEXT_ID
カスタム フィールドのオプション(リストの選択値一覧)の取得
curl \
    -s -u "${ID}:${PASSWD}" \
    --request GET \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" | jq

カスタムフィールドのオプション更新

ここでは選択リスト(カスケード)のオプション変更を例にメモを記載します。

親リスト追加
BODY_DATA='{
  "options": [
    {
      "disabled": false,
      "value": "hoge hoge"
    }
  ]
}'
curl \
    -s -u "${ID}:${PASSWD}" \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" \
    --data "${BODY_DATA}"
作成した親リストにカスタム フィールド オプションを追加する
PARENT_ID='作成した親のIDを指定'
BODY_DATA='{
  "options": [
    {
      "optionId": "'"${PARENT_ID}"'",
      "disabled": true,
      "value": "Manhattan"
    }
  ]
}'

curl \
    -s -u "${ID}:${PASSWD}" \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" \
    --data "${BODY_DATA}"
既存のカスタムフィールド オプションの更新
CUSTOME_FIELD_OPTION_ID='変更したい既存フィールドのIDを指定'
BODY_DATA='{
  "options": [
    {
      "id": "'"${CUSTOME_FIELD_OPTION_ID}"'",
      "disabled": false,
      "value": "Manhattan"
    }
  ]
}'

curl \
    -s -u "${ID}:${PASSWD}" \
    --request PUT \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" \
    --data "${BODY_DATA}"

Jira Service ManagementでRest APIで課題を操作したメモ

はじめに

Jira Service ManagementのREST API操作を調査したメモです。
最終的にServer PlatformでREST APIを叩く必要があったため、Jira REST API V3ではなくJira REST API V2前提で調べていますので、注意ください。

JiraのREST API

REST API リファレンス

Jiraにはクラウド版とサーバ版があり、それぞれAPIのバージョンが異なるようです。

2022/9/1時点では、クラウド版がJira REST API V3で、サーバ版の最新が Jira REST API V2(Jira 9.2.0)のようです。
ただ使った限りではクラウド版でもJira REST API V2が実行できるようです。

Jira Server platformのREST APIの種類

JiraのAPIは以下の3種類があるようですが、とりあえず基本の Jira Server platform REST API を見ておけばよさそうです。

REST APIで課題の情報を取得する

curl -u "${ID}:${PASSWD}" https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID} | jq

以下はあらかじめ変数で設定しておきます。

  • ${ID} : 接続用に使うJiraユーザのID
  • ${PASSWD}: : API トークン(ログインパスワードではないです。アカウント管理 -> セキュリティ -> APIトークンで生成します)
  • ${ISSUE_ID}/ : 課題のキー(XX-99)か、課題ID(XMLエクスポートして、<key id="99999">XX-99</key>の99999部分)を指定

REST APIで課題をトラジションする

Transitionsの取得

curl -u "${ID}:${PASSWD}" https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions

Transitionsする

Transitionsの取得で取得したデータから、実行したいTransitionのidを取得して以下を実行します。

TRANSITION_ID=99   #取得したISSUE IDに書き換え

BODY='{
    "transition": {
        "id": "'"${TRANSITION_ID}"'"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X POST -d "${BODY}"  \
    --header 'Content-Type: application/json'  \
    --header 'accept: application/json'  \
    --url https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions

Transitionsしてかつコメントを追記する

前提

前提として該当Transistionを実行したときに以下のような画面が出てくるよう、Transistionを設定する必要があります。

  • Trasition時にコメント追加ができるようにする設定方法
    • 画面(Screen)を新規作成します。フィールドタブは何も設定しないでOKです。
    • 該当ワークフローを編集します。
      • 対象のトランジットを開き、編集を開きます
      • 画面で、新規作成した画面を指定して保存します。
REST API実行
TRANSITION_ID=99   #取得したISSUE IDに書き換え

BODY='{
    "update": {
        "comment": [
            {
                "add": {
                    "body": "Test Message."
                }
            }
        ]
    },
    "transition": {
        "id": "'"${TRANSITION_ID}"'"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X POST -d "${BODY}"  \
    --header 'Content-Type: application/json'  \
    --header 'accept: application/json'  \
    --url https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions

Transitions時に特定のフィールド値を変更する

前提
  • その1) Transitionの画面設定

前提として、該当Transitionでフィールド更新ができるよう、Transitionに割り当てた画面設定で、更新したいフィールドを追加する必要があります。

  • その2) カスタムフィールドの内部キー取得

動かした限りでは、例えばカスタムフィールドAwsAccountIdを更新しようとする時、transitionでAwsAccountIdは指定できず、内部で管理しているcustomfield_10016のような無機質なキーで指定する必要があるようです。Jiraの残念な仕様ですね。

カスタムフィールドの名前と内部のキーのマッピングの一覧は以下で取得することができるので、これで事前に変更したいカスタフィールドの内部キーが何かを確認しておく必要があります。

 curl -u "${ID}:${PASSWD}" "https://<Project-name>.atlassian.net/rest/api/2/search?maxResults=1&expand=names"
REST API実行
TRANSITION_ID=99   #取得したISSUE IDに書き換え

BODY='{
    "fields": {
        "customfield_10075": "bbb hoge hoge"
    },
    "transition": {
        "id": "'"${TRANSITION_ID}"'"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X POST -d "${BODY}"  \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions?expand=transitions.fields"

課題(ISSUE)の特定フィールドを変更する

ISSUEの特定フィールドを更新する場合は、ISSUEの編集で対応できます。

BODY='{
    "fields": {
        "customfield_10075": "000000000000"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X PUT -d "${BODY}"  \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}"

Windowsサーバ ドメイン参加 + AD管理用サーバをPower Shellでやってみる

備忘録として、メモしておきます。

手順

AD管理ツールのセットアップ

  • Windowsに管理者権限でログイン
  • AD管理に必要なツールをPowerShellでインストールする。
Import-Module ServerManager
Install-WindowsFeature -Name GPMC,RSAT-AD-PowerShell,RSAT-AD-AdminCenter,RSAT-ADDS-Tools,RSAT-DNS-Server

ドメイン参加

DNS参照先をADのDNSアドレス(IPアドレス)に変更
Get-NetAdapter | Set-DnsClientServerAddress -ServerAddresses <ADの1つ目のIPアドレス>,<ADの2つ目のIPアドレス>

設定の確認

Get-NetAdapter | Get-DnsClientServerAddress
ドメイン追加

ドメインに参加させます。実行すると 管理者ユーザのパスワードを聞かれるので、パスワードを入力します。

Add-Computer -DomainName <ドメイン名> -Credential <ドメインの管理者ユーザ名>

例えば、ドメインhogehoge.internal、管理者ユーザadministratorの場合の実行コマンドは、Add-Computer -DomainName hogehoge.internal -Credential administratorとなる。

サーバのリブート

  • 有効にするためリブートする。
Restart-Computer
  • 再起動したら、ドメインユーザ&パスワードでログインする。

GitHub ActionsによるTerraform実行環境(更新ディレクトリ特定+checkovによるセキュリティチェック付き)

はじめに

GitHub ActionsによるTerraformのCIサンプルです。
実際のコードはGitHubのリポジトリを参照ください。
このサンプルの特徴は以下の通りです。

  • OpenID Connect(OIDC)でのAWS認証によるActions実行により、アクセスキー&シークレットキー管理が不要
  • Terraformのデファクトとなっているディレクトリ構成で作成
  • その上でActionsでは、更新があったディレクトリを特定し、そのディレクトリのみTerraformを実行
  • CIに、IaCコードの静的解析を行うcheckovを組み込み、Pull Requestで自動実行(最近流行りの、DevSecOpsぽいものを実現)


terrafrom/envs/xxxのように環境面でTerraform実行先が固定されている場合(実行面数が増加しない場合)は、こんな面倒なやり方をせずワークフローを分けてon:で、paths:条件で実行する構成にした方がシンプルで望ましいです。
一方で、アカウントやプロジェクトなどTerraform実行ディレクトリが単純増加するユースケースで、GitHub ActionsでTerraformを実行したいケースでは、このサンプルのワークフローが効果を発揮すると思います。

前提とするTerraformのディレクトリ構成

terraformのモジュールの標準的なディレクトリ構成を前提としています。
/terraform/accounts/配下にそれぞれのアカウント単位でterrafomを管理するイメージです。
アカウントを追加する場合は、_templateフォルダを任意の名称でコピーして適切に設定を変更してterraformでapplyします。

.
└── terraform
    ├── accounts
    │   ├── _template
    │   ├── user-a
    │   │   ├── backend.tf
    │   │   ├── main.tf
    │   │   ├── provider.tf
    │   │   └── terraform.tf
    │   ├── user-b
    │   └── user-c
    └── modules
        └── xxxxx

ActionsからのAWS認証

ActionsからAWS環境にアクセスする際の認証には、OIDCを利用しています。OIDC利用の詳細は下記ドキュメントを参照ください。

Actionsのワークフローコード

実際のコードはこちらのワークフローのYAMLファイルを確認ください。
ジョブは、(1)更新ディレクトリ特定ジョブと、(2)Terraform実行ジョブ、の2つで構成されています。

更新ディレクトリ特定ジョブ

シェル芸の塊です。ここではざっくりとは以下の処理をしています。

  1. git logによる更新一覧取得
  2. sedawkuniqなどを駆使して、更新ディレクトリ情報のみ抽出
  3. 抽出した情報をjpコマンドでJSONに変換
  4. 変換したJSONは、次のTerraform実行ジョブでのmatrixに利用

具体的な処理の中身については、GitHubのREADMEこちらの記事を参照ください。

 detect_dirs:
    name: "Detect modified directories"
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    outputs:
      TARGET_DIR: ${{ steps.detectddir.outputs.TARGET_DIR }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Detect modified project directories
        id: detectddir
        run: |
          # git fetch
          echo "::group::git fetch"
          TARGET_BRANCH="${{ github.base_ref }}"  
          echo "TAGET_BRANCE = ${TARGET_BRANCH}"
          git fetch --depth 1 origin ${TARGET_BRANCH}

          # If modules is changed, execute terraform to all .
          echo "::group::check modules directory"
          LINES=$( git diff origin/${TARGET_BRANCH} HEAD --name-only -- ${{env.TERRAFORM_MODULES_DIR}} | wc -l )
          if [ ${LINES} -gt 0 ]; then
            flag_all_envs='true'
          else
            flag_all_envs='false'
          fi
          echo "::group::flag_all_envs = ${flag_all_envs}"

          # Detect target directories
          echo "::group::detect target directories"
          if [ "${flag_all_envs}" == 'true' ]; then
            TARGET_DIR=$( \
              find ${{env.TERRAFORM_TARGET_DIR}} -type d -not -name ${{env.TERRAFORM_ENVS_EXCLUDED_DIR}} -maxdepth 1 -mindepth 1 | \
              jq -scR 'split("\n") | .[:-1]' \
            );
          else
            TARGET_DIR=$( \
              git diff origin/${TARGET_BRANCH} HEAD --name-only -- ${{ env.TERRAFORM_TARGET_DIR }} | \
              sed -E 's:(^${{ env.TERRAFORM_TARGET_DIR }}/[^/]*/)(.*$):\1:' | \
              sort | uniq | \
              awk '{ if( system("[ -d "$1" ]") == 0 && $1 !~ /${{env.TERRAFORM_ENVS_EXCLUDED_DIR}}/ ){print $1} }'  | \
              jq -scR 'split("\n") | .[:-1]'
            );
          fi

          # Output target directories
          echo "::endgroup::"
          # Output results
          echo "::group::results"
          echo "TARGET_DIR = ${TARGET_DIR}"
          echo "::endgroup::"
          # End processing
          echo "::set-output name=TARGET_DIR::${TARGET_DIR}"
          exit 0

Terraform実行ジョブ

先の更新ディレクトリ特定ジョブから、jobのoutputsを利用し特定したディレクトリ一覧を取得し、Matrixを利用し並列実行しています。
checkovの詳細については、こちらの記事を参照ください。

  run_terraform:
    name: "Run terraform"
    needs: detect_dirs
    if: ${{ needs.detect_dirs.outputs.TARGET_DIR != '[]' }}
    strategy:
      fail-fast: false
      max-parallel: 2
      matrix:
        target: ${{fromJson(needs.detect_dirs.outputs.TARGET_DIR)}}
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    env:
      TERRAFORM_WORK_DIR: ${{ matrix.target }}
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{secrets.ASSUME_ROLE_ARN}}
          role-session-name: pullrequest
          aws-region: ap-northeast-1
      - name: AWS Sts Get Caller Identity
        run: aws sts get-caller-identity
      - name: Setup terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: ${{env.TERRAFORM_VERSION}}
      - name: Set up Python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: 3.9
      - name: Test with Checkov
        id: checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: ${{env.TERRAFORM_ROOT_DIR}}
          framework: terraform
          quiet: true
          skip_check: ${{env.CHECKOV_SKIP_CHECK}}
      - name: Terraform Format
        run: terraform fmt -recursive -check=true
      - name: Terraform Init
        run: terraform -chdir=${TERRAFORM_WORK_DIR} init
      - name: Terraform Validate
        run: terraform -chdir=${TERRAFORM_WORK_DIR} validate -no-color
      - name: Terraform Plan
        run: terraform -chdir=${TERRAFORM_WORK_DIR} plan

セットアップ手順

具体的手順は、GitHubのリポジトリのREADMEに記載しています。

  1. Terraform実行用にIODCプロバイダー、IAMロール、S3バケット、DynamoDB作成と設定
    1. 作成用のCloudFormationを準備しているので、これで作成します。
    2. IAMロールのANRをGitHubリポジトリに、ASSUME_ROLE_ARNという名称のシークレットに格納します。
    3. 作成したS3バケット、DynamoDBを、Terraformコードに設定
  2. GitHubリポジトリへmainブランチ以外の任意のブランチでPush
  3. pull requestを作成し、mainにマージする。

TerraformコードのセキュリティチェックでCheckovをGitHub Actionsに組み込んでみた

はじめに

Terraformコードのセキュリティーチェックを行う必要があり、IaC用の静的コード解析ツールであるcheckovをGitHub ActionsのCIに組み込んでみた時のメモです。
最初にcheckovをローカルで実行する場合のやりかたを説明して、最後にActionsへの組み込み方法を説明します。

Checkovとは

Checkovは、Infrastracture as code(IaC)コード用の静的コード解析ツールです。Checkovを利用することで、セキュリティやコンプライアンスの問題につながる可能性のある箇所を摘出することができます。解析可能なIaCは以下の通りです(2022/5現在)

  • Terraform (for AWS, GCP, Azure and OCI)
  • CloudFormation (including AWS SAM)
  • Azure Resource Manager (ARM)
  • Serverless framework
  • Helm charts
  • Kubernetes
  • Docker

ローカルPCでの使い方

インストール

インストール方法は、checkovドキュメントのinstalling Checkovを参照ください。

たとえばpip3を利用してインストールする場合は以下のようになります。

pip3 install checkov

インストール後、下記のようにコマンドを打って実行可能なことを確認します。

checkov --version
2.0.1211

(留意事項)レベルアップ

なお、引数なしで実行すると下記のような画面で「追加機能を使えるようレベルアップするか?」という質問が出ます。このレベルアップをするためにyを選択すると、レベルアップに必須となるhttps://www.bridgecrew.cloud/gettingStartedのアカウント作成に必要な情報を対話形式で入力する流れになります。

この設定をすると、checkovの実行結果がbridgecrew.cloudのサイトにアップされることになるようなので、アップされるのを回避したい場合はnを選択して終了するようにしてください。

もしレベルアップを選択してbridgecrew.cloudのサイトにアップされるようになった後に解除する場合ですが、~/.bridgecrew/credentialsにサイトに接続するためのトークンがあるので、このファイルを削除してしまうことで接続されないようになります。

静的解析のやりかた

静的解析の実行方法

たとえばTerraformの実行ディレクトリが/data/HogeHogeSystem/envs/development/の場合、以下のコマンドで静的解析を実施することができます。

checkov --quiet -d /data/HogeHogeSystem/envs/development/
  • --quiet: FAILEDになったものだけ表示されます。(quietをつけない場合は、PASSした結果も表示されます)

実行結果の例です。

指摘事項のスキップ方法(Terraformのオブジェクト単位)

上記の例では、セキュリティーグループを作成しているけど使用されていないよというエラーになります。
ここで指摘内容が正しければコード修正しますが、問題ない場合は該当モジュールにcheckov:skip=check_id:suppression_commentの形式でコメントを追加すると、checkovでチェックをスキップすることができます。

# SG for EC2 instances
resource "aws_security_group" "ec2" {
   #checkov:skip=CKV2_AWS_5:There is no problem because it will be used in the EC2 instance to be added later.
   name        = "${var.vpcname}-ec2"
   description = "For EC2"
   vpc_id      = aws_vpc.this.id
   egress {
     description = "Allow all traffic."
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }
   tags = {
     Name = "${var.vpcname}-sg-ec2"
   }
}

詳しい内容は、Suppressing and Skipping Policies - checkovを参照ください。

指摘事項のスキップ方法(ルール単位)

また、--skip-checkで指定することで、チェック全体から特定のルールを除外することができます。
たとえばcheckovでは、LOWMEDIUMHIGHCRITICALの4つのseverity区分があります。またルールには、CKV_123のような番号が振られています。
severityがLOWのものと、CKV_123をチェックから除外する場合は、以下のように実行します。

checkov --quiet --skip-check LOW,CKV_123 -d /data/HogeHogeSystem/envs/development/

GitHub ActionsへのCheckovの組み込み

GitHub Actionsのワークフローに、checkovを組み込むことができます。詳細については以下のドキュメントを確認ください。

具体的にはGitHub Actionsに以下のような記載をすることで、Actionsでcheckovを実行することができます。この場合FAILEDになるルールが発生した場合はcheckovコマンドのリターン値が0ではないので、その時点でエラーでフローがアベンドします。
またcheckovの前提でpythonが必要になるため、 actions/setup-python@v4を利用しPythonをセットアップします。

      - name: Terraform Format
        run: terraform fmt -recursive -check -diff

      - name: Set up Python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: 3.9
      - name: Test with Checkov
        id: checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: .
          framework: terraform
          quiet: true
          skip_check: LOW

      - name: Terraform Init
        run: terraform init -no-color

エラーが発生した時のスキップ方法など運用方法は、ローカルPCの時と同じになります。
運用としては、このcheckovをmainブランチへのPull Request & Push時のフローに組み込み、セキュリティチェックが通過しないとmainブランチにマージできないようにするなどが考えられます。