morito

個人的に勉強したことのメモを投稿していく技術ブログ。最近はWebアプリ開発と量子コンピュータの勉強をしてます。

ハードリンクとシンボリックリンク

リンクとはファイルの別名を構成し、
同じファイルにアクセスできるようにする仕組みです。
Linuxでは2種類のリンクファイルが用意されています。

ハードリンク
  • 元のファイルとリンクファイルでiノードとファイルの実態を共有する仕組み
  • 元のファイルが削除されてもアクセスできる
シンボリックリンク
  • 元のファイルのパスを記録しているリンクファイルを作成する仕組み(Windowsのショートカットと同じ)
  • ディレクトリ宛のリンクを作成できる
  • 異なるファイルシステム宛のリンクを作成できる



実際に使ってみる

リンクをつくるにはlnコマンドを使います

lnコマンド書式

ln [option]  リンク元 リンク

Option


lnコマンドでリンクを作る↓
> touch original.txt
> ls
original.txt
> ln original.txt hard.ln
> ln -s original.txt symbol.ln
> ls -li *.ln
63007659 -rw-r--r--  2 [user]  staff   0  3 21 14:12 hard.ln
63007696 lrwxr-xr-x  1 [user]  staff  12  3 21 14:13 symbol.ln@ -> original.txt

シンボリックリンクls -lで見たときファイルの種類がlになってる

リンクの中身を参照してみると、オリジナルと同じになっている

> echo "test" > original.txt 
> cat hard.ln
test
> cat symbol.ln 
test


リンク元であるオリジナルを消してみる
> rm -f original.txt 
> ls -l *.ln
-rw-r--r--  1 [user]  staff   5  3 21 14:28 hard.ln
lrwxr-xr-x  1 [user]  staff  12  3 21 14:13 symbol.ln@ -> original.txt
> cat hard.ln
test
> cat symbol.ln 
cat: symbol.ln: No such file or directory

ハードリンクは既存のファイルとiノードを共有する仕組みなので,
オリジナルファイルが削除されてもリンクファイルはiノードを引き続き参照できる。
リンクファイルも削除されてリンク数が0になったらiノードが解放される。

シンボリックリンクはオリジナルのパスが記録されているだけなので
オリジナルが削除されたらファイルの内容に参照できなくなる。

go build のエラー `go build main.go- build output "main.go" already exists and is not an object file`

goでpackageをbuildしようとしたらタイトルのようなエラーが出た↓

# go build 
go build main.go: build output "main.go" already exists and is not an object file
環境
  • Docker Version 20.10.2
  • docker-compose version 1.27.4, build 40524192
  • go version go1.14 linux/amd64
ディレクトリ構造
.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── keys
│   ├── go.mod
│   └── keys.go
├── main.go
└── tweet.go
解決法

main 以外の名前でbuildすればよかった

# go build -o app

go run main.goしても分割したファイルの関数が読まれない

Go言語初心者です

# go run main.go 
# command-line-arguments
./main.go:5:2: undefined: tweet

main.go のmain関数から
同じpackageのtweet.goに記述した
tweet関数を呼び出そうとしたらエラーが出ました

main.goもtweet.goも同じpackageなので呼び出せるはずなのですが
よくわからなかったので調べました。

ディレクトリ構成はこんな感じ↓

.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── keys
│   ├── go.mod
│   └── keys.go
├── main.go
└── tweet.go

原因

go runはカレントディレクトリ以下の全ファイルを読んでくれるわけではないため

必要なファイルを全てオプションで渡す必要がある

go run *.go or go run main.go tweet.go

もしくは

# go build -o app

TwitterAPI使って自動でTweetするGoのプログラムを作る with Docker

前回の続きです

今回はTwitterAPIを使ってGoで書かれたプログラムからツイートしてみます



環境

  • Docker Version 20.10.2
  • docker-compose version 1.27.4, build 40524192



前準備

Twitterのアカウントを取得して以下の4つを発行します



ソースコード

TwitterAPIを有効にするためOAuth認証を通します

keys.go

package keys

import 
(
    //Twitter APIに必要なimportになります。なければインストールしましょう。   
    "github.com/ChimeraCoder/anaconda"
    "os"
)

func GetTwitterApi() *anaconda.TwitterApi {
    anaconda.SetConsumerKey(os.Getenv("TFC_API_KEY"))
    anaconda.SetConsumerSecret(os.Getenv("TFC_API_SECRET_KEY"))
    api := anaconda.NewTwitterApi(os.Getenv("TFC_ACCESS_TOKEN"), os.Getenv("TFC_ACCESS_SECRET"))
    return api
}

ツイートするための
main.go

package main

import (
    "fmt"
    . "fmt"
    "local.packages/keys" // go modulus環境でローカルパッケージをインポート
)

func main() {
    api := keys.GetTwitterApi()

    tweet_text := fmt.Sprintf("テストツイート")

    tweet, err := api.PostTweet(tweet_text, nil)
    if err != nil {
        panic(err)
    }
    Print(tweet.Text)
 
    fmt.Println("posting!")
}

importでローカルにあるkeysパッケージをもってくるのですがこの書き方がベストプラクティスかどうかは微妙です。

この記事を参考にしています

mainパッケージとkeysパッケージ両方にgo.modを配置してmainのほうのgo.modファイルをこうします↓

module main.go

go 1.14

replace local.packages/keys => ./keys // modules名を相対パスに置き換えている

require (
    // ry
)

なんでこれでうまくいってるのかよくわかってないです

ディレクトリ構成はこうなります↓ keysパッケージのほうのmodファイルには何も書いてません

> tree
.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── keys
│   ├── go.mod
│   └── keys.go
├── main.go



Dockerfileを修正

Dockerfileにアナコンダをとってくる文を追加します

FROM golang:1.14.0-alpine3.11

# ビルド時にashを使う
SHELL ["/bin/ash", "-c"]
WORKDIR /go/src/app
COPY ./ ./

# Go Modulesを使うと明言する
ENV GO111MODULE=on

# apkはalpine linux(超軽量なLinux)の独自のパッケージ管理システム
RUN apk add --no-cache alpine-sdk

# Golang 環境構築
# (追加)TwitterAPIを捌くのにアナコンダを使用します
RUN go get github.com/ChimeraCoder/anaconda 



環境変数受け渡しのためにdocker-compose.ymlを修正

安全にトークンとAPIキーを持ってくるために書き加えます

ホスト(コンテナの外)のシェルに環境変数をセット

私はFishシェラーをなのでset -x環境変数をセットします
TFCは今回のプロジェクトの名前なので気にしないでください

set -x TFC_ACCESS_TOKEN xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
set -x TFC_ACCESS_SECRET xxxxxxxxxxxxxxxxxxxxxxxxxxxx
set -x TFC_API_KEY xxxxxxxxxxxxxxxxxxxxx
set -x TFC_API_SECRET_KEY xxxxxxxxxxxxxxxxxxxxx
docker-composeでコンテナ起動時に環境変数を設定するようにする

こちらの記事を参考にして .ymlファイルを書きました。↓

version: '3' # composeファイルのバーション指定
services:
  app: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化
    volumes:
      - .:/go/src/app # マウントディレクトリ指定
    environment: 
      TFC_API_KEY: ${TFC_API_KEY?err} # composeが起動しているシェルの環境変数を利用
      TFC_API_SECRET_KEY: ${TFC_API_SECRET_KEY?err} # 未設定またはempty値の場合にエラ〜メッセージを表示して終了する
      TFC_ACCESS_TOKEN: ${TFC_ACCESS_TOKEN?err}
      TFC_ACCESS_SECRET: ${TFC_ACCESS_SECRET?err}
コンテナの中に入って環境変数を確認
$ docker-compose up -d
$ docker-compose exec app /bin/ash
$ set

省略

setコマンドで先ほどセットしたKEYやTOKENが表示されたら成功です



ツイートしてみる

コンテナの中に入ってgo runします

> docker-compose exec app /bin/sh
/go/src/app # go run main.go 
テストツイートposting!

自分のツイッターアカウントを見にいってみたら成功してました

ちなみに2連続で同じ内容を呟くと以下のようなエラーが返ってきました

panic: Get https://api.twitter.com/1.1/statuses/update.json returned status 403, {"errors":[{"code":187,"message":"Status is a duplicate."}]}

おつかれさまです。次回はツイートの取得をやります。



参考サイト

案外知られてないdocker-composeの環境変数定義の記法

Go Modules でインターネット上のレポジトリにはないローカルパッケージを import する方法

TwitterはGoでゴー。

Docker + GitでGoの開発を始める

環境

  • Docker Version 20.10.2
  • docker-compose version 1.27.4, build 40524192

Githubリポジトリを作る

f:id:sakanamori:20210309154953p:plain

URLをとってきて任意のディレクトリで

Git clone [URL]

Docker環境作成

Dockerfileを記述
FROM golang:1.14.0-alpine3.11

# ビルド時にashを使う
SHELL ["/bin/ash", "-c"]
WORKDIR /go/src/app
COPY ./ ./
# EXPOSE 8080

# Go Modulesを使うと明言する
ENV GO111MODULE=on

# apkはalpine linux(超軽量なLinux)の独自のパッケージ管理システム
RUN apk add --no-cache alpine-sdk
docker-compose.ymlを記述
version: '3' # composeファイルのバーション指定
services:
  app: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化
    volumes:
      - .:/go/src/app # マウントディレクトリ指定

dockerコンテナの中で作業していく

docker-compose up -d
docker-compose exec app /bin/ash

bashではなくashというSHELLを利用していることに注意。alpineベースだとBashはないらしくて怒られた。

touch main.go
go mod init main

main.goを作り
Go modulesで初期化する
ローカルディレクトリにもファイルが作られていたら成功

main.goにはとりあえずHelloWorldを書く

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
/go/src/app # go run main.go 
Hello, World!

ちゃんと出力されました。

ここまででディレクトリ構成はこんな感じになってます。

.
├── Dockerfile
├── README.md
├── docker-compose.yml
├── go.mod
└── main.go

とりあえずGoのコードを実行するところまで環境構築できました。
次はDocker+Goでアプリ開発していきます。
Go Modules周りとかで詰まりそうで怖い〜

Dockerチュートリアルやってみたメモ

参考にしたQiita記事

qiita.com

環境

MacOS Catalina 10.15.6
Docker for Mac
Docker version 20.10.2
docker-compose version 1.27.4

メモ

Docker run -it の-itとはなにか

docker run -it の「-it」とはなにか - Qiita

-it と入力した場合,
-i or --interactive と -t or --tty 2つのオプションを指定したという意味.
interactive オプションは, 「標準入力」のこと.

データの永続化

Named Volumes
  1. docker volume createコマンドでボリュームを作成。
    2.コンテナを起動するとき、ボリューム接続を指定するのに-vフラグを付け加える

docker volume inspectコマンドで実際の保存場所を確認

$ docker volume inspect todo-db
[
    {
        "CreatedAt": "2021-02-28T11:44:06Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpointがディスク上にデータが保存されている実際の場所

バインドマウント

バインドマウントを使えば、ホスト上の正確なMountpointをコントロールできます。
データの永続化にも使用できますが、追加のデータをコンテナに提供するのによく使われます。
アプリを開発する場合、バインドマウントでソースコードをコンテナに接続して、
コードを変更したり、応答したり、変更をすぐに確認したりできます。

fish: $(...) is not supported.

fish shellはコマンド置換の書き方が異なるので$ははずす

filterを使ってオブジェクトの配列から任意の要素を削除する方法

let array = [ {name:'tarou',id:2},
              {name:'jirou',id:4},
              {name:'hanako',id:5} ];

たとえばこのようなオブジェクトの配列から
IDが5の要素を削除したい場合

let array = [ {name:'tarou',id:2},
              {name:'jirou',id:4},
              {name:'hanako',id:5} ];
console.log(`before: array = ${JSON.stringify(array)}`);
// before: array = [{"name":"tarou","id":2},{"name":"jirou","id":4},{"name":"hanako","id":5}]


array = array.filter( obj => obj.id!==5)
console.log(`after: array = ${JSON.stringify(array)}`)
// after: array = [{"name":"tarou","id":2},{"name":"jirou","id":4}]

filterを使って特定の要素だけ抽出することができる