Class: GeoEngineer::Resource
- Inherits:
-
Object
- Object
- GeoEngineer::Resource
- Includes:
- HasAttributes, HasLifecycle, HasSubResources, HasValidations
- Defined in:
- lib/geoengineer/resource.rb
Overview
Resources are the core of GeoEngineer and are mapped 1:1 to terraform resources
For example, aws_security_group is a resource
A Resource can have arbitrary attributes, validation rules and lifecycle hooks
Direct Known Subclasses
GeoEngineer::Resources::AwsAlb, GeoEngineer::Resources::AwsAlbListener, GeoEngineer::Resources::AwsAlbListenerRule, GeoEngineer::Resources::AwsAlbTargetGroup, GeoEngineer::Resources::AwsApiGatewayAccount, GeoEngineer::Resources::AwsApiGatewayApiKey, GeoEngineer::Resources::AwsApiGatewayAuthorizer, GeoEngineer::Resources::AwsApiGatewayBasePathMapping, GeoEngineer::Resources::AwsApiGatewayClientCertificate, GeoEngineer::Resources::AwsApiGatewayDeployment, GeoEngineer::Resources::AwsApiGatewayDomainName, GeoEngineer::Resources::AwsApiGatewayIntegration, GeoEngineer::Resources::AwsApiGatewayIntegrationResponse, GeoEngineer::Resources::AwsApiGatewayMethod, GeoEngineer::Resources::AwsApiGatewayMethodResponse, GeoEngineer::Resources::AwsApiGatewayModel, GeoEngineer::Resources::AwsApiGatewayResource, GeoEngineer::Resources::AwsApiGatewayRestApi, GeoEngineer::Resources::AwsApiGatewayUsagePlan, GeoEngineer::Resources::AwsCloudfrontDistribution, GeoEngineer::Resources::AwsCloudtrail, GeoEngineer::Resources::AwsCloudwatchEventRule, GeoEngineer::Resources::AwsCloudwatchEventTarget, GeoEngineer::Resources::AwsCloudwatchMetricAlarm, GeoEngineer::Resources::AwsCustomerGateway, GeoEngineer::Resources::AwsDbInstance, GeoEngineer::Resources::AwsDbParameterGroup, GeoEngineer::Resources::AwsDynamodbTable, GeoEngineer::Resources::AwsEip, GeoEngineer::Resources::AwsElasticacheCluster, GeoEngineer::Resources::AwsElasticacheParameterGroup, GeoEngineer::Resources::AwsElasticacheReplicationGroup, GeoEngineer::Resources::AwsElasticacheSubnetGroup, GeoEngineer::Resources::AwsElasticsearchDomain, GeoEngineer::Resources::AwsElb, GeoEngineer::Resources::AwsEmrCluster, GeoEngineer::Resources::AwsIamAccountPasswordPolicy, GeoEngineer::Resources::AwsIamGroup, GeoEngineer::Resources::AwsIamGroupMembership, GeoEngineer::Resources::AwsIamInstanceProfile, GeoEngineer::Resources::AwsIamPolicy, GeoEngineer::Resources::AwsIamPolicyAttachment, GeoEngineer::Resources::AwsIamRole, GeoEngineer::Resources::AwsIamRolePolicy, GeoEngineer::Resources::AwsIamUser, GeoEngineer::Resources::AwsInstance, GeoEngineer::Resources::AwsInternetGateway, GeoEngineer::Resources::AwsKinesisStream, GeoEngineer::Resources::AwsKmsKey, GeoEngineer::Resources::AwsLambdaAlias, GeoEngineer::Resources::AwsLambdaEventSourceMapping, GeoEngineer::Resources::AwsLambdaFunction, GeoEngineer::Resources::AwsLambdaPermission, GeoEngineer::Resources::AwsLbCookieStickinessPolicy, GeoEngineer::Resources::AwsLoadBalancerBackendServerPolicy, GeoEngineer::Resources::AwsLoadBalancerPolicy, GeoEngineer::Resources::AwsMainRouteTableAssociation, GeoEngineer::Resources::AwsNatGateway, GeoEngineer::Resources::AwsNetworkAcl, GeoEngineer::Resources::AwsNetworkAclRule, GeoEngineer::Resources::AwsNetworkInterface, GeoEngineer::Resources::AwsPlacementGroup, GeoEngineer::Resources::AwsProxyProtocolPolicy, GeoEngineer::Resources::AwsRedshiftCluster, GeoEngineer::Resources::AwsRoute, GeoEngineer::Resources::AwsRoute53Record, GeoEngineer::Resources::AwsRoute53Zone, GeoEngineer::Resources::AwsRouteTable, GeoEngineer::Resources::AwsRouteTableAssociation, GeoEngineer::Resources::AwsS3Bucket, GeoEngineer::Resources::AwsS3BucketNotification, GeoEngineer::Resources::AwsS3BucketObject, GeoEngineer::Resources::AwsSecurityGroup, GeoEngineer::Resources::AwsSesReceiptRule, GeoEngineer::Resources::AwsSesReceiptRuleSet, GeoEngineer::Resources::AwsSnsTopic, GeoEngineer::Resources::AwsSnsTopicSubscription, GeoEngineer::Resources::AwsSqsQueue, GeoEngineer::Resources::AwsSubnet, GeoEngineer::Resources::AwsVpc, GeoEngineer::Resources::AwsVpcDhcpOptions, GeoEngineer::Resources::AwsVpcDhcpOptionsAssociation, GeoEngineer::Resources::AwsVpcEndpoint, GeoEngineer::Resources::AwsVpcPeeringConnection, GeoEngineer::Resources::AwsVpnConnection, GeoEngineer::Resources::AwsVpnConnectionRoute, GeoEngineer::Resources::AwsVpnGateway, GeoEngineer::Resources::AwsVpnGatewayAttachment, GeoEngineer::Resources::AwsWafIpset, GeoEngineer::Resources::AwsWafRule, GeoEngineer::Resources::AwsWafWebAcl
Constant Summary collapse
- DEFAULT_PROVIDER =
"default_provider".freeze
Constants included from HasValidations
HasValidations::MAX_POLICY_LENGTH
Instance Attribute Summary collapse
-
#environment ⇒ Object
Returns the value of attribute environment.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#project ⇒ Object
Returns the value of attribute project.
-
#template ⇒ Object
Returns the value of attribute template.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
Class Method Summary collapse
- ._deep_symbolize_keys(obj) ⇒ Object
-
._fetch_remote_resources(provider) ⇒ Object
This method must be implemented for each resource type it must return a list of hashes with at least the key.
- ._ignore_remote_resource?(resource) ⇒ Boolean
-
._resources_to_ignore ⇒ Object
This method allows you to specify certain remote resources that for whatever reason, cannot or should not be codified.
- .build(resource_hash) ⇒ Object
- .clear_remote_resource_cache ⇒ Object
- .fetch_remote_resources(provider) ⇒ Object
-
.type_from_class_name ⇒ Object
CLASS METHODS.
Instance Method Summary collapse
-
#_find_remote_resource ⇒ Object
This method will fetch the remote resource that has the same _geo_id as the codified resource.
- #_json_file(attribute, path, binding_obj = nil) ⇒ Object
- #_normalize_json(json) ⇒ Object
- #build_individual_remote_resource ⇒ Object
- #depends_on(list_or_item) ⇒ Object
- #duplicate(new_id, &block) ⇒ Object
- #duplicate_resource(parent, progenitor, new_id) ⇒ Object
-
#fetch_provider ⇒ Object
There are two types of provider, the string given to a resource, and the object with attributes this method takes the string on the resource and returns the object.
-
#find_remote_as_individual? ⇒ Boolean
By default, remote resources are bulk-retrieved.
- #for_resource ⇒ Object
- #in_project ⇒ Object
-
#initialize(type, id, &block) ⇒ Resource
constructor
A new instance of Resource.
- #matched_remote_resource ⇒ Object
- #merge_parent_tags ⇒ Object
- #merge_tags(source) ⇒ Object
-
#new? ⇒ Boolean
Look up the resource remotly to see if it exists This method will not work within a resource definition.
- #remote_resource ⇒ Object
- #remote_resource_params ⇒ Object
- #reset ⇒ Object
- #setup_tags_if_needed ⇒ Object
-
#short_id ⇒ Object
strip project information if project.
- #short_name ⇒ Object
-
#short_type ⇒ Object
VIEW METHODS.
-
#support_tags? ⇒ Boolean
VALIDATION METHODS.
- #terraform_name ⇒ Object
-
#to_id_or_ref ⇒ Object
This tries to return the terraform ID, if that is nil, then it will return the ref.
- #to_ref(attribute = "id") ⇒ Object
-
#to_s ⇒ Object
Override to_s.
-
#to_terraform ⇒ Object
Terraform methods.
- #to_terraform_json ⇒ Object
- #to_terraform_state ⇒ Object
- #validate_has_tag(tag) ⇒ Object
- #validate_required_subresource(subresource) ⇒ Object
- #validate_subresource_required_attributes(subresource, keys) ⇒ Object
Methods included from HasLifecycle
Methods included from HasValidations
#errors, included, #validate_at_least_one_present, #validate_cidr_block, #validate_only_one_present, #validate_policy_length, #validate_required_attributes
Methods included from HasSubResources
#assign_block, #attribute_missing, #delete_all_subresources, #delete_subresources_where, #subresources
Methods included from HasAttributes
#[], #[]=, #assign_attribute, #assign_block, #attribute_missing, #attribute_procs, #attributes, #delete, #eager_load_attributes, #method_missing, #reset_attributes, #retrieve_attribute, #terraform_attribute_ref, #terraform_attributes, #timeout
Constructor Details
#initialize(type, id, &block) ⇒ Resource
Returns a new instance of Resource.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/geoengineer/resource.rb', line 26 def initialize(type, id, &block) @type = type @id = id # Remembering parents, grand parents ... @environment = nil @project = nil @template = nil # Most resources will have the same _geo_id and _terraform_id # Each resource must define _terraform_id _geo_id -> { _terraform_id } instance_exec(self, &block) if block_given? execute_lifecycle(:after, :initialize) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class HasAttributes
Instance Attribute Details
#environment ⇒ Object
Returns the value of attribute environment.
18 19 20 |
# File 'lib/geoengineer/resource.rb', line 18 def environment @environment end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
20 21 22 |
# File 'lib/geoengineer/resource.rb', line 20 def id @id end |
#project ⇒ Object
Returns the value of attribute project.
18 19 20 |
# File 'lib/geoengineer/resource.rb', line 18 def project @project end |
#template ⇒ Object
Returns the value of attribute template.
18 19 20 |
# File 'lib/geoengineer/resource.rb', line 18 def template @template end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
20 21 22 |
# File 'lib/geoengineer/resource.rb', line 20 def type @type end |
Class Method Details
._deep_symbolize_keys(obj) ⇒ Object
233 234 235 236 237 238 239 240 241 242 |
# File 'lib/geoengineer/resource.rb', line 233 def self._deep_symbolize_keys(obj) case obj when Hash then obj.each_with_object({}) do |(key, value), hash| hash[key.to_sym] = _deep_symbolize_keys(value) end when Array then obj.map { |value| _deep_symbolize_keys(value) } else obj end end |
._fetch_remote_resources(provider) ⇒ Object
This method must be implemented for each resource type it must return a list of hashes with at least the key
218 219 220 |
# File 'lib/geoengineer/resource.rb', line 218 def self._fetch_remote_resources(provider) throw "NOT IMPLEMENTED ERROR for #{name}" end |
._ignore_remote_resource?(resource) ⇒ Boolean
229 230 231 |
# File 'lib/geoengineer/resource.rb', line 229 def self._ignore_remote_resource?(resource) _resources_to_ignore.include?(_deep_symbolize_keys(resource)[:_geo_id]) end |
._resources_to_ignore ⇒ Object
This method allows you to specify certain remote resources that for whatever reason, cannot or should not be codified. It expects a list of _geo_ids, and can be overriden in child classes.
225 226 227 |
# File 'lib/geoengineer/resource.rb', line 225 def self._resources_to_ignore [] end |
.build(resource_hash) ⇒ Object
244 245 246 247 248 |
# File 'lib/geoengineer/resource.rb', line 244 def self.build(resource_hash) GeoEngineer::Resource.new(type_from_class_name, resource_hash['_geo_id']) { resource_hash.each { |k, v| self[k] = v } } end |
.clear_remote_resource_cache ⇒ Object
250 251 252 |
# File 'lib/geoengineer/resource.rb', line 250 def self.clear_remote_resource_cache @_rr_cache = nil end |
.fetch_remote_resources(provider) ⇒ Object
205 206 207 208 209 210 211 212 213 214 |
# File 'lib/geoengineer/resource.rb', line 205 def self.fetch_remote_resources(provider) # The cache key is the provider # no provider no resource provider_id = provider&.terraform_id || DEFAULT_PROVIDER @_rr_cache ||= {} return @_rr_cache[provider_id] if @_rr_cache[provider_id] @_rr_cache[provider_id] = _fetch_remote_resources(provider) .reject { |resource| _ignore_remote_resource?(resource) } .map { |resource| GeoEngineer::Resource.build(resource) } end |
.type_from_class_name ⇒ Object
CLASS METHODS
326 327 328 329 330 331 332 |
# File 'lib/geoengineer/resource.rb', line 326 def self.type_from_class_name # from http://stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby name.split('::').last .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .tr("-", "_").downcase end |
Instance Method Details
#_find_remote_resource ⇒ Object
This method will fetch the remote resource that has the same _geo_id as the codified resource. This method will:
-
return resource individually if class has defined how to do so
-
return nil if no resource is found
-
return an instance of Resource with the remote attributes
-
throw an error if more than one resource has the same _geo_id
172 173 174 175 176 177 178 179 |
# File 'lib/geoengineer/resource.rb', line 172 def _find_remote_resource return GeoEngineer::Resource.build(remote_resource_params) if find_remote_as_individual? matches = matched_remote_resource throw "ERROR:\"#{type}.#{id}\" has #{matches.length} remote resources" if matches.length > 1 matches.first end |
#_json_file(attribute, path, binding_obj = nil) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/geoengineer/resource.rb', line 148 def _json_file(attribute, path, binding_obj = nil) raise "file #{path} not found" unless File.file?(path) raw = File.open(path, "rb").read interpolated = ERB.new(raw).result(binding_obj).to_s # normalize JSON to prevent terraform from e.g. newlines as legitimate changes normalized = _normalize_json(interpolated) send(attribute, normalized) end |
#_normalize_json(json) ⇒ Object
160 161 162 |
# File 'lib/geoengineer/resource.rb', line 160 def _normalize_json(json) JSON.parse(json).to_json end |
#build_individual_remote_resource ⇒ Object
191 192 193 |
# File 'lib/geoengineer/resource.rb', line 191 def build_individual_remote_resource self.class.build(remote_resource_params) end |
#depends_on(list_or_item) ⇒ Object
50 51 52 53 |
# File 'lib/geoengineer/resource.rb', line 50 def depends_on(list_or_item) self[:depends_on] ||= [] self[:depends_on].concat([list_or_item].flatten.compact) end |
#duplicate(new_id, &block) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/geoengineer/resource.rb', line 117 def duplicate(new_id, &block) parent = @project || @environment return unless parent duplicated = duplicate_resource(parent, self, new_id) duplicated.reset duplicated.instance_exec(duplicated, &block) if block_given? duplicated.execute_lifecycle(:after, :initialize) duplicated end |
#duplicate_resource(parent, progenitor, new_id) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/geoengineer/resource.rb', line 129 def duplicate_resource(parent, progenitor, new_id) parent.resource(progenitor.type, new_id) do # We want to set all attributes from the parent, EXCEPT _geo_id and _terraform_id # Which should be set according to the init logic progenitor.attributes.each do |key, value| self[key] = value unless %w(_geo_id _terraform_id).include?(key) end progenitor.subresources.each do |subresource| duplicated_subresource = GeoEngineer::SubResource.new(self, subresource.type) do subresource.attributes.each do |key, value| self[key] = value end end self.subresources << duplicated_subresource end end end |
#fetch_provider ⇒ Object
There are two types of provider, the string given to a resource, and the object with attributes this method takes the string on the resource and returns the object
201 202 203 |
# File 'lib/geoengineer/resource.rb', line 201 def fetch_provider environment&.find_provider(provider) end |
#find_remote_as_individual? ⇒ Boolean
By default, remote resources are bulk-retrieved. In order to fetch a remote resource as an individual, the child-class over-write ‘find_remote_as_individual?’ and ‘remote_resource_params’
183 184 185 |
# File 'lib/geoengineer/resource.rb', line 183 def find_remote_as_individual? false end |
#for_resource ⇒ Object
274 275 276 |
# File 'lib/geoengineer/resource.rb', line 274 def for_resource "for resource \"#{type}.#{id}\" #{in_project}" end |
#in_project ⇒ Object
270 271 272 |
# File 'lib/geoengineer/resource.rb', line 270 def in_project project.nil? ? "" : "in project \"#{project.full_name}\"" end |
#matched_remote_resource ⇒ Object
195 196 197 |
# File 'lib/geoengineer/resource.rb', line 195 def matched_remote_resource self.class.fetch_remote_resources(fetch_provider).select { |r| r._geo_id == _geo_id } end |
#merge_parent_tags ⇒ Object
282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/geoengineer/resource.rb', line 282 def return unless %i(project environment).each do |source| parent = send(source) next unless parent next unless parent.methods.include?(:attributes) next unless parent&. (source) end end |
#merge_tags(source) ⇒ Object
294 295 296 297 298 299 |
# File 'lib/geoengineer/resource.rb', line 294 def (source) send(source)..map(&:attributes).reduce({}, :merge) .each { |key, value| .attributes[key] ||= value } end |
#new? ⇒ Boolean
Look up the resource remotly to see if it exists This method will not work within a resource definition
57 58 59 |
# File 'lib/geoengineer/resource.rb', line 57 def new? !remote_resource end |
#remote_resource ⇒ Object
42 43 44 45 46 47 48 |
# File 'lib/geoengineer/resource.rb', line 42 def remote_resource return @_remote if @_remote_searched @_remote = _find_remote_resource @_remote_searched = true @_remote&.local_resource = self @_remote end |
#remote_resource_params ⇒ Object
187 188 189 |
# File 'lib/geoengineer/resource.rb', line 187 def remote_resource_params {} end |
#reset ⇒ Object
110 111 112 113 114 115 |
# File 'lib/geoengineer/resource.rb', line 110 def reset reset_attributes @_remote_searched = false @_remote = nil self end |
#setup_tags_if_needed ⇒ Object
278 279 280 |
# File 'lib/geoengineer/resource.rb', line 278 def {} unless end |
#short_id ⇒ Object
strip project information if project
260 261 262 263 264 |
# File 'lib/geoengineer/resource.rb', line 260 def short_id si = id.to_s.tr('-', "_") si = si.gsub(project.full_id_name, '') if project si.gsub('__', '_').gsub(/^_|_$/, '') end |
#short_name ⇒ Object
266 267 268 |
# File 'lib/geoengineer/resource.rb', line 266 def short_name "#{short_type}.#{short_id}" end |
#short_type ⇒ Object
VIEW METHODS
255 256 257 |
# File 'lib/geoengineer/resource.rb', line 255 def short_type type end |
#support_tags? ⇒ Boolean
VALIDATION METHODS
302 303 304 |
# File 'lib/geoengineer/resource.rb', line 302 def true end |
#terraform_name ⇒ Object
92 93 94 |
# File 'lib/geoengineer/resource.rb', line 92 def terraform_name "#{type}.#{id}" end |
#to_id_or_ref ⇒ Object
This tries to return the terraform ID, if that is nil, then it will return the ref
106 107 108 |
# File 'lib/geoengineer/resource.rb', line 106 def to_id_or_ref _terraform_id || to_ref end |
#to_ref(attribute = "id") ⇒ Object
101 102 103 |
# File 'lib/geoengineer/resource.rb', line 101 def to_ref(attribute = "id") "${#{terraform_name}.#{attribute}}" end |
#to_s ⇒ Object
Override to_s
97 98 99 |
# File 'lib/geoengineer/resource.rb', line 97 def to_s terraform_name end |
#to_terraform ⇒ Object
Terraform methods
62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/geoengineer/resource.rb', line 62 def to_terraform sb = ["resource #{@type.inspect} #{@id.inspect} { "] sb.concat terraform_attributes.map { |k, v| " #{k.to_s.inspect} = #{v.inspect}" } sb.concat subresources.map(&:to_terraform) sb << " }" sb.join("\n") end |
#to_terraform_json ⇒ Object
74 75 76 77 78 79 80 81 |
# File 'lib/geoengineer/resource.rb', line 74 def to_terraform_json json = terraform_attributes subresources.map(&:to_terraform_json).each do |k, v| json[k] ||= [] json[k] << v end json end |
#to_terraform_state ⇒ Object
83 84 85 86 87 88 89 90 |
# File 'lib/geoengineer/resource.rb', line 83 def to_terraform_state { type: @type, primary: { id: _terraform_id } } end |
#validate_has_tag(tag) ⇒ Object
318 319 320 321 322 323 |
# File 'lib/geoengineer/resource.rb', line 318 def validate_has_tag(tag) errs = [] errs << validate_required_subresource(:tags) errs.concat(validate_subresource_required_attributes(:tags, [tag])) errs end |
#validate_required_subresource(subresource) ⇒ Object
306 307 308 |
# File 'lib/geoengineer/resource.rb', line 306 def validate_required_subresource(subresource) "Subresource '#{subresource}'' required #{for_resource}" if send(subresource.to_sym).nil? end |
#validate_subresource_required_attributes(subresource, keys) ⇒ Object
310 311 312 313 314 315 316 |
# File 'lib/geoengineer/resource.rb', line 310 def validate_subresource_required_attributes(subresource, keys) send("all_#{subresource}".to_sym).map do |sr| keys.map do |key| "#{key} attribute on subresource #{subresource} nil #{for_resource}" if sr[key].nil? end end.flatten.compact end |