ServerlessFrameworkでdeployを成功させるまでに嵌ったことまとめ

環境

OS: linux
Node Version: 8.12.0
Serverless Version: 1.32.0
Python Version: 3.6

本題

$ sls deploy -v を実行時に発生したエラーで、個人的に解決まで時間がかかったものを書く

The security token included in the request is invalid.

  Serverless Error ---------------------------------------
 
  ServerlessError: The security token included in the request is invalid.
  • ~/.aws/credentials に記載の aws_access_key_idaws_secret_access_key の記述が間違ってた

vim で手動で書いたためミスした

sls config credentials --provider aws --key XXXKEYXXX --secret XXXSECRETXXX とコマンドから発行すれば解決した

User is not authorized to perform: cloudformation:DescribeStacks on resource: arn:aws:cloudformation

  Serverless Error ---------------------------------------
 
  ServerlessError: User: arn:aws:iam::XXXXXXXXXX:user/(user名) is not authorized to perform: cloudformation:DescribeStacks on resource: (省略)

同じエラーメッセージに出会った人はいるらしい。

github.com

cloudformation:CreateStack
cloudformation:DescribeStacks
cloudformation:DescribeStackEvents
cloudformation:DescribeStackResources

cloudformationに関しては、けっこう怒られるが、 上記の4つの権限を付与したら解消した。

⇒ このあと、下の③が発生し、これを解決したら

cloudfortmation:ValidateTemplate
cloudfortmation:UpdateStack

の権限がないことで怒られた。

Missing required key 'Bucket' in params

  Serverless Error ---------------------------------------
 
  Missing required key 'Bucket' in params

いい感じに解説してくれている記事は見つからなかったが、

AWSコンソールから「CloudFormation」でスタックを削除したら、問題が解消された

このとき「状況」は「ROLLBACK_FAILED」みたいな赤字表記になっており、Rollbackが途中で止まったっぽい様子だった。

※表記は正しく覚えてない

webpack4でビルドしたときにエラーになる

■前提

webpack初心者

■本題

qiita.com

もともとはこの記事にしたがってVue.jsを勉強してて、

qiita.com

この記事にしたがって、Sassをビルドしようとした

するとこのコマンドでビルドしようとしたらエラーになった

$ webpack --watch --progress
tkfric:Sample tkfric$ webpack --watch --progress
Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration[0].module has an unknown property 'loaders'. These properties are valid:
   object { exprContextCritical?, exprContextRecursive?, exprContextRegExp?, exprContextRequest?, noParse?, rules?, defaultRules?, unknownContextCritical?, unknownContextRecursive?, unknownContextRegExp?, unknownContextRequest?, unsafeCache?, wrappedContextCritical?, wrappedContextRecursive?, wrappedContextRegExp?, strictExportPresence?, strictThisContextOnImports? }
   -> Options affecting the normal modules (`NormalModuleFactory`).

qiita.com

ググったらあった。

webpack.config.jsのloadersプロパティがunknownなので、これをrulesに変えればいいらしい。

tkfric:Sample tkfric$ webpack --watch --progress
  0% [0] compiling
webpack is watching the files…

(node:5770) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
 77% [0] module and chunk tree optimization unnamed compat plugin/(Sampleディレクトリパス)/node_modules/webpack/lib/Chunk.js:824
        throw new Error(
        ^

Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead
    at Chunk.get (/(Sampleディレクトリパス)/node_modules/webpack/lib/Chunk.js:824:9)
    at /(Sampleディレクトリパス)/node_modules/extract-text-webpack-plugin/dist/index.js:176:48
    at Array.forEach (<anonymous>)
    at /(Sampleディレクトリパス)/node_modules/extract-text-webpack-plugin/dist/index.js:171:18
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/(Sampleディレクトリパス)/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:12:1)
    at AsyncSeriesHook.lazyCompileHook (/Users/tkfric/WORKS/MYDEV/VuePractice/node_modules/tapable/lib/Hook.js:154:20)
    at Compilation.seal (/(Sampleディレクトリパス)/node_modules/webpack/lib/Compilation.js:1214:27)
    at hooks.make.callAsync.err (/(Sampleディレクトリパス)/node_modules/webpack/lib/Compiler.js:547:17)
    at _err0 (eval at create (/(Sampleディレクトリパス)/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:11:1)
    at _addModuleChain (/(Sampleディレクトリパス)/node_modules/webpack/lib/Compilation.js:1065:12)
    at processModuleDependencies.err (/(Sampleディレクトリパス)/node_modules/webpack/lib/Compilation.js:981:9)
    at process._tickCallback (internal/process/next_tick.js:61:11)

それでもエラーになる

ptna.hateblo.jp

$ npm i -D extract-text-webpack-plugin@next

とりあえずこのブログの通りに実行してみた

tkfric:Sample tkfric$ webpack --watch --progress
  0% [0] compiling
webpack is watching the files…

[0] Hash: a7e5ea6c26ddff43982f
Version: webpack 4.19.1
Child
    Hash: a7e5ea6c26ddff43982f
    Time: 227ms
    Built at: 2018-09-19 02:18:27
     2 assets
    Entrypoint style = style.css style.css.map
    [0] ./style.scss 1.13 KiB [built] [failed] [1 error]
    [1] ./style.scss 200 bytes [built] [failed] [1 error]
        + 2 hidden modules

    WARNING in configuration
    The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
    You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

    ERROR in ./style.scss
    Module build failed (from ../node_modules/extract-text-webpack-plugin/dist/loader.js):
    ModuleParseError: Module parse failed: Unexpected token (1:5)
    You may need an appropriate loader to handle this file type.
    > body { font-size: 14px;}
    | p { margin: 0.5em;}
    | @keyframes fade-in {
        at handleParseError (/(Sampleディレクトリパス)/node_modules/webpack/lib/NormalModule.js:432:19)
        at doBuild.err (/(Sampleディレクトリパス)/node_modules/webpack/lib/NormalModule.js:466:5)
        at runLoaders (/(Sampleディレクトリパス)/node_modules/webpack/lib/NormalModule.js:327:12)
        at /(Sampleディレクトリパス)/node_modules/loader-runner/lib/LoaderRunner.js:370:3
        at iterateNormalLoaders (/(Sampleディレクトリパス)/node_modules/loader-runner/lib/LoaderRunner.js:211:10)
        at /(Sampleディレクトリパス)/node_modules/loader-runner/lib/LoaderRunner.js:202:4
        at process.nextTick (/(Sampleディレクトリパス)/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:73:15)
        at process._tickCallback (internal/process/next_tick.js:61:11)
     @ ./style.scss

    ERROR in ./style.scss 1:5
    Module parse failed: Unexpected token (1:5)
    You may need an appropriate loader to handle this file type.
    > body { font-size: 14px;}
    | p { margin: 0.5em;}
    | @keyframes fade-in {
     @ ./style.scss 2:14-39
    Child extract-text-webpack-plugin ../node_modules/extract-text-webpack-plugin/dist ../node_modules/style-loader/index.js!style.scss:
        Entrypoint undefined = extract-text-webpack-plugin-output-filename
        [0] ../node_modules/style-loader!./style.scss 967 bytes {0} [built]
        [1] ./style.scss 200 bytes {0} [built] [failed] [1 error]
            + 2 hidden modules

        ERROR in ./style.scss 1:5
        Module parse failed: Unexpected token (1:5)
        You may need an appropriate loader to handle this file type.
        > body { font-size: 14px;}
        | p { margin: 0.5em;}
        | @keyframes fade-in {
         @ ./style.scss (../node_modules/style-loader!./style.scss) 2:14-39

まだ何か出る

WARNING in configuration
    The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
    You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

これはこんな記述をすれば解決するらしい。

"scripts": {
 ...
    "dev": "webpack --mode development",
    "build": "webpack --mode production",
    "production": "webpack --mode production"

でもまだ下のようなwarningが残る

    ERROR in ./style.scss
    Module build failed (from ../node_modules/extract-text-webpack-plugin/dist/loader.js):
    ModuleParseError: Module parse failed: Unexpected token (1:5)
    You may need an appropriate loader to handle this file type.

qiita.com

調べたらこの記事が出てきたのでひとまず参考に以下の記述をwebpack.config.jsに追記

    module: {
        rules: [
            ....
            { test: /\.css$/, loader: "style!css" },
            { test: /\.(png|jpg|jpeg|gif|woff)$/, loader: 'url?limit=8192' },
            { test: /\.(otf|eot|ttf)$/, loader: "file?prefix=font/" },
            { test: /\.svg$/, loader: "file" }
        ]
    },

まだエラーが残る

ERROR in Entry module not found: Error: Can't resolve 'css/style.scss' in '/(Sampleディレクトリパス)/css'

top-men.hatenablog.com

この記事を読めば最初からうまくいったのかもしれない。

いったん * cssディレクトリをsrcに変更 * context entryの記述を削除 * src下に空のindex.jsを作成 これで無事に解決した(っぽい)

tkfric:Sample tkfric$ webpack --watch --progress --mode production
  0% [0] compiling
webpack is watching the files…

[0] Hash: aa5b75cccf66cd9b1ffa
Version: webpack 4.19.1
Child
    Hash: aa5b75cccf66cd9b1ffa
    Time: 119ms
    Built at: 2018-09-19 03:18:25
           Asset      Size  Chunks             Chunk Names
        main.css  3.61 KiB       0  [emitted]  main
    main.css.map  3.51 KiB       0  [emitted]  main
    Entrypoint main = main.css main.css.map
    [0] ./src/index.js 0 bytes {0} [built]

でも結論としてはまだわかってない

Laravelでバリデーションルールにorやandがほしかった話

経緯と概要

携わってるプロジェクトで、メールアドレスのバリデーションを変えるということになった。

既存会員 : オリジナルの正規表現

新規会員 : Laravelのemailルール(RFC準拠)

となったのだが、

f:id:tkfric:20180824170604p:plain

結果としてこの形で全員救う必要性が出てきた。

でも、正規表現も当てて、Laravelのemailも当てて…という都合の良いものはいくら調べても出てこなくて、 結果自力でどうにかするかと思い立ち実装した次第。

実装した内容

前提として

  • Laravel5.3
  • PHP7.0

バリデーションルールを複数指定し(Laravel内のもの、カスタム問わず)、

  • or : どちらかが通る
  • and : どちらも通る

としてカスタムバリデーションルールを作った。

まず、カスタムバリデーションを記述するファイルとして app\Providers\ValidationServiceProvider.php を作成

処理の概要としては、指定されたバリデーションルールで1つずつバリデータを通すだけの簡単なもの。

/**
 * 複数のバリデーションルールでorチェックする
 */
Validator::extend('or', function ($attribute, $value, $parameters, $validator) {
    foreach ($parameters as $key => $val) {
        $validator = Validator::make([$attribute => $value], [$attribute => $val]);
        if ($validator->fails()) {
            continue;
        }
        return true;
    }
    return false;
});

/**
 * 複数のバリデーションルールでandチェックする
 */
Validator::extend('and', function ($attribute, $value, $parameters, $validator) {
    foreach ($parameters as $key => $val) {
        $validator = Validator::make([$attribute => $value], [$attribute => $val]);
        if ($validator->fails()) {
            return false;
        }
    }
    return true;
});

最後に

こんなことする必要あったのかなぁ

Python3でリクエストパラメータ付のPOST通信をしたときに、リクエストパラメータが抜け落ちる

tkfric.hatenablog.com

この記事でやったことの続き

def http_post(url, headers, body):
    req = urllib.request.Request(url, json.dumps(body).encode(), headers, 'POST')
    try:
        with urllib.request.urlopen(req) as res:
            body = res.read().decode()

でbodyの中身が

{
    'id' : 1
}

という形だったのだが、

POST先のAPIから「idは必須です」というメッセージが返ってきた。

かなりはまって、かなり検索しまくって、

結果的にどの記事を見たかは覚えてないが、

(たしかStackoverflowだった)

以下のように書き直したら動いた

def http_post(url, headers, body):
    data = urllib.parse.urlencode(body).encode('utf-8')
    request = urllib.request.Request(url, data, headers)
    
    try:
        with urllib.request.urlopen(url=url, data=data) as response:
            body = response.read().decode("utf-8")

リクエストパラメータの処理の仕方を json.dumps(body).encode() から urllib.parse.urlencode(body).encode('utf-8') に書き換えた。

これでタイトルの問題は解決したのだが、非常に謎。

can't concat bytes to str: TypeErrorを解決する

コードは以下

def http_post(url, headers, body):
    req = urllib.request.Request(url, body, headers, 'POST')
    try:
        with urllib.request.urlopen(req) as res:
            body = res.read().decode()

でてきたエラーは以下

can't concat bytes to str: TypeError
Traceback (most recent call last):
File "/var/task/sample.py", line xx, in handler
http_post(url, headers, body)
File "/var/task/sample.py", line xx, in http_post
with urllib.request.urlopen(req) as res:
File "/var/lang/lib/python3.6/urllib/request.py", line 223, in urlopen
return opener.open(url, data, timeout)
File "/var/lang/lib/python3.6/urllib/request.py", line 526, in open
response = self._open(req, data)
File "/var/lang/lib/python3.6/urllib/request.py", line 544, in _open
'_open', req)
File "/var/lang/lib/python3.6/urllib/request.py", line 504, in _call_chain
result = func(*args)
File "/var/lang/lib/python3.6/urllib/request.py", line 1361, in https_open
context=self._context, check_hostname=self._check_hostname)
File "/var/lang/lib/python3.6/urllib/request.py", line 1318, in do_open
encode_chunked=req.has_header('Transfer-encoding'))
File "/var/lang/lib/python3.6/http/client.py", line 1239, in request
self._send_request(method, url, body, headers, encode_chunked)
File "/var/lang/lib/python3.6/http/client.py", line 1285, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "/var/lang/lib/python3.6/http/client.py", line 1234, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "/var/lang/lib/python3.6/http/client.py", line 1064, in _send_output
+ b'\r\n'
TypeError: can't concat bytes to str

参考にした記事

stackoverflow.com

分かったこと

・byte型とstring型は結合できないよ、というエラー

じゃあどうしたらいいの?

Stackoverflowの記事を参考にこう直してみた。

def http_post(url, headers, body):
    req = urllib.request.Request(url, json.dumps(body).encode(), headers, 'POST')
    try:
        with urllib.request.urlopen(req) as res:
            body = res.read().decode()

動いたっぽい

LambdaでHTTPリクエストしたときに遭遇したやつ

Lambdaから外部のAPIを叩いてたときに遭遇した

<urllib.request.Request object at 0x7faa06fe2278>

サンプルコードは以下

def http_post(url, headers, body):
    method = 'POST'
    data = urllib.parse.urlencode(body).encode()

    req = urllib.request.Request(url, data, headers, method)
    try:
        with urllib.request.urlopen(req) as res:
            body = res.read().decode()
    except urllib.error.HTTPError as err:
        logger.info(err.code)
    except urllib.error.URLError as err:
        logger.info(err.reason)

とりあえずそのままググったら、1件目にstackoverflowで引っかかった。 でも2013年6月で少し不安 stackoverflow.com

とりあえず回答にあるようにreq.__dist__をログに出してみた

{
    '_full_url': 'APIのURL',
    'fragment': None,
    'type': 'https',
    'host': 'APIのドメイン',
    'selector': 'APIのURI',
    'headers': {},
    'unredirected_hdrs': {},
    '_data': b'key1=val1&key2=val2&…’,
    '_tunnel_host': None,
    'origin_req_host': 'POST',
    'unverifiable': False
}

一応dir(req)もログに出してみた (※見やすいように適度に改行を入れている)

これでreqで使用できるキーが出るとのこと。

[
'__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_data', '_full_url',
'_parse', '_tunnel_host', 'add_header', 'add_unredirected_header',
'data', 'fragment', 'full_url', 'get_full_url',
'get_header', 'get_method', 'has_header',
'has_proxy', 'header_items', 'headers', 'host',
'origin_req_host', 'remove_header', 'selector', 'set_proxy',
'type', 'unredirected_hdrs', 'unverifiable'
]

結果よくわからず数日経って、

minus9d.hatenablog.com

これ読んだら解決した。

PHPでいうvar_dump()みたいなのがほしいとずっと思ってた。

Pythonを初めて数日の僕にはありがたい記事でした。

awkの使い方を毎回調べてしまうから自戒の念を込める

タイトル通り

アプリケーションログやアクセスログから調査をするときに 気付けばいつも「awk 使い方」でググってしまう。

いつもやってること

$ cat アクセスログ | awk -F'[ ]' '{print $1,$2,$3}' | less

-F のあとに[] を使ってdelimiterの指定 上の例では半角スペース

そのあとに$1 のように区切った中から、{print}と一緒に抽出したい列数を指定

余談

あと他によくやってるのが、

$ cat アクセスログ | awk -F'[ ]' '{print URI}' | sort | uniq -c | sort -nr | less
  • URIで抽出して
  • 重複削除しつつ、カウントして
  • 件数の降順でlessする

これも備忘録的に。