月: 2019年12月

  • AWS lambda(Python 3.8)でimagemagickを使う

    AWS lambda(Python 3.8)でimagemagickを使う

    tl;dr;

    AWS lambda(Python 3.8)で画像処理ツールのimagemagickを動かした。

    install方法からlambda内でimagemagickの動かす際のコードまでを紹介。

    imagemagickとは

    imagemagick は動的に画像を編集する古典的なツール。こちらの記事によると、なんと1987年から開発されている。wikipediaを確認する限り、Photoshopのリリースが1990年だから、その古さに驚かされる。

    しかしこのツール、度重なる脆弱性は報告されているものの、画像編集の機能としてまだまだ現役として使っているプロジェクトも存在する。

    今回はそのimagemagickをlambdaで。Python 3.8のランタイムから使う方法を紹介する。

    Serverless.Pub

    ありがたい事にServerlessを支援し積極的に情報発信をしてくれている団体がいる。

    彼らが作ったlambdaの`binally layer`をCloudFormationでdeployする機能も提供してくれている。

    https://serverless.pub/lambda-utility-layers/

    GitHubはこちら

    https://github.com/serverlesspub/imagemagick-aws-lambda-2

    こちらを`git clone`して、`make all` すればimagemagickのlambdaの`binally layer`ができる。

    その方法を使ってもよいのだが、
    実は以下のページからDeployボタンを押せば、それだけで自動でCloudFormationが自分のAWSで動き、lambdaの中に`binally layer`が作成されることもできる。

    https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:145266761615:applications~image-magick-lambda-layer

    しかし、注意すべきはここの部分。

    要約すると、nodejs10.xランタイムのようなAmazon Linux 2で使えるよ。とのこと。PythonのPの字も見当たらない。
    対象外かと思い当初諦めていた。

    そんな中、最近、`Python 3.8`ランタイムがlambdaで使えるようになった事を知り、公式ドキュメントを読んでみると、今までのPythonで使っていたOSとは異なり、`Amazon linux2`がベースとなっていることがわかった。

    node.jsに絞っているのはAmazonLinux2がベースのように見受けられたため、これはlambdaの中でPythonでもimagemagickを動かせるかもと思い今回試す事にした。

    やってみた

    https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:145266761615:applications~image-magick-lambda-layer

    を開きDeployボタンを押す。
    しかし、この時点でいきなりDeployされるわけではない。

    DeployはCloudFormationによって行われる。CloudFormationのスタック名確定させたら(デフォルトでもよい)デプロイを押下。

    しばらくすると、AWS LambdaのLayersに新たに`image-magick`が登録される。

    Lambda関数から、Layersを選択する

    レイヤーの追加ボタンを押し、imagemagickを選択する。

    レイヤーに表示されない場合は、以下のようにARNを直接入力する。

    これで下準備は完了

    AWS lambda(Python 3.8)でimagemagickを使う

     ここからがPythonからimagemagickを使えるかの確認。

    今回はシンプルにversionを表示させることで使える事にしたい。

    コードはこちら。

    import json
    import subprocess
    
    def lambda_handler(event, context):
        subprocess.check_call(['convert', '-version'])
      
        return {
            'body': json.dumps('Hello from Lambda!')
        }
    

    subprocessを使って、Pythonから、lambdaが動いているOS側のコマンドを実行する。

    imagemagickをinstallすると使えるようになる`convert`コマンドを叩く。

     

    結果は以下の通り

    無事imagemagickのversionを確認できた。

    最後に

    私が実施した際はレイヤーの追加ボタンを押しても選択肢としてimagemagickが表示されなかった。

    これは、CloudFormationによって作られるimagemagickのレイヤーがnode.jsに限定しているため。不便なのでpull reqを出してmergeしてもらった。

    https://github.com/serverlesspub/imagemagick-aws-lambda-2/pull/18/files

    今はレイヤー選択画面から互換性のあるレイヤーとして選択できるはずだ。

     

  • Cloud functionsを使ってCloud Storageに画像がuploadされるとサムネイルを作成する

    Cloud functionsを使ってCloud Storageに画像がuploadされるとサムネイルを作成する

    tl;dr;

    Cloud Storageにuploadされると、イベントフックでCloud functionsが実行されサムネイル作成処理が走る処理を作った。

    事前準備

    gcloudコマンドのインストール。既に設定済ならスキップを。

    https://cloud.google.com/storage/docs/gsutil_install?hl=ja

    バケットの作成。今回は変換対象、変換後の格納先を作成。

    export YOUR_INPUT_BUCKET_NAME=tsukada-input
    gsutil mb gs://$YOUR_INPUT_BUCKET_NAME
    
    export YOUR_OUTPUT_BUCKET_NAME=tsukada-output
    gsutil mb gs://$YOUR_OUTPUT_BUCKET_NAME

    deploy

    sample productをダウンロード。deploy。

    mkdir project
    cd project
    git clone https://github.com/GitSumito/cloudfunctions-imagemagick-on-gcp
    cd cloudfunctions-imagemagick-on-gcp
    
    # deploy
    gcloud functions deploy ImageConvert --runtime go111 --trigger-bucket $YOUR_INPUT_BUCKET_NAME --set-env-vars THUMBNAILED=$YOUR_OUTPUT_BUCKET_NAME

    `gcloud functions deploy`の後ろは、実行する関数名を入力する。

    また、引数として `(ctx context.Context, e GCSEvent)` を受け付ける必要があるので注意。

    Cloud Functionsのコマンドでは予め`–trigger-bucket`というオプションが用意されていて、任意のバケットを指定すれば簡単にイベント処理を紐付けることができる。便利。

    deployコマンドを実行すると

    ` Deploying function (may take a while – up to 2 minutes)…⠼     `

     と表示され、しばらく待つ。

    result

    左がアップロードしたオリジナルの画像。

    右側がEventを検知して、Cloud Functionsが実行され、サムネイル作成された画像。

    使ってみると非常に簡単にイベント駆動処理を作ることができた。使い方次第では活躍しそう。

     

  • Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

    Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

    概要

    useradd コマンドで作成したユーザでログインできない。

    Permission denied (publickey,gssapi-keyex,gssapi-with-mic).  というエラーが出た際の対処。

    はじめに

    以下の手順でアカウントを作成した。

    useradd sumito.tsukada

    鍵の作成

    ssh-keygen -t rsa

    作成された秘密鍵を、接続元サーバにコピー

    ~/.ssh/id_rsa

    接続元で権限は 400 とした。

    いざsshしてみようとしたら

    Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

    が発生。

    原因

    接続先のサーバに `.ssh/authorized_keys` が存在していない。

    対処

    接続先のサーバで公開鍵をコピーし、権限を644に設定する。

    cp .ssh/id_rsa.pub .ssh/authorized_keys
    chmod 644 .ssh/authorized_keys

    参考情報

    http://dqn.sakusakutto.jp/2012/01/sshpermission_denied_publickey.html

  • Macで録ったmovファイルをgifに変換する

    Macで録ったmovファイルをgifに変換する

    tl;dr

    Macで撮ったmovファイルをgifに変換する方法を紹介。

    手軽に動画のスクリーンショットを撮れるようになっていた

    気がついたらショートカット1発で動画が録れるようになっていた。Mojave から `command + shift + 5 ` で動画のスクリーンショットを録ることができる。

    しかし、デフォルトではmovファイルとして出力される。mov画像は容量も大きくなるし、軽くて使い勝手の良いgitにしたいことがある。 

    `ffmpeg`を使う。

    まだインストールしていなければインストールする。brewコマンドで一発。

    brew install ffmpeg

     

    変換コマンド

    ffmpeg -i [変換元movファイル] -r 24 [変換後gifファイル]

    例)

    ffmpeg -i ~/Desktop/画面収録\ 2019-12-22\ 23.47.33.mov -r 24 chrome.gif

    アウトプット(gifファイル)

    以下の通り。非常に軽量なのでサイトに載せることも容易になった。

     今日からgif多めのブログになりそうだ。

  • MacのターミナルからChromeを開いてサイトにアクセスする

    MacのターミナルからChromeを開いてサイトにアクセスする

    tl;dr;

    ターミナルからGoogle chromeを起動する方法を紹介。起動シェルに登録すれば1行で任意のページを開くことができる。

    起動方法

    実は、macのopenコマンドの後ろに` -a` オプションを付ければ任意のアプリケーションで起動することができる。

    もっと使いやすくする

    起動シェルにaliasを登録する事で、もっと短くすることができる。

    `vi ~/.zshrc`

    以下の行を追加

    alias chrome="open -a 'Google Chrome'"

    その後、sourceコマンドで`~/.zshrc`を再読み込みさせる。

    source ~/.zshrc

    これで、`chrome https://tsukada.sumito.jp` と入力すると、上記と同じ結果になる。

    他によく使うツールを登録する。

    例えばmacに標準搭載のPreviewなど登録すると便利。

    alias preview="open -a 'Preview'"

    `source` コマンドで読み込ませればよい。

  • ERROR: boto3 1.9.226 has requirement botocore=1.12.226, but you’ll have botocore 1.13.37 which is incompatible.

    ERROR: boto3 1.9.226 has requirement botocore<1.13.0,>=1.12.226, but you’ll have botocore 1.13.37 which is incompatible.

    TL;DR

    awsコマンドを実施中に遭遇。versionが求められているものと違うようだ。

    Encountered while executing aws command. The version seems different from what is required.

    ERROR: boto3 1.9.226 has requirement botocore<1.13.0,>=1.12.226, but you'll have botocore 1.13.37 which is incompatible.
    

    対処 deal

    awscliを最新にする

    Update awscli

    pip3 install awscli --upgrade --user

     

    その後、botocore、boto3をuninstall

    Then uninstall botocore, boto3

    pip3 uninstall botocore
    
    pip3 uninstall boto3

     

    再度botocore、boto3をinstall

    Install botocore and boto3 again

    pip3 install botocore
    
    pip3 install boto3

     

    以上。

    That’s it.

    参考情報 FYI

    https://stackoverflow.com/questions/51911075/how-to-check-awscli-and-compatible-botocore-package-is-installed

     

     

  • laravelのImplicit Bindingについて調べた

    laravelのImplicit Bindingについて調べた

    はじめに

    Laravelでは、決められた変数名が、ルートセグメント名と一致するルートと、コントローラーアクションで定義されたEloquentモデルを自動的に紐付けるようだ。
    便利なんだろうけど、少し難しいので整理する。

    具体的な動き

    Route::get('api/users/{user}', function (App\User $user) {
        return $user->email;
    });

    この場合、$user変数は“` App\User “` モデルとしてタイプされ、変数名は“` {user}“` URLセグメントと一致するため、LarvelはリクエストURIの対応する値と、一致するIDをもつモデルインスタンスを自動的に挿入する。

    一致するモデルインスタンスが見つからない場合は、404 status codeを返却する。

    サンプルコード

    database

    router

    <?php
    
    Route::get('/', 'PostsController@index');
    
    // 通常の書き方
    //Route::get('/posts/{id}','PostsController@show');
    
    // Implicit Bindingを使った書き方
    Route::get('/posts/{post}', 'PostsController@show');

    Controller

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Post;
    
    class PostsController extends Controller
    {
        public function index(){
            $posts = Post::all();
            return view('posts.index')->with('senddata', $posts);
        }
    
        // 通常の書き方
        // public function show($id){
        //     $posts = Post::findOrFail($id);
        //     return view('posts.show')->with('posts', $posts);
        // }\
    
        // Implicit Bindingを使った書き方
        public function show(Post $post){
            return view('posts.show')->with('posts', $post);
        }
    }
    

    blade file

    <!DOCUMENT html>
    
        <html lang="ja">
            <head>
                <meta charset="utf-8">
                <title>this is title</title>
                <link rel="stylesheet" href="/css/style.css">
            </head>
    
            <body>
                <div class='container'>
    
                    <h1> {{ $posts->title}} </h1>
                    <p>{!!  nl2br(e($posts->body)) !!} </p> 
    
                </dev>
            </body>
    
        </html>
    
    <!DOCUMENT html>
    
        <html lang="ja">
            <head>
                <meta charset="utf-8">
                <title>this is title</title>
                <link rel="stylesheet" href="/css/style.css">
            </head>
    
            <body>
                <div class='container'>
                    <h1> this is it!</h1>
                    <ul>
                        @forelse ($senddata as $data)
                        <!--通常の書き方
                        <li> <a href="{{ action('PostsController@show', $data->id) }} "> {{ $data->title }} </a></li>
                        --> 
    
                        <!--Implicit Bindingを使った書き方--> 
                        <li> <a href="{{ action('PostsController@show', $data) }} "> {{ $data->title }} </a></li>
                        @empty
                        empty!
                        @endforelse
                    </ul>
    
                </dev>
            </body>
    
        </html>

    結果

    参考情報

    https://laravel.com/docs/5.5/routing#implicit-binding

     

  • laravelのview(blade)でcssを読み込ませる

    laravelのview(blade)でcssを読み込ませる

    はじめに

    laravelのviewにcssを読み込ませる方法をまとめた

    ベースはこちらの記事。

    https://tsukada.sumito.jp/2019/12/11/laravel%e3%81%a7db%e3%81%ae%e5%8f%96%e5%be%97%e7%b5%90%e6%9e%9c%e3%82%92view%e3%81%ab%e5%87%ba%e3%81%99/

     

    bladeファイルの変更点

    <!DOCUMENT html>
    
        <html lang="ja">
            <head>
                <meta charset="utf-8">
                <title>this is title</title>
                <link rel="stylesheet" href="/css/style.css">
            </head>
    
            <body>
                <div class='container'>
                    <h1> this is it!</h1>
                    <ul>
                        @forelse ($senddata as $data)
                        <li>{{ $data->title }}</li>
                        @empty
                        empty!
                        @endforelse
                    </ul>
    
                </dev>
            </body>
    
        </html>
    

     

    cssファイル

    publicディレクトリがデフォルトで読み込まれる。

    “` public/css/style.css “` ファイルを新規で作りそのcssファイルを読ませる形にした。

    body{
        font-family: Verdana, Geneva, Tahoma, sans-serif;
        font-size: 20px;
    }
    
    .container {
        width: 300px;
        margin: 30ps;
    }
    
    h1 {
        font-size: 20px;
    }
    
    ul > li{
        margin-bottom: 10px;
    }

    結果

    無事cssが読み込まれたようだ

     

    laravel の実践向け書籍

  • firebase deploy時のeslintを無効にする

    firebase deploy時のeslintを無効にする

    はじめに

    firebase functionを使えるようにした際、知らず知らずにESLintを有効にしてしまっていた。今回は対処した際の方法を紹介。

    問題

    firebase deploy
    
    === Deploying to 'auth-xxx'...
    
    i  deploying database, functions, hosting
    Running command: npm --prefix "$RESOURCE_DIR" run lint
    
    > functions@ lint /Users/coco/Documents/firebase-auth/functions
    > eslint .
    
    
    /Users/coco/Documents/firebase-auth/functions/index.js
      31:7   error    Expected return with your callback function                     callback-return
      38:24  warning  Use path.join() or path.resolve() instead of + to create paths  no-path-concat
    
    ✖ 2 problems (1 error, 1 warning)
    
    npm ERR! code ELIFECYCLE
    npm ERR! errno 1
    npm ERR! functions@ lint: `eslint .`
    npm ERR! Exit status 1
    npm ERR! 
    npm ERR! Failed at the functions@ lint script.
    npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
    
    npm ERR! A complete log of this run can be found in:
    npm ERR!     /Users/coco/.npm/_logs/2019-12-10T15_36_22_429Z-debug.log
    
    Error: functions predeploy error: Command terminated with non-zero exit code1
    darkenagy:firebase-auth coco$ cat /Users/coco/.npm/_logs/2019-12-10T15_36_22_429Z-de

    ESLintで引っかかっているようだ。

    ふりかえり

    そもそも、本当にESLintを有効にしたんだっけ。。
    どのようにfirebase functionを有効にしたか振り返る。

    firebase init functions
    
         ######## #### ########  ######## ########     ###     ######  ########
         ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
         ######    ##  ########  ######   ########  #########  ######  ######
         ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
         ##       #### ##     ## ######## ########  ##     ##  ######  ########
    
    You're about to initialize a Firebase project in this directory:
    
      /Users/coco/Documents/firebase-auth
    
    Before we get started, keep in mind:
    
      * You are initializing in an existing Firebase project directory
    
    
    === Project Setup
    
    First, let's associate this project directory with a Firebase project.
    You can create multiple project aliases by running firebase use --add, 
    but for now we'll just set up a default project.
    
    i  .firebaserc already has a default project, using auth-xxx.
    
    === Functions Setup
    
    A functions directory will be created in your project with a Node.js
    package pre-configured. Functions can be deployed with firebase deploy.
    
    ? What language would you like to use to write Cloud Functions? JavaScript
    ? Do you want to use ESLint to catch probable bugs and enforce style? Yes
    ✔  Wrote functions/package.json
    ✔  Wrote functions/.eslintrc.json
    ✔  Wrote functions/index.js
    ✔  Wrote functions/.gitignore
    ? Do you want to install dependencies with npm now? Yes
    
    > protobufjs@6.8.8 postinstall /Users/coco/Documents/firebase-auth/functions/node_modules/protobufjs
    > node scripts/postinstall
    
    npm notice created a lockfile as package-lock.json. You should commit this file.
    added 344 packages from 245 contributors and audited 869 packages in 11.579s
    found 0 vulnerabilities
    
    
    i  Writing configuration info to firebase.json...
    i  Writing project information to .firebaserc...
    
    ✔  Firebase initialization complete!
    
    
       ╭───────────────────────────────────────────╮
       │                                           │
       │      Update available 7.8.1 → 7.9.0       │
       │   Run npm i -g firebase-tools to update   │
       │                                           │
       ╰───────────────────────────────────────────╯
    

    しっかりESLintを有効にしてた。

    現在の設定を確認

    firebase.jsonを確認する

    cat firebase.json 
    {
      "database": {
        "rules": "database.rules.json"
      },
      "hosting": {
        "public": "public",
        "rewrites": [
          {
            "source": "**",
            "function": "firebaseAuth"
          }
        ],
        "ignore": [
          "firebase.json",
          "**/.*",
          "**/node_modules/**"
        ]
      },
      "functions": {
        "predeploy": [
          "npm --prefix \"$RESOURCE_DIR\" run lint"
        ]
      }
    }

    黄色の箇所を削除し、再度“` firebase deploy“`を行うと、ESlintが行われずdeployされる。

    参考情報

  • laravelでDBの取得結果をviewに出す

    laravelでDBの取得結果をviewに出す

    はじめに

    viewへのデータの受け渡しについて、router,Controller,viewに分けて整理する。

    データベースの中身

    router

    <?php
    
    Route::get('/', 'PostsController@index');

    Controller

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Post;
    
    class PostsController extends Controller
    {
        //
        public function index()
        {
            $posts = Post::all();
    
            // 意図的に空にする
            //$posts = [];
    
            // 両方同じ意味
           // return view('posts.index', ['posts'=> $posts]);
           return view('posts.index')->with('senddata', $posts);
    
        }
    }
    

    a

     view

    <!DOCUMENT html>
    
        <html lang="ja">
            <head>
                <meta charset="utf-8">
                <title>this is title</title>
            </head>
    
            <body>
                <div class='container'>
                    <h1> this is it!</h1>
                    <ul>
                        @forelse ($senddata as $data)
                        <li>{{ $data->title }}</li>
                        @empty
                        empty!
                        @endforelse
                    </ul>
    
                </dev>
            </body>
    
        </html>
    

    結果

    laravel の実践向け書籍