cf_deployer 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -0
  3. data/ChangeLog.md +16 -0
  4. data/DETAILS.md +268 -0
  5. data/FAQ.md +61 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.lock +51 -0
  8. data/LICENSE +22 -0
  9. data/QUICKSTART.md +96 -0
  10. data/README.md +36 -0
  11. data/Rakefile +32 -0
  12. data/bin/cf_deploy +10 -0
  13. data/cf_deployer.gemspec +23 -0
  14. data/lib/cf_deployer/application.rb +74 -0
  15. data/lib/cf_deployer/application_error.rb +4 -0
  16. data/lib/cf_deployer/aws_constants.rb +3 -0
  17. data/lib/cf_deployer/cli.rb +111 -0
  18. data/lib/cf_deployer/component.rb +103 -0
  19. data/lib/cf_deployer/config_loader.rb +189 -0
  20. data/lib/cf_deployer/config_validation.rb +138 -0
  21. data/lib/cf_deployer/defaults.rb +10 -0
  22. data/lib/cf_deployer/deployment_strategy/auto_scaling_group_swap.rb +102 -0
  23. data/lib/cf_deployer/deployment_strategy/base.rb +88 -0
  24. data/lib/cf_deployer/deployment_strategy/blue_green.rb +70 -0
  25. data/lib/cf_deployer/deployment_strategy/cname_swap.rb +108 -0
  26. data/lib/cf_deployer/deployment_strategy/create_or_update.rb +57 -0
  27. data/lib/cf_deployer/driver/auto_scaling_group.rb +86 -0
  28. data/lib/cf_deployer/driver/cloud_formation_driver.rb +85 -0
  29. data/lib/cf_deployer/driver/dry_run.rb +27 -0
  30. data/lib/cf_deployer/driver/elb_driver.rb +17 -0
  31. data/lib/cf_deployer/driver/instance.rb +29 -0
  32. data/lib/cf_deployer/driver/route53_driver.rb +79 -0
  33. data/lib/cf_deployer/driver/verisign_driver.rb +21 -0
  34. data/lib/cf_deployer/hook.rb +32 -0
  35. data/lib/cf_deployer/logger.rb +34 -0
  36. data/lib/cf_deployer/stack.rb +154 -0
  37. data/lib/cf_deployer/status_presenter.rb +195 -0
  38. data/lib/cf_deployer/version.rb +3 -0
  39. data/lib/cf_deployer.rb +97 -0
  40. data/spec/fakes/instance.rb +32 -0
  41. data/spec/fakes/route53_client.rb +23 -0
  42. data/spec/fakes/stack.rb +65 -0
  43. data/spec/functional/deploy_spec.rb +73 -0
  44. data/spec/functional/kill_inactive_spec.rb +57 -0
  45. data/spec/functional_spec_helper.rb +3 -0
  46. data/spec/spec_helper.rb +8 -0
  47. data/spec/unit/application_spec.rb +191 -0
  48. data/spec/unit/component_spec.rb +142 -0
  49. data/spec/unit/config_loader_spec.rb +356 -0
  50. data/spec/unit/config_validation_spec.rb +480 -0
  51. data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +435 -0
  52. data/spec/unit/deployment_strategy/base_spec.rb +44 -0
  53. data/spec/unit/deployment_strategy/cname_swap_spec.rb +294 -0
  54. data/spec/unit/deployment_strategy/create_or_update_spec.rb +113 -0
  55. data/spec/unit/deployment_strategy/deployment_strategy_spec.rb +29 -0
  56. data/spec/unit/driver/auto_scaling_group_spec.rb +127 -0
  57. data/spec/unit/driver/cloud_formation_spec.rb +32 -0
  58. data/spec/unit/driver/elb_spec.rb +11 -0
  59. data/spec/unit/driver/instance_spec.rb +30 -0
  60. data/spec/unit/driver/route53_spec.rb +85 -0
  61. data/spec/unit/driver/verisign_spec.rb +18 -0
  62. data/spec/unit/hook_spec.rb +64 -0
  63. data/spec/unit/stack_spec.rb +150 -0
  64. data/spec/unit/status_presenter_spec.rb +108 -0
  65. metadata +197 -0
@@ -0,0 +1,74 @@
1
+ module CfDeployer
2
+ class Application
3
+ attr_reader :components
4
+
5
+ def initialize(context = {})
6
+ @context = context
7
+ get_components
8
+ add_component_dependencies
9
+ @components.sort!
10
+ end
11
+
12
+ def get_components
13
+ @components = []
14
+ @context[:components].keys.each do | key |
15
+ component = Component.new(@context[:application], @context[:environment], key.to_s, @context[:components][key])
16
+ @components << component
17
+ end
18
+ end
19
+
20
+ def add_component_dependencies
21
+ @context[:components].keys.each do | key |
22
+ component = @components.find { |c| c.name == key.to_s }
23
+ dependencies = @context[:components][key][:'depends-on'] || []
24
+ dependencies.each do | parent_name |
25
+ parent = @components.find { |c| c.name == parent_name }
26
+ if parent
27
+ parent.children << component
28
+ component.dependencies << parent
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def deploy
35
+ Log.debug @context
36
+ components = get_targets().sort
37
+ components.each &:deploy
38
+ end
39
+
40
+ def json
41
+ components = get_targets().sort
42
+ components.each &:json
43
+ end
44
+
45
+ def status component_name, verbosity
46
+ statuses = {}
47
+ @components.select { |component| component_name.nil? || component_name == component.name }.each do |component|
48
+ statuses[component.name] = component.status(verbosity != 'stacks')
49
+ end
50
+ statuses
51
+ end
52
+
53
+ def destroy
54
+ components = get_targets.sort { |a, b| b <=> a }
55
+ components.each &:destroy
56
+ end
57
+
58
+ def kill_inactive
59
+ component = get_targets.first
60
+ component.kill_inactive
61
+ end
62
+
63
+ def switch
64
+ @context[:targets].each do | component_name |
65
+ @components.find { |c| c.name == component_name }.switch
66
+ end
67
+ end
68
+
69
+ private
70
+ def get_targets
71
+ targets = @components.select { |c| @context[:targets].include?(c.name) }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ module CfDeployer
2
+ class ApplicationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module AwsConstants
2
+ ELB_RESOURCE_TYPE = 'AWS::ElasticLoadBalancing::LoadBalancer'
3
+ end
@@ -0,0 +1,111 @@
1
+ require 'thor'
2
+
3
+ module CfDeployer
4
+ class CLI < Thor
5
+ def self.exit_on_failure?
6
+ true
7
+ end
8
+
9
+ class_option :'config-file', :aliases => '-f', :desc => "cf_deployer config file", :default => 'config/cf_deployer.yml'
10
+ class_option :'log-level', :aliases => '-l', :desc => "logging level", :enum => %w(info debug aws-debug), :default => 'info'
11
+ class_option :'dry-run', :aliases => '-d', :desc => "Say what we would do but don't actually make changes to anything", :banner => ''
12
+ class_option :settings, :aliases => '-s', :type => :hash, :desc => "key:value pair to overwrite setting in config"
13
+ class_option :inputs, :aliases => '-i', :type => :hash, :desc => "key:value pair to overwrite in template inputs"
14
+ class_option :region, :aliases => '-r', :desc => "Amazon region", :default => 'us-east-1'
15
+
16
+ desc "deploy [ENVIRONMENT] [COMPONENT]\t", 'Deploy the specified components'
17
+ def deploy environment, component = nil
18
+ prep_for_action :deploy, environment, component
19
+ CfDeployer.deploy merged_options
20
+ end
21
+
22
+ desc "destroy [ENVIRONMENT] [COMPONENT]\t", 'Destroy the specified environment/component'
23
+ def destroy environment, component = nil
24
+ prep_for_action :destroy, environment, component
25
+ CfDeployer.destroy merged_options
26
+ end
27
+
28
+ desc "config [ENVIRONMENT]", 'Show parsed config'
29
+ def config environment
30
+ prep_for_action :config, environment
31
+ CfDeployer.config merged_options
32
+ end
33
+
34
+ desc "json [ENVIRONMENT] [COMPONENT]", 'Show parsed CloudFormation JSON for the target component'
35
+ def json environment, component = nil
36
+ prep_for_action :json, environment, component
37
+ CfDeployer.json merged_options
38
+ end
39
+
40
+ desc 'status [ENVIRONMENT] [COMPONENT]', 'Show the status of the specified Cloud Formation components specified in your yml'
41
+ method_option :verbosity, :aliases => '-v', :desc => 'Verbosity level', :enum => ['stacks','instances','all'], :default => 'instances'
42
+ method_option :'output-format', :aliases => '-o', :enum => ['human','json'], :default => 'human', :desc => 'Output format'
43
+ def status environment, component = nil
44
+ prep_for_action :status, environment, component
45
+ CfDeployer.status merged_options
46
+ end
47
+
48
+ desc 'kill_inactive [ENVIRONMENT] [COMPONENT]', 'Destroy the inactive stack for a given component/environment'
49
+ def kill_inactive environment, component
50
+ prep_for_action :kill_inactive, environment, component
51
+ CfDeployer.kill_inactive merged_options
52
+ end
53
+
54
+ desc 'switch [ENVIRONMENT] [COMPONENT]', 'Switch active and inactive stacks'
55
+ def switch environment, component
56
+ prep_for_action :switch, environment, component
57
+ CfDeployer.switch merged_options
58
+ end
59
+
60
+ no_commands do
61
+
62
+ def prep_for_action action, environment, component = nil
63
+ no_component_required_actions = [:config, :status]
64
+ if environment == 'help' || (no_component_required_actions.include? action && component == nil)
65
+ self.class.command_help shell, action
66
+ exit 0
67
+ end
68
+ @environment = environment
69
+ @component = [component].compact
70
+ validate_cli options
71
+ set_log_level
72
+ detect_dry_run
73
+ end
74
+
75
+ def merged_options
76
+ symbolize_all_keys options.merge({:environment => @environment, :component => @component})
77
+ end
78
+
79
+ def set_log_level
80
+ if options[:'log-level'] == 'aws-debug'
81
+ CfDeployer::Log.level = 'debug'
82
+ AWS.config :logger => Logger.new($stdout)
83
+ else
84
+ CfDeployer::Log.level = options[:'log-level']
85
+ end
86
+ end
87
+
88
+ def detect_dry_run
89
+ CfDeployer::Driver::DryRun.enable if options[:'dry-run']
90
+ end
91
+
92
+ def validate_cli cli_options
93
+ unless File.file?(options[:'config-file'])
94
+ error_exit "ERROR: #{options[:'config-file']} is not a file."
95
+ end
96
+ error_exit "ERROR: No environment specified!" unless @environment
97
+ end
98
+
99
+ def symbolize_all_keys(hash)
100
+ return hash unless hash.is_a?(Hash)
101
+ hash.inject({}){|memo,(k,v)| memo.delete(k); memo[k.to_sym] = symbolize_all_keys(v); memo}
102
+ end
103
+
104
+ def error_exit message
105
+ puts message
106
+ exit 1
107
+ end
108
+
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,103 @@
1
+ module CfDeployer
2
+ class Component
3
+ attr_reader :name, :dependencies, :children
4
+
5
+ def initialize(application_name, environment_name, component_name, context)
6
+ @application_name = application_name
7
+ @environment_name = environment_name
8
+ @name = component_name
9
+ @context = context
10
+ @dependencies = []
11
+ @children = []
12
+ Log.debug "initializing #{name}.."
13
+ Log.debug @context
14
+ end
15
+
16
+ def exists?
17
+ strategy.exists?
18
+ end
19
+
20
+ def kill_inactive
21
+ strategy.kill_inactive
22
+ end
23
+
24
+ def deploy
25
+ Log.debug "deploying #{name}..."
26
+ @dependencies.each do |parent|
27
+ parent.deploy unless(parent.exists?)
28
+ end
29
+ resolve_settings
30
+ strategy.deploy
31
+ end
32
+
33
+ def json
34
+ resolve_settings
35
+ puts "#{name} json template:"
36
+ puts ConfigLoader.component_json(name, @context)
37
+ end
38
+
39
+ def destroy
40
+ raise ApplicationError.new("Unable to destroy #{name}, it is depended on by other components") if any_children_exist?
41
+ strategy.destroy
42
+ end
43
+
44
+ def switch
45
+ exists? ? strategy.switch : (raise ApplicationError.new("No stack exists for component: #{name}"))
46
+ end
47
+
48
+
49
+ def output_value(key)
50
+ strategy.output_value(key)
51
+ end
52
+
53
+ def <=>(other)
54
+ i_am_depednent = depends_on? other
55
+ it_is_dependent = other.depends_on? self
56
+
57
+ if i_am_depednent
58
+ 1
59
+ elsif it_is_dependent
60
+ -1
61
+ else
62
+ 0
63
+ end
64
+ end
65
+
66
+ def inspect
67
+ "component: #{name}"
68
+ end
69
+
70
+ def depends_on?(component, source=self)
71
+ raise ApplicationError.new("Cyclic dependency") if @dependencies.include?(source)
72
+ @dependencies.include?(component) || @dependencies.any? { |d| d.depends_on?(component, source) }
73
+ end
74
+
75
+ def status get_resource_statuses
76
+ strategy.status get_resource_statuses
77
+ end
78
+
79
+ private
80
+
81
+ def resolve_settings
82
+ inputs.each do |key, value|
83
+ if(value.is_a? Hash)
84
+ dependency = @dependencies.find { |d| d.name == value[:component] }
85
+ output_key = value[:'output-key']
86
+ inputs[key] = dependency.output_value(output_key)
87
+ end
88
+ end
89
+ end
90
+
91
+ def any_children_exist?
92
+ children.any?(&:exists?)
93
+ end
94
+
95
+ def inputs
96
+ @context[:inputs]
97
+ end
98
+
99
+ def strategy
100
+ @strategy ||= DeploymentStrategy.create(@application_name, @environment_name, name, @context)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,189 @@
1
+ module CfDeployer
2
+ class ConfigLoader
3
+
4
+ def self.component_json component, config
5
+ json_file = File.join(config[:config_dir], "#{component}.json")
6
+ raise ApplicationError.new("#{json_file} is missing") unless File.exists?(json_file)
7
+ ERB.new(File.read(json_file)).result(binding)
8
+ end
9
+
10
+ def load(options)
11
+ config_text = File.read(options[:'config-file'])
12
+ erbed_config = erb_with_environment_and_region(config_text, options[:environment], options[:region])
13
+ yaml = symbolize_all_keys(load_yaml(erbed_config))
14
+ @config = options.merge(yaml)
15
+ @config[:components] ||= {}
16
+ @config[:settings] ||= {}
17
+ @config[:environments] ||= {}
18
+ @config[:tags] ||= {}
19
+ get_targets
20
+ copy_config_dir
21
+ merge_section(:settings)
22
+ merge_section(:inputs)
23
+ merge_section(:tags)
24
+ copy_region_app_env_component
25
+ get_cf_template_keys('Parameters')
26
+ get_cf_template_keys('Outputs')
27
+ set_default_settings
28
+ @config.delete(:settings)
29
+ @config
30
+ end
31
+
32
+ private
33
+
34
+ def load_yaml(text)
35
+ YAML.load text
36
+ rescue
37
+ raise ApplicationError.new("The config file is not a valid yaml file")
38
+ end
39
+
40
+
41
+ def merge_section(section)
42
+ merge_component_options section
43
+ merge_environment_options(@config[:environment], section)
44
+ merge_environment_variables section
45
+ @config[:cli_overrides] ||= {}
46
+ merge_options(@config[:cli_overrides][section] || {}, section)
47
+ end
48
+
49
+ def get_targets
50
+ @config[:component] ||= []
51
+ @config[:targets] = @config[:component].length == 0 ? @config[:components].keys.map(&:to_s) : @config[:component]
52
+ end
53
+
54
+ def copy_region_app_env_component
55
+ @config[:components].each do |component_name, component|
56
+ component[:settings][:region] = @config[:region]
57
+ component[:inputs][:region] = @config[:region]
58
+
59
+ component[:settings][:application] = @config[:application]
60
+ component[:inputs][:application] = @config[:application]
61
+
62
+ component[:settings][:component] = component_name.to_s
63
+ component[:inputs][:component] = component_name.to_s
64
+
65
+ component[:settings][:environment] = @config[:environment]
66
+ component[:inputs][:environment] = @config[:environment]
67
+ end
68
+ end
69
+
70
+ def set_default_settings
71
+ @config[:components].each do |component_name, component|
72
+ if component[:'deployment-strategy'] == 'cname-swap'
73
+ component[:settings][:'elb-name-output'] ||= Defaults::ELBName
74
+ component[:settings][:'dns-driver'] ||= Defaults::DNSDriver
75
+ end
76
+ component[:settings][:'raise-error-for-unused-inputs'] ||= Defaults::RaiseErrorForUnusedInputs
77
+ component[:settings][:'auto-scaling-group-name-output'] ||= [Defaults::AutoScalingGroupName] if component[:'deployment-strategy'] == 'auto-scaling-group-swap'
78
+ component[:settings][:'auto-scaling-group-name-output'] ||= [Defaults::AutoScalingGroupName] if component[:'defined_outputs'].keys.include?(Defaults::AutoScalingGroupName.to_sym)
79
+ if component[:settings][:'keep-previous-stack'] == nil
80
+ component[:settings][:'keep-previous-stack'] = Defaults::KeepPreviousStack
81
+ end
82
+ end
83
+ end
84
+
85
+ def get_cf_template_keys(name)
86
+ @config[:components].keys.each do |component|
87
+ parameters = cf_template(component)[name] || {}
88
+ @config[:components][component]["defined_#{name.downcase}".to_sym] = symbolize_all_keys(parameters)
89
+ end
90
+ end
91
+
92
+ def cf_template(component)
93
+ config = deep_dup(@config[:components][component])
94
+ config[:inputs].each do |key, value|
95
+ if value.is_a?(Hash)
96
+ output_key = value[:'output-key']
97
+ config[:inputs][key] = "#{value[:component]}::#{output_key}"
98
+ end
99
+ end
100
+
101
+ json_content = self.class.component_json component.to_s, config
102
+ begin
103
+ JSON.load json_content
104
+ rescue JSON::ParserError => e
105
+ puts json_content
106
+ puts '=' * 80
107
+ puts e.message[0..300]
108
+ puts '=' * 80
109
+ raise "Couldn't parse JSON for component #{component}"
110
+ end
111
+ end
112
+
113
+ def config_dir
114
+ File.dirname(@config[:'config-file'])
115
+ end
116
+
117
+ def copy_config_dir
118
+ @config[:components].each do |component_name, component|
119
+ component ||= {}
120
+ @config[:components][component_name] = component
121
+ component[:config_dir] = config_dir
122
+ end
123
+ end
124
+
125
+ def merge_component_options(section)
126
+ common_options = @config[section] || {}
127
+ @config[:components].keys.each do |component|
128
+ @config[:components][component] ||= {}
129
+ component_options = @config[:components][component].delete(section) || {}
130
+ @config[:components][component][section] = common_options.merge(component_options)
131
+ end
132
+ end
133
+
134
+ def merge_environment_options(environment_name, section)
135
+ return unless environment_name
136
+ environment = @config[:environments][environment_name.to_sym] || {}
137
+ environment_options = environment[section] || {}
138
+ merge_options(environment_options, section)
139
+ environment_components = environment[:components] || {}
140
+ merge_environment_component(environment_components, section)
141
+ end
142
+
143
+ def merge_options(options, section)
144
+ @config[:components].keys.each do |component|
145
+ component_options = @config[:components][component].delete(section) || {}
146
+ @config[:components][component][section] = component_options.merge(options)
147
+ end
148
+ end
149
+
150
+ def merge_environment_component(environment_components, section)
151
+ environment_components.keys.each do |component|
152
+ @config[:components][component] ||= {}
153
+ component_options = @config[:components][component].delete(section) || {}
154
+ @config[:components][component][section] = component_options.merge(environment_components[component][section] || {})
155
+ end
156
+ end
157
+
158
+ def merge_environment_variables(section)
159
+ @config[:components].keys.each do |component|
160
+ merge_environment_variables_to_options( @config[:components][component][section], section)
161
+ end
162
+ end
163
+
164
+ def merge_environment_variables_to_options(options, section)
165
+ options.keys.each do |key|
166
+ environment_variable = ENV["cfdeploy_#{section}_#{key.to_s}"]
167
+ options[key] = environment_variable if environment_variable
168
+ end
169
+ end
170
+
171
+ def symbolize_all_keys(hash)
172
+ return hash unless hash.is_a?(Hash)
173
+ hash.inject({}){|memo,(k,v)| memo.delete(k); memo[k.to_sym] = symbolize_all_keys(v); memo}
174
+ end
175
+
176
+ def erb_with_environment_and_region(contents, environment, region)
177
+ ERB.new(contents).result(binding)
178
+ end
179
+
180
+ def deep_dup(hash)
181
+ new_hash = {}
182
+ hash.each do |key, value|
183
+ value.is_a?(Hash) ? new_hash[key] = deep_dup(value) : new_hash[key] = value
184
+ end
185
+ new_hash
186
+ end
187
+
188
+ end
189
+ end
@@ -0,0 +1,138 @@
1
+ module CfDeployer
2
+ class ConfigValidation
3
+
4
+ class ValidationError < ApplicationError
5
+ end
6
+
7
+ CommonInputs = [:application, :environment, :component, :region]
8
+ EnvironmentOptions = [:settings, :inputs, :tags, :components]
9
+ ComponentOptions = [:settings, :inputs, :tags, :'depends-on', :'deployment-strategy', :'before-destroy', :'after-create', :'after-swap', :'defined_outputs', :'defined_parameters', :config_dir, :capabilities]
10
+
11
+ def validate config, validate_inputs = true
12
+ @config = config
13
+ @errors = []
14
+ @warnings = []
15
+ check_application_name
16
+ check_components validate_inputs
17
+ check_environments
18
+ @warnings.each { |message| puts "WARNNING:#{message}" }
19
+ raise ValidationError.new(@errors.join("\n")) if @errors.length > 0
20
+ end
21
+
22
+ private
23
+
24
+
25
+ def check_asg_name_output(component)
26
+ component[:settings][:'auto-scaling-group-name-output'] ||= []
27
+ outputs = component[:settings][:'auto-scaling-group-name-output'].map { |name| name.to_sym }
28
+ missing_output_keys = (outputs - component[:defined_outputs].keys)
29
+ @errors << "'#{missing_output_keys.map(&:to_s)}' is not a CF stack output" unless missing_output_keys.empty?
30
+ end
31
+
32
+ def check_cname_swap_options(component)
33
+ @errors << "dns-fqdn is required when using cname-swap deployment-strategy" unless component[:settings][:'dns-fqdn']
34
+ @errors << "dns-zone is required when using cname-swap deployment-strategy" unless component[:settings][:'dns-zone']
35
+ @errors << "'#{component[:settings][:'elb-name-output']}' is not a CF stack output, which is required by cname-swap deployment" unless component[:defined_outputs].keys.include?(component[:settings][:'elb-name-output'].to_sym)
36
+ end
37
+
38
+ def check_application_name
39
+ @config[:application] = "" unless @config[:application]
40
+ return @errors << "Application name is missing in config" unless @config[:application].length > 0
41
+ @errors << "Application name cannot be longer than 100 and can only contain letters, numbers, '-' and '.'" unless @config[:application] =~ /^[a-zA-Z0-9\.-]{1,100}$/
42
+ end
43
+
44
+ def check_components validate_inputs
45
+ @config[:components] ||= {}
46
+ return @errors << "At least one component must be defined in config" unless @config[:components].length > 0
47
+ deployable_components = @config[:targets] || []
48
+ component_targets = @config[:components].select { |key, value| deployable_components.include?(key.to_s) }
49
+ invalid_names = deployable_components - component_targets.keys.map(&:to_s)
50
+ @errors << "Found invalid deployment components #{invalid_names}" if invalid_names.size > 0
51
+ component_targets.each do |component_name, component|
52
+ component[:settings] ||= {}
53
+ component[:inputs] ||= {}
54
+ component[:defined_outputs] ||= {}
55
+ @errors << "Component name cannot be longer than 100 and can only contain letters, numbers, '-' and '.': #{component_name}" unless component_name =~ /^[A-Za-z0-9\.-]{1,100}$/
56
+ check_parameters component_name, component if validate_inputs
57
+ check_cname_swap_options(component) if component[:'deployment-strategy'] == 'cname-swap'
58
+ check_asg_name_output(component)
59
+ check_hooks(component)
60
+ check_component_options(component_name, component)
61
+ end
62
+ end
63
+
64
+ def check_component_options(name, component)
65
+ component.keys.each do |option|
66
+ @errors << "The option '#{option}' of the component '#{name}' is not valid" unless ComponentOptions.include?(option)
67
+ end
68
+ end
69
+
70
+ def check_hooks(component)
71
+ hook_names = [:'before-destroy', :'after-create', :'after-swap']
72
+ hook_names.each do |hook_name|
73
+ next unless component[hook_name] && component[hook_name].is_a?(Hash)
74
+ @errors << "Invalid hook '#{hook_name}'" unless component[hook_name][:file] || component[hook_name][:code]
75
+ check_hook_file(component, hook_name)
76
+ end
77
+ end
78
+
79
+ def check_hook_file(component, hook_name)
80
+ file_name = component[hook_name][:file]
81
+ return unless file_name
82
+ path = File.join(component[:config_dir], file_name)
83
+ @errors << "File '#{path}' does not exist, which is required by hook '#{hook_name}'" unless File.exists?(path)
84
+ end
85
+
86
+ def check_environments
87
+ @config[:environments] ||= {}
88
+ @config[:environments].each do | name, environment |
89
+ @errors << "Environment name cannot be longer than 12 and can only contain letters, numbers, '-' and '.': #{name}" unless name =~ /^[a-zA-Z0-9\.-]{1,12}$/
90
+ check_environment_options(name, environment)
91
+ end
92
+ end
93
+
94
+ def check_environment_options(name, environment)
95
+ environment.keys.each do |option|
96
+ @errors << "The option '#{option}' of the environment '#{name}' is not valid" unless EnvironmentOptions.include?(option)
97
+ end
98
+ end
99
+
100
+
101
+ def check_parameters(component_name, component)
102
+ component[:defined_parameters] ||= {}
103
+ component[:defined_outputs] ||= {}
104
+ component[:defined_parameters].each do | parameter_name, parameter |
105
+ if component[:inputs].keys.include?(parameter_name) || parameter[:Default]
106
+ check_output_reference(parameter_name, component_name)
107
+ else
108
+ @errors << "No input setting '#{parameter_name}' found for CF template parameter in component #{component_name}"
109
+ end
110
+ end
111
+ check_un_used_inputs(component_name, component)
112
+ end
113
+
114
+ def check_un_used_inputs(component_name, component)
115
+ component[:inputs].keys.each do |input|
116
+ unless component[:defined_parameters].keys.include?(input) || CommonInputs.include?(input)
117
+ message = "The input '#{input}' defined in the component '#{component_name}' is not used in the json template as a parameter"
118
+ if component[:settings][:'raise-error-for-unused-inputs']
119
+ @errors << message
120
+ else
121
+ @warnings << message
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+
128
+ def check_output_reference(setting_name, component_name)
129
+ setting = @config[:components][component_name][:inputs][setting_name]
130
+ return unless setting.is_a?(Hash)
131
+ ref_component_name = setting[:component].to_sym
132
+ ref_component = @config[:components][ref_component_name]
133
+ output_key = setting[:'output-key'].to_sym
134
+ @errors << "No output '#{output_key}' found in CF template of component #{ref_component_name}, which is referenced by input setting '#{setting_name}' in component #{component_name}" unless ref_component[:defined_outputs].keys.include?(output_key)
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,10 @@
1
+ module CfDeployer
2
+ module Defaults
3
+ ELBName = 'ELBName'
4
+ AutoScalingGroupName = 'AutoScalingGroupName'
5
+ Timeout = 900
6
+ DNSDriver = 'CfDeployer::Driver::Route53'
7
+ RaiseErrorForUnusedInputs = false
8
+ KeepPreviousStack = true
9
+ end
10
+ end