geoengineer 0.1.0 → 0.1.1

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +5 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +5 -5
  5. data/lib/geoengineer/cli/geo_cli.rb +4 -5
  6. data/lib/geoengineer/cli/status_command.rb +7 -1
  7. data/lib/geoengineer/environment.rb +53 -51
  8. data/lib/geoengineer/project.rb +5 -24
  9. data/lib/geoengineer/resource.rb +89 -20
  10. data/lib/geoengineer/resources/aws_customer_gateway.rb +23 -0
  11. data/lib/geoengineer/resources/aws_eip.rb +43 -0
  12. data/lib/geoengineer/resources/aws_iam_group.rb +26 -0
  13. data/lib/geoengineer/resources/aws_iam_group_membership.rb +50 -0
  14. data/lib/geoengineer/resources/aws_iam_policy.rb +12 -4
  15. data/lib/geoengineer/resources/aws_iam_policy_attachment.rb +95 -0
  16. data/lib/geoengineer/resources/aws_iam_role.rb +45 -0
  17. data/lib/geoengineer/resources/aws_instance.rb +7 -4
  18. data/lib/geoengineer/resources/aws_internet_gateway.rb +23 -0
  19. data/lib/geoengineer/resources/aws_lambda_alias.rb +50 -0
  20. data/lib/geoengineer/resources/aws_lambda_event_source_mapping.rb +47 -0
  21. data/lib/geoengineer/resources/aws_lambda_function.rb +30 -0
  22. data/lib/geoengineer/resources/aws_lambda_permission.rb +74 -0
  23. data/lib/geoengineer/resources/aws_main_route_table_association.rb +51 -0
  24. data/lib/geoengineer/resources/aws_nat_gateway.rb +29 -0
  25. data/lib/geoengineer/resources/aws_network_acl.rb +38 -0
  26. data/lib/geoengineer/resources/aws_network_acl_rule.rb +50 -0
  27. data/lib/geoengineer/resources/aws_route.rb +47 -0
  28. data/lib/geoengineer/resources/aws_route53_record.rb +4 -0
  29. data/lib/geoengineer/resources/aws_route_table.rb +26 -0
  30. data/lib/geoengineer/resources/aws_route_table_association.rb +45 -0
  31. data/lib/geoengineer/resources/aws_security_group.rb +8 -5
  32. data/lib/geoengineer/resources/aws_subnet.rb +24 -0
  33. data/lib/geoengineer/resources/aws_vpc.rb +24 -0
  34. data/lib/geoengineer/resources/aws_vpc_dhcp_options.rb +29 -0
  35. data/lib/geoengineer/resources/aws_vpc_dhcp_options_association.rb +40 -0
  36. data/lib/geoengineer/resources/aws_vpc_endpoint.rb +26 -0
  37. data/lib/geoengineer/resources/aws_vpc_peering_connection.rb +29 -0
  38. data/lib/geoengineer/resources/aws_vpn_connection.rb +23 -0
  39. data/lib/geoengineer/resources/aws_vpn_connection_route.rb +35 -0
  40. data/lib/geoengineer/resources/aws_vpn_gateway.rb +22 -0
  41. data/lib/geoengineer/resources/aws_vpn_gateway_attachment.rb +41 -0
  42. data/lib/geoengineer/template.rb +20 -4
  43. data/lib/geoengineer/utils/aws_clients.rb +4 -0
  44. data/lib/geoengineer/utils/crc32.rb +61 -0
  45. data/lib/geoengineer/utils/has_attributes.rb +25 -11
  46. data/lib/geoengineer/utils/has_projects.rb +21 -0
  47. data/lib/geoengineer/utils/has_resources.rb +17 -4
  48. data/lib/geoengineer/utils/has_templates.rb +31 -0
  49. data/lib/geoengineer/utils/has_validations.rb +18 -3
  50. data/lib/geoengineer/version.rb +1 -1
  51. data/spec/environment_spec.rb +40 -19
  52. data/spec/project_spec.rb +2 -2
  53. data/spec/resource_spec.rb +87 -6
  54. data/spec/resources/aws_customer_gateway_spec.rb +24 -0
  55. data/spec/resources/aws_eip_spec.rb +29 -0
  56. data/spec/resources/aws_iam_group_membership_spec.rb +83 -0
  57. data/spec/resources/aws_iam_group_spec.rb +43 -0
  58. data/spec/resources/aws_iam_policy_attachment_spec.rb +80 -0
  59. data/spec/resources/{aws_iam_policy.rb → aws_iam_policy_spec.rb} +6 -5
  60. data/spec/resources/aws_iam_role_spec.rb +45 -0
  61. data/spec/resources/aws_internet_gateway_spec.rb +24 -0
  62. data/spec/resources/aws_lambda_alias_spec.rb +39 -0
  63. data/spec/resources/aws_lambda_event_source_mapping_spec.rb +53 -0
  64. data/spec/resources/aws_lambda_function_spec.rb +29 -0
  65. data/spec/resources/aws_lambda_permission_spec.rb +90 -0
  66. data/spec/resources/aws_main_route_table_association_spec.rb +57 -0
  67. data/spec/resources/aws_nat_gateway_spec.rb +31 -0
  68. data/spec/resources/aws_network_acl_rule_spec.rb +73 -0
  69. data/spec/resources/aws_network_acl_spec.rb +31 -0
  70. data/spec/resources/aws_route53_record_spec.rb +5 -0
  71. data/spec/resources/aws_route_spec.rb +47 -0
  72. data/spec/resources/aws_route_table_association_spec.rb +47 -0
  73. data/spec/resources/aws_route_table_spec.rb +24 -0
  74. data/spec/resources/aws_security_group_spec.rb +36 -2
  75. data/spec/resources/aws_subnet_spec.rb +24 -0
  76. data/spec/resources/aws_vpc_dhcp_options_association_spec.rb +43 -0
  77. data/spec/resources/aws_vpc_dhcp_options_spec.rb +24 -0
  78. data/spec/resources/aws_vpc_endpoint_spec.rb +41 -0
  79. data/spec/resources/aws_vpc_peering_connection_spec.rb +33 -0
  80. data/spec/resources/aws_vpc_spec.rb +24 -0
  81. data/spec/resources/aws_vpn_connection_route_spec.rb +43 -0
  82. data/spec/resources/aws_vpn_connection_spec.rb +41 -0
  83. data/spec/resources/aws_vpn_gateway_attachment_spec.rb +41 -0
  84. data/spec/resources/aws_vpn_gateway_spec.rb +39 -0
  85. data/spec/spec_helper.rb +3 -1
  86. data/spec/utils/crc32_spec.rb +14 -0
  87. data/spec/utils/has_attributes_spec.rb +22 -0
  88. data/spec/utils/has_resources_spec.rb +4 -0
  89. data/spec/utils/has_validations_spec.rb +45 -0
  90. metadata +117 -6
  91. metadata.gz.sig +1 -0
@@ -0,0 +1,24 @@
1
+ ########################################################################
2
+ # AwsVpc is the +aws_vpc+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpc.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpc < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:cidr_block]) }
8
+ validate -> { validate_has_tag(:Name) }
9
+ validate -> { validate_cidr_block(self.cidr_block) if self.cidr_block }
10
+
11
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
12
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
13
+
14
+ def self._fetch_remote_resources
15
+ AwsClients.ec2.describe_vpcs['vpcs'].map(&:to_h).map do |vpc|
16
+ vpc.merge(
17
+ {
18
+ _terraform_id: vpc[:vpc_id],
19
+ _geo_id: vpc[:tags]&.find { |tag| tag[:key] == "Name" }&.dig(:value)
20
+ }
21
+ )
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ ########################################################################
2
+ # AwsVpcDhcpOptions is the +aws_vpc_dhcp_options+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpc_dhcp_options.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpcDhcpOptions < GeoEngineer::Resource
7
+ validate -> { validate_has_tag(:Name) }
8
+ validate -> {
9
+ validate_at_least_one_present(
10
+ %i(
11
+ domain_name domain_name_servers ntp_servers netbios_name_servers netbios_node_type
12
+ )
13
+ )
14
+ }
15
+
16
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
17
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
18
+
19
+ def self._fetch_remote_resources
20
+ AwsClients.ec2.describe_dhcp_options['dhcp_options'].map(&:to_h).map do |options|
21
+ options.merge(
22
+ {
23
+ _terraform_id: options[:dhcp_options_id],
24
+ _geo_id: options[:tags]&.find { |tag| tag[:key] == "Name" }&.dig(:value)
25
+ }
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ ########################################################################
2
+ # AwsVpcDhcpOptionsAssociation is the +aws_vpc_dhcp_options_association+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpc_dhcp_options_association.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpcDhcpOptionsAssociation < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:dhcp_options_id, :vpc_id]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> { "#{dhcp_options_id}-#{vpc_id}" }
11
+ }
12
+
13
+ def to_terraform_state
14
+ tfstate = super
15
+ tfstate[:primary][:attributes] = {
16
+ 'vpc_id' => vpc_id,
17
+ 'dhcp_options_id' => dhcp_options_id
18
+ }
19
+ tfstate
20
+ end
21
+
22
+ def support_tags?
23
+ false
24
+ end
25
+
26
+ def self._fetch_remote_resources
27
+ AwsClients
28
+ .ec2
29
+ .describe_vpcs['vpcs']
30
+ .map(&:to_h)
31
+ .select { |vpc| vpc[:dhcp_options_id] }
32
+ .map do |vpc|
33
+ {
34
+ vpc_id: vpc[:vpc_id],
35
+ dhcp_options_id: vpc[:dhcp_options_id],
36
+ _terraform_id: "#{vpc[:dhcp_options_id]}-#{vpc[:vpc_id]}"
37
+ }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ ########################################################################
2
+ # AwsVpcEndpoint is the +aws_vpc_endpoint+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpc_endpoint.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpcEndpoint < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:vpc_id, :service_name]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { "#{vpc_id}::#{service_name}" } }
11
+
12
+ def support_tags?
13
+ false
14
+ end
15
+
16
+ def self._fetch_remote_resources
17
+ AwsClients.ec2.describe_vpc_endpoints['vpc_endpoints'].map(&:to_h).map do |endpoint|
18
+ endpoint.merge(
19
+ {
20
+ _terraform_id: endpoint[:vpc_endpoint_id],
21
+ _geo_id: "#{endpoint[:vpc_id]}::#{endpoint[:service_name]}"
22
+ }
23
+ )
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ ########################################################################
2
+ # AwsVpcPeeringConnection is the +aws_vpc_peering_connection+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpc_peering.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpcPeeringConnection < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:peer_owner_id, :peer_vpc_id, :vpc_id]) }
8
+ validate -> { validate_has_tag(:Name) }
9
+
10
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
11
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
12
+
13
+ def self._fetch_remote_resources
14
+ AwsClients
15
+ .ec2
16
+ .describe_vpc_peering_connections['vpc_peering_connections']
17
+ .map(&:to_h)
18
+ .map { |connection| _merge_ids(connection) }
19
+ end
20
+
21
+ def self._merge_ids(connection)
22
+ connection.merge(
23
+ {
24
+ _terraform_id: connection[:vpc_peering_connection_id],
25
+ _geo_id: connection[:tags]&.find { |tag| tag[:key] == "Name" }&.dig(:value)
26
+ }
27
+ )
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ ########################################################################
2
+ # AwsVpnConnection is the +aws_vpn_connection+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpn_connection.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpnConnection < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:customer_gateway_id, :vpn_gateway_id, :type]) }
8
+ validate -> { validate_has_tag(:Name) }
9
+
10
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
11
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
12
+
13
+ def self._fetch_remote_resources
14
+ AwsClients.ec2.describe_vpn_connections['vpn_connections'].map(&:to_h).map do |connection|
15
+ connection.merge(
16
+ {
17
+ _terraform_id: connection[:vpn_connection_id],
18
+ _geo_id: connection[:tags].find { |tag| tag[:key] == "Name" }&.dig(:value)
19
+ }
20
+ )
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ ########################################################################
2
+ # AwsVpnConnectionRoute is the +aws_vpn_connection+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpn_connection_route.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpnConnectionRoute < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:destination_cidr_block, :vpn_connection_id]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { "#{destination_cidr_block}:#{vpn_connection_id}" } }
10
+
11
+ def support_tags?
12
+ false
13
+ end
14
+
15
+ def self._fetch_remote_resources
16
+ AwsClients
17
+ .ec2
18
+ .describe_vpn_connections['vpn_connections']
19
+ .map(&:to_h)
20
+ .select { |connection| !connection[:routes].empty? }
21
+ .map { |connection| _generate_routes(connection) }
22
+ .flatten
23
+ end
24
+
25
+ def self._generate_routes(connection)
26
+ connection[:routes].map do |route|
27
+ route.merge(
28
+ {
29
+ _terraform_id: "#{route[:destination_cidr_block]}:#{connection[:vpn_connection_id]}",
30
+ vpn_connection_id: connection[:vpn_connection_id]
31
+ }
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ ########################################################################
2
+ # AwsVpnGateway is the +aws_vpn_gateway+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpn_gateway.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpnGateway < GeoEngineer::Resource
7
+ validate -> { validate_has_tag(:Name) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
11
+
12
+ def self._fetch_remote_resources
13
+ AwsClients.ec2.describe_vpn_gateways['vpn_gateways'].map(&:to_h).map do |gateway|
14
+ gateway.merge(
15
+ {
16
+ _terraform_id: gateway[:vpn_gateway_id],
17
+ _geo_id: gateway[:tags]&.find { |tag| tag[:key] == "Name" }&.dig(:value)
18
+ }
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ ########################################################################
2
+ # AwsVpnGatewayAttachment is the +aws_vpn_gateway_attachment+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/vpn_gateway_attachment.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsVpnGatewayAttachment < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:vpc_id, :vpn_gateway_id]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> { "vpn-attachment-#{Crc32.hashcode(vpc_id + '-' + vpn_gateway_id)}" }
11
+ }
12
+ after :initialize, -> { _geo_id -> { "#{vpc_id}::#{vpn_gateway_id}" } }
13
+
14
+ def support_tags?
15
+ false
16
+ end
17
+
18
+ def self._fetch_remote_resources
19
+ AwsClients
20
+ .ec2
21
+ .describe_vpn_gateways['vpn_gateways']
22
+ .map(&:to_h)
23
+ .select { |gateway| !gateway[:vpc_attachments].empty? }
24
+ .map { |gateway| _generate_attachment(gateway) }
25
+ end
26
+
27
+ def self._generate_attachment(gateway)
28
+ # Terraform ID generation via:
29
+ # https://github.com/hashicorp/terraform/blob/master/builtin/providers/aws/resource_aws_vpn_gateway_attachment.go#L209
30
+ vpc_id = gateway[:vpc_attachments].first[:vpc_id]
31
+ id_string = "#{vpc_id}-#{gateway[:vpn_gateway_id]}"
32
+ terraform_id = "vpn-attachment-#{Crc32.hashcode(id_string)}"
33
+
34
+ {
35
+ _terraform_id: terraform_id,
36
+ _geo_id: "#{vpc_id}::#{gateway[:vpn_gateway_id]}",
37
+ vpn_gateway_id: gateway[:vpn_gateway_id],
38
+ vpc_id: vpc_id
39
+ }
40
+ end
41
+ end
@@ -5,21 +5,37 @@ class GeoEngineer::Template
5
5
  include HasAttributes
6
6
  include HasResources
7
7
 
8
- def initialize(name, project, parameters = {})
9
- @project = project
8
+ def initialize(name, parent, parameters = {})
10
9
  @name = name
11
- @environment = @project.environment
10
+ case parent
11
+ when GeoEngineer::Project then add_project_attributes(parent)
12
+ when GeoEngineer::Environment then add_env_attributes(parent)
13
+ end
14
+ end
15
+
16
+ # Helper method to accomodate different parents
17
+ def add_project_attributes(project)
18
+ @project = project
19
+ @environment = project.environment
20
+ end
21
+
22
+ def add_env_attributes(environment)
23
+ @environment = environment
12
24
  end
13
25
 
14
26
  def resource(type, id, &block)
15
27
  return find_resource(type, id) unless block_given?
16
28
  resource = create_resource(type, id, &block)
17
29
  resource.template = self
18
- resource.project = @project
19
30
  resource.environment = @environment
31
+ resource.project = @project if @project
20
32
  resource
21
33
  end
22
34
 
35
+ def all_resources
36
+ resources
37
+ end
38
+
23
39
  # The resources that are passed to the block on instantiation
24
40
  # This can be overridden to specify the order of the templates resources
25
41
  def template_resources
@@ -60,4 +60,8 @@ class AwsClients
60
60
  def self.sqs
61
61
  @aws_sqs ||= Aws::SQS::Client.new({ stub_responses: stubbed? })
62
62
  end
63
+
64
+ def self.lambda
65
+ @aws_lambda ||= Aws::Lambda::Client.new({ stub_responses: stubbed? })
66
+ end
63
67
  end
@@ -0,0 +1,61 @@
1
+ ########################################################################
2
+ # Crc32 is a pure ruby implementation of CRC32. Copied from:
3
+ # - http://www.codeography.com/2009/05/22/pure-ruby-crc32-adccp-pkzip.html
4
+ # - https://github.com/postmodern/digest-crc/blob/master/lib/digest/crc32.rb
5
+ ########################################################################
6
+ class Crc32
7
+ CRC_TABLE = [
8
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
9
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
10
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
11
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
12
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
13
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
14
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
15
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
16
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
17
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
18
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
19
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
20
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
21
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
22
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
23
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
24
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
25
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
26
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
27
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
28
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
29
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
30
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
31
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
32
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
33
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
34
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
35
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
36
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
37
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
38
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
39
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
40
+ ].freeze
41
+
42
+ INIT_CRC = 0xffffffff
43
+
44
+ XOR_MASK = 0xffffffff
45
+
46
+ def self.checksum(string, crc = INIT_CRC)
47
+ string.each_byte do |byte|
48
+ crc = (((crc >> 8) & 0x00ffffff) ^ CRC_TABLE[(crc ^ byte) & 0xff])
49
+ end
50
+ crc ^ XOR_MASK
51
+ end
52
+
53
+ def self.hashcode(string)
54
+ return 0 unless string
55
+
56
+ value = checksum(string).to_i
57
+ return value if value >= 0
58
+ return -value if value <= 0
59
+ 0
60
+ end
61
+ end
@@ -3,8 +3,12 @@
3
3
  ########################################################################
4
4
  module HasAttributes
5
5
  def attributes
6
- @_attributes = {} unless @_attributes
7
- @_attributes
6
+ @_attributes ||= {}
7
+ end
8
+
9
+ # Contains the procs used to calculate attributes
10
+ def attribute_procs
11
+ @_procs ||= {}
8
12
  end
9
13
 
10
14
  def [](key)
@@ -27,20 +31,30 @@ module HasAttributes
27
31
  # this is a setter
28
32
  name = name[0...-1] if name.end_with?"="
29
33
  val = args.length == 1 ? args[0] : args
34
+ attribute_procs[name] = val if val.is_a?(Proc)
30
35
  attributes[name] = val
31
- val
32
36
  end
33
37
 
34
38
  def retrieve_attribute(name)
35
39
  # 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
40
+ val = if attributes.key?(name)
41
+ attributes[name]
42
+ else
43
+ attribute_missing(name)
44
+ end
45
+ return val unless val.is_a?(Proc)
46
+ attributes[name] = val.call() # cache the value to override the Proc
47
+ end
48
+
49
+ # For any value that has been lazily calculated, recalculate it
50
+ def reset_attributes
51
+ attribute_procs.each { |name, function| attributes[name] = function }
52
+ self
53
+ end
54
+
55
+ def eager_load_attributes
56
+ attribute_procs.each { |name, function| attributes[name] = function.call() }
57
+ self
44
58
  end
45
59
 
46
60
  def attribute_missing(name)