20200414のGitに関する記事は8件です。

Git で、トラッキングしていないファイルをまとめて一括で .gitignore に追加する

目的

簡単なプロジェクトで、バージョン管理が必要なファイルが少なく、IDE が吐き出した管理不要なほとんどのファイルを無視したい、といった場合に。

手順

先ずはじめに、バージョン管理が必要なファイルだけ、ファイル名を指定した上で git commit しておきます。

残りは、管理する必要がないファイルなので無視しても良いのですが、git status するたびに出てきてしまうので、.gitignore に入れてしまいたい。でも、手作業でそれをするのは敗北を意味する。

そこで、以下のコマンドを実行します。

git status --porcelain | grep '^??' | cut -c4- >> .gitignore

参考

何度かお世話になっているワンライナーですが、どうも毎回 Web 検索に苦労しているような気がするので、検索が容易になりそうなタイトルで投稿しておきます。

元ネタは以下です。

github - How can I revert this command "git status --porcelain | grep '^??' | cut -c4- >> .gitignore"? - Stack Overflow

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数のコミットを一括でcherry-pickする

複数の連続したコミットをcherry-pickしたい

コマンド

git cherry-pick {始点となるコミットの1つ前のコミットハッシュ}..{終点となるコミットハッシュ}

コミットハッシュを..でつなぐことで、間のコミットがcherry-pickされる

cherry-pickしたい1つ前のコミットを指定することに注意

始点のコミットハッシュは、cherry-pickの対象に含まれないことに注意。

  • commitA
  • commitB
  • commitC
  • commitD

のうちcommitBからcommitDをcherry-pickしたい場合

git cherry-pick {commitA}..{commitD}

となることに注意。

git cherry-pick {commitB}..{commitD}

としてしまうと、commitCとcommitDのみがcherry-pickされてしまう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsで初めて作成したポートフォリオをGitHub Pagesで公開しました

この記事は、「Nuxt.js ってなんですか?」な状態の人間が、やりたいことを盛り込んで作成したポートフォリオについて書いたものです。

作成したポートフォリオ

https://tayuta.github.io/portfolio/
GitHub Pages に公開しています。

https://github.com/tayuta/portfolio
ソースコードです。

キャプチャ4.PNG
キャプチャ5.PNG
キャプチャ6.PNG
キャプチャ7.PNG
キャプチャ8.PNG

作成に至った動機

ポートフォリオを作成することになったのは、社内でポートフォリオ部(正式名称:そうだポトフを作ろう! 略称:ポトフ)が発足されたことがきっかけです。
もともと業務では、JavaScriptやHTML、CSSなど、フロント系はほとんど触れあったことがなかったので、勉強になるかと思い、入部しました。

プロジェクトの作成

ポートフォリオの研究

ポートフォリオが一体どういったものか知らなかったので、ほかの人が作成しているものを片っ端から見あさりました。
また、ポートフォリオの作成だけでなく、自分でレイアウトを考えたりすることも初めてだったので、デザイン手法などについても学習しました。

【学習に利用した記事】
全部知ってる?主要なWebデザイン手法のまとめ

ポートフォリオ概要決め

つぎに、どのようなポートフォリオを作るか、なんとなく決めました。

  • SPA
  • Nuxt.jsで作成する
  • テーマは、海の中(最終的には全く違うものができました。←)
  • 以下のコンテンツを縦に並べる
    • プロフィール
    • スキル
    • これまで作ったもの(ほぼありませんが...)
    • 愛犬について(あまりにコンテンツが少なかったので後から追加)

Nuxt.js プロジェクトの作成

以下の記事を参考に、Nuxt.js プロジェクトを作成しました。
Nuxt.js を導入しよう

設定内容が少し変わっていたので、こちらの記事も参考になります。
Nuxt.js の導入 -2.8.1ver-

画面が表示できたので、あとは自分がやりたいことを好きなように盛り込みました。

盛り込んだこと

背景に泡をぷかぷかさせる

当初のテーマは海だったので、背景に泡をぷかぷかさせたいと思いました。

導入方法

bubbly-bg.js をポートフォリオに組み込みます。
GitHub : https://github.com/tipsy/bubbly-bg

※Nuxt.js の場合、JavaScriptファイルをどこに置いて、どこで読み込めばいいのか分からなかったので有識者に質問したところ、ファイルの内容を mounted にぶち込むように教わったので、そうしました。
※mounted が何なのかはもちろん分からなかったので、以下の記事を読んでなんとなく理解しました。
Vueのライフサイクルを完全に理解した

想定通りに動くことを確認した後、?ソースなので、整理をします。

index.vue
<template>
  <section class="container">
    <canvas id="bubbly"></canvas>
    <div class="body">
      <!-- 省略 -->
    </div>
  </section>
</template>
<script>
export default {
  mounted() {
    const canvas = document.getElementById('bubbly')
    const width = (canvas.width = window.innerWidth)
    const height = (canvas.height = window.innerHeight)
    const context = canvas.getContext('2d')
    context.shadowColor = '#c9ffbf'
    context.shadowBlur = 4
    const gradient = this.setGradient(context, width, height)
    const bubbles = this.setBubbles(width, height)

    const draw = () => {
      requestAnimationFrame(draw)
      this.anim(context, gradient, bubbles, width, height)
    }
    draw()
  },
  methods: {
    // メソッド内省略
  }
}
</script>
<style lang="scss">
#bubbly {
  position: fixed;
  z-index: -5;
  top: 0;
  left: 0;
  min-width: 100vw;
  min-height: 100vh;
}
</style>

以下の記事を参考にしました。
背景のグラデーションや、泡の数・動く速度を変更したい場合も、こちらが参考になります。
【JavaScript】美しい泡が漂う[Beautiful bubbly backgrounds]の使い方

背景を斜めにする

背景が斜めだとおしゃれかと思い、背景を斜めにしました。

導入方法

背景を斜めにするためには、要素全体を回転させ、その内側の要素を反対に回転させます。
ただ要素を回転させると、左右に空白ができてしまうため、横幅を大きめに指定します。

sample.vue
<template>
  <section class="container">
    <div class="contents">
      <div class="inner">
        コンテンツ
      </div>
    </div>
  </section>
</template>

<style scoped lang="scss">
.contents {
  -webkit-transform: rotate(10deg) translate3d(0, 0, 0);
  transform: rotate(10deg) translate3d(0, 0, 0);
  background-color: #ffdadf;
  width: 200%;
  margin-left: -180px;    
  .inner {
    -webkit-transform: rotate(-10deg) translate3d(0, 0, 0);
    transform: rotate(-10deg) translate3d(0, 0, 0);
    max-width: 50%;
    margin-left: 157px;
  }
}
</style>

また、このままでは、背景が横にはみ出てしまい、横スクロールが発生してしまいます。
そのため、はみ出た部分を非表示にする必要があるので、親要素である container に「overflow-x: hidden;」を指定します。

sample.vue
<style scoped lang="scss">
.container {
  overflow-x: hidden;
}
</style>

以下の記事を参考にしました。
斜めの背景を作るためのCSSは「回転させて元に戻す」で書く!

スクロールにより、要素をふわっと表示させる

要素が画面に表示されてからアニメーションを発火させるために、カスタムスクロールディレクティブを作成しました。

導入方法

まずは、plugins フォルダ内に以下のファイルを作成します。

scroll.js
import Vue from 'vue'
Vue.directive('scroll', {
  inserted: function(el, binding) {
    const f = function(evt) {
      if (binding.value(evt, el)) {
        window.removeEventListener('scroll', f)
      }
    }
    window.addEventListener('scroll', f)
  }
})

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/scroll']
}

mixin.js に handleScroll メソッドを定義します。

mixin.js
export default {
  methods: {
    handleScroll: (evt, el) => {
      const top = el.getBoundingClientRect().top
      if (window.scrollY > top + window.pageYOffset - 600) {
        el.classList.add('isView')
        return true
      }
      return false
    }
  }
}

ふわっと表示させたい要素に、v-scroll ディレクティブを追加し、アニメーションを設定します。
アニメーションを変更することで、ふわっと表示以外にも、横から表示・回転しながら表示などもできるかと思います。

sample.vue
<template>
  <div v-scroll="handleScroll" class="fade-in">ふわっと表示</div>
</template>

<script>
import mixin from './mixin'
export default {
  mixins: [mixin]
}
</script>

<style scoped lang="scss">
.fade-in {
  opacity: 0;
}
.fade-in.isView {
  animation-name: fade-in;
  animation-duration: 2s;
  animation-timing-function: ease-out;
  animation-delay: 0.2s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: both;
}
@keyframes fade-in {
  0% {
    opacity: 0;
    transform: translate3d(0, 20px, 0);
  }
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}
</style>

以下の記事を参考にしました。
Nuxt.js でスクロールでふわっと要素が出現するやつを「カスタムディレクティブ」で実装する

また、handleScroll メソッドに関しては、sample.vue に記述しても問題ありませんが、私は複数コンポーネントで利用する予定だったので、mixinに記述しました。
mixin については、以下が参考になります。
ミックスイン

スクロールしながら画面遷移を行う

メニューのリンクをクリックした際に、スクロールしながら指定場所を表示させたいと思い、「vue-scrollto」を導入しました。

導入方法

まずは、プロジェクトディレクトリで下記のコマンドを実行します。

npm i vue-scrollto

その後、plugins フォルダ内に以下のファイルを作成します。

vue-scrollto.js
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'

Vue.use(VueScrollTo, {
  duration: 700,           // スクロール継続時間
  easing: [0, 0, 0.1, 1],  // 速度の緩急
  offset: -70              // 遷移後の位置調整
})

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/vue-scrollto']
}

あとは、以下のように、v-scroll-to に遷移先の要素の id を指定することで画面内遷移が可能です。

sample.vue
<template>
  <nuxt-link v-scroll-to="'#bottom'" to>Scroll to bottom</nuxt-link>
  <div id="bottom">bottom</div>
</template>

以下の記事を参考にしました。
スクロール継続時間などのオプションについても、こちらが参考になります。
「vue-scrollto」を使ってイージングスクロールを実装する

写真をスライダーで表示

コンテンツが少なかったので、愛犬の写真を表示することにしました。
複数の写真を見てもらうため、自動で写真が切り替わるようにしたかったので、実現することができそうな「vue-awesome-swiper」を導入しました。

導入方法

まずは、プロジェクトディレクトリで下記のコマンドを実行します。

npm install vue-awesome-swiper --save

その後、plugins フォルダ内に以下のファイルを作成します。

vue-awesome-swiper.js
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'

Vue.use(VueAwesomeSwiper)

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/vue-awesome-swiper']
}

以下のように、スライダーを組み込みます。

sample.vue
<template>
  <swiper :options="swiperOption" ref="mySwiper1">
    <swiper-slide v-for="(item, index) in items" :key="index">
      <img :src="item.img" alt="..." />
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
    <div class="swiper-button-prev" slot="button-prev"></div>
    <div class="swiper-button-next" slot="button-next"></div>
  </swiper>
</template>

<script>
export default {
  data: function() {
    return {
      items: [
        { img: require('~/static/img_1.jpg') },
        { img: require('~/static/img_2.jpg') }
      ],
      swiperOption: {
        autoplay: {
          delay: 2500,
          disableOnInteraction: false
        },
        pagination: {
          el: '.swiper-pagination',
          clickable: true
        },
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev'
        }
      }
    }
  }
}
</script>

<style scoped lang="scss">
.swiper-container {
  height: 300px;
  width: 100%;
}
.swiper-slide {
  text-align: center;
  font-size: 38px;
  font-weight: 700;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  border-left:1px solid #fff;
}
</style>

以下の記事を参考にしました。
「swiperOption」のその他の設定についても、こちらが参考になります。
モバイルタッチスライダー「vue-awesome-swiper」の使い方

注意点

フェードアニメーションと複数カラムスライドは同時に設定できませんでした。
以下の記述の場合、「slidesPerView: 3」の設定が無視されます。

sample.vue
swiperOption: {
  effect: 'fade',
  slidesPerView: 3,
  ...
}

パララックス

パララックスを利用しているサイトかっこいい!と思い導入しましたが、どこに使えばいいか分からず、ひっそりと利用しています。
私が今回導入したプラグインは、「vue-prlx」ですが、その他に、「vue-parallax-js」などがあるようです。以下が参考になるかと思います。
nuxt.jsでパララックスをするならvue-parallax-jsがお手軽。Cool!

細かい挙動の設定が「vue-prlx」のほうが分かりやすかったので、今回は「vue-prlx」を選択しましたが、気が向いたら「vue-parallax-js」も使ってみようかな、と思っています。(思っているだけです。

導入方法

まずは、プロジェクトディレクトリで下記のコマンドを実行します。

npm i vue-prlx

その後、plugins フォルダ内に以下のファイルを作成します。

vue-prlx.js
import Vue from 'vue'
import VuePrlx from 'vue-prlx'

Vue.use(VuePrlx)

nuxt.config.js の plugins キー内にファイルパスを追加します。

nuxt.config.js
export default {
  plugins: ['~/plugins/vue-prlx']
}

あとは、パララックス効果を追加したい要素に対して、v-prlx ディレクティブを追加するだけです。

sample.vue
<template>
  <img src="sample.png" alt="" v-prlx>
</template>

以下の記事を参考にしました。
細かい挙動の設定についても、こちらを参照してください。
vue-prlxを使ってさくっとパララックス体験をしてみる

ファビコンの作成

キャプチャ3.PNG
私は、「X-Icon Editor」を利用して、作成しました。
簡単なデザインだったので、問題なく作成できました。

GitHub Pagesに公開

ポートフォリオなので、ゆくゆくは人目に触れるようにしたいと思い、ひとまず手軽にできると聞いた GitHub Pages に公開することにしました。

公開方法

まず、nuxt.config.js の router の base と favicon のパスにリポジトリ名を記述。
※ router の base を編集することにより、localhostのパスが「localhost:3000/リポジトリ名」になります

nuxt.config.js
export default {
  router: {
    base: '/リポジトリ名/'
  },

  head: {
    title: pkg.name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: pkg.description }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/リポジトリ名/favicon.ico' }
    ]
  }

次に、ローカルにgh-pagesブランチを作成し、gitignoreからビルド先フォルダを外します。

# Nuxt generate
# dist

ビルドコマンドを実行します。

npm run generate

dist フォルダが作成されるので、ローカルのgh-pagesブランチにコミットします。(masterブランチにコミットする必要はありません。)
最後に、以下のコマンドを実行し、dist フォルダのみを gh-pages ブランチに切り離してリポジトリに push します。

git subtree push --prefix dist/ origin gh-pages

うまく公開できている場合、GitHub の Setting から公開されたページを確認することができます。
キャプチャ1.PNG
キャプチャ2.PNG

以下の記事を参考にしました。
Nuxt.jsで作ったWebサイトをささっとGithub Pagesに公開する

悩んだこと・大変だったこと

無名関数内で this を利用する

無名関数内で、this を利用する際に、以下のように記述していました。

sample.vue
<script>
export default {
  data: function() {
    return {
      count: 0,
    }
  },
  methods() {
    increment: function() {
      const a = function() {
        this.count++
      }
      a()
    }
  }
}
</script>

この記述では、Vueインスタンスが参照できないため、「Cannot read property 'count' of undefined」というエラーが発生します。

対応方法① this を別の変数に代入しておく

sample.vue
<script>
export default {
  data: function() {
    return {
      count: 0,
    }
  },
  methods() {
    increment: function() {
      const me = this
      const a = function() {
        me.count++
      }
      a()
    }
  }
}
</script>

余分に me という変数が出現するため、個人的にはあまり好きではないと思いました。

対応方法② アロー関数を利用する

sample.vue
<script>
export default {
  data: function() {
    return {
      count: 0,
    }
  },
  methods() {
    increment: function() {
      const a = () => {
        this.count++
      }
      a()
    }
  }
}
</script>

私が実際に採用した方法です。
余分に変数が増えない分、すっきりしました。

モバイルルータで画像が表示されない

流行りのコロナウイルスによりテレワークになった際に、会社から支給されたモバイルルータが少し遅めだったこともあり、画像が表示されないという事件が発生しました。
原因は、画像サイズが大きすぎたことでした。

対応方法① png ファイルを jpeg ファイルに変更

なんとなく png ファイルにそろえていましたが、容量的には jpeg ファイルの方が良いということを初めて知りました。
今後は、透過画像以外は jpeg ファイル、と心に刻みました。

対応方法② 画像サイズを縮小

そもそも、高画質である必要がない画像ばかりだったので、画像サイズを縮小しました。

斜めレイアウトの調整

marginの調整などの際、斜めに移動するため、思い通りにレイアウトできるまでに時間がかかりました。
数学が得意であれば、計算でもう少しスムーズにできたのかもしれないです...

これからやりたいこと

  • Netlifyで正式にリリース
    • それっぽいドメインにできるそうなので、気が向いたら変えたい。
  • Lighthouseを使ってチューニング
    • 画像サイズ以外にも改善できるところを改善したい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[備忘録]vimをgitのエディタのdefault設定に

やったこと

3回くらい調べちゃったので書いておく

$ git config --global core.editor vim

bash -> zsh乗り換え時にdefaultのエディタがnanoになるので、慣れたvimを使いたい時のおまじない。
設定値は~/.gitconfigファイルに設定されています。

[core]
    editor = vim

参考記事

【Git】commitメッセージなど、デフォルトのテキストエディタを変更する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git revertでコンフリクトした記録

エラー

以下のようなエラーが出ました。

revert.sh
$ git revert HEAD~4
error: could not revert 54ea783... Adjustment plugin
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

status.sh
 $git status
On branch feature
You are currently reverting commit 54ea783.
  (fix conflicts and run "git revert --continue")
  (use "git revert --abort" to cancel the revert operation)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   example.php

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add/rm <file>..." as appropriate to mark resolution)

        deleted by them: example2.php
        both modified:   example3.css
        both modified:   example4.css

状況

featureブランチを進めていて
ローカルでadd → commit
リモートへ向けてプッシュ
github上でマスターにマージして
ローカルでrevertしようとしたら前述のエラーが出ました。

最初心当たりがなく、コンフリクトしている状況というのも?という状態でしたが
よくよく確認してみるとテスト用に作成したtestブランチが残っており
(git branch -D testしたのですがなぜか消えていなかった)
featureで編集していたファイルと被っていて
testブランチはマージされていない状態っぽいことがわかりました。

コンフリクト解消

特にphpstorm上でgitの設定をした覚えはないのですが
example.php
example2.php
example3.css
example4.css
この4つのファイルがphpstorm上で赤く表記されており
以下のように変更内容のどちらを残すかという形になっておりました。
phpstormに助けられました。IDE,エディタの設定や選定も重要ですね。

example.php
<<<<<<< HEAD
# featureブランチで行った変更内容
echo "feature";
=======
# testブランチで行った変更内容
echo "test";
>>>>>>> develop

今回は以下のようにfeatureブランチの内容を残して上書き保存しました。

example.php
echo "feature";

参考にさせていただきました

上記ページに従ってともかくコンフリクト解消したものを
add → commit → push

してみるとエラー自体が解消されたので
revertする必要もなくなりました。

原因として予想している事

今回消したはずのtestブランチが残っていて
しかもマージされていない状態があったこと。
featureは独自にコミットを進めてしまっていて
リモートプッシュ、リモートでのプルリクマージまで進んでしまったこと。
これらの状況があってrevertする際に
初めてコンフリクトが起こってしまったのではないかと予想しています。

コンフリクトを再現してみました

以下の画像のように2つのブランチが走っていて
1.png
枝分かれ部分のコミットをrevertしようとしたところ
前述したコンフリクトが再現できました。

againconflict.sh
$git revert bfa14cd3f7a3c525b
error: could not revert bfa14cd... Added test1230
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

ただコンフリクトしているファイルtest.htmlを開いてみても
<<<<<<<<<< HEADなどの表記はありませんでした。

よく確認してみるとこの時点でtest1230.htmlが生成されただけの状態で
featureブランチ、testブランチ間での差異はない状態でした。

add → commitでrevertコミットが生成されました。
2.png

コンフリクト再現2

ファイルの差分がある状態を再現できれば
最初のコンフリクトと同じ状態を作れると予想し以下を実行しました。

conflict.sh
$git revert 8c01b7f7cee4393df596758
error: could not revert 8c01b7f... added text tesst1230.html
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

以下はその時のコミットログです。
3.png

test1230.htmlを開くと

test1230.html
test1230
<<<<<<< HEAD

added from feature branch

added from feature branch2
=======
>>>>>>> parent of 8c01b7f... added text tesst1230.html

となっており

test1230.html
test1230

added from feature branch

added from feature branch2

として保存しadd → commit したら
無事revertコミットが作成されました。
スクリーンショット 2020-04-14 13.46.50.png

教訓

コミットを逆にたどるような場合もコンフリクトが発生する。
いらないブランチはすぐに消しておく方が良さそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CodePipelineが対応していないgitリポからのCI/CD構築

本記事の対象読者

  • AWS CodePipelineに対応していないgitリポから、CodePipelineに乗せたい人
  • AWS Lambdaでgitコマンドを使いたい人

CI/CDしたい

とあるAWSプロジェクトで、ソースコード管理にBacklog gitを利用していました。
どうせならCI/CDを組んでしまおうと考えたのですが、AWS PipelineがBacklog gitに対応しておらず。。。

そこで、Backlogのwebhookを使ってS3にまでソースコードを連携する以下のようなアーキテクチャ構成としました。
アプリ自体はnuxt.jsで作成しており、buildしてS3にアップロード、静的WEBとして公開することがゴールです。
image.png

実装環境

AWS Cloud9

  • Serverless Frameworkを利用したので、node.js環境が必要
  • Docker環境

API Gateway, Lambdaの構築

Serverless Framworkを利用して作成しました。
Cloud9使うならLambdaデプロイできる機能あるじゃん!!
と言われそうですが、ymlファイル等で管理できる&ソースのgit管理が容易、という点で自分はServerless Frameworkいいな!って思いました。

severless.ymlの作成 → Lambdaで使うライブラリの用意 → Lambda関数コード
の順で説明していきます。

serverless.ymlの作成

以下がAPI GatewayとLambdaをデプロイするためのserverless.ymlです。
下記サイトを参考にプロジェクトを作成しました。
[2] Serverless Frameworkの使い方まとめ

serverless.yml
service: backlog-to-s3

# frameworkVersion: "=1.67.0"

plugins:
  - serverless-kms-secrets

provider:
  name: aws
  runtime: python3.8
  stage: dev
  region: ap-northeast-1

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:PutObject"
      Resource: "arn:aws:s3:::{your_bucket_name}/*"
    - Effect: "Allow"
      Action:
        - KMS:Decrypt
      Resource: ${self:custom.kmsSecrets.keyArn}

custom:
  kmsSecrets:
    file: ${file(kms-secrets.${opt:stage, self:provider.stage}.${opt:region, self:provider.region}.yml)}
    keyArn: 'arn:aws:kms:ap-northeast-1:***************:key/{your_key_id}'

functions:
  git_clone:
    handler: handler.git_clone
    memorySize: 128
    timeout: 300
    events:
      - http:
          path: git/push
          method: post
          async: true
          integration: lambda
          request:
            parameters:
              querystrings:
                payload: true
          cors:
            origin: "*"
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
    environment:
        USER_NAME: git_user_name
        BUCKET_NAME: your_bucket_name
        S3_KEY_SUFFIX: your_s3_key/
        PASS: ${self:custom.kmsSecrets.file.secrets.GIT_PASSWORD}


  • plugins
    serverless-kms-secretsを利用して、後述するgitのパスワードを暗号化しました。
    AWS KMSを利用して暗号化・復号化を行っています。

  • provider
    ServerlessFrameworkで使うサービス等を指定します。
    今回はAWS Lambdaに持たせるサービスロールも含めて指定します。
    ここでは、git cloneしたソースコードをS3に配置する権限、KMSを使ってパスワードを復号する権限を付与しています。

  • custom
    KMSでパスワードを暗号化すると、暗号化後の値が記載されたymlファイルが作成されます。
    ここでは、そのymlファイルを指定してserverless.ymlの中で利用できるように設定しています。
    暗号化、復号化は下記サイト参考。
    [3] Serverless FrameworkでAWS KMS

  • functions
    Lambda関数の設定をしていきます。
    ServerlessFrameworkで扱う際の関数名を指定します。(実際に作成されるLambda関数名はやや異なります)
    eventsの項ではLambdaのキックイベントを指定でき、ここではAPI Gatewayの設定を行っています。
    environmentの項では、Lambdaに持たせる環境変数を設定しています。
    ポイントとしては、

    • events: async
      API Gatewayは最大で29秒までしかリスポンスを待ってくれないので、念のため非同期でLambdaをキックするようにしています。
    • events: cors
      Backlog gitのwebhookからAPIが叩かれるため、CORSの設定をしておきます。
    • environment: PASS
      GIT_PASSWORDというキーで暗号化したパスワードを呼び出しています。

Lambdaで使うライブラリの用意

次に、ライブラリを準備します。
gitコマンドを使うために必要なライブラリをそろえるため、下記サイトを参考にDockerイメージからコピーしてきます。
[4] Lambdaでgitコマンドを使う方法

詳しくは上記を参考にしていただきたいですが、自分の場合/usr/share/git-coreが不足していてLambda実行時にエラーが発生したため、build.shに追記をしています。

build.sh
#!/bin/sh

OUTPUT_PATH=${OUTPUT_PATH:-output}

yum install -y git
cp -a /usr/bin/git ${OUTPUT_PATH}
cp -a /usr/libexec/git-core/git-remote-https ${OUTPUT_PATH}
cp -a /usr/libexec/git-core/git-remote-http ${OUTPUT_PATH}
cp -a /usr/share/git-core ${OUTPUT_PATH}

少し怖かったので、一旦別のディレクトリにgitライブラリをコピーしてきたのち、別途ServerlessFrameworkのプロジェクトディレクトリに配置しました。

Lambda関数コード

Lambda関数のコードは以下です。
serverless.ymlのhandlerで指定したメソッドから実行がスタートします。(git_clone)

handler.py
import json
import os
import subprocess
import urllib
import boto3
import base64
import shutil
import datetime
now = datetime.datetime.now()
now = now.strftime("%Y%m%d%H%M%S")


kms = boto3.client('kms')
password = kms.decrypt(
        CiphertextBlob=base64.b64decode(os.getenv('PASS'))
    )['Plaintext'].decode('utf-8')

# Lambda関数は前回実行時のインスタンスを再度使用する場合があり、ディレクトリ名などがかぶることがあるので対策する
tmpdir = "/tmp/" + now
os.makedirs(tmpdir)
target = tmpdir + "/{your_git_repo_name}"

# ユーザー名がメアドだったりすると「@」でバグがでるのでURIエンコードする
username = os.environ['USER_NAME'].replace("@","%40")
source_bucket = os.environ['BUCKET_NAME']

def zip_files():
    print('ソースコードを zip で圧縮します.')
    try:
        shutil.make_archive(target, 'zip', target)
    except Exception as e:
        print(e)

def upload_to_s3(env):
    print('圧縮したソースコードを s3 にアップロードします.')

    zip_file_name = target.split('/')[-1]
    s3_key = env + "-" + os.getenv("S3_KEY_SUFFIX")

    s3 = boto3.client('s3')
    try:
        s3.upload_file(target + '.zip', source_bucket, s3_key + zip_file_name + '.zip')
    except Exception as e:
        print(e)

def git_clone(event, context):
    # Backlogのwebhookからブランチ名を取り出す
    print(event)
    body = event["body"]
    branch = body["content"]["ref"].split("/")[-1]

    # master, developブランチへの変更のみdeployしたかった
    if branch in ["master","develop"]:
        print("deploy start")
        root = os.path.abspath(os.path.join(__file__, ".."))
        print("root : {}".format(root))
        os.environ["HOME"] = "/tmp"
        os.environ["GIT_TEMPLATE_DIR"] = os.path.join(
            root, "git-core/templates"
        )

        repo_url = '{your_clone_url_https}'
        print(repo_url + ' からソースコードを取得します.')

        parsed_url = urllib.parse.urlparse(repo_url)
        src = parsed_url.scheme + '://' + username + ':' + password + '@' + parsed_url.netloc + parsed_url.path

        os.chdir(tmpdir)
        print("git clone start")
        subprocess.call([os.path.join(root, "git"),f"--exec-path={root}","clone","--depth","1","--branch", branch, src])
        print("git clone end")
        os.chdir(target)
        print("branch check")
        subprocess.call([os.path.join(root, "git"),f"--exec-path={root}","branch","-a"])

        zip_files()
        env = "prd" if branch == "master" else "dev"
        upload_to_s3(env)

        # 後処理
        os.chdir(root)
        shutil.rmtree(tmpdir)
    else:
        print("not deploy target brach")

    response = {
        "statusCode": 200,
        'headers':{
            "Access-Control-Allow-Origin":"*"
        }
    }

    return response

developとmasterブランチでそれぞれ別々のS3パスにソースコードをアップロードして、発火させるCodePipelineが別になるようにしています。
CodePipelineを2本作成し、それぞれを検証環境、本番環境へのデプロイに対応させています。

また、前回実行時と同じインスタンスが使用された時の挙動に注意です。
tmpディレクトリ内のファイル名は気を付けていましたが、カレントディレクトリも同じ状態で引き継がれるようです。。。

上記ファイルが準備できたら、Lambda関数をデプロイします。

serverless deploy -v

ここまでで一苦労、、、
でもここからは割と楽だった

CodePipeline, CodeBuildの構築

まずは、CodeBuildで利用するbuildspec.ymlを作成します。
このymlファイルでは、build時のコマンドなどを設定します。
buildに必要なパッケージ等がある場合などにも、ここに記載してインストールします。
すこし雑ですが、今回は必要最低限の以下の内容としました。

buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - cd ./{your_nuxt_project_name}
      - npm install
      - npm run build
    finally:
      - |
        if [ ${ENVIRONMENT} -eq 'prd' ]; then
          aws s3 sync --exact-timestamps --delete ./dist/ s3://{your_bucket_for_prd}/
        else
          aws s3 sync --exact-timestamps --delete ./dist/ s3://{your_bucket_for_dev}/
        fi
      - |

上記のbuildspec.ymlをリポジトリのルートディレクトリに配置しておきます。

次に、CodePipelineをコンソールから作成します。
image.png
image.png
↑ ソースコードを配置するS3パスを指定します。
image.png
↑ ここで、「プロジェクトを作成する」から新しいCodeBuildプロジェクトを作成します。
image.png
image.png
↑ サービスロールにはS3へのPUT権限が必要です。
image.png
CodePipelineに戻ります。
image.png
↑ 環境変数を設定することで、buildspec.yml内で利用できます。
image.png
↑ 今回、build, S3へのデプロイをCodeBuildで行うため、デプロイステージはスキップします。

以上で構築は完了です!
Backlog gitのmaster, developに変更が加わると本番・検証環境用のS3バケットに自動デプロイされます!

まとめ

CodePipelineに対応していないgitリポジトリからもAWSへCI/CDできました!
Circle CI等、外部ツールを使う方法も今後試してみようと思います!

参考

[1] Backlog gitからAWS CodePipeline
[2] Serverless Frameworkの使い方まとめ
[3] Serverless FrameworkでAWS KMS
[4] Lambdaでgitコマンドを使う方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gti cloneでリポジトリの中身だけ取ってくる

普通にgit cloneするとリポジトリ名フォルダが作られる。
中身だけほしいときは[orogin-url]のあとにドットを記述する。

$git clone [origin-url] .
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git 過去の複数コミットのコメントを修正する

これはなに

過去の複数のコミットのコメントを一斉に修正した際のメモ。

考え方

git rebase を使用して、既存のコミットを新規コミットで置き換える際に、コメントを書き換える。

手順

まず、修正するコミットのハッシュを調査する。

$ git log --first-parent <branch-name>

修正したいコミットの、ひとつ前のコミットのハッシュを調べる。

つぎに、git rebase を開始する。

$ git rebase -i <target-hash>

指定した hash の次以後が rebase の対象となる。
標準 editor で以下のような編集画面が開く。

pick d66d1fc3a feat: define placeholder at patients
pick fa93d61e7 feat: define placeholder at groups
pick 4a015ad56 wip: rem placeholder
pick 73c8ae6a4 feat: add place holder

# Rebase d860920d9..73c8ae6a4 onto d860920d9 (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

ここで、修正したいコミットの pickedit に変更し save する。
もし行を消去すると、該当行はコミットから除外されるので注意。

editreword の違いについて(たしかこんな感じだった)
- edit は、コミット毎に amend が実行され、コメントを編集する。
- reword は、対象コミットのすべてのコメントを(同一の内容で)置き換える

そして、edit に修正したコミットの編集を開始する。
git commit --amend を実行する。

$ git commit --amend

コミットを編集し、save する。
つぎのコミットを編集するため、git rebase --continue する

$ git rebase --continue

前述と同様に、git commit --amendを実施する。
これを edit に変更した全コミット分繰り返す。
全コミットに分完了すると、git rebase の完了となる。

$ git rebase --continue
Successfully rebased and updated refs/heads/fix/10541_hoge_fuga.

つぎに、この変更した branch をリモート(origin)に反映する。

$ git push -f origin <branch-name>

-f オプションを付与し、強制的に更新する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む