geoengineer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +476 -0
- data/bin/geo +7 -0
- data/lib/geoengineer.rb +29 -0
- data/lib/geoengineer/cli/geo_cli.rb +208 -0
- data/lib/geoengineer/cli/status_command.rb +101 -0
- data/lib/geoengineer/cli/terraform_commands.rb +59 -0
- data/lib/geoengineer/environment.rb +186 -0
- data/lib/geoengineer/output.rb +27 -0
- data/lib/geoengineer/project.rb +79 -0
- data/lib/geoengineer/resource.rb +200 -0
- data/lib/geoengineer/resources/aws_db_instance.rb +46 -0
- data/lib/geoengineer/resources/aws_db_parameter_group.rb +25 -0
- data/lib/geoengineer/resources/aws_elasticache_cluster.rb +50 -0
- data/lib/geoengineer/resources/aws_elasticache_parameter_group.rb +30 -0
- data/lib/geoengineer/resources/aws_elasticache_replication_group.rb +44 -0
- data/lib/geoengineer/resources/aws_elasticache_subnet_group.rb +25 -0
- data/lib/geoengineer/resources/aws_elasticsearch_domain.rb +35 -0
- data/lib/geoengineer/resources/aws_elb.rb +57 -0
- data/lib/geoengineer/resources/aws_iam_policy.rb +53 -0
- data/lib/geoengineer/resources/aws_iam_user.rb +42 -0
- data/lib/geoengineer/resources/aws_instance.rb +24 -0
- data/lib/geoengineer/resources/aws_proxy_protocol_policy.rb +39 -0
- data/lib/geoengineer/resources/aws_redshift_cluster.rb +23 -0
- data/lib/geoengineer/resources/aws_route53_record.rb +32 -0
- data/lib/geoengineer/resources/aws_route53_zone.rb +21 -0
- data/lib/geoengineer/resources/aws_s3_bucket.rb +54 -0
- data/lib/geoengineer/resources/aws_security_group.rb +53 -0
- data/lib/geoengineer/resources/aws_ses_receipt_rule.rb +38 -0
- data/lib/geoengineer/resources/aws_ses_receipt_rule_set.rb +28 -0
- data/lib/geoengineer/resources/aws_sns_topic.rb +28 -0
- data/lib/geoengineer/resources/aws_sns_topic_subscription.rb +42 -0
- data/lib/geoengineer/resources/aws_sqs_queue.rb +37 -0
- data/lib/geoengineer/resources/iam/statement.rb +43 -0
- data/lib/geoengineer/sub_resource.rb +35 -0
- data/lib/geoengineer/template.rb +28 -0
- data/lib/geoengineer/utils/aws_clients.rb +63 -0
- data/lib/geoengineer/utils/has_attributes.rb +97 -0
- data/lib/geoengineer/utils/has_lifecycle.rb +54 -0
- data/lib/geoengineer/utils/has_resources.rb +63 -0
- data/lib/geoengineer/utils/has_sub_resources.rb +43 -0
- data/lib/geoengineer/utils/has_validations.rb +57 -0
- data/lib/geoengineer/utils/null_object.rb +17 -0
- data/lib/geoengineer/version.rb +3 -0
- data/spec/environment_spec.rb +140 -0
- data/spec/output_spec.rb +3 -0
- data/spec/project_spec.rb +79 -0
- data/spec/resource_spec.rb +169 -0
- data/spec/resources/aws_db_instance_spec.rb +23 -0
- data/spec/resources/aws_db_parameter_group_spec.rb +23 -0
- data/spec/resources/aws_elasticache_replication_group_spec.rb +29 -0
- data/spec/resources/aws_elasticache_subnet_group_spec.rb +31 -0
- data/spec/resources/aws_elasticcache_cluster_spec.rb +23 -0
- data/spec/resources/aws_elasticcache_parameter_group_spec.rb +26 -0
- data/spec/resources/aws_elasticsearch_domain_spec.rb +22 -0
- data/spec/resources/aws_elb_spec.rb +65 -0
- data/spec/resources/aws_iam_policy.rb +35 -0
- data/spec/resources/aws_iam_user.rb +35 -0
- data/spec/resources/aws_instance_spec.rb +26 -0
- data/spec/resources/aws_proxy_protocol_policy_spec.rb +5 -0
- data/spec/resources/aws_redshift_cluster_spec.rb +25 -0
- data/spec/resources/aws_route53_record_spec.rb +41 -0
- data/spec/resources/aws_route53_zone_spec.rb +34 -0
- data/spec/resources/aws_s3_bucket_spec.rb +39 -0
- data/spec/resources/aws_security_group_spec.rb +80 -0
- data/spec/resources/aws_ses_receipt_rule.rb +33 -0
- data/spec/resources/aws_ses_receipt_rule_set.rb +25 -0
- data/spec/resources/aws_sns_topic_spec.rb +23 -0
- data/spec/resources/aws_sns_topic_subscription.rb +35 -0
- data/spec/resources/aws_sqs_queue_spec.rb +20 -0
- data/spec/rubocop_spec.rb +13 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/sub_resource_spec.rb +20 -0
- data/spec/template_spec.rb +39 -0
- data/spec/utils/has_attributes_spec.rb +118 -0
- data/spec/utils/has_lifecycle_spec.rb +24 -0
- data/spec/utils/has_resources_spec.rb +68 -0
- data/spec/utils/has_subresources_spec.rb +29 -0
- data/spec/utils/has_validations_spec.rb +35 -0
- data/spec/utils/null_object_spec.rb +18 -0
- metadata +303 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsSecurityGroup is the +aws_security_group+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/security_group.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsSecurityGroup < GeoEngineer::Resource
|
7
|
+
validate :validate_correct_cidr_blocks
|
8
|
+
validate -> { validate_required_attributes([:name, :description]) }
|
9
|
+
validate -> {
|
10
|
+
validate_subresource_required_attributes(:ingress, [:from_port, :protocol, :to_port])
|
11
|
+
}
|
12
|
+
validate -> {
|
13
|
+
validate_subresource_required_attributes(:egress, [:from_port, :protocol, :to_port])
|
14
|
+
}
|
15
|
+
validate -> { validate_has_tag(:Name) }
|
16
|
+
|
17
|
+
before :validation, -> { flatten_cidr_and_sg_blocks }
|
18
|
+
|
19
|
+
after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
|
20
|
+
after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
|
21
|
+
|
22
|
+
def flatten_cidr_and_sg_blocks
|
23
|
+
(self.all_ingress + self.all_egress).each do |in_eg|
|
24
|
+
in_eg.cidr_blocks = in_eg.cidr_blocks.flatten if in_eg.cidr_blocks
|
25
|
+
in_eg.security_groups = in_eg.security_groups.flatten if in_eg.security_groups
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_correct_cidr_blocks
|
30
|
+
errors = []
|
31
|
+
(self.all_ingress + self.all_egress).each do |in_eg|
|
32
|
+
next unless in_eg.cidr_blocks
|
33
|
+
in_eg.cidr_blocks.each do |cidr|
|
34
|
+
_, error = validate_cidr_block(cidr)
|
35
|
+
errors << error unless error.nil?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
errors
|
39
|
+
end
|
40
|
+
|
41
|
+
def short_type
|
42
|
+
"sg"
|
43
|
+
end
|
44
|
+
|
45
|
+
def self._fetch_remote_resources
|
46
|
+
AwsClients.ec2.describe_security_groups['security_groups'].map(&:to_h).map do |sg|
|
47
|
+
sg[:name] = sg[:group_name]
|
48
|
+
sg[:_terraform_id] = sg[:group_id]
|
49
|
+
sg[:_geo_id] = sg[:tags] ? sg[:tags].select { |x| x[:key] == "Name" }.first[:value] : nil
|
50
|
+
sg
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsSesReceiptRule is the +ses_receipt_rule+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/ses_receipt_rule.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsSesReceiptRule < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:name, :rule_set_name]) }
|
8
|
+
|
9
|
+
after :initialize, -> {
|
10
|
+
_terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
|
11
|
+
}
|
12
|
+
after :initialize, -> {
|
13
|
+
_geo_id -> { name.to_s }
|
14
|
+
}
|
15
|
+
|
16
|
+
def to_terraform_state
|
17
|
+
tfstate = super
|
18
|
+
tfstate[:primary][:attributes] = {
|
19
|
+
'name' => name,
|
20
|
+
'rule_set_name' => rule_set_name,
|
21
|
+
'enabled' => (enabled || 'false')
|
22
|
+
}
|
23
|
+
tfstate
|
24
|
+
end
|
25
|
+
|
26
|
+
def support_tags?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def self._fetch_remote_resources
|
31
|
+
AwsClients.ses.describe_active_receipt_rule_set.rules.map(&:to_h).map do |rule|
|
32
|
+
{
|
33
|
+
'_terraform_id' => rule[:name],
|
34
|
+
'_geo_id' => rule[:name]
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsSesReceiptRuleSet is the +ses_receipt_rule_set+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/ses_receipt_rule_set.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsSesReceiptRuleSet < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:rule_set_name]) }
|
8
|
+
|
9
|
+
after :initialize, -> {
|
10
|
+
_terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
|
11
|
+
}
|
12
|
+
after :initialize, -> {
|
13
|
+
_geo_id -> { rule_set_name.to_s }
|
14
|
+
}
|
15
|
+
|
16
|
+
def support_tags?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def self._fetch_remote_resources
|
21
|
+
AwsClients.ses.list_receipt_rule_sets.rule_sets.map(&:to_h).map do |rule_set|
|
22
|
+
{
|
23
|
+
'_terraform_id' => rule_set[:name],
|
24
|
+
'_geo_id' => rule_set[:name]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsSnsTopic is the +aws_sns_topic+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/sns_topic.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsSnsTopic < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:name]) }
|
8
|
+
|
9
|
+
after :initialize, -> {
|
10
|
+
_terraform_id -> {
|
11
|
+
"arn:aws:sns:#{environment.region}:#{environment.account_id}:#{name}"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
def support_tags?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def self._fetch_remote_resources
|
20
|
+
AwsClients.sns.list_topics.topics.map(&:to_h).map do |topic|
|
21
|
+
{
|
22
|
+
'_terraform_id' => topic[:topic_arn],
|
23
|
+
'_geo_id' => topic[:topic_arn],
|
24
|
+
'name' => topic[:topic_arn].split(':').last
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsSnsSubscription is the +sns_topic_subscription+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/sns_topic_subscription.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsSnsTopicSubscription < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:protocol, :topic_arn, :endpoint]) }
|
8
|
+
|
9
|
+
after :initialize, -> {
|
10
|
+
_terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
|
11
|
+
}
|
12
|
+
after :initialize, -> {
|
13
|
+
_geo_id -> { "#{topic_arn}::#{protocol}::#{endpoint}" }
|
14
|
+
}
|
15
|
+
|
16
|
+
def to_terraform_state
|
17
|
+
tfstate = super
|
18
|
+
tfstate[:primary][:attributes] = {
|
19
|
+
'topic_arn' => topic_arn,
|
20
|
+
'endpoint' => endpoint,
|
21
|
+
'protocol' => protocol,
|
22
|
+
'confirmation_timeout_in_minutes' => "1",
|
23
|
+
'endpoint_auto_confirms' => "false"
|
24
|
+
}
|
25
|
+
tfstate
|
26
|
+
end
|
27
|
+
|
28
|
+
def support_tags?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def self._fetch_remote_resources
|
33
|
+
AwsClients.sns.list_subscriptions.subscriptions.map(&:to_h).map do |subscription|
|
34
|
+
{
|
35
|
+
'_terraform_id' => subscription[:subscription_arn],
|
36
|
+
'_geo_id' => "#{subscription[:topic_arn]}::" \
|
37
|
+
"#{subscription[:protocol]}::" \
|
38
|
+
"#{subscription[:endpoint]}"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsSqsQueue is the +aws_sqs_queue+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/sqs_queue.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsSqsQueue < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:name]) }
|
8
|
+
|
9
|
+
after :initialize, -> {
|
10
|
+
_terraform_id -> {
|
11
|
+
"https://sqs.#{environment.region}.amazonaws.com/#{environment.account_id}/#{name}"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
# The loadbalancer and the instance ports are necessary in the terraform state for the policy
|
16
|
+
def to_terraform_state
|
17
|
+
tfstate = super
|
18
|
+
tfstate[:primary][:attributes] = {
|
19
|
+
'name' => name
|
20
|
+
}
|
21
|
+
tfstate
|
22
|
+
end
|
23
|
+
|
24
|
+
def support_tags?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def self._fetch_remote_resources
|
29
|
+
AwsClients.sqs.list_queues['queue_urls'].map do |queue|
|
30
|
+
{
|
31
|
+
'_terraform_id' => queue,
|
32
|
+
'_geo_id' => queue,
|
33
|
+
'name' => URI.parse(queue).path.split('/').last
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module GeoEngineer::IAM
|
2
|
+
########################################################################
|
3
|
+
# A Statement object is a single iam policy statement with a Sid,
|
4
|
+
# effect, action, and condition. Used to assist validating IAM policies.
|
5
|
+
########################################################################
|
6
|
+
class Statement
|
7
|
+
attr_reader :sid, :action, :effect
|
8
|
+
|
9
|
+
def initialize(raw)
|
10
|
+
@action = raw["Action"]
|
11
|
+
@effect = raw["Effect"]
|
12
|
+
@raw = raw
|
13
|
+
@sid = raw["Sid"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def secure_transport?
|
17
|
+
secure_transport = @raw.dig('Condition', 'Bool', 'aws:SecureTransport')
|
18
|
+
secure_transport == "true"
|
19
|
+
end
|
20
|
+
|
21
|
+
def ip_restrictions
|
22
|
+
cidr_blocks = []
|
23
|
+
cidr_blocks << @raw.dig('Condition', 'IpAddress', 'aws:SourceIP')
|
24
|
+
cidr_blocks << @raw.dig('Condition', 'IpAddressIfExists', 'aws:SourceIP')
|
25
|
+
cidr_blocks.flatten.compact
|
26
|
+
end
|
27
|
+
|
28
|
+
def ip_restriction_exists?
|
29
|
+
return true unless ip_restrictions.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def vpc_restrictions
|
33
|
+
vpcs = []
|
34
|
+
vpcs << @raw.dig('Condition', 'StringEqualsifExists', 'aws:sourceVpce')
|
35
|
+
vpcs << @raw.dig('Condition', 'ForAnyValue:StringEquals', 'aws:sourceVpce')
|
36
|
+
vpcs.flatten.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def vpc_restriction_exists?
|
40
|
+
return true unless vpc_restrictions.empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
########################################################################
|
2
|
+
# SubResources are included in resources with their own rules
|
3
|
+
#
|
4
|
+
# For example, +ingress+ in +aws_security_group+ is a subresource.
|
5
|
+
#
|
6
|
+
# A SubResource can have arbitrary attributes
|
7
|
+
########################################################################
|
8
|
+
class GeoEngineer::SubResource
|
9
|
+
include HasAttributes
|
10
|
+
|
11
|
+
attr_reader :type
|
12
|
+
|
13
|
+
def initialize(resource, type, &block)
|
14
|
+
@resource = resource
|
15
|
+
@type = type.to_s
|
16
|
+
instance_exec(self, &block) if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def _terraform_id
|
20
|
+
@resource._terraform_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_terraform
|
24
|
+
sb = [" #{@type} { "]
|
25
|
+
sb.concat terraform_attributes.map { |k, v|
|
26
|
+
" #{k.to_s.inspect} = #{v.inspect}"
|
27
|
+
}
|
28
|
+
sb << " }"
|
29
|
+
sb.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_terraform_json
|
33
|
+
[@type, terraform_attributes]
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
########################################################################
|
2
|
+
# Override to define recommended patterns of resource use
|
3
|
+
########################################################################
|
4
|
+
class GeoEngineer::Template
|
5
|
+
include HasAttributes
|
6
|
+
include HasResources
|
7
|
+
|
8
|
+
def initialize(name, project, parameters = {})
|
9
|
+
@project = project
|
10
|
+
@name = name
|
11
|
+
@environment = @project.environment
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource(type, id, &block)
|
15
|
+
return find_resource(type, id) unless block_given?
|
16
|
+
resource = create_resource(type, id, &block)
|
17
|
+
resource.template = self
|
18
|
+
resource.project = @project
|
19
|
+
resource.environment = @environment
|
20
|
+
resource
|
21
|
+
end
|
22
|
+
|
23
|
+
# The resources that are passed to the block on instantiation
|
24
|
+
# This can be overridden to specify the order of the templates resources
|
25
|
+
def template_resources
|
26
|
+
resources
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsClients contains a list of aws-clients for use
|
3
|
+
# The main reason for their central management is their initialisation testing and stubbing
|
4
|
+
########################################################################
|
5
|
+
class AwsClients
|
6
|
+
def self.stub!
|
7
|
+
@stub_aws = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.stubbed?
|
11
|
+
@stub_aws || false
|
12
|
+
end
|
13
|
+
|
14
|
+
# Clients
|
15
|
+
|
16
|
+
def self.ec2
|
17
|
+
@aws_ec2 ||= Aws::EC2::Client.new({ stub_responses: stubbed? })
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.elasticache
|
21
|
+
@aws_elasticache ||= Aws::ElastiCache::Client.new({ stub_responses: stubbed? })
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.elasticsearch
|
25
|
+
@aws_elasticsearch ||= Aws::ElasticsearchService::Client.new({ stub_responses: stubbed? })
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.elb
|
29
|
+
@aws_elb ||= Aws::ElasticLoadBalancing::Client.new({ stub_responses: stubbed? })
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.iam
|
33
|
+
@aws_iam ||= Aws::IAM::Client.new({ stub_responses: stubbed? })
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.rds
|
37
|
+
@aws_rds ||= Aws::RDS::Client.new({ stub_responses: stubbed? })
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.redshift
|
41
|
+
@aws_redshift ||= Aws::Redshift::Client.new({ stub_responses: stubbed? })
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.route53
|
45
|
+
@aws_route53 ||= Aws::Route53::Client.new({ stub_responses: stubbed? })
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.s3
|
49
|
+
@aws_s3 ||= Aws::S3::Client.new({ stub_responses: stubbed? })
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.ses
|
53
|
+
@aws_ses ||= Aws::SES::Client.new({ stub_responses: stubbed? })
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.sns
|
57
|
+
@aws_sns ||= Aws::SNS::Client.new({ stub_responses: stubbed? })
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.sqs
|
61
|
+
@aws_sqs ||= Aws::SQS::Client.new({ stub_responses: stubbed? })
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
########################################################################
|
2
|
+
# HasAttributes allows objects to have arbitrary attributes associated with it.
|
3
|
+
########################################################################
|
4
|
+
module HasAttributes
|
5
|
+
def attributes
|
6
|
+
@_attributes = {} unless @_attributes
|
7
|
+
@_attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
retrieve_attribute(key.to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(key, val)
|
15
|
+
assign_attribute(key.to_s, [val])
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(key)
|
19
|
+
attributes.delete(key.to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def assign_block(name, *args, &block)
|
23
|
+
throw "#{self.class.inspect} cannot handle block"
|
24
|
+
end
|
25
|
+
|
26
|
+
def assign_attribute(name, args)
|
27
|
+
# this is a setter
|
28
|
+
name = name[0...-1] if name.end_with?"="
|
29
|
+
val = args.length == 1 ? args[0] : args
|
30
|
+
attributes[name] = val
|
31
|
+
val
|
32
|
+
end
|
33
|
+
|
34
|
+
def retrieve_attribute(name)
|
35
|
+
# this is a getter
|
36
|
+
val = attributes[name]
|
37
|
+
val = attribute_missing(name) if val.nil?
|
38
|
+
if val.is_a? Proc
|
39
|
+
val = val.call()
|
40
|
+
# cache the value to override the Proc
|
41
|
+
attributes[name] = val
|
42
|
+
end
|
43
|
+
val
|
44
|
+
end
|
45
|
+
|
46
|
+
def attribute_missing(name)
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# This method allows attributes to be defined on an instance
|
51
|
+
# without explicitly defining which attributes are allowed
|
52
|
+
#
|
53
|
+
# The flow is:
|
54
|
+
# 1. If the method receives a block, it will pass it to the `assign_block` method to be handled.
|
55
|
+
# By default this method will throw an error, but can be overridden by the class.
|
56
|
+
# 2. If the method has one or more argument it will assume it is a setter
|
57
|
+
# and store the argument as an attribute.
|
58
|
+
# 3. If the method has no arguments it will assume it is a getter and retrieve the value.
|
59
|
+
# If the retrieved value is a `Proc` it will execute it and store the returned value,
|
60
|
+
# this will allow for caching expensive calls and only calling if requested
|
61
|
+
def method_missing(name, *args, &block)
|
62
|
+
name = name.to_s
|
63
|
+
if block_given?
|
64
|
+
# A class can override a
|
65
|
+
assign_block(name, *args, &block)
|
66
|
+
elsif args.length >= 1
|
67
|
+
assign_attribute(name, args)
|
68
|
+
elsif args.empty?
|
69
|
+
retrieve_attribute(name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# must override because of Object#timeout
|
74
|
+
def timeout(*args)
|
75
|
+
method_missing(:timeout, *args)
|
76
|
+
end
|
77
|
+
|
78
|
+
# This method creates a set of attributes for terraform to consume
|
79
|
+
def terraform_attributes
|
80
|
+
attributes
|
81
|
+
.select { |k, v| !k.nil? && !k.start_with?('_') }
|
82
|
+
.map { |k, v| [k, terraform_attribute_ref(k)] }
|
83
|
+
.select { |k, v| !v.nil? }
|
84
|
+
.to_h
|
85
|
+
end
|
86
|
+
|
87
|
+
def terraform_attribute_ref(k)
|
88
|
+
v = retrieve_attribute(k)
|
89
|
+
if v.is_a? GeoEngineer::Resource # convert Resource to reference for terraform
|
90
|
+
v.to_ref
|
91
|
+
elsif v.is_a? Array # map resources if attribute is an array
|
92
|
+
v.map { |vi| vi.is_a?(GeoEngineer::Resource) ? vi.to_ref : vi }
|
93
|
+
else
|
94
|
+
v
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|