つい先日、Terraform でずっと気になっていたAmazon API Gatewayのselection_pattern
のpull request がmergeされました。
今まではAPI GWをInfrastructure As Codeで構築するにあたって複数のintegration responseパターンを返却できないのがネックだったのが、これでようやく解決。途中までTerraformで作って、その後に以下のようにawscliで追加するというちょっと煩わしい手順でした。
1
2
3
REST_ID=$(aws apigateway get-rest-apis --query 'items[?name==`my_api`].id' --output text)
RESOURCE_ID=$(aws apigateway get-resources --rest-api-id $REST_ID --query 'items[?path==`/my_path`].id' --output text)
aws apigateway put-integration-response --rest-api-id $REST_ID --resource-id $RESOURCE_ID --http-method GET --status-code 400 --response-templates '{"application/json": "$input.path('$').errorMessage"}' --selection-pattern "[^0-9](.|\n)*"
というわけで、早速実験。お題は以前紹介した Elastic Beanstalk ssh用のAPI GWで。
Terraform version
まずは、master
にmergeされた開発版Terraformのビルド。やり方はこちら 。
1
2
$ terraform version
Terraform v0.6.16-dev - 5cd27c2
Terraform file
API GWのterraform化はこんな感じで。
(*) permissionはlambda作成後に許可
Terraform plan/apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
$ terraform plan
Refreshing Terraform state prior to plan...
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.
Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
+ aws_api_gateway_deployment.eb_deployment
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
stage_name: "" => "prod"
+ aws_api_gateway_integration.ip_get
http_method: "" => "GET"
integration_http_method: "" => "POST"
request_templates.#: "" => "1"
request_templates.application/json: "" => "{ \"env_name\": \"$input.params('env_name')\" }"
resource_id: "" => "${aws_api_gateway_resource.ip.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
type: "" => "AWS"
uri: "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"
+ aws_api_gateway_integration.server_num_get
http_method: "" => "GET"
integration_http_method: "" => "POST"
request_templates.#: "" => "1"
request_templates.application/json: "" => "{\n \"env_name\": \"$input.params('env_name')\",\n \"server_num\": \"$input.params('server_num')\" \n}"
resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
type: "" => "AWS"
uri: "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"
+ aws_api_gateway_integration_response.ip_get_200
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.ip.id}"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$')"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
status_code: "" => "200"
+ aws_api_gateway_integration_response.ip_get_400
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.ip.id}"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$').errorMessage"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
selection_pattern: "" => "[^0-9](.|\n)*"
status_code: "" => "400"
+ aws_api_gateway_integration_response.server_num_get_200
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$')"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
status_code: "" => "200"
+ aws_api_gateway_integration_response.server_num_get_400
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$').errorMessage"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
selection_pattern: "" => "[^0-9](.|\n)*"
status_code: "" => "400"
+ aws_api_gateway_method.ip_get
api_key_required: "" => "0"
authorization: "" => "NONE"
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.ip.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
+ aws_api_gateway_method.server_num_get
api_key_required: "" => "0"
authorization: "" => "NONE"
http_method: "" => "GET"
api_key_required: "" => "0"
authorization: "" => "NONE"
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
+ aws_api_gateway_method_response.ip_200
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.ip.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
status_code: "" => "200"
+ aws_api_gateway_method_response.ip_400
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.ip.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
status_code: "" => "400"
+ aws_api_gateway_method_response.server_num_200
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
status_code: "" => "200"
+ aws_api_gateway_method_response.server_num_400
http_method: "" => "GET"
resource_id: "" => "${aws_api_gateway_resource.server_num.id}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
status_code: "" => "400"
+ aws_api_gateway_resource.eb
parent_id: "" => "${aws_api_gateway_rest_api.EB.root_resource_id}"
path: "" => "<computed>"
path_part: "" => "eb"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
+ aws_api_gateway_resource.env_name
parent_id: "" => "${aws_api_gateway_resource.eb.id}"
path: "" => "<computed>"
path_part: "" => "{env_name}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
+ aws_api_gateway_resource.ip
parent_id: "" => "${aws_api_gateway_resource.env_name.id}"
path: "" => "<computed>"
path_part: "" => "ip"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
+ aws_api_gateway_resource.server_num
parent_id: "" => "${aws_api_gateway_resource.ip.id}"
path: "" => "<computed>"
path_part: "" => "{server_num}"
rest_api_id: "" => "${aws_api_gateway_rest_api.EB.id}"
+ aws_api_gateway_rest_api.EB
description: "" => "get EB info"
name: "" => "EB"
root_resource_id: "" => "<computed>"
Plan: 18 to add, 0 to change, 0 to destroy.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
$ terraform apply
aws_api_gateway_rest_api.EB: Creating...
description: "" => "get EB info"
name: "" => "EB"
root_resource_id: "" => "<computed>"
aws_api_gateway_rest_api.EB: Creation complete
aws_api_gateway_resource.eb: Creating...
parent_id: "" => "k9x3d7qlhd"
path: "" => "<computed>"
path_part: "" => "eb"
rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_resource.eb: Creation complete
aws_api_gateway_resource.env_name: Creating...
parent_id: "" => "nr2lkm"
path: "" => "<computed>"
path_part: "" => "{env_name}"
rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_resource.env_name: Creation complete
aws_api_gateway_resource.ip: Creating...
parent_id: "" => "g29h7n"
path: "" => "<computed>"
path_part: "" => "ip"
rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_resource.ip: Creation complete
aws_api_gateway_resource.server_num: Creating...
parent_id: "" => "sthj28"
path: "" => "<computed>"
path_part: "" => "{server_num}"
rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_method.ip_get: Creating...
api_key_required: "" => "0"
authorization: "" => "NONE"
http_method: "" => "GET"
resource_id: "" => "sthj28"
rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_method.ip_get: Creation complete
aws_api_gateway_method_response.ip_200: Creating...
http_method: "" => "GET"
resource_id: "" => "sthj28"
rest_api_id: "" => "mdsyn3w42a"
status_code: "" => "200"
aws_api_gateway_integration.ip_get: Creating...
http_method: "" => "GET"
integration_http_method: "" => "POST"
request_templates.#: "" => "1"
request_templates.application/json: "" => "{ \"env_name\": \"$input.params('env_name')\" }"
resource_id: "" => "sthj28"
rest_api_id: "" => "mdsyn3w42a"
type: "" => "AWS"
uri: "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"
aws_api_gateway_resource.server_num: Creation complete
aws_api_gateway_method.server_num_get: Creating...
api_key_required: "" => "0"
authorization: "" => "NONE"
http_method: "" => "GET"
resource_id: "" => "9w68fs"
rest_api_id: "" => "mdsyn3w42a"
aws_api_gateway_method_response.ip_200: Creation complete
aws_api_gateway_method_response.ip_400: Creating...
http_method: "" => "GET"
resource_id: "" => "sthj28"
rest_api_id: "" => "mdsyn3w42a"
status_code: "" => "400"
aws_api_gateway_integration_response.ip_get_200: Creating...
http_method: "" => "GET"
resource_id: "" => "sthj28"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$')"
rest_api_id: "" => "mdsyn3w42a"
status_code: "" => "200"
aws_api_gateway_integration.ip_get: Creation complete
aws_api_gateway_method.server_num_get: Creation complete
aws_api_gateway_method_response.server_num_200: Creating...
http_method: "" => "GET"
resource_id: "" => "9w68fs"
rest_api_id: "" => "mdsyn3w42a"
status_code: "" => "200"
aws_api_gateway_integration.server_num_get: Creating...
http_method: "" => "GET"
integration_http_method: "" => "POST"
request_templates.#: "" => "1"
request_templates.application/json: "" => "{\n \"env_name\": \"$input.params('env_name')\",\n \"server_num\": \"$input.params('server_num')\" \n}"
resource_id: "" => "9w68fs"
rest_api_id: "" => "mdsyn3w42a"
type: "" => "AWS"
uri: "" => "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:eb_ip/invocations"
aws_api_gateway_integration_response.ip_get_200: Creation complete
aws_api_gateway_method_response.ip_400: Creation complete
aws_api_gateway_integration_response.ip_get_400: Creating...
http_method: "" => "GET"
resource_id: "" => "sthj28"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$').errorMessage"
rest_api_id: "" => "mdsyn3w42a"
selection_pattern: "" => "[^0-9](.|\n)*"
status_code: "" => "400"
aws_api_gateway_integration.server_num_get: Creation complete
aws_api_gateway_method_response.server_num_200: Creation complete
aws_api_gateway_method_response.server_num_400: Creating...
http_method: "" => "GET"
resource_id: "" => "9w68fs"
rest_api_id: "" => "mdsyn3w42a"
status_code: "" => "400"
aws_api_gateway_method_response.server_num_400: Creation complete
aws_api_gateway_integration_response.ip_get_400: Creation complete
aws_api_gateway_integration_response.server_num_get_200: Creating...
http_method: "" => "GET"
resource_id: "" => "9w68fs"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$')"
rest_api_id: "" => "mdsyn3w42a"
status_code: "" => "200"
aws_api_gateway_integration_response.server_num_get_200: Creation complete
aws_api_gateway_integration_response.server_num_get_400: Creating...
http_method: "" => "GET"
resource_id: "" => "9w68fs"
response_templates.#: "" => "1"
response_templates.application/json: "" => "$input.path('$').errorMessage"
rest_api_id: "" => "mdsyn3w42a"
selection_pattern: "" => "[^0-9](.|\n)*"
status_code: "" => "400"
aws_api_gateway_integration_response.server_num_get_400: Creation complete
aws_api_gateway_deployment.eb_deployment: Creating...
rest_api_id: "" => "mdsyn3w42a"
stage_name: "" => "prod"
aws_api_gateway_deployment.eb_deployment: Creation complete
Apply complete! Resources: 18 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
できた!
考察
リソースを作成するのに並列処理が出来なかったり、依存関係がまだうまく対応できてないので、depends_on
を駆使する必要があるのが若干まだ面倒。ない場合は、BadRequestException: Unable to complete operation due to concurrent modification. Please try again later
やBadRequestException: No integration defined for method status code: 400
等のエラーが発生する。
Integration/Method Response Headers
CORS等の設定するする際にはAccess-Control-Allow-Origin
等のヘッダーをMethodやIntegrationのResponse Headerに設定をする必要があるけど、.
の扱い問題で未対応(#2143 )。それまではawscliで以下のようにすると事で回避。
1
aws apigateway update-integration-response --rest-api-id $rest_id --resource-id $appo_resource_id --http-method OPTIONS --status-code 200 --patch-operations op=add,path="/responseParameters/method.response.header.Access-Control-Allow-Headers",value="\"'Content-Type,X-Amz-Date,Authorization,X-Api-Key, Access-Control-Allow-Origin, x-amz-security-token'\""
Issueはこの前上げたので(#6092 )、ウォッチしておくと良い。
API Gatewayはresource
, method
, integration
, method response
、integration response
等を記述しないといけないので、どうしてもコードが多くになってしまう事からSwaggerでやった方が楽だったりするかも。ただ、その場合はInfrastructure as YAML になってしまうけど。。また、YAMLは整形してからimportする必要があったりするので、その辺は諸々トレードオフかなぁ。