ansibleで空配列の定義と値の入れ込みを1つのタスク内で処理する

そうしたい時もあると思います。

過去、ループで回した値を定義した配列に順次入れ込んでいきたい場合はこんな感じにしていた。

- name: Define empty array
  ansible.builtin.set_fact:
    array: []

- name: Set array
  ansible.builtin.set_fact:
    array: "{{ array + [item] }}"
  loop:
  - "a"
  - "b"

最初のタスク Define empty array で空配列 array を定義して、次のタスクで実際の値を入れていく形。
これで Set array 後、 array の中身は ["a", "b"] となる。
まぁこの書き方でも別に良いのだけど、なんだか冗長だなーと。
できれば空配列の定義と、それに値を入れ込む処理は1タスクでいい感じに処理したい。
で、調べたり検証したところ、以下のように書けば一つの処理で完結できることがわかった。

- name: Define array and Set
  ansible.builtin.set_fact:
    array: "{{ array | default([]) + [item] }}"
  loop:
  - "a"
  - "b"

default フィルターを使うことで、 array が定義されていない場合に空配列 [] が設定される。
この default フィルターは、変数が定義されていない場合にのみ適用されるため、初回以外は無視されるのだ。
その後にループで回した値が入れられていくので、結果は上のタスクと同じとなる。

これをどう使うかというと register で取得したタスクの結果とか、自分で定義した vars の配列をループさせて必要な情報だけ取り出したい時とか、そういう時に使ったりする。

変数定義に1タスク使うのもなんだかなではあるのでこういう形で短縮できると見通しも良くなっていいんじゃないだろうか。
まあ場合によるとは思うけど…。
例えばこのタスクにwhen句があって、場合によって処理されない時があるのに、後続処理では定義したarrayを絶対使う…とかだと、空配列の定義と値の入れ込み処理は分けた方が良さそう。
まあやっぱり処理の方法による…か…。

この書き方は元々確かStack OverFlowに記載されていたのだけど、そのURLは失念…。
どう調べたら出てくるかなあ…。

PrometheusでSSL証明書の有効期限をYYMMDDの年月日形式に変換するPromQL

監視の一環として、PrometheusでSSL証明書の有効期限を追うようにしている。
blackbox_exporterでSSL証明書の有効期限が出せるので、元々死活監視だけをするつもりだったがついでにSSL証明書の有効期限も監視することにした。
blackbox_exporter内の設定はこんな感じ(一部省略)。

module
  cert_expires:
    prober: http
    http:
      method: GET

Prometheus内の設定はこんな感じ(一部省略)。

rule_files:
  - "/etc/prometheus/conf.d/notifiers/slack_notifications.yml"

scrape_configs:
  - job_name: 'cert_expires'
    scrape_timeout: 10s
    scrape_interval: 5m
    metrics_path: /probe
    params:
      module: ['cert_expires']
    file_sd_configs:
    - files:
      - conf.d/cert_expires/*.yml
    relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - source_labels: [__param_target]
      target_label: url
    - target_label: __address__
      replacement: 127.0.0.1:9115

file_sd_configsconf.d/cert_expires/ 以下のymlファイルを全て読むようにしている。
読み込んでいるymlファイルの中身はこんな感じ(一部省略)。

- targets:
  - https://example.com

rule_files で読み込んでいる設定ファイルの中身はこんな感じ(一部省略)。

groups:
- name: blackbox_exporter
  rules:
  - alert: SSLCertificateExpiresIn30days
    expr: round(sort((probe_ssl_earliest_cert_expiry{job="cert_expires"} - time()) / 86400), 1)
    for: 0m
    labels:
      severity: info
    annotations:
      summary: "SSL Certificate expires in 30 days"
      description: "{{ $labels.url }} expires: {{ $value }}"

通知に使っているalertmanagerの設定はこんな感じ(一部省略)。

mute_time_intervals:
- name: daily_mute_1000-0900
  time_intervals:
  - times:
    - start_time: 01:00 # JST 10:00
      end_time:   24:00 # JST 09:00

route:
  - receiver: 'cert_expires'
    continue: false
    group_by:
    - alertname
    matchers:
    - severity="info"
    - job="cert_expires"
    mute_time_intervals:
    - daily_mute_1000-0900

上では30日の設定しか記載していないが、60日前、30日前、14日前、10日前、7日前、3日以内とあり、残り有効期限がどれかに引っかかったら一日に一回、日本時間で9時〜10時の間にSlackに通知を出してくれる。
のだが、これだと有効期限が相対日数でしか出せない。
「あとn日で期限切れになるよ」というのは出せるが、「n年n月n日に期限切れになるよ」というのが出せない。

blackbox_exporterで取得できる有効期限はUnixtimeなので、 有効期限 - 現在時刻 * 86400 をしてやれば「あとn日」は出せる。
最初はこれで出していたのだが、相対日数は実際いつ期限切れになるのかわかりづらい…わかりづらくない…?
「n日後」の実際の月日がパッと見でどうしてもわかりづらいのだ。
ちょっと前に来た通知で14日後とか言われても、じゃあ実際いつ期限切れるんだっけ…?みたいな感じである。
そんなこんなで調べてみたところ、Unixtimeを直接年や月、日、時間に変える関数が存在するらしいことに気付いた。

https://prometheus.io/docs/prometheus/latest/querying/functions/

よし、これを使おう。と思い立ったのが発端で、タイトルのPromQLを作り始めた。

続きを読む

Go言語でメソッドを実装する

Go言語は type で定義した構造体、もしくは別名を付けた型定義に対してメソッドを作成できる。
書式は以下。[]内のものが実際に指定する値。

func ( レシーバ[変数 型] ) メソッド名 ( 引数[変数 型] ) 戻り値[型] { 処理 }

例えば、 Student 構造体に Hello メソッドを作ってみるとする。

type Student struct {
  Name string
  Age int
}

func main() {
  taro := Student{"Tanaka Taro", 11}
  taro.Hello()
}

func (s Student) Hello() {
  fmt.Printf("Hello, %s.\n", s.Name)
}

実行すると以下のようになる。

Hello, Tanaka Taro.

関数と同様参照渡しも使える。
多分メソッドを作成する場合参照渡しが基本になるんじゃないだろうか。
なんとなくだけど、メソッドを使った上で別の変数にそれを代入するのはちょっと煩わしい気がする。
以下では PutAge() メソッドで年齢を更新し、 PrintAge() メソッドで出力してみている。

type Student struct {
  Name string
  Age int
}

func main() {
  taro := Student{"Tanaka Taro", 11}
  taro.PrintAge()

  taro.PutAge(14)
  taro.PrintAge()
}

func (s *Student) PutAge(age int) {
  s.Age = age
}

func (s Student) PrintAge() {
  fmt.Printf("%s age is %d\n", s.Name, s.Age)
}

実行すると以下のようになる。

Tanaka Taro age is 11
Tanaka Taro age is 14

値が書き換わっているが、 PutAge() メソッドで参照渡しをしているため、直接 taro.Age の値を操作できるというわけだ。すごい。
書いてて思ったがRubyでいうところの破壊的変更っぽいか?どうだろう。
大きい値は値渡しをしてしまうと関数呼び出しの際にコピーしてしまうため、大きい値や構造体の場合は参照渡しをした方が良いとのこと。
うーん...なんだか脳死で参照渡しにしてしまいそうだな...。
このケースだと値渡しで、このケースなら参照渡し、みたいなのがあると嬉しいなあ。

Go言語構造体と値渡し、参照渡しおさらい

ちょっとGo言語ハンズオンをやっているのだけど、メソッドが出てきてから脳の処理が追いつかなくなってきました。
理解がね...追いついていないのです…。

なのでとりあえず書き出そうかと。

構造体

通常使うとしたら以下

type 構造体の名称 struct {
...
}

// 例
type Student struct {
  Name string
  Age int
}

あまり使わなさそうだが変数に直接定義する場合

var 変数 struct {
...
}

// 例
var taro struct {
  Name string
  Age int
}

構造体を利用する場合は以下

// 前者
taro := Student{"Tanaka Taro", 11}

// もしくは
taro := Student{
  Name: "Tanaka Taro",
  Age: 11,
}

// 後者
taro.Name = "Tanaka Taro"
taro.Age = "11"

これはわかる。まだ。

構造体の値渡しと参照渡し

構造体を引数とした関数を作る場合、値渡しと参照渡しの2つの方法がある。
値渡しの例は以下

func main() {
  taro := Student{"Tanaka Taro", 11}
  // 変数 taro を引数として関数 plus1 を呼び出し、結果を変数 taro_1 に格納する
  taro_1 := plus1(taro)
  // 変数は taro と taro_1 の2つを保持することになる
  fmt.Println(taro)
  fmt.Println(taro_1)
}

func plus1(s Student) Student {
  // s.Age に +1 してから再度書き込む
  s.Age += 1
  // 戻り値は Student型(構造体) なので、 s.Age に +1 した変数 s を return する
  return s
}

参照渡しの例は以下

func main() {
  taro := Student{"Tanaka Taro", 11}
  // 関数 plus1 に変数 taro のポインタを渡す
  plus1(&taro)
  fmt.Println(taro)
}

// ポインタを渡されているため、型の前に * を付けて値を参照できるようにする
func plus1(s *Student) {
  // ポインタ s の Age を操作するため、 (*s) として参照先とし、参照先の Age に +1 する
  // ただし、 s.Age += 1 としてもいける(ポインタレシーバというやつか?)。
  (*s).Age += 1
}

値渡し参照渡しが引っかかりポイントとしてある。
参照渡しの時 (*s) としなくても正しくコンパイルが通るのは以下の暗黙的変換が効いているからだろうか…?

skatsuta.github.io

書籍は読み進めても書いていなかった...。どういうことなんだ...。

とりあえず一旦そういうものとして理解するしかないか...。
この辺なんて検索すれば出てくるのだろうか。

GitHubに草を生やす活動を始めて2ヶ月目になった。

草を生やす

今こんな感じですね。

f:id:i_luv_kneesox:20211002012639p:plain
GitHub Grass

もうすぐ3ヶ月目になります。

草を生やすために

大したことはやっておらず、とりあえず「Go言語やりてえ」という思いから、8月頭から「改訂2版 基礎からわかる Go言語」からやってみることに。

改訂2版 基礎からわかる Go言語

改訂2版 基礎からわかる Go言語

  • 作者:古川昇
  • シーアンドアール研究所
Amazon

とりあえずリポジトリとして sandbox-go というのを作り、そこに毎日1コミット以上コミットしていくことを目標に取り組み始めました。

github.com

とりあえず習慣化していきたかったので、理解はさておき一日1コミットは絶対するようにしました。
まあ、序盤はたまに失念してしまったり仕事に忙殺されてできなかったりとかもあったのですが…。

で、ひと月くらいやってみて「一日1コミットは全然いけるな」と気付いたわけです。
とにかく無理せず嫌にならずに進めようとしていたのですが、それでも一日1コミットは余裕過ぎます。 理解はさておき、サンプルコード1つ書いて動かしてコミットして...であれば10分もあればできますからね。
そこで途中から「平日は最低2コミット、休日は最低3コミット」とすることにしました。
9月の2週目辺りですね。

ちなみにここでいう「休日」は土日祝及び、有給などの自主的な休暇も含みます。
なので平日も3コミットしているところは、1コミット分頑張ったか有給かどちらかです。

そんなわけで「改訂2版 基礎からわかる Go言語」は一応全てサンプルコードを書き終えました。
ただし、如何せん知識がまだまだついていません。まだまだ「Go書けますよ」というレベルではないですね。
それに「改訂2版 基礎からわかる Go言語」は、基礎は確かに網羅していると思うのですが、Goのバージョンが1.4だったり、外部モジュールの使い方がなかったりと、実践知識に欠けるかなと。
そこで、別の書籍に手を出しました。「Go言語ハンズオン」です。

今はこれをやっています。
まだchapter2ですが、2冊目になるのでさすがに今度は理解しながら進めていきたいですね。

モチベ維持効果

2ヶ月近く続けてみて思ったのですが、コントリビューションが可視化されるのは非常に良いです。
中身は大したことやっていない、なんのことはない書籍の写経ですが、こう濃淡のあるコントリビューションを見ると「自分なんかやってるように見えるな」って思っちゃいます。
実際やってることはまあ...うん...という感じではありますが...。

これから

今までは雑草も生えていない砂漠でしたが、8月から新芽が芽吹くようになってきました。
緑が多くなるのは良いことです。環境保全二酸化炭素排出量も減るんじゃないでしょうか。
SDGsに則ってこれからも毎日続けていきたいですね。

目指せ、デプロイツール作成。

転職2回目をやらかしてしまったので記録していく

前職の経営が非常に不安定になった事と、仕事自体に絶望したため転職活動をしていました(8ヶ月ぶり2回目)。
退職エントリに関してはこちらをどうぞ。

ine1127.hateblo.jp

期間について

2019年6月末を以て念願叶い無職となりました。
退職の意向を上司に伝えた後からちょっとだけ活動し始め、有給消化期間中から本格的に活動し始めました。
メインで使っていたのはGreenで、エージェントさんは特になし。
他は過去に登録したen転職とかGeeklyとかで求人を見てたくらいです。

というわけで記録していくぞ。

続きを読む