stack_master 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -0
  3. data/features/apply.feature +1 -1
  4. data/features/apply_with_s3.feature +106 -0
  5. data/features/step_definitions/stack_steps.rb +5 -0
  6. data/features/support/env.rb +1 -0
  7. data/lib/stack_master.rb +78 -58
  8. data/lib/stack_master/aws_driver/s3.rb +66 -0
  9. data/lib/stack_master/cli.rb +1 -0
  10. data/lib/stack_master/commands/apply.rb +52 -5
  11. data/lib/stack_master/commands/init.rb +2 -0
  12. data/lib/stack_master/commands/list_stacks.rb +2 -0
  13. data/lib/stack_master/commands/outputs.rb +2 -0
  14. data/lib/stack_master/commands/status.rb +3 -0
  15. data/lib/stack_master/parameter_resolver.rb +1 -1
  16. data/lib/stack_master/parameter_resolvers/secret.rb +2 -0
  17. data/lib/stack_master/stack.rb +20 -13
  18. data/lib/stack_master/stack_definition.rb +63 -13
  19. data/lib/stack_master/stack_differ.rb +2 -0
  20. data/lib/stack_master/stack_events/fetcher.rb +1 -1
  21. data/lib/stack_master/stack_events/presenter.rb +1 -1
  22. data/lib/stack_master/stack_events/streamer.rb +1 -1
  23. data/lib/stack_master/template_compiler.rb +3 -1
  24. data/lib/stack_master/template_compilers/cfndsl.rb +4 -2
  25. data/lib/stack_master/template_compilers/json.rb +4 -0
  26. data/lib/stack_master/template_compilers/sparkle_formation.rb +5 -0
  27. data/lib/stack_master/template_compilers/yaml.rb +4 -0
  28. data/lib/stack_master/test_driver/cloud_formation.rb +50 -36
  29. data/lib/stack_master/test_driver/s3.rb +34 -0
  30. data/lib/stack_master/testing.rb +1 -0
  31. data/lib/stack_master/utils.rb +19 -0
  32. data/lib/stack_master/version.rb +1 -1
  33. data/spec/fixtures/stack_master.yml +4 -1
  34. data/spec/stack_master/aws_driver/s3_spec.rb +130 -0
  35. data/spec/stack_master/commands/apply_spec.rb +26 -0
  36. data/spec/stack_master/config_spec.rb +8 -1
  37. data/spec/stack_master/parameter_resolver_spec.rb +5 -0
  38. data/spec/stack_master/sparkle_formation/user_data_file_spec.rb +2 -0
  39. data/spec/stack_master/stack_events/presenter_spec.rb +1 -1
  40. data/spec/stack_master/stack_spec.rb +23 -5
  41. data/spec/stack_master/template_compiler_spec.rb +1 -0
  42. data/spec/stack_master/template_compilers/cfndsl_spec.rb +2 -0
  43. data/spec/stack_master/test_driver/s3_spec.rb +17 -0
  44. data/stack_master.gemspec +0 -1
  45. metadata +10 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 462256c45f5be50e9afe763ac0bd7dc8a466ce98
4
- data.tar.gz: d2feec3d48f34397eef2e26223c15e9745fd5bcb
3
+ metadata.gz: 24f9543391ced08e41349c5cd75aaecacce6aab2
4
+ data.tar.gz: 0130dc01c3bfacea757155caf6896aef4daaebaf
5
5
  SHA512:
6
- metadata.gz: c78d5094529357d8e17581d46092a7b8f5ff155cf8ffb2081be549fb081c331bf96d97f35e6cb6f6de208fd6edb79baba2f2766b459667a87acf05de0c08525b
7
- data.tar.gz: ddc6dae60758049e6b01a538254e29e14c048d5c82fc63ef3afa59c897f1e7575531c124db1e2fc702aae803ead82a7e3b28d85aabd14f1ca7af0e94cbfa1f41
6
+ metadata.gz: 95bf919587f2d04956c97ef82caa0f9d79747b2272e1c97075549950a5d5606dbf084cfba5ef65a4e61935a4d3c11fd13c06183f02e9305f55e8f98353ded7be
7
+ data.tar.gz: d5523800109d4f3ac1a5a04a4f2f3246ad51d2014b76234c2b35c54f18e39b24a8b7fe4e5b9696682d1b299f8bf5b969961b0d49355c54381d2cc247e7cde26d
data/README.md CHANGED
@@ -95,6 +95,28 @@ stacks:
95
95
  purpose: vpc
96
96
  ```
97
97
 
98
+ ## S3
99
+
100
+ StackMaster can optionally use S3 to store the templates before creating a stack.
101
+ This requires to configure an S3 bucket in stack_master.yml:
102
+
103
+ ```yaml
104
+ stack_defaults:
105
+ s3:
106
+ bucket: my_bucket_name
107
+ prefix: cfn_templates/my-awesome-app
108
+ region: us-west-2
109
+ ```
110
+
111
+ Additional files can be configured to be uploaded to S3 alongside the templates:
112
+ ```yaml
113
+ stacks:
114
+ production:
115
+ myapp-vpc:
116
+ template: myapp_vpc.rb
117
+ files:
118
+ - userdata.sh
119
+ ```
98
120
  ## Directories
99
121
 
100
122
  - `templates` - CloudFormation, SparkleFormation or CfnDsl templates.
@@ -90,7 +90,7 @@ Feature: Apply command
90
90
  | Parameters diff: |
91
91
  | KeyName: my-key |
92
92
  | aborted |
93
- And the output should not match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
93
+ And the output should not match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
94
94
  Then the exit status should be 0
95
95
 
96
96
  Scenario: Run apply with region only and create 2 stacks
@@ -0,0 +1,106 @@
1
+ Feature: Apply command
2
+
3
+ Background:
4
+ Given a file named "stack_master.yml" with:
5
+ """
6
+ stack_defaults:
7
+ s3:
8
+ bucket: my-bucket
9
+ region: us-east-1
10
+ prefix: cfn_templates/my-app
11
+ stacks:
12
+ us_east_1:
13
+ myapp_vpc:
14
+ template: myapp_vpc.rb
15
+ files:
16
+ - user_data.sh
17
+ """
18
+ And a directory named "parameters"
19
+ And a file named "parameters/myapp_vpc.yml" with:
20
+ """
21
+ KeyName: my-key
22
+ """
23
+ And a file named "parameters/myapp_web.yml" with:
24
+ """
25
+ VpcId: vpc-blah
26
+ """
27
+ And a directory named "templates"
28
+ And a file named "files/user_data.sh" with:
29
+ """
30
+ #!/bin/bash
31
+ echo "HI"
32
+ """
33
+ And a file named "templates/myapp_vpc.rb" with:
34
+ """
35
+ SparkleFormation.new(:myapp_vpc) do
36
+ description "Test template"
37
+ set!('AWSTemplateFormatVersion', '2010-09-09')
38
+
39
+ parameters.key_name do
40
+ description 'Key name'
41
+ type 'String'
42
+ end
43
+
44
+ resources.vpc do
45
+ type 'AWS::EC2::VPC'
46
+ properties do
47
+ cidr_block '10.200.0.0/16'
48
+ end
49
+ end
50
+
51
+ outputs do
52
+ vpc_id do
53
+ description 'A VPC ID'
54
+ value ref!(:vpc)
55
+ end
56
+ end
57
+ end
58
+ """
59
+
60
+ Scenario: Run apply and create a new stack
61
+ Given I stub the following stack events:
62
+ | stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
63
+ | 1 | 1 | myapp-vpc | TestSg | CREATE_COMPLETE | AWS::EC2::SecurityGroup | 2020-10-29 00:00:00 |
64
+ | 1 | 1 | myapp-vpc | myapp-vpc | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
65
+ When I run `stack_master apply us-east-1 myapp-vpc --trace`
66
+ And the output should contain all of these lines:
67
+ | Stack diff: |
68
+ | + "Vpc": { |
69
+ | Parameters diff: |
70
+ | KeyName: my-key |
71
+ And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
72
+ And an S3 file in bucket "my-bucket" with key "cfn_templates/my-app/myapp_vpc.json" exists with content:
73
+ """
74
+ {
75
+ "Description": "Test template",
76
+ "AWSTemplateFormatVersion": "2010-09-09",
77
+ "Parameters": {
78
+ "KeyName": {
79
+ "Description": "Key name",
80
+ "Type": "String"
81
+ }
82
+ },
83
+ "Resources": {
84
+ "Vpc": {
85
+ "Type": "AWS::EC2::VPC",
86
+ "Properties": {
87
+ "CidrBlock": "10.200.0.0/16"
88
+ }
89
+ }
90
+ },
91
+ "Outputs": {
92
+ "VpcId": {
93
+ "Description": "A VPC ID",
94
+ "Value": {
95
+ "Ref": "Vpc"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ """
101
+ And an S3 file in bucket "my-bucket" with key "cfn_templates/my-app/user_data.sh" exists with content:
102
+ """
103
+ #!/bin/bash
104
+ echo "HI"
105
+ """
106
+ Then the exit status should be 0
@@ -64,3 +64,8 @@ end
64
64
  Given(/^I stub CloudFormation validate calls to fail validation with message "([^"]*)"$/) do |message|
65
65
  allow(StackMaster.cloud_formation_driver).to receive(:validate_template).and_raise(Aws::CloudFormation::Errors::ValidationError.new('', message))
66
66
  end
67
+
68
+ When(/^an S3 file in bucket "([^"]*)" with key "([^"]*)" exists with content:$/) do |bucket, key, body|
69
+ file = StackMaster.s3_driver.find_file(bucket: bucket, object_key: key)
70
+ expect(file).to eq body
71
+ end
@@ -12,4 +12,5 @@ end
12
12
 
13
13
  Before do
14
14
  StackMaster.cloud_formation_driver.reset
15
+ StackMaster.s3_driver.reset
15
16
  end
data/lib/stack_master.rb CHANGED
@@ -1,69 +1,82 @@
1
1
  require "commander"
2
2
  require "yaml"
3
- require "virtus"
4
3
  require "aws-sdk"
5
- require "diffy"
6
4
  require "colorize"
7
- require "table_print"
8
5
  require 'active_support/core_ext/string'
9
- require "erb"
10
- require 'sparkle_formation'
11
- require 'dotgpg'
12
- require 'ruby-progressbar'
13
-
14
- require "stack_master/ctrl_c"
15
- require "stack_master/sparkle_formation/user_data_file"
16
- require "stack_master/command"
17
- require "stack_master/version"
18
- require "stack_master/stack"
19
- require "stack_master/prompter"
20
- require "stack_master/aws_driver/cloud_formation"
21
- require "stack_master/test_driver/cloud_formation"
22
- require "stack_master/stack_events/fetcher"
23
- require "stack_master/stack_events/presenter"
24
- require "stack_master/stack_events/streamer"
25
- require "stack_master/stack_states"
26
- require "stack_master/stack_status"
27
- require "stack_master/sns_topic_finder"
28
- require "stack_master/security_group_finder"
29
- require "stack_master/parameter_loader"
30
- require "stack_master/parameter_resolver"
31
- require "stack_master/resolver_array"
32
- require "stack_master/parameter_resolvers/ami_finder"
33
- require "stack_master/parameter_resolvers/stack_output"
34
- require "stack_master/parameter_resolvers/secret"
35
- require "stack_master/parameter_resolvers/sns_topic_name"
36
- require "stack_master/parameter_resolvers/security_group"
37
- require "stack_master/parameter_resolvers/latest_ami_by_tags"
38
- require "stack_master/parameter_resolvers/latest_ami"
39
- require "stack_master/utils"
40
- require "stack_master/config"
41
- require "stack_master/paged_response_accumulator"
42
- require "stack_master/stack_definition"
43
- require "stack_master/template_compiler"
44
- require "stack_master/template_compilers/sparkle_formation"
45
- require "stack_master/template_compilers/json"
46
- require "stack_master/template_compilers/yaml"
47
- require "stack_master/template_compilers/cfndsl"
48
- require "stack_master/commands/terminal_helper"
49
- require "stack_master/commands/apply"
50
- require "stack_master/change_set"
51
- require "stack_master/commands/events"
52
- require "stack_master/commands/outputs"
53
- require "stack_master/commands/init"
54
- require "stack_master/commands/diff"
55
- require "stack_master/commands/list_stacks"
56
- require "stack_master/commands/validate"
57
- require "stack_master/commands/resources"
58
- require "stack_master/commands/delete"
59
- require "stack_master/commands/status"
60
- require "stack_master/stack_differ"
61
- require "stack_master/validator"
62
- require "stack_master/cli"
63
6
 
64
7
  module StackMaster
65
8
  extend self
66
9
 
10
+ autoload :Initializable, 'stack_master/utils/initializable'
11
+ autoload :ChangeSet, 'stack_master/change_set'
12
+ autoload :CLI, 'stack_master/cli'
13
+ autoload :CtrlC, 'stack_master/ctrl_c'
14
+ autoload :Command, 'stack_master/command'
15
+ autoload :Version, 'stack_master/version'
16
+ autoload :Stack, 'stack_master/stack'
17
+ autoload :Prompter, 'stack_master/prompter'
18
+ autoload :StackStates, 'stack_master/stack_states'
19
+ autoload :StackStatus, 'stack_master/stack_status'
20
+ autoload :SnsTopicFinder, 'stack_master/sns_topic_finder'
21
+ autoload :SecurityGroupFinder, 'stack_master/security_group_finder'
22
+ autoload :ParameterLoader, 'stack_master/parameter_loader'
23
+ autoload :ParameterResolver, 'stack_master/parameter_resolver'
24
+ autoload :ResolverArray, 'stack_master/resolver_array'
25
+ autoload :Resolver, 'stack_master/resolver_array'
26
+ autoload :Utils, 'stack_master/utils'
27
+ autoload :Config, 'stack_master/config'
28
+ autoload :PagedResponseAccumulator, 'stack_master/paged_response_accumulator'
29
+ autoload :StackDefinition, 'stack_master/stack_definition'
30
+ autoload :TemplateCompiler, 'stack_master/template_compiler'
31
+
32
+ autoload :StackDiffer, 'stack_master/stack_differ'
33
+ autoload :Validator, 'stack_master/validator'
34
+
35
+ require 'stack_master/template_compilers/sparkle_formation'
36
+ require 'stack_master/template_compilers/json'
37
+ require 'stack_master/template_compilers/yaml'
38
+ require 'stack_master/template_compilers/cfndsl'
39
+
40
+ module Commands
41
+ autoload :TerminalHelper, 'stack_master/commands/terminal_helper'
42
+ autoload :Apply, 'stack_master/commands/apply'
43
+ autoload :Events, 'stack_master/commands/events'
44
+ autoload :Outputs, 'stack_master/commands/outputs'
45
+ autoload :Init, 'stack_master/commands/init'
46
+ autoload :Diff, 'stack_master/commands/diff'
47
+ autoload :ListStacks, 'stack_master/commands/list_stacks'
48
+ autoload :Validate, 'stack_master/commands/validate'
49
+ autoload :Resources, 'stack_master/commands/resources'
50
+ autoload :Delete, 'stack_master/commands/delete'
51
+ autoload :Status, 'stack_master/commands/status'
52
+ end
53
+
54
+ module ParameterResolvers
55
+ autoload :AmiFinder, 'stack_master/parameter_resolvers/ami_finder'
56
+ autoload :StackOutput, 'stack_master/parameter_resolvers/stack_output'
57
+ autoload :Secret, 'stack_master/parameter_resolvers/secret'
58
+ autoload :SnsTopicName, 'stack_master/parameter_resolvers/sns_topic_name'
59
+ autoload :SecurityGroup, 'stack_master/parameter_resolvers/security_group'
60
+ autoload :LatestAmiByTags, 'stack_master/parameter_resolvers/latest_ami_by_tags'
61
+ autoload :LatestAmi, 'stack_master/parameter_resolvers/latest_ami'
62
+ end
63
+
64
+ module AwsDriver
65
+ autoload :CloudFormation, 'stack_master/aws_driver/cloud_formation'
66
+ autoload :S3, 'stack_master/aws_driver/s3'
67
+ end
68
+
69
+ module TestDriver
70
+ autoload :CloudFormation, 'stack_master/test_driver/cloud_formation'
71
+ autoload :S3, 'stack_master/test_driver/s3'
72
+ end
73
+
74
+ module StackEvents
75
+ autoload :Fetcher, 'stack_master/stack_events/fetcher'
76
+ autoload :Presenter, 'stack_master/stack_events/presenter'
77
+ autoload :Streamer, 'stack_master/stack_events/streamer'
78
+ end
79
+
67
80
  def interactive?
68
81
  !non_interactive?
69
82
  end
@@ -106,6 +119,14 @@ module StackMaster
106
119
  @cloud_formation_driver = value
107
120
  end
108
121
 
122
+ def s3_driver
123
+ @s3_driver ||= AwsDriver::S3.new
124
+ end
125
+
126
+ def s3_driver=(value)
127
+ @s3_driver = value
128
+ end
129
+
109
130
  def stdout
110
131
  @stdout || $stdout
111
132
  end
@@ -126,4 +147,3 @@ module StackMaster
126
147
  @stderr = io
127
148
  end
128
149
  end
129
-
@@ -0,0 +1,66 @@
1
+ require 'digest/md5'
2
+
3
+ module StackMaster
4
+ module AwsDriver
5
+ class S3ConfigurationError < StandardError; end
6
+
7
+ class S3
8
+ def set_region(region)
9
+ @region = region
10
+ @s3 = nil
11
+ end
12
+
13
+ def upload_files(bucket: nil, prefix: nil, region: nil, files: {})
14
+ raise StackMaster::AwsDriver::S3ConfigurationError, 'A bucket must be specified in order to use S3' unless bucket
15
+
16
+ return if files.empty?
17
+
18
+ s3 = new_s3_client(region: region)
19
+
20
+ current_objects = s3.list_objects(
21
+ prefix: prefix,
22
+ bucket: bucket
23
+ ).map(&:contents).flatten.inject({}){|h,obj|
24
+ h.merge(obj.key => obj)
25
+ }
26
+
27
+ StackMaster.stdout.puts "Uploading files to S3:"
28
+
29
+ files.each do |template, file|
30
+ body = file.fetch(:body)
31
+ path = file.fetch(:path)
32
+ object_key = template.dup
33
+ object_key.prepend("#{prefix}/") if prefix
34
+ compiled_template_md5 = Digest::MD5.hexdigest(body).to_s
35
+ s3_md5 = current_objects[object_key] ? current_objects[object_key].etag.gsub("\"", '') : nil
36
+
37
+ next if compiled_template_md5 == s3_md5
38
+ s3_uri = "s3://#{bucket}/#{object_key}"
39
+ StackMaster.stdout.print "- #{File.basename(path)} => #{s3_uri} "
40
+
41
+ s3.put_object(
42
+ bucket: bucket,
43
+ key: object_key,
44
+ body: body,
45
+ metadata: { md5: compiled_template_md5 }
46
+ )
47
+ StackMaster.stdout.puts "done."
48
+ end
49
+ end
50
+
51
+ def url(bucket:, prefix:, region:, template:)
52
+ if region == 'us-east-1'
53
+ ["https://s3.amazonaws.com", bucket, prefix, template].compact.join('/')
54
+ else
55
+ ["https://s3-#{region}.amazonaws.com", bucket, prefix, template].compact.join('/')
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def new_s3_client(region: nil)
62
+ Aws::S3::Client.new(region: region || @region)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,4 +1,5 @@
1
1
  require 'commander'
2
+ require 'table_print'
2
3
 
3
4
  module StackMaster
4
5
  class CLI
@@ -4,10 +4,11 @@ module StackMaster
4
4
  include Command
5
5
  include Commander::UI
6
6
  include StackMaster::Prompter
7
- TEMPLATE_TOO_LARGE_ERROR_MESSAGE = 'The (space compressed) stack is larger than the limit set by AWS. See http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html'
7
+ TEMPLATE_TOO_LARGE_ERROR_MESSAGE = 'The (space compressed) stack is larger than the limit set by AWS. See http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html'.freeze
8
8
 
9
9
  def initialize(config, stack_definition, options = {})
10
10
  @config = config
11
+ @s3_config = stack_definition.s3
11
12
  @stack_definition = stack_definition
12
13
  @from_time = Time.now
13
14
  end
@@ -26,6 +27,10 @@ module StackMaster
26
27
  @cf ||= StackMaster.cloud_formation_driver
27
28
  end
28
29
 
30
+ def s3
31
+ @s3 ||= StackMaster.s3_driver
32
+ end
33
+
29
34
  def stack
30
35
  @stack ||= Stack.find(region, stack_name)
31
36
  end
@@ -38,6 +43,10 @@ module StackMaster
38
43
  !stack.nil?
39
44
  end
40
45
 
46
+ def use_s3?
47
+ !@s3_config.empty?
48
+ end
49
+
41
50
  def diff_stacks
42
51
  StackDiffer.new(proposed_stack, stack).output_diff
43
52
  end
@@ -51,9 +60,10 @@ module StackMaster
51
60
  end
52
61
 
53
62
  def create_stack
54
- unless ask?("Create stack (y/n)? ")
63
+ unless ask?('Create stack (y/n)? ')
55
64
  failed!("Stack creation aborted")
56
65
  end
66
+ upload_files
57
67
  cf.create_stack(stack_options.merge(tags: proposed_stack.aws_tags))
58
68
  end
59
69
 
@@ -73,17 +83,54 @@ module StackMaster
73
83
  ChangeSet.delete(@change_set.id)
74
84
  halt! "Stack update aborted"
75
85
  end
86
+ upload_files
76
87
  execute_change_set
77
88
  end
78
89
 
90
+ def upload_files
91
+ return unless use_s3?
92
+ s3.upload_files(s3_options)
93
+ end
94
+
95
+ def template_method
96
+ use_s3? ? :template_url : :template_body
97
+ end
98
+
99
+ def template_value
100
+ if use_s3?
101
+ s3.url(bucket: @s3_config['bucket'], prefix: @s3_config['prefix'], region: @s3_config['region'], template: @stack_definition.s3_template_file_name)
102
+ else
103
+ proposed_stack.maybe_compressed_template_body
104
+ end
105
+ end
106
+
107
+ def files_to_upload
108
+ return {} unless use_s3?
109
+ @stack_definition.s3_files.tap do |files|
110
+ files[@stack_definition.s3_template_file_name] = {
111
+ path: @stack_definition.template_file_path,
112
+ body: proposed_stack.maybe_compressed_template_body
113
+ }
114
+ end
115
+ end
116
+
79
117
  def stack_options
80
118
  {
81
119
  stack_name: stack_name,
82
- template_body: proposed_stack.maybe_compressed_template_body,
83
120
  parameters: proposed_stack.aws_parameters,
84
121
  capabilities: ['CAPABILITY_IAM'],
85
122
  notification_arns: proposed_stack.notification_arns,
86
- stack_policy_body: proposed_stack.stack_policy_body
123
+ stack_policy_body: proposed_stack.stack_policy_body,
124
+ template_method => template_value
125
+ }
126
+ end
127
+
128
+ def s3_options
129
+ {
130
+ bucket: @s3_config['bucket'],
131
+ prefix: @s3_config['prefix'],
132
+ region: @s3_config['region'],
133
+ files: files_to_upload
87
134
  }
88
135
  end
89
136
 
@@ -110,7 +157,7 @@ module StackMaster
110
157
  end
111
158
 
112
159
  def ensure_valid_template_body_size!
113
- if proposed_stack.too_big?
160
+ if proposed_stack.too_big?(use_s3?)
114
161
  failed! TEMPLATE_TOO_LARGE_ERROR_MESSAGE
115
162
  end
116
163
  end