Class: CloudFormationTool::CloudFormation

Inherits:
Object
  • Object
show all
Defined in:
lib/cloud_formation_tool/cloud_formation.rb,
lib/cloud_formation_tool/cloud_formation/stack.rb,
lib/cloud_formation_tool/cloud_formation/lambda_code.rb,
lib/cloud_formation_tool/cloud_formation/nested_stack.rb,
lib/cloud_formation_tool/cloud_formation/cloud_front_distribution.rb

Defined Under Namespace

Modules: CloudFrontDistribution, CloudFrontInvalidation Classes: LambdaCode, NestedStack, Stack

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ CloudFormation

Returns a new instance of CloudFormation.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 15

def initialize(path)
  log "Loading #{path}"
  @path = path
  @path = "#{@path}/cloud-formation.yaml" if File.directory? @path
  @path = "#{@path}.yaml" if !File.exist? @path and File.exist? "#{@path}.yaml"
  @basedir = File.dirname(@path)
  @compiled = false
  @params = nil
  begin
    text = File.read(@path)
    # remove comments because white space seen between comments can seriously psych Psych
    text.gsub!(/^#.*\n/s,'')
    text = fixShorthand(text)
    @data = YAML.load(text).to_h
  rescue Psych::SyntaxError => e
    e.message =~ /line (\d+) column (\d+)/
    lines = text.split "\n"
    raise CloudFormationTool::Errors::AppError, "Error parsing #{path} at line #{e.line} column #{e.column}:\n" +
      "#{lines[e.line-1]}\n" +
      "#{(' ' * (e.column - 1 ))}^- #{e.problem} #{e.context}"
  rescue Errno::ENOENT => e
    raise CloudFormationTool::Errors::AppError, "Error reading #{path}: #{e.message}"
  end
end

Instance Attribute Details

#basedirObject (readonly)

Returns the value of attribute basedir.



13
14
15
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 13

def basedir
  @basedir
end

Class Method Details

.parse(path) ⇒ Object



9
10
11
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 9

def self.parse(path)
  CloudFormation.new(path)
end

Instance Method Details

#compile(parameters = nil) ⇒ Object



40
41
42
43
44
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 40

def compile(parameters = nil)
  @params = parameters unless parameters.nil?
  embed_includes
  @data = load_files(@data)
end

#eachObject



202
203
204
205
206
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 202

def each
  compile['Parameters'].each do |name, param|
    yield name, param['Default']
  end
end

#embed_includesObject



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
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 95

def embed_includes
  (@data.delete(@data.keys.find{|k| k.start_with? 'Include'}) || []).each do |path|
    realpath = "#{@basedir}/#{path}"
    cfile_key = File.dirname(realpath).gsub(%r{/(.)}){|m| $1.upcase }.gsub(/\W+/,'')
    rewrites = Hash.new
    CloudFormation.new(realpath).compile.each do |category, catdata|
      # some categories are meta-data that we can ignore from includes
      next if %w(AWSTemplateFormatVersion Description).include? category
      
      case category
      when "Parameters"
        (@data[category]||={}).each do |name, param|
          if catdata.has_key? name
            next if param['Default'] == catdata[name]['Default']
             
            if catdata[name].has_key?('Override') and catdata[name]['Override'] == false
              catdata.delete(name)
            else
              newname = "#{cfile_key}z#{name}"
              log "Rewriting conflicting parameter #{name} (='#{catdata[name]['Default']}') to #{newname}"
              catdata[newname] = catdata.delete name
              rewrites[name] = newname
            end
          else
            @data[category][name] = param
          end
        end
      else
        # warn against duplicate entities, resources or outputs
        (@data[category] ||= {}).keys.each do |key|
          if catdata.has_key? key
            raise CloudFormationTool::Errors::AppError, "Error compiling #{path} - duplicate '#{category}' item: #{key}"
          end 
        end
        catdata = fixrefs(catdata, rewrites)
      end
      
      # add included properties
      @data[category].merge! catdata
    end
  end
end

#embed_includes_futureObject



85
86
87
88
89
90
91
92
93
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 85

def embed_includes_future
  (@data.delete(@data.keys.find{|k| k.start_with? 'Include'}) || []).each do |path|
    case path
    when Hash
    when String
      embed_included_path path
    end
  end
end

#fixrefs(data, rmap) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 65

def fixrefs(data, rmap)
  case data
  when Hash
    data.inject({}) do |h,(k,v)|
      h[k] = if k == "Ref"
        rmap[v] || v
      else
        fixrefs(v,rmap)
      end
      h
    end
  when Array
    data.collect do |item|
      fixrefs(item, rmap)
    end
  else
    return data
  end
end

#fixShorthand(text) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 51

def fixShorthand(text)
  text.gsub(/(?:(\s*)([^![:space:]]+))?(\s+)!(\w+)/) do |match|
    case $4
    when *%w(Base64 FindInMap GetAtt GetAZs ImportValue Join Select Sub Split
      And Equals If Not Or)
      ($2.nil? ? "" : "#{$1}#{$2}\n#{$1} ") + "#{$3}\"Fn::#{$4}\":"
    when 'Ref'
      "#{$1}#{$2}\n#{$1} #{$3}#{$4}:"
    else
      match
    end
  end
end

#load_files(data, restype = nil) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 161

def load_files(data, restype = nil)
  case data
  when Array
    data.collect { |data| load_files(data, restype) }
  when Hash
    # remember the current resource type
    restype = data['Type'] if restype.nil? and data.key?('Type')
    data.inject({}) do |dict, (key, val)|
      dict[key] = case restype
        when 'AWS::AutoScaling::LaunchConfiguration'
          if (key == "UserData") and (val["File"]) 
            # Support LaunchConfiguration UserData from file
            CloudInit.new("#{@basedir}/#{val["File"]}").to_base64
          elsif (key == "UserData") and (val["FileTemplate"]) 
            # Support LaunchConfiguration UserData from file with substitutions
            { "Fn::Base64" => { "Fn::Sub" => CloudInit.new("#{@basedir}/#{val["FileTemplate"]}").compile } }
          else
            load_files(val, restype)
          end
        when 'AWS::Lambda::Function'
          if key == 'Code'
            LambdaCode.new(val, self, data['Runtime']).to_cloudformation
          else
            load_files(val, restype)
          end
        when 'AWS::CloudFormation::Stack'
          if key == 'Properties' and val.key?('Template')
            NestedStack.new(val, self).to_cloudformation
          else
            load_files(val, restype)
          end
        else
          load_files(val, restype)
      end
      dict
    end
  else
    data
  end
end

#resolveVal(value) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 138

def resolveVal(value)
  case value
  when Hash
    if value.key? 'Ref'
      if @params.nil?
        # no parameters, we are probably in a sub template, just return the ref and hope
        # a parent template has what it takes to resolve the ref
        value
      else # parameters are set for this template - we can try to resolve
        res = @params[value['Ref']] || ((@data['Parameters']||{})[value['Ref']] || {})['Default']
        if res.nil?
          raise CloudFormationTool::Errors::AppError, "Reference #{value['Ref']} can't be resolved"
        end
        res
      end
    else
      raise CloudFormationTool::Errors::AppError, "Value #{value} is not a valid value or reference"
    end
  else
    value
  end
end

#to_yaml(parameters = {}) ⇒ Object



46
47
48
49
# File 'lib/cloud_formation_tool/cloud_formation.rb', line 46

def to_yaml(parameters = {})
  @params = parameters
  compile.to_yaml
end