uc3-sam-sceptre 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bf7d0e753bbcbfc977d046ad6beee0d8837459749b24c4518b4071e653c2b2d0
4
+ data.tar.gz: bf7717c153cc8414bab7e6a79fdf21759358e906525e69823d7a3cbace98f1b3
5
+ SHA512:
6
+ metadata.gz: 11a60179364b3915ada6b464a6fa2fea14bbc1f59d6af4fb4e428b8432b4a0785eb2e739210e0703d59d49c277c4e3fd06a48cca7a2560083e40c14d37c799be
7
+ data.tar.gz: fbca8e612283e589377a04093427851b2e652403aeb92ce07be76e584ff02fad65ad237007826cbce6ba8cb490fadefca3015d4ca1befd7f87a641a14056a7a8
data/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # UC3 SAM Sceptre gem
2
+
3
+ This gem provides functionality that can be used to help Sceptre trigger a SAM build and/or SAM deploy
4
+ to create an image and push it to an ECR repository or build the ZIP archive and put it into an S3 bucket
5
+ and then run the CloudFormation create/update/delete.
6
+
7
+ ## Prerequisites
8
+
9
+ This gem assumes that you have organized your Sceptre config by environment! For example:
10
+ ```
11
+ my_project
12
+ |
13
+ ------ config
14
+ | |
15
+ | ---- dev
16
+ | | |
17
+ | | ----- s3.yaml
18
+ | |
19
+ | ---- prd
20
+ | |
21
+ | ----- s3.yaml
22
+ |
23
+ ------ templates
24
+ |
25
+ ------ s3.yaml
26
+ ```
27
+
28
+ This gem assumes that you have access to run CloudFormation tasks in 2 AWS accounts: `dev` and `prd`
29
+
30
+ If you specify `env: prd` or `env: stg` when initializing the Proxy, your resources will be constucted in the `prd` AWS account otherwise they will be created in the `dev` account.
31
+
32
+ Since SAM is not directly connected to Sceptre and cannot therefore access any of Sceptre's resolvers, you will need to ensure access to any SAM template parameters defined in your template.
33
+
34
+ This gem will retrieve values from either the SSM parameter store or exported CloudFormation stack outputs. The SSM parameters and stack outputs **MUST** live within the same region that you are running your SAM deploy!
35
+
36
+ For example, given the following SAM template:
37
+ ```
38
+ AWSTemplateFormatVersion: '2010-09-09'
39
+ Transform: 'AWS::Serverless-2016-10-31'
40
+
41
+ Parameters:
42
+ VpcId:
43
+ Type: 'AWS::EC2::VPC::Id'
44
+ ```
45
+
46
+ You would need to make the `VpcId` available as an SSM parameter or as an exported output from one of your other Sceptre managed CloudFormation stacks like this:
47
+ ```
48
+ Outputs:
49
+ VpcId:
50
+ Value: !Ref Vpc
51
+ Export:
52
+ Name: VpcId
53
+ ```
54
+
55
+ You should place your SAM template and the Lambda code under the Sceptre project's `templates` directory. For example:
56
+ ```
57
+ my_project
58
+ |
59
+ ------ config
60
+ | |
61
+ | ----- dev
62
+ | |
63
+ | ----- api-gateway.yaml # Your Sceptre config for the API Gateway that
64
+ | # includes a hook to run the SAM build + deploy
65
+ |
66
+ ------ templates
67
+ |
68
+ ------- api-gateway.yaml # Your SAM template that builds the API Gateway
69
+ |
70
+ |
71
+ ------- lambdas
72
+ |
73
+ ----- src
74
+ |
75
+ ------ my_lambda
76
+ | |
77
+ | ------ file(s) # Your Lambda source
78
+ |
79
+ ------- Gemfile # Gemfile that includes this gem
80
+ |
81
+ ------- sam_build_deploy.rb # The Ruby script that initializes and uses this gem
82
+ |
83
+ ------- template.yaml # Your SAM template for the Lambda(s)
84
+ ```
85
+
86
+ ## Usage
87
+
88
+ This gem is intended to be invoked from a Ruby script that you create and store in the same directory as your SAM template. The script can be run manually at any time and via a Sceptre hook.
89
+
90
+ Since CloudFormation requires your Lambda code to exist in an S3 bucket or an ECR when you create your stack, we advise that you at least add this script as a hook to an appropriate Sceptre config. For example as an `after_create` hook on the Sceptre config for the S3 bucket or ECR that will be used to store your Lambda code.
91
+
92
+ ### Build a Ruby script
93
+ Create a Gemfile that is adjacent to your SAM template (see the location of the `Gemfile` file in the directory structure outlined above). This file should look like this:
94
+ ```
95
+ # frozen_string_literal: true
96
+
97
+ source 'https://rubygems.org'
98
+
99
+ gem 'uc3-sam-sceptre'
100
+ ```
101
+
102
+ Create a Ruby script that is adjacent to your SAM template (see the location of the `sam_build_deploy.rb` file in the directory structure outlined above). (See the exmaples below)
103
+
104
+ The following arguments can be used in your Ruby script when initializing the SAM proxy:
105
+ ```
106
+ # REQUIRED arguments:
107
+ # ------------------------
108
+ service: 'dmp' # REQUIRED - The service name
109
+ subservice: 'hub' # REQUIRED - The subservice name
110
+ git_repo: 'https://github.com/MYORG/my-repo' # REQUIRED - The location of your SAM code
111
+ stack_suffix: 'docker-lambdas' # REQUIRED - Used to generate the CloudFormation stack name
112
+ admin_email_key: 'AdminEmail' # REQUIRED - An SSM parameter name or a CF stack export name
113
+
114
+ # Include one of the following:
115
+ ecr_uri_key: 'MyEcrRepoUri # REQUIRED if Lambda is a Docker Image - An SSM parameter name or a CF stack export name
116
+ s3_arn_key: 'S3PrivateBucketArn' # REQUIRED if Lambda is NOT a Docker Image - An SSM parameter name or a CF stack export name
117
+
118
+ # An entry for each of your SAM template parameters.
119
+ # - Fetchable will be looked up in either the SSM paramter store or available CloudFormation stack exports
120
+ # - Static will be used as-is
121
+ #
122
+ fetchable_cf_params: [
123
+ {
124
+ stack_export_name: 'CloudFrontDistroId' # REQUIRED - An SSM parameter name or a CF stack export name
125
+ template_param_name: 'CFDistributionId' # (defaults to :stack_export_name) The name of the parameter your SAM template is looking for
126
+ }
127
+ ],
128
+ static_cf_params: [
129
+ {
130
+ template_param_name: 'Version' # REQUIRED - The name of the parameter your SAM template is looking for
131
+ value: 'v0' # REQUIRED - The value of the parameter
132
+ }
133
+ ]
134
+
135
+ # OPTIONAL arguments:
136
+ # ------------------------
137
+ program: 'uc3' # The group that owns the service (default: 'uc3')
138
+ env: 'stg' # The environment (default: 'dev')
139
+ region: 'us-east-1' # The AWS region (default: 'us-west-2')
140
+ auto: false # Whether or not to run the SAM deploy in `--guided` mode (default: true)
141
+ ```
142
+
143
+ The `:program, :service, :subservice, and :env` are used to:
144
+ - Create the SAM CloudFormation stack name (along with the `:stack_suffix`) For example: `uc3-dmp-hub-dev-foo-bar`
145
+ - Help find the values of parameters in the `:fetchable_cf_params` by limiting the stacks to those that share your Sceptre project's naming conventions. For example: `uc3-dmp-hub-dev`
146
+ - Create resource tags along with the `:git_repo` and the value found for the `:admin_email_key` to tag all resources created by SAM with tags for `Program, Service, Subservice, Environment, CodeRepo and Contact`
147
+
148
+ The entries within the `:fetchable_cf_params` and `:static_cf_params` will all be passed to the SAM deploy as `--parameter-overrides`
149
+
150
+ This gem will automatically append the `:program + :service + :subservice + :env` to the names of the SSM parameter keys you provide (unless you provided the fully qulified key name). So for example if you specify `/uc3/dmp/hub/dev/AdminEmail`, it will be used as-is; if you provide `AdminEmail` then the gem will prepend `/uc3/dmp/hub/dev` to the name.
151
+
152
+ #### Build the bundle
153
+
154
+ You will need to run `bundle install` so that this gem is installed and ready
155
+
156
+ #### Add the Sceptre hook
157
+
158
+ Recommended but optional.
159
+
160
+ Add a hook to an existing Sceptre config that will run this script. Since CloudFormation requires your Lambda code to already exist in an S3 bucket or an ECR when you are creating your SAM stacks. For example here is a hook that will run a SAM build and deploy immediately after an S3 bucket is created:
161
+
162
+ ```
163
+ # config/dev/s3.yaml
164
+ template:
165
+ path: s3.yaml
166
+ type: file
167
+
168
+ parameters:
169
+ LogBucketObjectLifeSpan: '30'
170
+
171
+ hooks:
172
+ after_create:
173
+ # Build the Lambda code and store in the bucket so it is available downstream
174
+ - !cmd './templates/lambdas/src/sam_build_deploy.rb'
175
+ ```
176
+
177
+ ### Examples Ruby scripts:
178
+ The following is an example of deploying to a Lambda that is a Docker image into an ECR.
179
+ ```
180
+ # frozen_string_literal: true
181
+
182
+ require 'uc3-sam-sceptre'
183
+
184
+ params = {
185
+ service: 'dmp',
186
+ subservice: 'hub',
187
+ git_repo: 'https://github.com/CDLUC3/dmp-hub-cfn',
188
+ stack_suffix: 'docker-lambdas',
189
+ admin_email_key: 'AdminEmail',
190
+
191
+ ecr_uri_key: 'EcrRepositoryUri',
192
+
193
+ fetchable_cf_params: [
194
+ { stack_export_name: 'VpcId' },
195
+ { stack_export_name: 'SubnetA' },
196
+ { stack_export_name: 'SubnetB' },
197
+ { stack_export_name: 'SubnetC' },
198
+ { stack_export_name: 'DeadLetterQueueArn' },
199
+ { stack_export_name: 'LambdaSecGroupId' },
200
+ { stack_export_name: 'SnsTopicEmailArn', template_param_name: 'SnsEmailTopicArn' }
201
+ { stack_export_name: 'DomainName' },
202
+ { stack_export_name: 'RdsHost' },
203
+ { stack_export_name: 'RdsPort' },
204
+ { stack_export_name: 'RdsDbName' }
205
+ ],
206
+ static_cf_params: [
207
+ { template_param_name: 'Version', value: 'v0' }
208
+ ]
209
+ }
210
+
211
+ proxy = Uc3SamSceptre::Proxy.new(params)
212
+ proxy.build
213
+ proxy.deploy
214
+ ```
215
+
216
+ The following is an example of deploying a Lambda that is NOT a Docker image into a ZIP archive stored in S3
217
+ ```
218
+ # frozen_string_literal: true
219
+
220
+ require 'uc3-sam-sceptre'
221
+
222
+ params = {
223
+ service: 'dmp',
224
+ subservice: 'hub',
225
+ git_repo: 'https://github.com/CDLUC3/dmp-hub-cfn',
226
+ ecr_uri_key: 'EcrRepositoryUri',
227
+ stack_suffix: 'docker-lambdas',
228
+ admin_email_key: 'AdminEmail',
229
+
230
+ s3_arn_key: 'S3PrivateBucketArn',
231
+
232
+ fetchable_cf_params: [
233
+ { stack_export_name: 'CloudFrontDistroId' },
234
+ { stack_export_name: 'S3PublicBucketArn' },
235
+ { stack_export_name: 'DynamoTableName' },
236
+ { stack_export_name: 'SnsTopicEmailArn', template_param_name: 'SnsEmailTopicArn' }
237
+ { stack_export_name: 'DomainName' }
238
+ ],
239
+ static_cf_params: [
240
+ { template_param_name: 'Version', value: 'v0' }
241
+ ]
242
+ }
243
+
244
+ proxy = Uc3SamSceptre::Proxy.new(params)
245
+ proxy.build
246
+ proxy.deploy
247
+ ```
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uc3SamSceptre
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,145 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ require 'aws-sdk-cloudformation'
5
+ require 'aws-sdk-ssm'
6
+
7
+ module Uc3SamSceptre
8
+ class ProxyError < StandardError; end
9
+
10
+ # Ruby proxy for running SAM dynamically
11
+ class Proxy
12
+ DEFAULT_REGION = 'us-west-2'
13
+ DEFAULT_PROGRAM = 'uc3'
14
+ DEFAULT_ENV = 'dev'
15
+
16
+ MSG_INVALID_PARAMS = 'Invalid initialization params. Expected at least the [:program, :service, :subservice, /
17
+ :env, :git_repo, :stack_suffix, :admin_email_key] and one of the [:ecr_uri_key, :s3_arn_key]
18
+ and at least one of the following arrays: [:fetchable_cf_params, :static_cf_params]. /
19
+ See the README.md for examples.'
20
+
21
+ attr_accessor :region, :program, :service, :subservice, :env, :git_repo, :stack_suffix, :admin_email_key, # Required
22
+ :ecr_uri_key, :s3_arn_key, # Require one of these
23
+ :ssm_args, :cf_args, :static_args, # Require at least one of these
24
+ :account_env, :prefix, :ssm_key_prefix, :ssm_client, :cf_client, :stack_exports
25
+
26
+ # Initialize the SAM proxy
27
+ def initialize(**params)
28
+ @region = params.fetch(:region, DEFAULT_REGION)
29
+ @program = params.fetch(:program, DEFAULT_PROGRAM)
30
+ @service = params[:service]
31
+ @subservice = params[:subservice]
32
+ @env = params.fetch(:env, DEFAULT_ENV)
33
+ @git_repo = params[:git_repo]
34
+ @stack_suffix = params[:stack_suffix]
35
+ @ecr_uri_key = params[:ecr_uri_key]
36
+ @s3_arn_key = params[:s3_arn_key]
37
+ @admin_email_key = params[:admin_email_key]
38
+
39
+ @auto = params.fetch(:auto, false)
40
+
41
+ # Determine which AWS account we are working with, dev or prd (stg and prd are shared)
42
+ @account_env = %w[stg prd].include?(@env) ? 'prd' : 'dev'
43
+ @prefix = "#{@program}-#{@service}-#{@subservice}-#{@env}"
44
+ @ssm_key_prefix = params.fetch(:ssm_key_prefix, "/#{@prefix.gsub('-', '/')}/")
45
+
46
+ @fetchable_cf_params = params.fetch(:fetchable_cf_params, [])
47
+ @static_cf_params = params.fetch(:static_cf_params, [])
48
+
49
+ raise ProxyError, MSG_INVALID_PARAMS if @service.nil? || @subservice.nil? || @git_repo.nil? || @stack_suffix.nil? ||
50
+ (@ecr_uri_key.nil? && @s3_arn_key.nil?) ||
51
+ (@fetchable_cf_params.empty? && @static_cf_params.empty?)
52
+
53
+ @ssm_client = Aws::SSM::Client.new(region: DEFAULT_REGION)
54
+ @cf_client = Aws::CloudFormation::Client.new(region: DEFAULT_REGION)
55
+
56
+ @stack_exports = cf_client.list_exports.exports
57
+ @stack_name = "#{@prefix}-#{@stack_suffix}"
58
+ rescue Aws::Errors::ServiceError => e
59
+ raise ClientError, "Unable to connect to AWS resource. - #{e.message}"
60
+ end
61
+
62
+ # Run a SAM build
63
+ def build
64
+ system 'sam build'
65
+ end
66
+
67
+ # Run a SAM deploy
68
+ def deploy(guided: false)
69
+ args = [
70
+ "--stack name #{@stack_name}",
71
+ "--confirm-changeset #{!@auto}",
72
+ '--capabilities CAPABILITY_NAMED_IAM',
73
+ '--disable-rollback false',
74
+ "--tags #{_sam_tags(admin_email: _locate_value(key: @admin_email_key)}",
75
+ "--parameter-overrides #{_build_deploy_overrides}"
76
+ ]
77
+ # Add the S3 or ECR details depending on what we're working with
78
+ args << "--s3-bucket #{_locate_value(key: @s3_arn_key)&..gsub('arn:aws:s3:::', '')}" unless @s3_arn_key.nil?
79
+ args << "--s3-prefix #{@stack_name}" unles @s3_arn_key.nil?
80
+ args << "--image-repository #{_locate_value(key: @ecr_uri_key)}" unless @ecr_uri_key.nil?
81
+
82
+ args << "--guided" if guided
83
+ system "sam deploy #{args.join(' ')}"
84
+ end
85
+
86
+ private
87
+
88
+ # Construct the AWS tags that SAM will use when building resources
89
+ def _sam_tags(admin_email: nil)
90
+ tags = [
91
+ "Program=#{@program}",
92
+ "Service=#{@service}",
93
+ "Subservice=#{@subservice}",
94
+ "Environment=#{@env}",
95
+ "CodeRepo=#{@git_repo}"
96
+ ]
97
+ tags << "Contact=#{admin_email}" unless admin_email.nil?
98
+ tags
99
+ end
100
+
101
+ # Construct the SAM deploy arguments
102
+ def _build_deploy_overrides
103
+ overrides = [_sam_param(key: 'Env', value: @env)]
104
+
105
+ @fetchable_cf_params.each do |hash|
106
+ val = _locate_value(name: hash[:stack_name])
107
+ msg = "Unable to locate value for #{hash[:stack_export_name]}! Make sure you have exported it from the CF Stack."
108
+ raise ProxyError, msg if val.nil?
109
+
110
+ overrides << { _sam_param(key: hash.fetch(:template_param_name, hash[:stack_export_name]), value: val) }
111
+ end
112
+
113
+ @static_cf_params.each do { |hash| overrides << _sam_param(key: hash[:template_param_name], value: hash[:value]) }
114
+ overrides
115
+ end
116
+
117
+ # Convert the Hash key and value into SAM deploy args
118
+ def _sam_param(key:, value:)
119
+ return '' if key.nil? || value.nil?
120
+
121
+ "ParameterKey=#{key},ParameterValue=#{value}"
122
+ end
123
+
124
+ # Search for the key in the following order: Stack exports, SSM parameters
125
+ def _locate_value(key:)
126
+ val = _fetch_stack_export(name: key)
127
+ return val unless val.nil?
128
+
129
+ _fetch_ssm_parameter(key: key)
130
+ end
131
+
132
+ # Fetches an SSM parameter key and returns the value
133
+ def _fetch_ssm_parameter(key:)
134
+ name = key.start_with?(@ssm_key_prefix) ? key : "#{@ssm_key_prefix}#{key}"
135
+ @ssm_client.get_parameter(name: name, with_decryption: true)&.parameter&.value
136
+ end
137
+
138
+ # Fetch a CloudFormation stack export
139
+ def _fetch_stack_export(name:)
140
+ key = name.to_s.downcase.strip
141
+ @stack_exports.select { |exp| exp[:exporting_stack_id].include?(@prefix) && exp[:name].downcase.strip == key }.first
142
+ end
143
+ end
144
+ end
145
+ # rubocop:enable Naming/FileName
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uc3-sam-sceptre
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Brian Riley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-06-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-s3cloudformation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.78'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.78'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-ssm
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.150'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.150'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 11.1.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 11.1.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.9.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 3.9.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.50.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.50.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.17.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 1.17.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 2.20.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 2.20.0
111
+ description: Allows you to kick off a SAM build/deploy from Sceptre
112
+ email:
113
+ - brian.riley@ucop.edu
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - README.md
119
+ - lib/uc3-sam-sceptre.rb
120
+ - lib/uc3-sam-sceptre/version.rb
121
+ homepage: https://github.com/CDLUC3/dmp-hub-cfn/blob/main/src/sam/gems/uc3-sam-sceptre
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ rubygems_mfa_required: 'false'
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '2.7'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubygems_version: 3.1.6
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Functionality to trigger SAM build and deploy using Sceptre hooks.
145
+ test_files: []