├── .expeditor ├── config.yml └── update_version.sh ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── lock.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── chef-provisioning-aws.gemspec ├── docs └── examples │ ├── auto_scaling.rb │ ├── auto_scaling_cleanup.rb │ ├── aws_cloudwatch_alarm.rb │ ├── aws_ebs_volume.rb │ ├── aws_nat_gateway.rb │ ├── aws_network_interface.rb │ ├── aws_rds_subnet_group.rb │ ├── aws_sg.rb │ ├── aws_tags.rb │ ├── aws_vpc.rb │ ├── eip.rb │ ├── iam_credentials.rb │ ├── load_balancer.rb │ ├── ref_destroy.rb │ ├── ref_elasticache.rb │ ├── ref_full.rb │ ├── route53.rb │ ├── route_table.rb │ └── s3.rb ├── lib └── chef │ ├── provider │ ├── aws_auto_scaling_group.rb │ ├── aws_cache_cluster.rb │ ├── aws_cache_replication_group.rb │ ├── aws_cache_subnet_group.rb │ ├── aws_cloudsearch_domain.rb │ ├── aws_cloudwatch_alarm.rb │ ├── aws_dhcp_options.rb │ ├── aws_ebs_volume.rb │ ├── aws_eip_address.rb │ ├── aws_elasticsearch_domain.rb │ ├── aws_iam_instance_profile.rb │ ├── aws_iam_role.rb │ ├── aws_image.rb │ ├── aws_instance.rb │ ├── aws_internet_gateway.rb │ ├── aws_key_pair.rb │ ├── aws_launch_configuration.rb │ ├── aws_load_balancer.rb │ ├── aws_nat_gateway.rb │ ├── aws_network_acl.rb │ ├── aws_network_interface.rb │ ├── aws_rds_instance.rb │ ├── aws_rds_parameter_group.rb │ ├── aws_rds_subnet_group.rb │ ├── aws_route_table.rb │ ├── aws_s3_bucket.rb │ ├── aws_security_group.rb │ ├── aws_server_certificate.rb │ ├── aws_sns_topic.rb │ ├── aws_sqs_queue.rb │ ├── aws_subnet.rb │ ├── aws_vpc.rb │ └── aws_vpc_peering_connection.rb │ ├── provisioning │ ├── aws_driver.rb │ ├── aws_driver │ │ ├── aws_provider.rb │ │ ├── aws_rds_resource.rb │ │ ├── aws_resource.rb │ │ ├── aws_resource_with_entry.rb │ │ ├── aws_taggable.rb │ │ ├── aws_tagger.rb │ │ ├── credentials.rb │ │ ├── credentials2.rb │ │ ├── driver.rb │ │ ├── exceptions.rb │ │ ├── resources.rb │ │ ├── super_lwrp.rb │ │ ├── tagging_strategy │ │ │ ├── auto_scaling.rb │ │ │ ├── ec2.rb │ │ │ ├── elasticsearch.rb │ │ │ ├── elb.rb │ │ │ ├── rds.rb │ │ │ └── s3.rb │ │ └── version.rb │ └── driver_init │ │ └── aws.rb │ └── resource │ ├── aws_auto_scaling_group.rb │ ├── aws_cache_cluster.rb │ ├── aws_cache_replication_group.rb │ ├── aws_cache_subnet_group.rb │ ├── aws_cloudsearch_domain.rb │ ├── aws_cloudwatch_alarm.rb │ ├── aws_dhcp_options.rb │ ├── aws_ebs_volume.rb │ ├── aws_eip_address.rb │ ├── aws_elasticsearch_domain.rb │ ├── aws_iam_instance_profile.rb │ ├── aws_iam_role.rb │ ├── aws_image.rb │ ├── aws_instance.rb │ ├── aws_internet_gateway.rb │ ├── aws_key_pair.rb │ ├── aws_launch_configuration.rb │ ├── aws_load_balancer.rb │ ├── aws_nat_gateway.rb │ ├── aws_network_acl.rb │ ├── aws_network_interface.rb │ ├── aws_rds_instance.rb │ ├── aws_rds_parameter_group.rb │ ├── aws_rds_subnet_group.rb │ ├── aws_route53_hosted_zone.rb │ ├── aws_route53_record_set.rb │ ├── aws_route_table.rb │ ├── aws_s3_bucket.rb │ ├── aws_security_group.rb │ ├── aws_server_certificate.rb │ ├── aws_sns_topic.rb │ ├── aws_sqs_queue.rb │ ├── aws_subnet.rb │ ├── aws_vpc.rb │ └── aws_vpc_peering_connection.rb ├── spec ├── aws_support.rb ├── aws_support │ ├── aws_resource_run_wrapper.rb │ ├── deep_matcher.rb │ ├── deep_matcher │ │ ├── fuzzy_match_objects.rb │ │ ├── match_values_failure_messages.rb │ │ ├── matchable_array.rb │ │ ├── matchable_object.rb │ │ └── rspec_monkeypatches.rb │ ├── delayed_stream.rb │ └── matchers │ │ ├── create_an_aws_object.rb │ │ ├── destroy_an_aws_object.rb │ │ ├── have_aws_object_tags.rb │ │ ├── match_an_aws_object.rb │ │ └── update_an_aws_object.rb ├── integration │ ├── aws_auto_scaling_group_spec.rb │ ├── aws_cache_cluster_spec.rb │ ├── aws_cache_subnet_group_spec.rb │ ├── aws_cloudsearch_domain_spec.rb │ ├── aws_cloudwatch_alarm_spec.rb │ ├── aws_dhcp_options_spec.rb │ ├── aws_ebs_volume_spec.rb │ ├── aws_eip_address_spec.rb │ ├── aws_elasticsearch_domain_spec.rb │ ├── aws_iam_instance_profile_spec.rb │ ├── aws_iam_role_spec.rb │ ├── aws_internet_gateway_spec.rb │ ├── aws_key_pair_spec.rb │ ├── aws_launch_configuration_spec.rb │ ├── aws_nat_gateway_spec.rb │ ├── aws_network_acl_spec.rb │ ├── aws_network_interface_spec.rb │ ├── aws_rds_instance_spec.rb │ ├── aws_rds_parameter_group_spec.rb │ ├── aws_rds_subnet_group_spec.rb │ ├── aws_route53_hosted_zone_spec.rb │ ├── aws_route_table_spec.rb │ ├── aws_s3_bucket_spec.rb │ ├── aws_security_group_spec.rb │ ├── aws_server_certificate_spec.rb │ ├── aws_subnet_spec.rb │ ├── aws_vpc_peering_connection_spec.rb │ ├── aws_vpc_spec.rb │ ├── load_balancer_spec.rb │ ├── machine_batch_spec.rb │ ├── machine_image_spec.rb │ └── machine_spec.rb ├── spec_helper.rb └── unit │ └── chef │ └── provisioning │ └── aws_driver │ ├── credentials_spec.rb │ ├── driver_spec.rb │ └── route53_spec.rb └── tools ├── purge_zone.rb └── travis_env.sh /.expeditor/config.yml: -------------------------------------------------------------------------------- 1 | # Documentation available at https://expeditor.chef.io/docs/getting-started/ 2 | --- 3 | # Slack channel in Chef Software slack to send notifications about build failures, etc 4 | slack: 5 | notify_channel: 6 | - chef-notify 7 | 8 | # This publish is triggered by the `built_in:publish_rubygems` artifact_action. 9 | rubygems: 10 | - chef-provisioning-aws 11 | 12 | github: 13 | # This deletes the GitHub PR branch after successfully merged into the release branch 14 | delete_branch_on_merge: true 15 | # allow bumping the minor release via label 16 | minor_bump_labels: 17 | - "Expeditor: Bump Minor Version" 18 | 19 | changelog: 20 | rollup_header: Changes not yet released to rubygems.org 21 | 22 | # These actions are taken, in order they are specified, anytime a Pull Request is merged. 23 | merge_actions: 24 | - built_in:bump_version: 25 | ignore_labels: 26 | - "Expeditor: Skip Version Bump" 27 | - "Expeditor: Skip All" 28 | - bash:.expeditor/update_version.sh: 29 | only_if: built_in:bump_version 30 | - built_in:update_changelog: 31 | ignore_labels: 32 | - "Expeditor: Exclude From Changelog" 33 | - "Expeditor: Skip All" 34 | - built_in:build_gem: 35 | only_if: built_in:bump_version 36 | 37 | promote: 38 | actions: 39 | - built_in:rollover_changelog 40 | - built_in:publish_rubygems 41 | -------------------------------------------------------------------------------- /.expeditor/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # After a PR merge, Chef Expeditor will bump the PATCH version in the VERSION file. 4 | # It then executes this file to update any other files/components with that new version. 5 | # 6 | 7 | set -evx 8 | 9 | sed -i -r "s/^(\s*)VERSION = \".+\"/\1VERSION = \"$(cat VERSION)\"/" lib/chef/provisioning/aws_driver/version.rb 10 | 11 | # Once Expeditor finshes executing this script, it will commit the changes and push 12 | # the commit as a new tag corresponding to the value in the VERSION file. 13 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Order is important. The last matching pattern has the most precedence. 2 | 3 | * @chef/client-maintainers 4 | .expeditor/** @chef/jex-team 5 | README.md @chef/docs-team -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Gem Version 5 | 6 | 7 | ## Platform Version 8 | 9 | Tell us which Operating System distribution and version you arerunning. 10 | 11 | ## Replication Case 12 | 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [Please describe what this change achieves] 4 | 5 | ### Issues Resolved 6 | 7 | [List any existing issues this PR resolves, or any Discourse or 8 | StackOverflow discussions that are relevant] 9 | 10 | ### Check List 11 | 12 | - [ ] New functionality includes tests 13 | - [ ] All tests pass 14 | - [ ] All commits have been signed-off for the Developer Certificate of Origin. See 15 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | daysUntilLock: 60 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | bin/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | *.bundle 20 | *.so 21 | *.o 22 | *.a 23 | mkmf.log 24 | *.sw* 25 | clients 26 | nodes 27 | docs/examples/clients 28 | docs/examples/nodes 29 | docs/examples/data_bags 30 | x.rb 31 | .idea/ 32 | coverage 33 | .envrc 34 | .ruby-version 35 | spec/persistence_file.txt 36 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -fd 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Layout/EndAlignment: 2 | Exclude: 3 | - 'lib/chef/provider/aws_s3_bucket.rb' 4 | - 'lib/chef/provider/aws_security_group.rb' 5 | - 'lib/chef/provisioning/aws_driver/driver.rb' 6 | 7 | Lint/AmbiguousBlockAssociation: 8 | Exclude: 9 | - 'docs/examples/aws_cloudwatch_alarm.rb' 10 | - 'docs/examples/load_balancer.rb' 11 | - 'docs/examples/ref_destroy.rb' 12 | 13 | Lint/DuplicatedKey: 14 | Exclude: 15 | - 'docs/examples/aws_sg.rb' 16 | 17 | Lint/IneffectiveAccessModifier: 18 | Exclude: 19 | - 'lib/chef/provisioning/aws_driver/aws_provider.rb' 20 | - 'lib/chef/provisioning/aws_driver/aws_resource.rb' 21 | - 'lib/chef/provisioning/aws_driver/aws_resource_with_entry.rb' 22 | - 'lib/chef/resource/aws_sqs_queue.rb' 23 | 24 | Lint/NestedMethodDefinition: 25 | Exclude: 26 | - 'spec/aws_support.rb' 27 | 28 | Lint/ParenthesesAsGroupedExpression: 29 | Exclude: 30 | - 'docs/examples/load_balancer.rb' 31 | 32 | Lint/ShadowingOuterLocalVariable: 33 | Exclude: 34 | - 'lib/chef/provisioning/aws_driver/aws_resource.rb' 35 | - 'lib/chef/provisioning/aws_driver/driver.rb' 36 | 37 | Lint/UselessAccessModifier: 38 | Exclude: 39 | - 'lib/chef/provisioning/aws_driver/aws_resource.rb' 40 | - 'lib/chef/resource/aws_sqs_queue.rb' 41 | 42 | Lint/UselessAssignment: 43 | Exclude: 44 | - 'docs/examples/aws_rds_subnet_group.rb' 45 | - 'lib/chef/provider/aws_key_pair.rb' 46 | - 'lib/chef/provider/aws_rds_instance.rb' 47 | - 'lib/chef/provider/aws_security_group.rb' 48 | - 'lib/chef/provisioning/aws_driver/aws_resource_with_entry.rb' 49 | - 'lib/chef/provisioning/aws_driver/driver.rb' 50 | - 'lib/chef/provisioning/aws_driver/tagging_strategy/s3.rb' 51 | - 'lib/chef/resource/aws_route53_hosted_zone.rb' 52 | - 'spec/integration/aws_security_group_spec.rb' 53 | 54 | Performance/TimesMap: 55 | Exclude: 56 | - 'spec/unit/chef/provisioning/aws_driver/route53_spec.rb' 57 | 58 | Security/Eval: 59 | Exclude: 60 | - 'lib/chef/provisioning/aws_driver/aws_resource.rb' 61 | 62 | Style/MultilineTernaryOperator: 63 | Exclude: 64 | - 'lib/chef/provider/aws_network_interface.rb' 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | cache: bundler 4 | branches: 5 | only: 6 | - master 7 | 8 | rvm: 9 | - 2.4.4 10 | env: 11 | global: 12 | # encrypted with `travis encrypt "AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=..."` 13 | - secure: D/GQrYonTbI4DfzKStcy72gsPvC41Y6ozVkEacmAlN6hXnZX19/cg6eNm24mtxrA0OV/bh0E8aFcRpyFL579UkpbzfEgHQZhRUtP6gOT9SS5QlyqUODXR8LwRKUvTLddtgCWRLj3G9rB3vmzMKy9O0yTYiVJT8EWGm1Itp/zPOM= 14 | matrix: 15 | - RAKE_TASK=chefstyle 16 | - RAKE_TASK=spec 17 | # We run the tests in different environments so they don't try to overwrite each other (deleting, creating the same VPC) 18 | # If we need to we can get rid of this but its nice for speed 19 | - RAKE_TASK="travis[integration]" AWS_TRAVIS_DRIVER=aws::us-west-2 20 | - RAKE_TASK="travis[super_slow]" AWS_TRAVIS_DRIVER=aws::us-west-1 21 | # machine_image is a special snowflake - they take so long to run we need to give them their own builder 22 | - RAKE_TASK="machine_image" AWS_TRAVIS_DRIVER=aws::eu-west-1 23 | 24 | bundler_args: --jobs 7 --without docs debug 25 | 26 | before_script: 27 | - mkdir -p /home/travis/.chef 28 | - . ./tools/travis_env.sh 29 | script: 30 | - bundle update 31 | - bundle exec rake build 32 | - bundle exec rake $RAKE_TASK 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Please refer to the Chef Community Code of Conduct at https://www.chef.io/code-of-conduct/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Chef Provisioning AWS 2 | 3 | - [Acceptance Criteria](#acceptance) 4 | - [Release Process](#release) 5 | 6 | 7 | ## Resource/Provider Acceptance Criteria 8 | 9 | All resources and providers in Chef Provisioning AWS must fulfill the [common acceptance criteria] 10 | (https://github.com/chef/chef-provisioning/blob/master/CONTRIBUTING.md) for 11 | all Chef Provisioning projects. In addition, Chef Provisioning AWS has the following additional 12 | acceptance criteria: 13 | 14 | 1. When an AWS object supports dependent objects, the resource must also have a `:purge` action which destroys all dependent AWS objects, as well as the specified object. 15 | 1. This is primarily for development purposes, and should be documented as such. 16 | 2. The action must print a warning when called, so that users know that the resource does not properly clean up data bag references to purged objects. 17 | 1. If an object can be tagged in the Amazon Ruby SDK, the resource must have a convergent `aws_tags` attribute. 18 | 1. This is traditionally done by extending the resource with the `Chef::Provisioning::AWSDriver::AWSTaggable` module and adding a `converge_tags` method to the provider which leverages the `AWSTagger` class. 19 | 1. If the AWS object is not identified primarily by its name but instead by an auto generated ID, the resource must store a mapping between the resource name and the AWS object ID. 20 | 1. This is to support `aws_instance 'my instance'` being able to converge the instance `'i-12345678'` after the first chef run when the id is generated by AWS. 21 | 1. Any AWS resource that referes to another AWS resource (EG, `subnet` has a `vpc` attribute) should be able to accept a resource name, the AWS object identifier (if it is different from the resource name), the AWS object or a resource object as the attribute value. 22 | 23 | 24 | ## Release Process 25 | 26 | See https://github.com/chef/chef-provisioning/blob/master/CONTRIBUTING.md#release-process 27 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "chef" 6 | gem "rb-readline" 7 | 8 | gem "chef-zero", ">= 4.0" 9 | gem "chefstyle", "~> 0.10.0" 10 | gem "rake" 11 | gem "rspec", "~> 3.0" 12 | gem "simplecov" 13 | 14 | group :debug do 15 | gem "pry" 16 | gem "pry-byebug" 17 | gem "pry-stack_explorer" 18 | end 19 | 20 | instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"] 21 | 22 | # If you want to load debugging tools into the bundle exec sandbox, 23 | # add these additional dependencies into Gemfile.local 24 | eval_gemfile(__FILE__ + ".local") if File.exist?(__FILE__ + ".local") 25 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | task default: :spec 5 | 6 | desc "run all non-integration specs" 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.pattern = "spec/**/*_spec.rb" 9 | # TODO: add back integration tests whenever we have strategy for keys 10 | spec.exclude_pattern = "spec/integration/**/*_spec.rb" 11 | end 12 | 13 | desc "run integration specs" 14 | RSpec::Core::RakeTask.new(:integration, [:pattern]) do |spec, args| 15 | spec.pattern = args[:pattern] || "spec/integration/**/*_spec.rb" 16 | spec.rspec_opts = "-b" 17 | end 18 | 19 | desc "run :super_slow specs (machine/machine_image)" 20 | RSpec::Core::RakeTask.new(:super_slow, [:pattern]) do |spec, args| 21 | spec.pattern = args[:pattern] || "spec/integration/**/*_spec.rb" 22 | spec.rspec_opts = "-b -t super_slow" 23 | end 24 | 25 | desc "run all specs, except :super_slow" 26 | RSpec::Core::RakeTask.new(:all) do |spec| 27 | spec.pattern = "spec/**/*_spec.rb" 28 | end 29 | 30 | desc "run all specs, including :super_slow" 31 | task :all_slow do 32 | %w{all slow}.each do |t| 33 | Rake::Task[t].invoke 34 | end 35 | end 36 | 37 | desc "travis specific task - runs CI integration tests (regular and super_slow in parallel) and sets up travis specific ENV variables" 38 | task :travis, [:sub_task] do |_t, args| 39 | sub_task = args[:sub_task] 40 | if sub_task == "super_slow" 41 | pattern = "load_balancer_spec.rb,aws_route_table_spec.rb,machine_spec.rb,aws_eip_address_spec.rb" # This is a comma seperated list 42 | pattern = pattern.split(",").map { |p| "spec/integration/**/*#{p}" }.join(",") 43 | else 44 | pattern = "spec/integration/**/*_spec.rb" 45 | end 46 | Rake::Task[sub_task].invoke(pattern) 47 | end 48 | 49 | desc "travis task for machine_image tests - these take so long to run that we only run the first test" 50 | RSpec::Core::RakeTask.new(:machine_image) do |spec| 51 | spec.pattern = "spec/integration/machine_image_spec.rb" 52 | spec.rspec_opts = "-b -t super_slow -e 'machine_image can create an image in the VPC'" 53 | end 54 | 55 | task :console do 56 | require "irb" 57 | require "irb/completion" 58 | ARGV.clear 59 | IRB.start 60 | end 61 | 62 | begin 63 | require "chefstyle" 64 | require "rubocop/rake_task" 65 | RuboCop::RakeTask.new(:chefstyle) do |task| 66 | task.options << "--display-cop-names" 67 | end 68 | rescue LoadError 69 | puts "chefstyle gem is not installed" 70 | end 71 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 3.0.9 -------------------------------------------------------------------------------- /chef-provisioning-aws.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.dirname(__FILE__) + "/lib") 2 | require "chef/provisioning/aws_driver/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "chef-provisioning-aws" 6 | s.version = Chef::Provisioning::AWSDriver::VERSION 7 | s.summary = "Provisioner for creating aws containers in Chef Provisioning." 8 | s.description = s.summary 9 | s.author = "Tyler Ball" 10 | s.email = "tball@chef.io" 11 | s.homepage = "https://github.com/chef/chef-provisioning-aws" 12 | s.license = "Apache-2.0" 13 | s.required_ruby_version = ">= 2.1.9" 14 | 15 | s.add_dependency "chef-provisioning", ">= 1.0", "< 3.0" 16 | 17 | s.add_dependency "aws-sdk", [">= 2.2.18", "< 3.0"] 18 | s.add_dependency "retryable", "~> 2.0", ">= 2.0.1" 19 | s.add_dependency "ubuntu_ami", "~> 0.4", ">= 0.4.1" 20 | 21 | s.require_path = "lib" 22 | s.files = %w{Gemfile Rakefile LICENSE} + Dir.glob("*.gemspec") + 23 | Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } 24 | end 25 | -------------------------------------------------------------------------------- /docs/examples/auto_scaling.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::eu-west-1" do 4 | aws_launch_config "my-sweet-launch-config" do 5 | image "ami-f0b11187" 6 | instance_type "t1.micro" 7 | end 8 | 9 | aws_auto_scaling_group "my-awesome-auto-scaling-group" do 10 | desired_capacity 3 11 | min_size 1 12 | max_size 5 13 | launch_configuration "my-sweet-launch-config" 14 | notification_configurations( 15 | topic: "arn::aws::sns::eu-west1::", 16 | types: [ 17 | "autoscaling:EC2_INSTANCE_LAUNCH", 18 | "autoscaling:EC2_INSTANCE_TERMINATE" 19 | ] 20 | ) 21 | scaling_policies( 22 | "my-amazing-scaling-policy" => { 23 | adjustment_type: "ChangeInCapacity", 24 | scaling_adjustment: 1 25 | } 26 | ) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /docs/examples/auto_scaling_cleanup.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::eu-west-1" do 4 | aws_auto_scaling_group "my-awesome-auto-scaling-group" do 5 | action :destroy 6 | end 7 | 8 | aws_launch_config "my-sweet-launch-config" do 9 | action :destroy 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /docs/examples/aws_cloudwatch_alarm.rb: -------------------------------------------------------------------------------- 1 | # A simple alarm publishing notifications to an SNS topic 2 | # which could potentially have email, sms or other subscriptions 3 | topic = aws_sns_topic "my-test-topic" 4 | 5 | aws_cloudwatch_alarm "Test Alert" do 6 | namespace "AWS/EC2" 7 | metric_name "MyTestMetric" 8 | comparison_operator "GreaterThanThreshold" 9 | evaluation_periods 1 10 | period 60 11 | statistic "Average" 12 | threshold 80 13 | alarm_actions [topic.arn] 14 | end 15 | 16 | # More complicated example settings up an alarm to scale up and auto-scaling 17 | # group if the CPU passes a certain threshold 18 | aws_launch_configuration "my-launch-config" do 19 | image "ami-ca3b11af" 20 | instance_type "t2.micro" 21 | end 22 | 23 | scaling_group = aws_auto_scaling_group "scaling_group" do 24 | desired_capacity 3 25 | min_size 1 26 | max_size 5 27 | launch_configuration "my-launch-config" 28 | availability_zones ["#{driver.region}a"] 29 | scaling_policies( 30 | "my-scaling-policy" => { 31 | adjustment_type: "ChangeInCapacity", 32 | scaling_adjustment: 2 33 | } 34 | ) 35 | end 36 | 37 | aws_cloudwatch_alarm "my-test-alert" do 38 | namespace "AWS/EC2" 39 | metric_name "CPUUtilization" 40 | comparison_operator "GreaterThanThreshold" 41 | evaluation_periods 1 42 | period 60 43 | statistic "Average" 44 | threshold 80 45 | alarm_actions lazy { 46 | [ 47 | scaling_group.aws_object.policies.select { |p| p.name == "my-scaling-policy" }.first.policy_arn 48 | ] 49 | } 50 | end 51 | -------------------------------------------------------------------------------- /docs/examples/aws_ebs_volume.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::us-west-2" 4 | 5 | aws_key_pair "ref-key-pair-ebs" 6 | 7 | with_machine_options bootstrap_options: { key_name: "ref-key-pair-ebs" } 8 | 9 | machine "ref-machine-1" 10 | machine "ref-machine-2" 11 | 12 | # create and attach ebs volume with machine 13 | machine "ref-machine-3" do 14 | machine_options bootstrap_options: { 15 | block_device_mappings: [{ 16 | device_name: "/dev/xvdf", 17 | ebs: { 18 | volume_size: 1 # 1 GB 19 | } 20 | }] 21 | } 22 | end 23 | 24 | # machine_batch do 25 | # machines 'ref-machine-1', 'ref-machine-2' 26 | # end 27 | 28 | # create volume 29 | aws_ebs_volume "ref-volume-ebs" do 30 | availability_zone "a" 31 | size 1 32 | end 33 | 34 | # attach to machine 35 | aws_ebs_volume "ref-volume-ebs" do 36 | machine "ref-machine-1" 37 | device "/dev/xvdf" 38 | end 39 | 40 | # reattach to different device 41 | aws_ebs_volume "ref-volume-ebs" do 42 | device "/dev/xvdg" 43 | end 44 | 45 | # reattach to different machine 46 | aws_ebs_volume "ref-volume-ebs" do 47 | machine "ref-machine-2" 48 | device "/dev/xvdf" 49 | end 50 | 51 | # skip reattachment attempt 52 | aws_ebs_volume "ref-volume-ebs" do 53 | machine "ref-machine-2" 54 | device "/dev/xvdf" 55 | end 56 | 57 | # create and attach 58 | aws_ebs_volume "ref-volume-ebs-2" do 59 | availability_zone "a" 60 | size 1 61 | machine "ref-machine-1" 62 | device "/dev/xvdf" 63 | end 64 | 65 | # detach 66 | aws_ebs_volume "ref-volume-ebs" do 67 | machine false 68 | end 69 | 70 | # delete volumes 71 | ["ref-volume-ebs", "ref-volume-ebs-2"].each do |volume| 72 | aws_ebs_volume volume do 73 | action :destroy 74 | end 75 | end 76 | 77 | machine_batch do 78 | machines "ref-machine-1", "ref-machine-2" 79 | action :destroy 80 | end 81 | 82 | aws_key_pair "ref-key-pair-ebs" do 83 | action :destroy 84 | end 85 | -------------------------------------------------------------------------------- /docs/examples/aws_nat_gateway.rb: -------------------------------------------------------------------------------- 1 | ## Recipe to create a nat gateway in public subnet and associate it to private route table in private subnet 2 | require "chef/provisioning/aws_driver" 3 | 4 | with_driver "aws::eu-west-1" 5 | 6 | aws_vpc "test-vpc" do 7 | cidr_block "10.0.0.0/16" 8 | internet_gateway true 9 | end 10 | 11 | aws_route_table "public_route" do 12 | vpc "test-vpc" 13 | routes "0.0.0.0/0" => :internet_gateway 14 | end 15 | 16 | aws_subnet "public_subnet" do 17 | vpc "test-vpc" 18 | cidr_block "10.0.1.0/24" 19 | availability_zone "eu-west-1a" 20 | map_public_ip_on_launch false 21 | route_table "public_route" 22 | end 23 | 24 | aws_eip_address "nat-elastic-ip" 25 | 26 | nat = aws_nat_gateway "nat-gateway" do 27 | subnet "public_subnet" 28 | eip_address "nat-elastic-ip" 29 | end 30 | 31 | aws_route_table "private_route" do 32 | routes "0.0.0.0/0" => nat.aws_object.id 33 | vpc "test-vpc" 34 | end 35 | 36 | aws_subnet "private_subnet" do 37 | vpc "test-vpc" 38 | cidr_block "10.0.3.0/24" 39 | availability_zone "eu-west-1a" 40 | map_public_ip_on_launch false 41 | route_table "private_route" 42 | end 43 | -------------------------------------------------------------------------------- /docs/examples/aws_network_interface.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | require "retryable" 3 | 4 | with_driver "aws::us-west-2" 5 | 6 | aws_key_pair "ref-key-pair-eni" 7 | 8 | aws_dhcp_options "ref-dhcp-options-eni" 9 | 10 | aws_vpc "ref-vpc-eni" do 11 | cidr_block "10.0.0.0/24" 12 | internet_gateway true 13 | main_routes "0.0.0.0/0" => :internet_gateway 14 | dhcp_options "ref-dhcp-options-eni" 15 | end 16 | 17 | aws_security_group "ref-sg1-eni" do 18 | vpc "ref-vpc-eni" 19 | inbound_rules "0.0.0.0/0" => 22 20 | end 21 | 22 | aws_security_group "ref-sg2-eni" do 23 | vpc "ref-vpc-eni" 24 | inbound_rules "ref-sg1-eni" => 2224 25 | outbound_rules 2224 => "ref-sg1-eni" 26 | end 27 | 28 | aws_route_table "ref-public-eni" do 29 | vpc "ref-vpc-eni" 30 | routes "0.0.0.0/0" => :internet_gateway 31 | end 32 | 33 | aws_subnet "ref-subnet-eni" do 34 | vpc "ref-vpc-eni" 35 | map_public_ip_on_launch true 36 | route_table "ref-public-eni" 37 | end 38 | 39 | with_machine_options bootstrap_options: { 40 | subnet_id: "ref-subnet-eni", 41 | key_name: "ref-key-pair-eni", 42 | security_group_ids: ["ref-sg1-eni", "ref-sg2-eni"] 43 | } 44 | 45 | ref_machine = machine "ref-machine-eni-1" do 46 | action :allocate 47 | end 48 | 49 | aws_network_interface "ref-eni-1" do 50 | machine "ref-machine-eni-1" 51 | subnet "ref-subnet-eni" 52 | security_groups ["ref-sg1-eni"] 53 | description "ref-eni-desc" 54 | end 55 | 56 | aws_network_interface "ref-eni-1" do 57 | security_groups ["ref-sg1-eni", "ref-sg2-eni"] 58 | description "new-ref-eni-desc" 59 | end 60 | 61 | aws_network_interface "ref-eni-1" do 62 | device_index 2 63 | end 64 | 65 | # raise can not be modifed exception 66 | # aws_network_interface 'ref-eni-1' do 67 | # subnet 'subnet-f0836387' 68 | # end 69 | 70 | aws_network_interface "ref-eni-1" do 71 | machine false 72 | end 73 | 74 | aws_network_interface "ref-eni-1" do 75 | action :destroy 76 | end 77 | 78 | instance = nil 79 | ruby_block "get instance" do 80 | block do 81 | instance = Chef::Resource::AwsInstance.get_aws_object(ref_machine.name, 82 | resource: ref_machine, 83 | driver: run_context.chef_provisioning.current_driver, 84 | run_context: run_context, 85 | managed_entry_store: Chef::Provisioning.chef_managed_entry_store(ref_machine.chef_server)) 86 | end 87 | end 88 | 89 | machine "ref-machine-eni-1" do 90 | action :destroy 91 | end 92 | 93 | ruby_block "wait for instance to terminate" do 94 | block do 95 | Retryable.retryable(tries: 60, sleep: 2) do 96 | raise "instance never terminated" if instance.status != :terminated 97 | end 98 | end 99 | end 100 | 101 | aws_subnet "ref-subnet-eni" do 102 | action :destroy 103 | end 104 | 105 | aws_route_table "ref-public-eni" do 106 | action :destroy 107 | end 108 | 109 | aws_security_group "ref-sg2-eni" do 110 | action :destroy 111 | end 112 | 113 | aws_security_group "ref-sg1-eni" do 114 | action :destroy 115 | end 116 | 117 | aws_vpc "ref-vpc-eni" do 118 | action :destroy 119 | end 120 | 121 | aws_dhcp_options "ref-dhcp-options-eni" do 122 | action :destroy 123 | end 124 | -------------------------------------------------------------------------------- /docs/examples/aws_rds_subnet_group.rb: -------------------------------------------------------------------------------- 1 | aws_vpc "coolvpc" do 2 | cidr_block "10.0.0.0/24" 3 | internet_gateway true 4 | end 5 | 6 | subnet1 = aws_subnet "subnet" do 7 | vpc "coolvpc" 8 | cidr_block "10.0.0.0/26" 9 | availability_zone "us-east-1a" 10 | end 11 | 12 | subnet2 = aws_subnet "subnet_2" do 13 | vpc "coolvpc" 14 | cidr_block "10.0.0.64/26" 15 | availability_zone "us-east-1c" 16 | end 17 | 18 | # Note that rds subnet groups need two subnets in different 19 | # availability zones to function, that is why two subnets are defined 20 | # in this example. 21 | aws_rds_subnet_group "db-subnet-group" do 22 | description "some_description" 23 | subnets ["subnet", subnet2.aws_object.id] 24 | end 25 | 26 | aws_rds_instance "rds-instance" do 27 | engine "postgres" 28 | publicly_accessible false 29 | db_instance_class "db.t1.micro" 30 | master_username "thechief" 31 | master_user_password "securesecure" # 2x security 32 | multi_az false 33 | db_subnet_group_name "db-subnet-group" 34 | end 35 | -------------------------------------------------------------------------------- /docs/examples/aws_sg.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::eu-west-1" 4 | 5 | vpc = aws_vpc "test-vpc" do 6 | cidr_block "10.0.0.0/24" 7 | internet_gateway true 8 | end 9 | 10 | # Empty 11 | 12 | aws_security_group "test-sg" do 13 | vpc "test-vpc" 14 | action :delete 15 | end 16 | 17 | aws_security_group "test-sg" do 18 | vpc "test-vpc" 19 | end 20 | aws_security_group "test-sg" do 21 | vpc "test-vpc" 22 | end 23 | 24 | # Things we can reference 25 | aws_security_group "other-sg" do 26 | vpc "test-vpc" 27 | end 28 | 29 | aws_subnet "test-subnet" do 30 | vpc "test-vpc" 31 | end 32 | 33 | recipe = self 34 | ruby_block "lb" do 35 | block do 36 | recipe.load_balancer "other-lb" do 37 | load_balancer_options subnets: vpc.aws_object.subnets.map { |s| s } 38 | end 39 | end 40 | end 41 | 42 | # Add/update 43 | aws_security_group "test-sg" do 44 | vpc "test-vpc" 45 | action :delete 46 | end 47 | aws_security_group "test-sg" do 48 | vpc "test-vpc" 49 | inbound_rules "0.0.0.0/0" => 22, 50 | "other-sg" => 1024..2048, 51 | { load_balancer: "other-lb" } => 1024..2048 52 | outbound_rules 443 => "0.0.0.0/0", 53 | 2048..4096 => "other-sg", 54 | 2048..4096 => { load_balancer: "other-lb" } 55 | end 56 | 57 | # Add one inbound rule, change one inbound rule, add to one inbound rule 58 | aws_security_group "test-sg" do 59 | vpc "test-vpc" 60 | inbound_rules "0.0.0.0/0" => 80, 61 | "other-sg" => [80, 1024..2048], 62 | "127.0.0.1" => 1024..2048, 63 | { load_balancer: "other-lb" } => 1024..2048 64 | end 65 | 66 | # Add one outbound rule, change one outbound rule, add to one outbound rule 67 | aws_security_group "test-sg" do 68 | vpc "test-vpc" 69 | outbound_rules 80 => "0.0.0.0/0", 70 | [80, 2048..4096] => "other-sg", 71 | 2048..4096 => "127.0.0.1", 72 | 1024..2048 => { load_balancer: "other-lb" } 73 | end 74 | 75 | # Idempotence 76 | aws_security_group "test-sg" do 77 | vpc "test-vpc" 78 | inbound_rules "0.0.0.0/0" => 80, 79 | "other-sg" => [80, 1024..2048], 80 | "127.0.0.1" => 1024..2048, 81 | { load_balancer: "other-lb" } => 1024..2048 82 | outbound_rules 80 => "0.0.0.0/0", 83 | [80, 2048..4096] => "other-sg", 84 | 2048..4096 => "127.0.0.1", 85 | 1024..2048 => { load_balancer: "other-lb" } 86 | end 87 | 88 | # Idempotence (alternate way of writing it) 89 | aws_security_group "test-sg" do 90 | vpc "test-vpc" 91 | inbound_rules [{ port: 80, sources: ["0.0.0.0/0"] }, 92 | { port: [80, 1024..2048], sources: ["other-sg"] }, 93 | { port: 1024..2048, sources: ["127.0.0.1"] }, 94 | { port: 1024..2048, sources: [{ load_balancer: "other-lb" }] }] 95 | outbound_rules [{ port: 80, destinations: ["0.0.0.0/0", "other-sg"] }, 96 | { port: [80, 2048..4096], destinations: ["other-sg"] }, 97 | { port: 2048..4096, destinations: ["other-sg", "127.0.0.1"] }, 98 | { port: 1024..2048, destinations: [{ load_balancer: "other-lb" }] }] 99 | end 100 | 101 | load_balancer "other-lb" do 102 | action :destroy 103 | end 104 | 105 | aws_subnet "test-subnet" do 106 | action :delete 107 | end 108 | 109 | aws_security_group "test-sg" do 110 | action :delete 111 | end 112 | 113 | aws_security_group "other-sg" do 114 | action :delete 115 | end 116 | 117 | aws_vpc "test-vpc" do 118 | action :delete 119 | end 120 | -------------------------------------------------------------------------------- /docs/examples/aws_tags.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::us-west-2" 4 | 5 | machine "ref-machine-1" do 6 | action :allocate 7 | end 8 | 9 | machine "ref-machine-1" do 10 | machine_options aws_tags: { marco: "polo", happyhappy: "joyjoy" } 11 | converge false 12 | end 13 | 14 | machine "ref-machine-1" do 15 | machine_options aws_tags: { othercustomtags: "byebye" } 16 | converge false 17 | end 18 | 19 | machine "ref-machine-1" do 20 | machine_options aws_tags: { Name: "new-name" } 21 | converge false 22 | end 23 | 24 | machine "ref-machine-1" do 25 | action :destroy 26 | end 27 | 28 | machine_batch "ref-batch" do 29 | machine "ref-machine-2" do 30 | machine_options aws_tags: { marco: "polo", happyhappy: "joyjoy" } 31 | converge false 32 | end 33 | machine "ref-machine-3" do 34 | machine_options aws_tags: { othercustomtags: "byebye" } 35 | converge false 36 | end 37 | end 38 | 39 | load_balancer "ref-elb" do 40 | load_balancer_options availability_zones: ["us-west-1a", "us-west-1b"] 41 | end 42 | 43 | load_balancer "ref-elb" do 44 | load_balancer_options aws_tags: { marco: "polo", happyhappy: "joyjoy" } 45 | end 46 | 47 | load_balancer "ref-elb" do 48 | load_balancer_options aws_tags: { othercustomtags: "byebye" } 49 | end 50 | 51 | load_balancer "ref-elb" do 52 | action :destroy 53 | end 54 | 55 | machine_batch "ref-batch" do 56 | action :destroy 57 | end 58 | 59 | machine_image "ref-image" do 60 | image_options aws_tags: { marco: "polo", happyhappy: "joyjoy" } 61 | end 62 | 63 | # There is a bug where machine_images do not update - so we cannot update 64 | # the tags on it 65 | machine_image "ref-image" do 66 | image_options aws_tags: { othercustomtags: "byebye" } 67 | end 68 | 69 | machine_image "ref-image" do 70 | action :destroy 71 | end 72 | -------------------------------------------------------------------------------- /docs/examples/aws_vpc.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::eu-west-1" 4 | 5 | aws_vpc "provisioning-vpc" do 6 | cidr_block "10.0.0.0/24" 7 | internet_gateway true 8 | main_routes "0.0.0.0/0" => :internet_gateway 9 | end 10 | 11 | aws_subnet "provisioning-vpc-subnet-a" do 12 | vpc "provisioning-vpc" 13 | cidr_block "10.0.0.0/26" 14 | availability_zone "eu-west-1a" 15 | map_public_ip_on_launch true 16 | end 17 | 18 | aws_subnet "provisioning-vpc-subnet-b" do 19 | vpc "provisioning-vpc" 20 | cidr_block "10.0.0.128/26" 21 | availability_zone "eu-west-1a" 22 | map_public_ip_on_launch true 23 | end 24 | 25 | machine_batch do 26 | machines %w{mario-a mario-b} 27 | action :destroy 28 | end 29 | 30 | machine_batch do 31 | machine "mario-a" do 32 | machine_options bootstrap_options: { subnet: "provisioning-vpc-subnet-a" } 33 | end 34 | 35 | machine "mario-b" do 36 | machine_options bootstrap_options: { subnet: "provisioning-vpc-subnet-b" } 37 | end 38 | end 39 | 40 | aws_security_group "provisioning-vpc-security-group" do 41 | inbound_rules [ 42 | { port: 2223, protocol: :tcp, sources: ["10.0.0.0/24"] }, 43 | { port: 80..100, protocol: :udp, sources: ["1.1.1.0/24"] } 44 | ] 45 | outbound_rules [ 46 | { port: 2223, protocol: :tcp, destinations: ["1.1.1.0/16"] }, 47 | { port: 8080, protocol: :tcp, destinations: ["2.2.2.0/24"] } 48 | ] 49 | vpc "provisioning-vpc" 50 | end 51 | -------------------------------------------------------------------------------- /docs/examples/eip.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::us-west-2" do 4 | machine "SRV_OR_Web_1" do 5 | machine_options bootstrap_options: { 6 | key_name: "Tst_Prov" 7 | } 8 | end 9 | 10 | # create a new eip and associate it to the machine 11 | # Note: in order to be able to associate an eip to a 12 | # machine in a vpc, set "associate_to_vpc true" 13 | # whenever you create an EIP 14 | aws_eip_address "Web_IP_1" do 15 | machine "SRV_OR_Web_1" 16 | end 17 | 18 | # Delete EIP - Will disassociate and release 19 | aws_eip_address "Web_IP_1" do 20 | action :destroy 21 | end 22 | end 23 | # You can create an EIP without associating it to a machine with the :create action 24 | # You can also disassociate an EIP without releasing with the :disassociate action 25 | # Existing EIPs can be hooked by specifying the public_ip attribute and then an action 26 | -------------------------------------------------------------------------------- /docs/examples/iam_credentials.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | iam = AWS::Core::CredentialProviders::EC2Provider.new 4 | 5 | with_driver( 6 | "aws:IAM:us-east-1", 7 | aws_credentials: { "IAM" => iam.credentials } 8 | ) 9 | 10 | machine "iam-machine-1" do 11 | machine_options bootstrap_options: { 12 | # subnet_id: 'ref-subnet', 13 | # security_group_ids: 'ref-sg1', 14 | # image_id: 'ref-machine_image1', 15 | # instance_type: 't2.small' 16 | } 17 | end 18 | -------------------------------------------------------------------------------- /docs/examples/ref_destroy.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::us-west-1" 4 | 5 | aws_elasticsearch_domain "ref-es-domain" do 6 | action :destroy 7 | end 8 | 9 | aws_cloudsearch_domain "ref-cs-domain" do 10 | action :destroy 11 | end 12 | 13 | aws_rds_instance "ref-rds-instance" do 14 | action :destroy 15 | end 16 | 17 | aws_rds_subnet_group "ref-db-subnet-group" do 18 | action :destroy 19 | end 20 | 21 | aws_sqs_queue "ref-sqs-queue" do 22 | action :destroy 23 | end 24 | 25 | aws_sns_topic "ref-sns-topic" do 26 | action :destroy 27 | end 28 | 29 | aws_s3_bucket "ref-s3-bucket" do 30 | action :destroy 31 | end 32 | 33 | aws_eip_address "ref-elastic-ip" do 34 | action :destroy 35 | end 36 | 37 | aws_ebs_volume "ref-volume" do 38 | action :destroy 39 | end 40 | 41 | aws_auto_scaling_group "ref-auto-scaling-group" do 42 | action :destroy 43 | end 44 | 45 | aws_launch_configuration "ref-launch-configuration" do 46 | action :destroy 47 | end 48 | 49 | load_balancer "ref-load-balancer" do 50 | action :destroy 51 | end 52 | 53 | machine_batch do 54 | action :destroy 55 | machines "ref-machine1", "ref-machine2" 56 | end 57 | 58 | machine_image "ref-machine_image3" do 59 | action :destroy 60 | end 61 | 62 | machine_image "ref-machine_image2" do 63 | action :destroy 64 | end 65 | 66 | machine_image "ref-machine_image1" do 67 | action :destroy 68 | end 69 | 70 | aws_subnet "ref-subnet" do 71 | action :destroy 72 | end 73 | 74 | aws_subnet "ref-subnet-2" do 75 | action :destroy 76 | end 77 | 78 | aws_route_table "ref-public" do 79 | action :destroy 80 | end 81 | 82 | aws_security_group "ref-sg2" do 83 | action :destroy 84 | end 85 | 86 | aws_security_group "ref-sg1" do 87 | action :destroy 88 | end 89 | 90 | aws_key_pair "ref-key-pair" do 91 | action :destroy 92 | end 93 | 94 | # You cannot delete the main route table, or delete a VPC which 95 | # has non-main route tables attached. So we first need to restore 96 | # the 'default' route tabled created during the `create_vpc` 97 | # call as the main route table. Then we can delete the 98 | # 'ref-main-route-table' (because it is no longer main) 99 | # and finally delete the VPC (which deletes the main route table) 100 | aws_vpc "ref-vpc" do 101 | main_route_table lazy { 102 | rt = aws_object.route_tables.reject(&:main?).first 103 | rt.id if rt 104 | } 105 | only_if { !aws_object.nil? } 106 | end 107 | 108 | aws_route_table "ref-main-route-table" do 109 | action :destroy 110 | end 111 | 112 | aws_vpc "ref-vpc" do 113 | action :destroy 114 | end 115 | 116 | aws_dhcp_options "ref-dhcp-options" do 117 | action :destroy 118 | end 119 | 120 | aws_server_certificate "ref-server-certificate" do 121 | action :destroy 122 | end 123 | -------------------------------------------------------------------------------- /docs/examples/ref_elasticache.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | with_driver "aws::us-east-1" 3 | 4 | aws_vpc "test" do 5 | cidr_block "10.0.0.0/24" 6 | end 7 | 8 | aws_subnet "public-test" do 9 | vpc "test" 10 | availability_zone "us-east-1a" 11 | cidr_block "10.0.0.0/24" 12 | end 13 | 14 | aws_cache_subnet_group "test-ec" do 15 | description "My awesome group" 16 | subnets ["public-test"] 17 | end 18 | 19 | aws_security_group "test-sg" do 20 | vpc "test" 21 | end 22 | 23 | aws_cache_cluster "my-cluster-mem" do 24 | az_mode "single-az" 25 | number_nodes 2 26 | node_type "cache.t2.micro" 27 | engine "memcached" 28 | engine_version "1.4.14" 29 | security_groups ["test-sg"] 30 | subnet_group_name "test-ec" 31 | end 32 | -------------------------------------------------------------------------------- /docs/examples/route_table.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | with_driver "aws::eu-west-1" 4 | 5 | aws_vpc "test-vpc" do 6 | cidr_block "10.0.0.0/24" 7 | internet_gateway true 8 | end 9 | 10 | aws_route_table "ref-public1" do 11 | vpc "test-vpc" 12 | routes "0.0.0.0/0" => :internet_gateway 13 | end 14 | 15 | aws_key_pair "ref-key-pair" 16 | 17 | machine "test" do 18 | machine_options bootstrap_options: { key_name: "ref-key-pair" } 19 | end 20 | 21 | # TODO: this still fails 22 | aws_route_table "ref-public2" do 23 | vpc "test-vpc" 24 | routes "0.0.0.0/0" => :internet_gateway, 25 | "0.0.0.1/0" => "test" 26 | end 27 | -------------------------------------------------------------------------------- /docs/examples/s3.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | with_driver "aws" 3 | 4 | aws_s3_bucket "aws-bucket" do 5 | enable_website_hosting true 6 | website_options index_document: { 7 | suffix: "index.html" 8 | }, 9 | error_document: { 10 | key: "not_found.html" 11 | } 12 | end 13 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_auto_scaling_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "set" 3 | require "chef/provisioning/aws_driver/tagging_strategy/auto_scaling" 4 | 5 | class Chef::Provider::AwsAutoScalingGroup < Chef::Provisioning::AWSDriver::AWSProvider 6 | include Chef::Provisioning::AWSDriver::TaggingStrategy::AutoScalingConvergeTags 7 | 8 | provides :aws_auto_scaling_group 9 | 10 | protected 11 | 12 | def create_aws_object 13 | converge_by "create Auto Scaling group #{new_resource.name} in #{region}" do 14 | options = desired_options.dup 15 | options[:min_size] ||= 1 16 | options[:max_size] ||= 1 17 | options[:auto_scaling_group_name] = new_resource.name 18 | options[:launch_configuration_name] = new_resource.launch_configuration if new_resource.launch_configuration 19 | options[:load_balancer_names] = new_resource.load_balancers if new_resource.load_balancers 20 | options[:vpc_zone_identifier] = [options.delete(:subnets)].flatten.join(",") if options[:subnets] 21 | 22 | aws_obj = new_resource.driver.auto_scaling_resource.create_group(options) 23 | 24 | new_resource.scaling_policies.each do |policy_name, policy| 25 | aws_obj.put_scaling_policy(policy_name: policy_name, adjustment_type: policy[:adjustment_type], scaling_adjustment: policy[:scaling_adjustment]) 26 | end 27 | 28 | new_resource.notification_configurations.each do |config| 29 | aws_obj.client.put_notification_configuration(auto_scaling_group_name: aws_obj.name, topic_arn: config[:topic], notification_types: config[:types]) 30 | end 31 | 32 | aws_obj 33 | end 34 | end 35 | 36 | def update_aws_object(group) 37 | # TODO: add updates for group 38 | end 39 | 40 | def destroy_aws_object(group) 41 | converge_by "delete Auto Scaling group #{new_resource.name} in #{region}" do 42 | group.delete(force_delete: true) 43 | group.wait_until_not_exists 44 | end 45 | end 46 | 47 | def desired_options 48 | @desired_options ||= begin 49 | options = new_resource.options.dup 50 | %w{min_size max_size availability_zones desired_capacity}.each do |var| 51 | var = var.to_sym 52 | value = new_resource.public_send(var) 53 | options[var] = value if value 54 | end 55 | AWSResource.lookup_options(options, resource: new_resource) 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_cache_replication_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsCacheReplicationGroup < Chef::Provisioning::AWSDriver::AWSProvider 4 | provides :aws_cache_replication_group 5 | 6 | protected 7 | 8 | def create_aws_object 9 | converge_by "create ElastiCache replication group #{new_resource.name} in #{region}" do 10 | driver.create_replication_group(desired_options) 11 | end 12 | end 13 | 14 | def update_aws_object(_cache_replication_group) 15 | Chef::Log.warn("Updating ElastiCache replication groups is currently unsupported") 16 | end 17 | 18 | def destroy_aws_object(cache_replication_group) 19 | converge_by "delete ElastiCache replication group #{new_resource.name} in #{region}" do 20 | driver.delete_replication_group( 21 | replication_group_id: cache_replication_group[:replication_group_id] 22 | ) 23 | end 24 | end 25 | 26 | private 27 | 28 | def driver 29 | new_resource.driver.elasticache 30 | end 31 | 32 | def desired_options 33 | @desired_options ||= begin 34 | options = {} 35 | options[:replication_group_id] = new_resource.group_name 36 | options[:replication_group_description] = new_resource.description 37 | options[:automatic_failover_enabled] = new_resource.automatic_failover 38 | options[:num_cache_clusters] = new_resource.number_cache_clusters 39 | options[:cache_node_type] = new_resource.node_type 40 | options[:engine] = new_resource.engine 41 | options[:engine_version] = new_resource.engine_version 42 | if new_resource.preferred_availability_zones 43 | options[:preferred_cache_cluster_a_zs] = 44 | new_resource.preferred_availability_zones 45 | end 46 | if new_resource.subnet_group_name 47 | options[:cache_subnet_group_name] = 48 | new_resource.subnet_group_name 49 | end 50 | options[:security_group_ids] = new_resource.security_groups 51 | AWSResource.lookup_options(options, resource: new_resource) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_cache_subnet_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsCacheSubnetGroup < Chef::Provisioning::AWSDriver::AWSProvider 4 | provides :aws_cache_subnet_group 5 | 6 | protected 7 | 8 | def create_aws_object 9 | converge_by "create ElastiCache subnet group #{new_resource.name} in #{region}" do 10 | driver.create_cache_subnet_group(desired_options) 11 | end 12 | end 13 | 14 | def update_aws_object(cache_subnet_group) 15 | if update_required?(cache_subnet_group) 16 | converge_by "update ElastiCache subnet group #{new_resource.name} in #{region}" do 17 | driver.modify_cache_subnet_group(desired_options) 18 | end 19 | end 20 | end 21 | 22 | def destroy_aws_object(cache_subnet_group) 23 | converge_by "delete ElastiCache subnet group #{new_resource.name} in #{region}" do 24 | driver.delete_cache_subnet_group( 25 | cache_subnet_group_name: cache_subnet_group[:cache_subnet_group_name] 26 | ) 27 | end 28 | end 29 | 30 | private 31 | 32 | def driver 33 | new_resource.driver.elasticache 34 | end 35 | 36 | def update_cache_subnet_group 37 | new_resource.driver.elasticache.modify_cache_subnet_group(desired_options) 38 | end 39 | 40 | def desired_options 41 | @desired_options ||= begin 42 | options = {} 43 | options[:cache_subnet_group_name] = new_resource.group_name 44 | options[:cache_subnet_group_description] = new_resource.description 45 | options[:subnet_ids] = new_resource.subnets 46 | AWSResource.lookup_options(options, resource: new_resource) 47 | end 48 | end 49 | 50 | def update_required?(cache_subnet_group) 51 | current_subnet_ids = cache_subnet_group[:subnets] 52 | .map { |subnet| subnet[:subnet_identifier] }.sort 53 | current_description = cache_subnet_group[:cache_subnet_group_description] 54 | if new_resource.description != current_description || 55 | desired_options[:subnet_ids].sort != current_subnet_ids 56 | true 57 | else 58 | false 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_cloudwatch_alarm.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsCloudwatchAlarm < Chef::Provisioning::AWSDriver::AWSProvider 4 | provides :aws_cloudwatch_alarm 5 | 6 | def create_aws_object 7 | converge_by "creating cloudwatch alarm #{new_resource.name} in #{region}" do 8 | new_resource.driver.cloudwatch_client.put_metric_alarm(desired_options) 9 | end 10 | end 11 | 12 | def update_aws_object(alarm) 13 | if update_required?(alarm) 14 | converge_by "updating cloudwatch alarm #{new_resource.name} in #{region}" do 15 | new_resource.driver.cloudwatch_client.put_metric_alarm(desired_options) 16 | end 17 | end 18 | end 19 | 20 | def destroy_aws_object(alarm) 21 | converge_by "destroying cloudwatch alarm #{new_resource.name} in #{region}" do 22 | alarm.delete 23 | end 24 | end 25 | 26 | def desired_options 27 | @desired_options ||= begin 28 | # Because an update is a PUT, we must ensure that any properties not specified 29 | # on the resource that are already present on the object stay the same 30 | aws_object = new_resource.aws_object 31 | opts = { alarm_name: new_resource.name } 32 | %i{namespace metric_name comparison_operator 33 | evaluation_periods period statistic threshold 34 | actions_enabled alarm_description unit}.each do |opt| 35 | if !new_resource.public_send(opt).nil? 36 | opts[opt] = new_resource.public_send(opt) 37 | elsif aws_object && !aws_object.public_send(opt).nil? 38 | opts[opt] = aws_object.public_send(opt) 39 | end 40 | end 41 | if !new_resource.dimensions.nil? 42 | opts[:dimensions] = new_resource.dimensions 43 | elsif aws_object && !aws_object.dimensions.nil? 44 | opts[:dimensions] = aws_object.dimensions.map!(&:to_h) 45 | end 46 | # Normally we would just use `lookup_options` here but because these parameters 47 | # don't necessarily sound like sns topics we manually do it 48 | %i{insufficient_data_actions ok_actions alarm_actions}.each do |opt| 49 | if !new_resource.public_send(opt).nil? 50 | opts[opt] = new_resource.public_send(opt) 51 | opts[opt].map! do |action| 52 | if action.is_a?(String) && action !~ /^arn:/ 53 | aws_object = Chef::Resource::AwsSnsTopic.get_aws_object(action, resource: new_resource) 54 | action = aws_object.attributes["TopicArn"] if aws_object 55 | end 56 | action 57 | end 58 | elsif aws_object && !aws_object.public_send(opt).nil? 59 | opts[opt] = aws_object.public_send(opt) 60 | end 61 | end 62 | opts 63 | end 64 | end 65 | 66 | def update_required?(alarm) 67 | %i{namespace metric_name comparison_operator 68 | evaluation_periods period statistic threshold 69 | actions_enabled alarm_description unit}.each do |opt| 70 | return true if alarm.public_send(opt) != desired_options[opt] 71 | end 72 | unless (Set.new(alarm.dimensions.map(&:to_h)) ^ Set.new(desired_options[:dimensions])).empty? 73 | return true 74 | end 75 | %i{insufficient_data_actions ok_actions alarm_actions}.each do |opt| 76 | unless (Set.new(alarm.public_send(opt)) ^ Set.new(desired_options[opt])).empty? 77 | return true 78 | end 79 | end 80 | false 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_dhcp_options.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsDhcpOptions < Chef::Provisioning::AWSDriver::AWSProvider 4 | include Chef::Provisioning::AWSDriver::TaggingStrategy::EC2ConvergeTags 5 | 6 | provides :aws_dhcp_options 7 | 8 | protected 9 | 10 | def create_aws_object 11 | options = desired_options 12 | options[:domain_name_servers] = "AmazonProvidedDNS" if options.empty? 13 | 14 | converge_by "create DHCP options #{new_resource.name} in #{region}" do 15 | create_dhcp_options options 16 | end 17 | end 18 | 19 | def create_dhcp_options(options) 20 | options = options.map { |k, v| { key: k.to_s.tr("_", "-"), values: Array(v).map(&:to_s) } } 21 | ec2_resource = ::Aws::EC2::Resource.new(new_resource.driver.ec2) 22 | dhcp_options = ec2_resource.create_dhcp_options(dhcp_configurations: options) 23 | retry_with_backoff(::Aws::EC2::Errors::InvalidDhcpOptionIDNotFound) do 24 | dhcp_options.create_tags(tags: [{ key: "Name", value: new_resource.name }]) 25 | end 26 | dhcp_options 27 | end 28 | 29 | def update_aws_object(dhcp_options) 30 | # Verify unmodifiable attributes of existing dhcp_options 31 | config = dhcp_options.data.to_h[:dhcp_configurations].map { |a| { a[:key].tr("-", "_").to_sym => a[:values].map { |k| k[:value] } } }.reduce({}, :merge) 32 | differing_options = desired_options.reject { |name, value| config[name] == Array(value).map(&:to_s) } 33 | unless differing_options.empty? 34 | old_dhcp_options = dhcp_options 35 | # Report what we are trying to change ... 36 | action_handler.report_progress "update #{new_resource}" 37 | differing_options.each do |name, value| 38 | action_handler.report_progress " set #{name} to #{value.inspect} (was #{config.key?(name) ? config[name].inspect : 'not set'})" 39 | end 40 | 41 | # create new dhcp_options 42 | if action_handler.should_perform_actions 43 | dhcp_options = create_dhcp_options(config.merge(desired_options)) 44 | end 45 | action_handler.report_progress "create DHCP options #{dhcp_options.id} with new attributes in #{region}" 46 | 47 | # attach dhcp_options to existing vpcs 48 | ec2_resource = ::Aws::EC2::Resource.new(new_resource.driver.ec2) 49 | ec2_resource.vpcs.each do |vpc| 50 | next unless vpc.dhcp_options_id == old_dhcp_options.id 51 | dhcp_options.associate_with_vpc( 52 | dry_run: false, 53 | vpc_id: vpc.id, # required 54 | ) 55 | end 56 | 57 | # delete old dhcp_options 58 | action_handler.perform_action "delete DHCP options #{old_dhcp_options.id}" do 59 | old_dhcp_options.delete 60 | end 61 | 62 | [:replaced_aws_object, dhcp_options] 63 | end 64 | end 65 | 66 | def destroy_aws_object(dhcp_options) 67 | converge_by "delete DHCP options #{new_resource.name} in #{region}" do 68 | dhcp_options.delete 69 | end 70 | end 71 | 72 | private 73 | 74 | def desired_options 75 | desired_options = {} 76 | %w{domain_name domain_name_servers ntp_servers netbios_name_servers netbios_node_type}.each do |attr| 77 | attr = attr.to_sym 78 | value = new_resource.public_send(attr) 79 | desired_options[attr] = value unless value.nil? 80 | end 81 | Chef::Provisioning::AWSDriver::AWSResource.lookup_options(desired_options, resource: new_resource) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_eip_address.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/resource/aws_instance" 3 | require "chef/provisioning/machine_spec" 4 | require "cheffish" 5 | 6 | class Chef::Provider::AwsEipAddress < Chef::Provisioning::AWSDriver::AWSProvider 7 | provides :aws_eip_address 8 | 9 | def action_create 10 | elastic_ip = super 11 | 12 | update_association(elastic_ip) unless new_resource.machine.nil? 13 | end 14 | 15 | protected 16 | 17 | def create_aws_object 18 | converge_by "create Elastic IP address in #{region}" do 19 | associate_to_vpc = new_resource.associate_to_vpc 20 | if associate_to_vpc.nil? 21 | if desired_instance.is_a?(::Aws::EC2::Instance) || desired_instance.is_a?(::Aws::EC2::Instance) 22 | associate_to_vpc = !!desired_instance.vpc_id 23 | Chef::Log.debug "Since associate_to_vpc is not specified and instance #{new_resource.machine} (#{desired_instance.id}) and #{associate_to_vpc ? 'is' : 'is not'} in a VPC, setting associate_to_vpc to #{associate_to_vpc}." 24 | end 25 | end 26 | new_resource.driver.ec2.allocate_address vpc: new_resource.associate_to_vpc 27 | end 28 | end 29 | 30 | def update_aws_object(elastic_ip) 31 | unless new_resource.associate_to_vpc.nil? 32 | if new_resource.associate_to_vpc != (elastic_ip.domain == "vpc") 33 | raise "#{new_resource}.associate_to_vpc = #{new_resource.associate_to_vpc}, but actual IP address has vpc? set to #{(elastic_ip.domain == 'vpc')}. Cannot be modified!" 34 | end 35 | end 36 | end 37 | 38 | def destroy_aws_object(elastic_ip) 39 | # if it's attached to something in a vpc, disassociate first 40 | if !elastic_ip.instance_id.nil? && elastic_ip.domain == "vpc" 41 | converge_by "dissociate Elastic IP address #{new_resource.name} (#{elastic_ip.public_ip}) from #{elastic_ip.instance_id}" do 42 | new_resource.driver.ec2.disassociate_address public_ip: elastic_ip.public_ip 43 | end 44 | end 45 | converge_by "delete Elastic IP address #{new_resource.name} (#{elastic_ip.public_ip}) in #{region}" do 46 | new_resource.driver.ec2.release_address allocation_id: elastic_ip.allocation_id 47 | end 48 | end 49 | 50 | private 51 | 52 | def desired_instance 53 | unless defined?(@desired_instance) 54 | if new_resource.machine == false 55 | @desired_instance = false 56 | else 57 | @desired_instance = Chef::Resource::AwsInstance.get_aws_object(new_resource.machine, resource: new_resource) 58 | end 59 | end 60 | @desired_instance 61 | end 62 | 63 | def update_association(elastic_ip) 64 | # 65 | # If we were told to associate the IP to a machine, do so 66 | # 67 | if desired_instance.is_a?(::Aws::EC2::Instance) || desired_instance.is_a?(::Aws::EC2::Instance) 68 | if desired_instance.id != elastic_ip.instance_id 69 | converge_by "associate Elastic IP address #{new_resource.name} (#{elastic_ip.public_ip}) with #{new_resource.machine} (#{desired_instance.id})" do 70 | new_resource.driver.ec2.associate_address instance_id: desired_instance.id, allocation_id: elastic_ip.allocation_id 71 | end 72 | end 73 | 74 | # 75 | # If we were told to set the association to false, disassociate it. 76 | # 77 | else 78 | unless elastic_ip.association_id.nil? 79 | converge_by "disassociate Elastic IP address #{new_resource.name} (#{elastic_ip.public_ip}) from #{elastic_ip.instance_id} in #{region}" do 80 | new_resource.driver.ec2.disassociate_address public_ip: elastic_ip.public_ip 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_iam_instance_profile.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsInstanceProfile < Chef::Provisioning::AWSDriver::AWSProvider 4 | provides :aws_iam_instance_profile 5 | 6 | def action_create 7 | iam_instance_profile = super 8 | 9 | update_attached_role(iam_instance_profile) 10 | end 11 | 12 | protected 13 | 14 | def detach_role(iam_instance_profile) 15 | iam_instance_profile.roles.each do |r| 16 | converge_by "detaching role #{r.name} from instance profile #{new_resource.name}" do 17 | iam_instance_profile.remove_role(role_name: r.name) 18 | end 19 | end 20 | end 21 | 22 | def update_attached_role(iam_instance_profile) 23 | options = Chef::Provisioning::AWSDriver::AWSResource.lookup_options({ iam_role: new_resource.role }, resource: new_resource) 24 | role = options[:iam_role] 25 | 26 | if new_resource.role && !iam_instance_profile.roles.map(&:name).include?(role) 27 | detach_role(iam_instance_profile) 28 | converge_by "associating role #{role} with instance profile #{new_resource.name}" do 29 | # Despite having collection methods for roles, instance profile can only have single role associated 30 | iam_instance_profile.add_role( 31 | role_name: role 32 | ) 33 | end 34 | end 35 | end 36 | 37 | def create_aws_object 38 | converge_by "create IAM instance profile #{new_resource.name}" do 39 | new_resource.driver.iam_resource.create_instance_profile( 40 | path: new_resource.path || "/", 41 | instance_profile_name: new_resource.name 42 | ) 43 | end 44 | end 45 | 46 | def update_aws_object(iam_instance_profile) 47 | # Nothing to update on our object because the role relationship is managed 48 | # through the action 49 | iam_instance_profile 50 | end 51 | 52 | def destroy_aws_object(iam_instance_profile) 53 | detach_role(iam_instance_profile) 54 | converge_by "delete #{iam_instance_profile.name}" do 55 | iam_instance_profile.delete 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_iam_role.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/json_compat" 3 | 4 | class Chef::Provider::AwsIamRole < Chef::Provisioning::AWSDriver::AWSProvider 5 | provides :aws_iam_role 6 | 7 | def iam_client 8 | new_resource.driver.iam_client 9 | end 10 | 11 | def iam_resource 12 | new_resource.driver.iam_resource 13 | end 14 | 15 | def action_create 16 | role = super 17 | 18 | update_inline_policy(role) unless new_resource.inline_policies.nil? 19 | end 20 | 21 | protected 22 | 23 | def create_aws_object 24 | converge_by "create IAM Role #{new_resource.name}" do 25 | iam_resource.create_role( 26 | path: new_resource.path, 27 | role_name: new_resource.name, 28 | assume_role_policy_document: new_resource.assume_role_policy_document 29 | ) 30 | end 31 | iam_resource.role(new_resource.name) 32 | end 33 | 34 | def update_aws_object(role) 35 | if new_resource.path && new_resource.path != role.path 36 | raise "Path of IAM Role #{new_resource.name} is #{role.path}, but desired path is #{new_resource.path}. IAM Role paths cannot be updated!" 37 | end 38 | if new_resource.assume_role_policy_document && policy_update_required?(role.assume_role_policy_document, new_resource.assume_role_policy_document) 39 | converge_by "update IAM Role #{role.name} assume_role_policy_document" do 40 | iam_client.update_assume_role_policy( 41 | role_name: new_resource.name, 42 | policy_document: new_resource.assume_role_policy_document 43 | ) 44 | end 45 | end 46 | end 47 | 48 | def destroy_aws_object(role) 49 | converge_by "delete IAM Role #{role.name}" do 50 | role.instance_profiles.each do |profile| 51 | profile.remove_role(role_name: role.name) 52 | end 53 | role.policies.each do |policy| 54 | converge_by "delete IAM Role inline policy #{policy.name}" do 55 | policy.delete 56 | end 57 | end 58 | role.delete 59 | end 60 | end 61 | 62 | private 63 | 64 | def update_inline_policy(role) 65 | desired_inline_policies = Hash[new_resource.inline_policies.map { |k, v| [k.to_s, v] }] 66 | current_inline_policies = Hash[role.policies.map { |p| [p.name, p.policy_document] }] 67 | 68 | policies_to_put = desired_inline_policies.reject { |k, v| current_inline_policies[k] && !policy_update_required?(current_inline_policies[k], v) } 69 | policies_to_delete = current_inline_policies.keys - desired_inline_policies.keys 70 | 71 | policies_to_put.each do |policy_name, policy| 72 | converge_by "Adding or updating inline Role policy #{policy_name}" do 73 | iam_client.put_role_policy( 74 | role_name: role.name, 75 | policy_name: policy_name, 76 | policy_document: policy 77 | ) 78 | end 79 | end 80 | 81 | policies_to_delete.each do |policy_name| 82 | converge_by "Deleting inline Role policy #{policy_name}" do 83 | iam_client.delete_role_policy( 84 | role_name: role.name, 85 | policy_name: policy_name 86 | ) 87 | end 88 | end 89 | end 90 | 91 | def policy_update_required?(current_policy, desired_policy) 92 | # We parse the JSON into a hash to get rid of whitespace and ordering issues 93 | Chef::JSONCompat.parse(URI.decode(current_policy)) != Chef::JSONCompat.parse(desired_policy) 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_image.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/provisioning/aws_driver/tagging_strategy/ec2" 3 | 4 | class Chef::Provider::AwsImage < Chef::Provisioning::AWSDriver::AWSProvider 5 | include Chef::Provisioning::AWSDriver::TaggingStrategy::EC2ConvergeTags 6 | 7 | provides :aws_image 8 | 9 | def destroy_aws_object(image) 10 | instance_id = image.tags.map { |t| [t.key, t.value] }.to_h["from-instance"] 11 | Chef::Log.debug("Found from-instance tag [#{instance_id}] on #{image.id}") 12 | unless instance_id 13 | # This is an old image and doesn't have the tag added - lets try and find it from the block device mapping 14 | image.block_device_mappings.map do |_dev, opts| 15 | snapshot = new_resource.driver.ec2_resource.snapshot(opts[:snapshot_id]) 16 | desc = snapshot.description 17 | m = /CreateImage\(([^\)]+)\)/.match(desc) 18 | if m 19 | Chef::Log.debug("Found [#{instance_id}] from snapshot #{snapshot.id} on #{image.id}") 20 | instance_id = m[1] 21 | end 22 | end 23 | end 24 | converge_by "deregister image #{new_resource} in #{region}" do 25 | image.deregister 26 | end 27 | if instance_id 28 | # As part of the image creation process, the source instance was automatically 29 | # destroyed - we just need to make sure that has completed successfully 30 | instance = new_resource.driver.ec2_resource.instance(instance_id) 31 | converge_by "waiting until instance #{instance.id} is :terminated" do 32 | if instance.exists? 33 | instance.wait_until_terminated do |w| 34 | w.delay = 5 35 | w.max_attempts = 60 36 | w.before_wait do |attempts, _response| 37 | action_handler.report_progress "waited #{(attempts - 1) * 5}/#{60 * 5}s for #{instance.id} status to terminate..." 38 | end 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_instance.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/provisioning/aws_driver/tagging_strategy/ec2" 3 | 4 | class Chef::Provider::AwsInstance < Chef::Provisioning::AWSDriver::AWSProvider 5 | include Chef::Provisioning::AWSDriver::TaggingStrategy::EC2ConvergeTags 6 | 7 | provides :aws_instance 8 | 9 | def create_aws_object(instance); end 10 | 11 | def update_aws_object(instance); end 12 | 13 | def destroy_aws_object(instance) 14 | message = "delete instance #{new_resource}" 15 | message += " in VPC #{instance.vpc.id}" unless instance.vpc.nil? 16 | message += " in #{region}" 17 | converge_by message do 18 | instance.terminate 19 | end 20 | converge_by "waited until instance #{new_resource} is :terminated" do 21 | # When purging, we must wait until the instance is fully terminated - thats the only way 22 | # to delete the network interface that I can see 23 | instance.wait_until_terminated do |w| 24 | # TODO: look at `wait_for_status` - delay and max_attempts should be configurable 25 | w.delay = 5 26 | w.max_attempts = 60 27 | w.before_wait do |attempts, _response| 28 | action_handler.report_progress "waited #{(attempts - 1) * 5}/#{60 * 5}s for #{instance.id} status to terminate..." 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_internet_gateway.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "retryable" 3 | 4 | class Chef::Provider::AwsInternetGateway < Chef::Provisioning::AWSDriver::AWSProvider 5 | include Chef::Provisioning::AWSDriver::TaggingStrategy::EC2ConvergeTags 6 | 7 | provides :aws_internet_gateway 8 | 9 | def action_detach 10 | internet_gateway = Chef::Resource::AwsInternetGateway.get_aws_object(new_resource.name, resource: new_resource) 11 | detach_vpc(internet_gateway) 12 | end 13 | 14 | protected 15 | 16 | def create_aws_object 17 | desired_vpc = Chef::Resource::AwsVpc.get_aws_object(new_resource.vpc, resource: new_resource) if new_resource.vpc 18 | 19 | converge_by "create internet gateway #{new_resource.name} in region #{region}" do 20 | ec2_resource = ::Aws::EC2::Resource.new(new_resource.driver.ec2) 21 | internet_gateway = ec2_resource.create_internet_gateway 22 | retry_with_backoff(::Aws::EC2::Errors::InvalidInternetGatewayIDNotFound) do 23 | internet_gateway.create_tags(tags: [{ key: "Name", value: new_resource.name }]) 24 | end 25 | 26 | attach_vpc(desired_vpc, internet_gateway) if desired_vpc 27 | 28 | internet_gateway 29 | end 30 | end 31 | 32 | def update_aws_object(internet_gateway) 33 | ec2_resource = new_resource.driver.ec2.describe_internet_gateways(internet_gateway_ids: [internet_gateway.id]) 34 | current_vpc = ec2_resource.internet_gateways.first.attachments.first 35 | 36 | if new_resource.vpc 37 | desired_vpc = Chef::Resource::AwsVpc.get_aws_object(new_resource.vpc, resource: new_resource) 38 | current_vpc_id = current_vpc.vpc_id unless current_vpc.nil? 39 | desired_vpc_id = desired_vpc.vpc_id unless desired_vpc.nil? 40 | if current_vpc_id != desired_vpc_id 41 | detach_vpc(internet_gateway) 42 | attach_vpc(desired_vpc, internet_gateway) 43 | end 44 | end 45 | end 46 | 47 | def destroy_aws_object(internet_gateway) 48 | converge_by "delete internet gateway #{new_resource.name} in region #{region}" do 49 | detach_vpc(internet_gateway) 50 | internet_gateway.delete 51 | end 52 | end 53 | 54 | private 55 | 56 | def attach_vpc(vpc, desired_gateway) 57 | if vpc.internet_gateways.first && vpc.internet_gateways.first != desired_gateway 58 | current_driver = new_resource.driver 59 | current_chef_server = new_resource.chef_server 60 | Cheffish.inline_resource(self, action) do 61 | aws_vpc vpc.id do 62 | cidr_block vpc.cidr_block 63 | internet_gateway false 64 | driver current_driver 65 | chef_server current_chef_server 66 | end 67 | end 68 | end 69 | converge_by "attach vpc #{vpc.id} to #{desired_gateway.id}" do 70 | desired_gateway.attach_to_vpc(vpc_id: vpc.id) 71 | end 72 | end 73 | 74 | def detach_vpc(internet_gateway) 75 | ec2_resource = new_resource.driver.ec2.describe_internet_gateways(internet_gateway_ids: [internet_gateway.id]) 76 | vpcid = ec2_resource.internet_gateways.first.attachments.first 77 | vpc_id = vpcid.vpc_id unless vpcid.nil? 78 | if vpc_id 79 | converge_by "detach vpc #{vpc_id} from internet gateway #{internet_gateway.id}" do 80 | internet_gateway.detach_from_vpc(vpc_id: vpc_id) 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_launch_configuration.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/resource/aws_image" 3 | require "base64" 4 | 5 | class Chef::Provider::AwsLaunchConfiguration < Chef::Provisioning::AWSDriver::AWSProvider 6 | provides :aws_launch_configuration 7 | 8 | protected 9 | 10 | def create_aws_object 11 | image_id = Chef::Resource::AwsImage.get_aws_object_id(new_resource.image, resource: new_resource) 12 | instance_type = new_resource.instance_type || new_resource.driver.default_instance_type 13 | options = AWSResource.lookup_options(new_resource.options || options, resource: new_resource) 14 | options[:launch_configuration_name] = new_resource.name if new_resource.name 15 | options[:image_id] = image_id 16 | options[:instance_type] = instance_type 17 | if options[:user_data] 18 | options[:user_data] = ensure_base64_encoded(options[:user_data]) 19 | end 20 | 21 | converge_by "create launch configuration #{new_resource.name} in #{region}" do 22 | new_resource.driver.auto_scaling_client.create_launch_configuration(options) 23 | end 24 | end 25 | 26 | def update_aws_object(launch_configuration) 27 | if new_resource.image 28 | image_id = Chef::Resource::AwsImage.get_aws_object_id(new_resource.image, resource: new_resource) 29 | if image_id != launch_configuration.image_id 30 | raise "#{new_resource}.image = #{new_resource.image}, but actual launch configuration has image set to #{launch_configuration.image_id}. Cannot be modified!" 31 | end 32 | end 33 | if new_resource.instance_type 34 | if new_resource.instance_type != launch_configuration.instance_type 35 | raise "#{new_resource}.instance_type = #{new_resource.instance_type}, but actual launch configuration has instance_type set to #{launch_configuration.instance_type}. Cannot be modified!" 36 | end 37 | end 38 | # TODO: compare options 39 | end 40 | 41 | def destroy_aws_object(launch_configuration) 42 | converge_by "delete launch configuration #{new_resource.name} in #{region}" do 43 | # TODO: add a timeout here. 44 | # TODO is InUse really a status guaranteed to go away?? 45 | begin 46 | new_resource.driver.auto_scaling_client.delete_launch_configuration(launch_configuration_name: launch_configuration.launch_configuration_name) 47 | rescue ::Aws::AutoScaling::Errors::ResourceInUse 48 | sleep 5 49 | retry 50 | end 51 | end 52 | end 53 | 54 | private 55 | 56 | def ensure_base64_encoded(data) 57 | Base64.strict_decode64(data) 58 | data 59 | rescue ArgumentError 60 | Base64.encode64(data) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_load_balancer.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsLoadBalancer < Chef::Provisioning::AWSDriver::AWSProvider 4 | def aws_tagger 5 | @aws_tagger ||= begin 6 | elb_strategy = Chef::Provisioning::AWSDriver::TaggingStrategy::ELB.new( 7 | new_resource.driver.elb_client, 8 | new_resource.name, 9 | new_resource.aws_tags 10 | ) 11 | Chef::Provisioning::AWSDriver::AWSTagger.new(elb_strategy, action_handler) 12 | end 13 | end 14 | 15 | def converge_tags 16 | aws_tagger.converge_tags 17 | end 18 | 19 | provides :aws_load_balancer 20 | 21 | def destroy_aws_object(load_balancer) 22 | converge_by "delete load balancer #{new_resource.name} (#{load_balancer.load_balancer_name}) in #{region}" do 23 | new_resource.driver.elb_client.delete_load_balancer(load_balancer_name: load_balancer.load_balancer_name) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_nat_gateway.rb: -------------------------------------------------------------------------------- 1 | # require 'chef/provisioning/aws_driver/aws_provider' 2 | require "retryable" 3 | 4 | class Chef::Provider::AwsNatGateway < Chef::Provisioning::AWSDriver::AWSProvider 5 | provides :aws_nat_gateway 6 | 7 | protected 8 | 9 | def create_aws_object 10 | if new_resource.subnet.nil? 11 | raise "Nat Gateway create action for '#{new_resource.name}' requires the 'subnet' attribute." 12 | end 13 | subnet = Chef::Resource::AwsSubnet.get_aws_object(new_resource.subnet, resource: new_resource) 14 | 15 | if new_resource.eip_address.nil? 16 | # TODO: Ideally it would be nice to automatically manage an eip address but 17 | # the lack of tagging support and the limited SDK interaction with these two 18 | # resources makes that too hard right now. So we force the user to manage their 19 | # eip address as a seperate resource. 20 | raise "Nat Gateway create action for '#{new_resource.name}' requires the 'eip_address' attribute." 21 | end 22 | eip_address = Chef::Resource::AwsEipAddress.get_aws_object(new_resource.eip_address, resource: new_resource) 23 | 24 | converge_by "create nat gateway #{new_resource.name} in region #{region} for subnet #{subnet}" do 25 | options = { 26 | subnet_id: subnet.id, 27 | allocation_id: eip_address.allocation_id 28 | } 29 | 30 | nat_gateway = new_resource.driver.ec2_resource.create_nat_gateway(options) 31 | wait_for_state(nat_gateway, :available) 32 | nat_gateway 33 | end 34 | end 35 | 36 | def update_aws_object(nat_gateway) 37 | subnet_id = Chef::Resource::AwsSubnet.get_aws_object_id(new_resource.subnet, resource: new_resource) if new_resource.subnet 38 | if subnet_id != nat_gateway.subnet_id 39 | raise "Nat gateway subnet cannot be changed after being created! Desired subnet for #{new_resource.name} (#{nat_gateway.id}) was \"#{nat_gateway.subnet_id}\" and actual description is \"#{subnet_id}\"" 40 | end 41 | 42 | if new_resource.eip_address 43 | eip_address = Chef::Resource::AwsEipAddress.get_aws_object(new_resource.eip_address, resource: new_resource) 44 | if eip_address.nil? || (eip_address.allocation_id != nat_gateway.nat_gateway_addresses.first.allocation_id) 45 | raise "Nat gateway elastic ip address cannot be changed after being created! Desired elastic ip address for #{new_resource.name} (#{nat_gateway.id}) was \"#{nat_gateway.nat_gateway_addresses.first.allocation_id}\" and actual description is \"#{eip_address.allocation_id}\"" 46 | end 47 | end 48 | end 49 | 50 | def destroy_aws_object(nat_gateway) 51 | converge_by "delete nat gateway #{new_resource.name} in region #{region} for subnet #{nat_gateway.subnet_id}" do 52 | nat_gateway.delete 53 | wait_for_state(nat_gateway, :deleted) 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_rds_instance.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/provisioning/aws_driver/tagging_strategy/rds" 3 | 4 | class Chef::Provider::AwsRdsInstance < Chef::Provisioning::AWSDriver::AWSProvider 5 | include Chef::Provisioning::AWSDriver::TaggingStrategy::RDSConvergeTags 6 | 7 | provides :aws_rds_instance 8 | 9 | REQUIRED_OPTIONS = %i{db_instance_identifier allocated_storage engine 10 | db_instance_class master_username master_user_password}.freeze 11 | 12 | OTHER_OPTIONS = %i{db_snapshot_identifier engine_version multi_az iops publicly_accessible db_name port db_subnet_group_name db_parameter_group_name}.freeze 13 | 14 | def update_aws_object(_instance) 15 | Chef::Log.warn("aws_rds_instance does not support modifying a started instance") 16 | # There are required optiosn (like `allocated_storage`) that the use may not 17 | # specify on a resource to perform an update. For example, they may want to 18 | # only specify iops to modify that attribute on an update after initial 19 | # creation. In this case we need to load the required options from the existing 20 | # aws_object and only override it if the user has specified a value in the 21 | # resource. Ideally, it would be nice to mark values as required on the 22 | # resource but right now there is not a `required_on_create`. This would 23 | # also be different if chef-provisioning performed resource cloning, which 24 | # it does not. 25 | end 26 | 27 | def create_aws_object 28 | converge_by "create RDS instance #{new_resource.db_instance_identifier} in #{region}" do 29 | if new_resource.db_snapshot_identifier 30 | snap_options_hash = %i{allocated_storage master_username master_user_password engine_version}.each { |k| options_hash.delete(k) } 31 | new_resource.driver.rds_client.restore_db_instance_from_db_snapshot(options_hash).db_instance 32 | else 33 | new_resource.driver.rds_resource.create_db_instance(options_hash) 34 | end 35 | end 36 | end 37 | 38 | def destroy_aws_object(instance) 39 | converge_by "delete RDS instance #{new_resource.db_instance_identifier} in #{region}" do 40 | instance.delete(skip_final_snapshot: true) 41 | end 42 | # Wait up to 10 minutes for the db instance to shutdown 43 | converge_by "waited until RDS instance #{new_resource.name} was deleted" do 44 | wait_for( 45 | aws_object: instance, 46 | # http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Status.html 47 | # It cannot _actually_ return a deleted status, we're just looking for the error 48 | query_method: :db_instance_status, 49 | expected_responses: ["deleted"], 50 | acceptable_errors: [::Aws::RDS::Errors::DBInstanceNotFound], 51 | tries: 60, 52 | sleep: 10, &:reload 53 | ) 54 | end 55 | end 56 | 57 | # Sets the additional options then overrides it with all required options from 58 | # the resource as well as optional options 59 | def options_hash 60 | @options_hash ||= begin 61 | opts = Hash[new_resource.additional_options.map { |(k, v)| [k.to_sym, v] }] 62 | REQUIRED_OPTIONS.each do |opt| 63 | opts[opt] = new_resource.send(opt) 64 | end 65 | OTHER_OPTIONS.each do |opt| 66 | opts[opt] = new_resource.send(opt) unless new_resource.send(opt).nil? 67 | end 68 | AWSResource.lookup_options(opts, resource: new_resource) 69 | opts 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_rds_subnet_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "chef/provisioning/aws_driver/tagging_strategy/rds" 3 | 4 | class Chef::Provider::AwsRdsSubnetGroup < Chef::Provisioning::AWSDriver::AWSProvider 5 | include Chef::Provisioning::AWSDriver::TaggingStrategy::RDSConvergeTags 6 | 7 | provides :aws_rds_subnet_group 8 | 9 | def create_aws_object 10 | converge_by "create RDS subnet group #{new_resource.name} in #{region}" do 11 | driver.create_db_subnet_group(desired_options) 12 | end 13 | end 14 | 15 | def destroy_aws_object(_subnet_group) 16 | converge_by "delete RDS subnet group #{new_resource.name} in #{region}" do 17 | driver.delete_db_subnet_group(db_subnet_group_name: new_resource.name) 18 | end 19 | end 20 | 21 | def update_aws_object(subnet_group) 22 | updates = required_updates(subnet_group) 23 | unless updates.empty? 24 | converge_by updates do 25 | driver.modify_db_subnet_group(desired_options) 26 | end 27 | end 28 | end 29 | 30 | def desired_options 31 | @desired_options ||= begin 32 | opts = {} 33 | opts[:db_subnet_group_name] = new_resource.name 34 | opts[:db_subnet_group_description] = new_resource.description 35 | opts[:subnet_ids] = new_resource.subnets 36 | AWSResource.lookup_options(opts, resource: new_resource) 37 | end 38 | end 39 | 40 | # Given an existing subnet group, return an array of update descriptions 41 | # representing the updates that need to be made. 42 | # 43 | # If no updates are needed, an empty array is returned. 44 | # 45 | def required_updates(subnet_group) 46 | ret = [] 47 | if desired_options[:db_subnet_group_description] != subnet_group[:db_subnet_group_description] 48 | ret << " set group description to #{desired_options[:db_subnet_group_description]}" 49 | end 50 | 51 | unless xor_array(desired_options[:subnet_ids], subnet_ids(subnet_group[:subnets])).empty? 52 | ret << " set subnets to #{desired_options[:subnet_ids]}" 53 | end 54 | 55 | unless desired_options[:aws_tags].nil? || desired_options[:aws_tags].empty? 56 | # modify_db_subnet_group doesn't support the tags key according to 57 | # http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/RDS/Client.html#modify_db_subnet_group-instance_method 58 | Chef::Log.warn "Updating tags for RDS subnet groups is not supported." 59 | end 60 | 61 | ret.unshift("update RDS subnet group #{new_resource.name} in #{region}") unless ret.empty? 62 | ret 63 | end 64 | 65 | private 66 | 67 | def subnet_ids(subnets) 68 | subnets.map { |i| i[:subnet_identifier] } 69 | end 70 | 71 | def xor_array(a, b) 72 | (a | b) - (a & b) 73 | end 74 | 75 | # To be in line with the other resources. The aws_tags property 76 | # takes a hash. But we actually need an array. 77 | def tag_hash_to_array(tag_hash) 78 | ret = [] 79 | tag_hash.each do |key, value| 80 | ret << { key: key, value: value } 81 | end 82 | ret 83 | end 84 | 85 | def driver 86 | new_resource.driver.rds 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_server_certificate.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsServerCertificate < Chef::Provisioning::AWSDriver::AWSProvider 4 | provides :aws_server_certificate 5 | 6 | def update_aws_object(_certificate) 7 | Chef::Log.warn("aws_server_certificate does not support modifying an existing certificate") 8 | end 9 | 10 | def create_aws_object 11 | converge_by "create server certificate #{new_resource.name}" do 12 | opts = { 13 | server_certificate_name: new_resource.name, 14 | certificate_body: new_resource.certificate_body, 15 | private_key: new_resource.private_key 16 | } 17 | opts[:certificate_chain] = new_resource.certificate_chain if new_resource.certificate_chain 18 | new_resource.driver.iam.upload_server_certificate(**opts) 19 | end 20 | end 21 | 22 | def destroy_aws_object(certificate) 23 | converge_by "delete server certificate #{new_resource.name}" do 24 | certificate.delete 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_sns_topic.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | require "date" 3 | 4 | class Chef::Provider::AwsSnsTopic < Chef::Provisioning::AWSDriver::AWSProvider 5 | provides :aws_sns_topic 6 | 7 | protected 8 | 9 | def create_aws_object 10 | converge_by "create SNS topic #{new_resource.name} in #{region}" do 11 | new_resource.driver.sns.create_topic(name: new_resource.name) 12 | end 13 | end 14 | 15 | def update_aws_object(topic); end 16 | 17 | def destroy_aws_object(topic) 18 | topic_arn_name = topic.attributes.values_at("TopicArn").first 19 | converge_by "delete SNS topic_arn #{topic_arn_name} in #{region}" do 20 | new_resource.driver.sns.delete_topic(topic_arn: topic_arn_name) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/chef/provider/aws_sqs_queue.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_provider" 2 | 3 | class Chef::Provider::AwsSqsQueue < Chef::Provisioning::AWSDriver::AWSProvider 4 | provides :aws_sqs_queue 5 | 6 | def create_aws_object 7 | options = AWSResource.lookup_options(new_resource.options || {}, resource: new_resource) 8 | option_sqs = {} 9 | option_sqs[:queue_name] = new_resource.name if new_resource.name 10 | option_sqs[:attributes] = options 11 | converge_by "create SQS queue #{new_resource.name} in #{region}" do 12 | retry_with_backoff(::Aws::SQS::Errors::QueueDeletedRecently) do 13 | new_resource.driver.sqs.create_queue(option_sqs) 14 | end 15 | end 16 | end 17 | 18 | def update_aws_object(queue); end 19 | 20 | def destroy_aws_object(queue) 21 | converge_by "delete SQS queue #{new_resource.name} in #{region}" do 22 | new_resource.driver.sqs.delete_queue(queue_url: queue.queue_url) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning" 2 | require "chef/provisioning/aws_driver/driver" 3 | 4 | require "chef/resource/aws_auto_scaling_group" 5 | require "chef/resource/aws_cache_cluster" 6 | require "chef/resource/aws_cache_replication_group" 7 | require "chef/resource/aws_cache_subnet_group" 8 | require "chef/resource/aws_cloudsearch_domain" 9 | require "chef/resource/aws_cloudwatch_alarm" 10 | require "chef/resource/aws_dhcp_options" 11 | require "chef/resource/aws_ebs_volume" 12 | require "chef/resource/aws_eip_address" 13 | require "chef/resource/aws_elasticsearch_domain" 14 | require "chef/resource/aws_iam_role" 15 | require "chef/resource/aws_iam_instance_profile" 16 | require "chef/resource/aws_image" 17 | require "chef/resource/aws_instance" 18 | require "chef/resource/aws_internet_gateway" 19 | require "chef/resource/aws_key_pair" 20 | require "chef/resource/aws_launch_configuration" 21 | require "chef/resource/aws_load_balancer" 22 | require "chef/resource/aws_nat_gateway" 23 | require "chef/resource/aws_network_acl" 24 | require "chef/resource/aws_network_interface" 25 | require "chef/resource/aws_rds_instance" 26 | require "chef/resource/aws_rds_subnet_group" 27 | require "chef/resource/aws_rds_parameter_group" 28 | require "chef/resource/aws_route_table" 29 | require "chef/resource/aws_route53_hosted_zone" 30 | require "chef/resource/aws_s3_bucket" 31 | require "chef/resource/aws_security_group" 32 | require "chef/resource/aws_server_certificate" 33 | require "chef/resource/aws_sns_topic" 34 | require "chef/resource/aws_sqs_queue" 35 | require "chef/resource/aws_subnet" 36 | require "chef/resource/aws_vpc" 37 | require "chef/resource/aws_vpc_peering_connection" 38 | 39 | module NoResourceCloning 40 | def prior_resource 41 | if resource_class <= Chef::Provisioning::AWSDriver::AWSResource 42 | Chef::Log.debug "Canceling resource cloning for #{resource_class}" 43 | nil 44 | else 45 | super 46 | end 47 | end 48 | 49 | def emit_cloned_resource_warning; end 50 | 51 | def emit_harmless_cloning_debug; end 52 | end 53 | 54 | # Chef 12.2 changed `load_prior_resource` logic to be in the Chef::ResourceBuilder class 55 | # but that class only exists in 12.2 and up 56 | if defined? Chef::ResourceBuilder 57 | # Ruby 2.0.0 has prepend as a protected method 58 | Chef::ResourceBuilder.send(:prepend, NoResourceCloning) 59 | end 60 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/aws_rds_resource.rb: -------------------------------------------------------------------------------- 1 | require_relative "aws_resource" 2 | 3 | module Chef::Provisioning::AWSDriver 4 | class AWSRDSResource < AWSResource 5 | def rds_tagging_type 6 | raise "You must add the RDS resource type lookup from http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Tagging.html#USER_Tagging.ARN" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/aws_taggable.rb: -------------------------------------------------------------------------------- 1 | module Chef::Provisioning::AWSDriver 2 | # This module is meant to be included in a resource that is taggable 3 | # This will add the appropriate attribute that can be converged by the provider 4 | # TODO it would be nice to not have two seperate modules (taggable/tagger) 5 | # and just have the provider decorate the resource or vice versa. Complicated 6 | # by resources <-> providers being many-to-many. 7 | module AWSTaggable 8 | def self.included(klass) 9 | # This should be a hash of tags to apply to the AWS object 10 | # 11 | # @param aws_tags [Hash] Should be a hash of keys & values to add. Keys and values 12 | # can be provided as symbols or strings, but will be stored in AWS as strings. 13 | klass.attribute :aws_tags, kind_of: Hash 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/aws_tagger.rb: -------------------------------------------------------------------------------- 1 | require "retryable" 2 | 3 | module Chef::Provisioning::AWSDriver 4 | # Include this module on a class or instance that is responsible for tagging 5 | # itself. Fill in the hook methods so it knows how to tag itself. 6 | class AWSTagger 7 | extend Forwardable 8 | 9 | attr_reader :action_handler 10 | 11 | def initialize(tagging_strategy, action_handler) 12 | @tagging_strategy = tagging_strategy 13 | @action_handler = action_handler 14 | end 15 | 16 | def_delegators :@tagging_strategy, :desired_tags, :current_tags, :set_tags, :delete_tags 17 | 18 | def converge_tags 19 | if desired_tags.nil? 20 | Chef::Log.debug "aws_tags not provided, nothing to converge" 21 | return 22 | end 23 | 24 | # Duplication and normalization 25 | # ::Aws::EC2::Errors::InvalidParameterValue: Tag value cannot be null. Use empty string instead. 26 | n_desired_tags = Hash[desired_tags.map { |k, v| [k.to_s, v.to_s] }] 27 | n_current_tags = Hash[current_tags.map { |k, v| [k.to_s, v.to_s] }] 28 | 29 | tags_to_set = n_desired_tags.reject { |k, v| n_current_tags[k] && n_current_tags[k] == v } 30 | tags_to_delete = n_current_tags.keys - n_desired_tags.keys 31 | # We don't want to delete `Name`, just all other tags 32 | # Tag keys and values are case sensitive - `Name` is special because it 33 | # shows as the name in the console 34 | tags_to_delete.delete("Name") 35 | 36 | # Tagging frequently fails so we retry with an exponential backoff, a maximum of 10 seconds 37 | Retryable.retryable( 38 | tries: 20, 39 | sleep: ->(n) { [2**n, 10].min }, 40 | on: [::Aws::EC2::Errors, Aws::S3::Errors, ::Aws::S3::Errors::ServiceError] 41 | ) do |retries, exception| 42 | if retries > 0 43 | Chef::Log.info "Retrying the tagging, previous try failed with #{exception.inspect}" 44 | end 45 | unless tags_to_set.empty? 46 | action_handler.perform_action "creating tags #{tags_to_set}" do 47 | set_tags(tags_to_set) 48 | end 49 | tags_to_set = [] 50 | end 51 | unless tags_to_delete.empty? 52 | action_handler.perform_action "deleting tags #{tags_to_delete}" do 53 | delete_tags(tags_to_delete) 54 | end 55 | tags_to_delete = [] 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/credentials2.rb: -------------------------------------------------------------------------------- 1 | require "aws-sdk" 2 | require "aws-sdk-core/credentials" 3 | require "aws-sdk-core/shared_credentials" 4 | require "aws-sdk-core/instance_profile_credentials" 5 | require "aws-sdk-core/assume_role_credentials" 6 | 7 | class Chef 8 | module Provisioning 9 | module AWSDriver 10 | class LoadCredentialsError < RuntimeError; end 11 | 12 | # Loads the credentials for the AWS SDK V2 13 | # Attempts to load credentials in the order specified at http://docs.aws.amazon.com/sdkforruby/api/index.html#Configuration 14 | class Credentials2 15 | attr_reader :profile_name 16 | 17 | # @param [Hash] options 18 | # @option options [String] :profile_name (ENV["AWS_DEFAULT_PROFILE"]) The profile name to use 19 | # when loading the config from '~/.aws/credentials'. This can be nil. 20 | def initialize(options = {}) 21 | @profile_name = options[:profile_name] || ENV["AWS_DEFAULT_PROFILE"] 22 | end 23 | 24 | # Try to load the credentials from an ordered list of sources and return the first one that 25 | # can be loaded successfully. 26 | def get_credentials 27 | # http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-environment 28 | credentials_file = ENV.fetch("AWS_SHARED_CREDENTIALS_FILE", ENV["AWS_CONFIG_FILE"]) 29 | shared_creds = ::Aws::SharedCredentials.new( 30 | profile_name: profile_name, 31 | path: credentials_file 32 | ) 33 | instance_profile_creds = ::Aws::InstanceProfileCredentials.new(retries: 1) 34 | 35 | if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"] 36 | creds = ::Aws::Credentials.new( 37 | ENV["AWS_ACCESS_KEY_ID"], 38 | ENV["AWS_SECRET_ACCESS_KEY"], 39 | ENV["AWS_SESSION_TOKEN"] 40 | ) 41 | elsif shared_creds.set? 42 | creds = shared_creds 43 | elsif instance_profile_creds.set? 44 | creds = instance_profile_creds 45 | else 46 | raise LoadCredentialsError, "Could not load credentials from the environment variables, the .aws/credentials file or the metadata service" 47 | end 48 | creds 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/exceptions.rb: -------------------------------------------------------------------------------- 1 | class Chef 2 | module Provisioning 3 | module AWSDriver 4 | module Exceptions 5 | class MultipleSecurityGroupError < RuntimeError 6 | def initialize(name, groups) 7 | super "Found security groups with ids [#{groups.map(&:id)}] that share name #{name}. " \ 8 | "Names are unique within VPCs - specify VPC to find by name." 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/resources.rb: -------------------------------------------------------------------------------- 1 | # Module under which all AWS resources live 2 | 3 | class Chef 4 | module Provisioning 5 | module AWSDriver 6 | module Resources 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/super_lwrp.rb: -------------------------------------------------------------------------------- 1 | require "chef/resource/lwrp_base" 2 | 3 | class Chef 4 | module Provisioning 5 | module AWSDriver 6 | class SuperLWRP < Chef::Resource::LWRPBase 7 | # 8 | # Add the :default lazy { ... } and :coerce validation_opts to `attribute` 9 | # 10 | if respond_to?(:properties) 11 | # in Chef 12.5+, properties replace attributes and these respond to 12 | # coerce and default with a lazy block - no need for overwriting! 13 | else 14 | def self.attribute(attr_name, validation_opts = {}) 15 | if validation_opts[:default].is_a?(Chef::DelayedEvaluator) 16 | lazy_default = validation_opts.delete(:default) 17 | end 18 | coerce = validation_opts.delete(:coerce) 19 | if lazy_default || coerce 20 | define_method(attr_name) do |arg = nil| 21 | arg = instance_exec(arg, &coerce) if coerce && !arg.nil? 22 | 23 | result = set_or_return(attr_name.to_sym, arg, validation_opts) 24 | 25 | if result.nil? && arg.nil? 26 | result = instance_eval(&lazy_default) if lazy_default 27 | end 28 | 29 | result 30 | end 31 | define_method(:"#{attr_name}=") do |arg| 32 | if arg.nil? 33 | remove_instance_variable(:"@#{arg}") 34 | else 35 | set_or_return(attr_name.to_sym, arg, validation_opts) 36 | end 37 | end 38 | else 39 | super 40 | end 41 | end 42 | 43 | # Below chef 12.5 you cannot do `default lazy: { ... }` - this adds that 44 | def self.lazy(&block) 45 | Chef::DelayedEvaluator.new(&block) 46 | end 47 | end 48 | 49 | # copy from Chef 12.5 params_validate.rb at http://redirx.me/?t35q. 50 | unless method_defined?(:_pv_is) 51 | def _pv_is(opts, key, to_be, raise_error: true) 52 | return true if !opts.key?(key.to_s) && !opts.key?(key.to_sym) 53 | value = _pv_opts_lookup(opts, key) 54 | to_be = [to_be].flatten(1) 55 | to_be.each do |tb| 56 | case tb 57 | when Proc 58 | return true if instance_exec(value, &tb) 59 | when Property 60 | validate(opts, key => tb.validation_options) 61 | return true 62 | else 63 | return true if tb === value 64 | end 65 | end 66 | 67 | if raise_error 68 | raise ::Chef::Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(', ')}! You passed #{value.inspect}." 69 | else 70 | false 71 | end 72 | end 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/tagging_strategy/auto_scaling.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_tagger" 2 | 3 | module Chef::Provisioning::AWSDriver::TaggingStrategy 4 | module AutoScalingConvergeTags 5 | def aws_tagger 6 | @aws_tagger ||= begin 7 | auto_scaling_strategy = Chef::Provisioning::AWSDriver::TaggingStrategy::AutoScaling.new( 8 | new_resource.driver.auto_scaling_client, 9 | new_resource.name, 10 | new_resource.aws_tags 11 | ) 12 | Chef::Provisioning::AWSDriver::AWSTagger.new(auto_scaling_strategy, action_handler) 13 | end 14 | end 15 | 16 | def converge_tags 17 | aws_tagger.converge_tags 18 | end 19 | end 20 | end 21 | 22 | module Chef::Provisioning::AWSDriver::TaggingStrategy 23 | class AutoScaling 24 | attr_reader :auto_scaling_client, :group_name, :desired_tags 25 | 26 | def initialize(auto_scaling_client, group_name, desired_tags) 27 | @auto_scaling_client = auto_scaling_client 28 | @group_name = group_name 29 | @desired_tags = desired_tags 30 | end 31 | 32 | def current_tags 33 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/AutoScaling/Client.html#describe_tags-instance_method 34 | resp = auto_scaling_client.describe_tags( 35 | filters: [ 36 | { 37 | name: "auto-scaling-group", 38 | values: [group_name] 39 | } 40 | ] 41 | ) 42 | Hash[resp.tags.map { |t| [t.key, t.value] }] 43 | end 44 | 45 | def set_tags(tags) 46 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/AutoScaling/Client.html#create_or_update_tags-instance_method 47 | auto_scaling_client.create_or_update_tags( 48 | tags: tags.map do |k, v| 49 | { 50 | resource_id: group_name, 51 | key: k, 52 | value: v, 53 | resource_type: "auto-scaling-group", 54 | propagate_at_launch: false 55 | } 56 | end 57 | ) 58 | end 59 | 60 | def delete_tags(tag_keys) 61 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/AutoScaling/Client.html#delete_tags-instance_method 62 | auto_scaling_client.delete_tags( 63 | tags: tag_keys.map do |k| 64 | { 65 | resource_id: group_name, 66 | key: k, 67 | value: nil, 68 | resource_type: "auto-scaling-group", 69 | propagate_at_launch: false 70 | } 71 | end 72 | ) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/tagging_strategy/ec2.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_tagger" 2 | 3 | module Chef::Provisioning::AWSDriver::TaggingStrategy 4 | module EC2ConvergeTags 5 | def aws_tagger 6 | @aws_tagger ||= begin 7 | ec2_strategy = Chef::Provisioning::AWSDriver::TaggingStrategy::EC2.new( 8 | new_resource.driver.ec2_client, 9 | new_resource.aws_object_id, 10 | new_resource.aws_tags 11 | ) 12 | Chef::Provisioning::AWSDriver::AWSTagger.new(ec2_strategy, action_handler) 13 | end 14 | end 15 | 16 | def converge_tags 17 | aws_tagger.converge_tags 18 | end 19 | end 20 | end 21 | 22 | module Chef::Provisioning::AWSDriver::TaggingStrategy 23 | class EC2 24 | attr_reader :ec2_client, :aws_object_id, :desired_tags 25 | 26 | def initialize(ec2_client, aws_object_id, desired_tags) 27 | @ec2_client = ec2_client 28 | @aws_object_id = aws_object_id 29 | @desired_tags = desired_tags 30 | end 31 | 32 | def current_tags 33 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Client.html#describe_tags-instance_method 34 | resp = ec2_client.describe_tags( 35 | filters: [ 36 | { 37 | name: "resource-id", 38 | values: [aws_object_id] 39 | } 40 | ] 41 | ) 42 | Hash[resp.tags.map { |t| [t.key, t.value] }] 43 | end 44 | 45 | def set_tags(tags) 46 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Client.html#create_tags-instance_method 47 | # "The value parameter is required, but if you don't want the tag to have a value, specify 48 | # the parameter with no value, and we set the value to an empty string." 49 | ec2_client.create_tags( 50 | resources: [aws_object_id], 51 | tags: tags.map { |k, v| { key: k, value: v } } 52 | ) 53 | end 54 | 55 | def delete_tags(tag_keys) 56 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Client.html#delete_tags-instance_method 57 | ec2_client.delete_tags( 58 | resources: [aws_object_id], 59 | tags: tag_keys.map { |k| { key: k } } 60 | ) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/tagging_strategy/elasticsearch.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_tagger" 2 | 3 | module Chef::Provisioning::AWSDriver::TaggingStrategy 4 | class Elasticsearch 5 | attr_reader :client, :arn, :desired_tags 6 | 7 | def initialize(client, arn, desired_tags) 8 | @client = client 9 | @arn = arn 10 | @desired_tags = desired_tags 11 | end 12 | 13 | def current_tags 14 | resp = client.list_tags(arn: arn) 15 | Hash[resp.tag_list.map { |t| [t.key, t.value] }] 16 | rescue ::Aws::ElasticsearchService::Errors::ResourceNotFoundException 17 | {} 18 | end 19 | 20 | def set_tags(tags) 21 | tags = tags.map do |k, v| 22 | if v.nil? 23 | { key: k } 24 | else 25 | { key: k, value: v } 26 | end 27 | end 28 | client.add_tags( 29 | arn: arn, 30 | tag_list: tags 31 | ) 32 | end 33 | 34 | def delete_tags(tag_keys) 35 | client.remove_tags(arn: arn, 36 | tag_keys: tag_keys) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/tagging_strategy/elb.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_tagger" 2 | 3 | module Chef::Provisioning::AWSDriver::TaggingStrategy 4 | class ELB 5 | attr_reader :elb_client, :access_point_name, :desired_tags 6 | 7 | def initialize(elb_client, access_point_name, desired_tags) 8 | @elb_client = elb_client 9 | @access_point_name = access_point_name 10 | @desired_tags = desired_tags 11 | end 12 | 13 | def current_tags 14 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/ElasticLoadBalancing/Client.html#describe_tags-instance_method 15 | resp = elb_client.describe_tags( 16 | load_balancer_names: [access_point_name] 17 | ) 18 | Hash[resp.tag_descriptions[0].tags.map { |t| [t.key, t.value] }] 19 | end 20 | 21 | def set_tags(tags) 22 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/ElasticLoadBalancing/Client.html#add_tags-instance_method 23 | elb_client.add_tags( 24 | load_balancer_names: [access_point_name], 25 | tags: tags.map { |k, v| { key: k, value: v } } 26 | ) 27 | end 28 | 29 | def delete_tags(tag_keys) 30 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/ElasticLoadBalancing/Client.html#remove_tags-instance_method 31 | elb_client.remove_tags( 32 | load_balancer_names: [access_point_name], 33 | tags: tag_keys.map { |k| { key: k } } 34 | ) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/tagging_strategy/rds.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_tagger" 2 | 3 | #################### 4 | # NOTE FROM http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Tagging.html 5 | # "Note that tags are cached for authorization purposes. Because of this, additions 6 | # and updates to tags on Amazon RDS resources may take several minutes before they 7 | # are available." 8 | #################### 9 | 10 | module Chef::Provisioning::AWSDriver::TaggingStrategy 11 | module RDSConvergeTags 12 | def aws_tagger 13 | @aws_tagger ||= begin 14 | rds_strategy = Chef::Provisioning::AWSDriver::TaggingStrategy::RDS.new( 15 | new_resource.driver.rds, 16 | construct_arn(new_resource), 17 | new_resource.aws_tags 18 | ) 19 | Chef::Provisioning::AWSDriver::AWSTagger.new(rds_strategy, action_handler) 20 | end 21 | end 22 | 23 | def converge_tags 24 | aws_tagger.converge_tags 25 | end 26 | 27 | # http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Tagging.html#USER_Tagging.ARN 28 | def construct_arn(new_resource) 29 | @arn ||= begin 30 | region = new_resource.driver.aws_config[:region] 31 | name = new_resource.name 32 | rds_type = new_resource.rds_tagging_type 33 | # Taken from example on https://forums.aws.amazon.com/thread.jspa?threadID=108012 34 | account_id = begin 35 | u = new_resource.driver.iam.get_user 36 | # We've got an AWS account root credential or an IAM admin with access rights 37 | u[:user][:arn].match("^arn:aws:iam::([0-9]{12}):.*$")[1] 38 | rescue ::Aws::IAM::Errors::AccessDenied => e 39 | # We've got an AWS IAM Credential 40 | e.to_s.match("^User: arn:aws:iam::([0-9]{12}):.*$")[1] 41 | end 42 | # arn:aws:rds:::: 43 | "arn:aws:rds:#{region}:#{account_id}:#{rds_type}:#{name}" 44 | end 45 | end 46 | end 47 | end 48 | 49 | module Chef::Provisioning::AWSDriver::TaggingStrategy 50 | class RDS 51 | attr_reader :rds_client, :rds_object_arn, :desired_tags 52 | 53 | def initialize(rds_client, rds_object_arn, desired_tags) 54 | @rds_client = rds_client 55 | @rds_object_arn = rds_object_arn 56 | @desired_tags = desired_tags 57 | end 58 | 59 | def current_tags 60 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/RDS/Client.html#list_tags_for_resource-instance_method 61 | resp = rds_client.list_tags_for_resource( 62 | resource_name: rds_object_arn 63 | ) 64 | Hash[resp.tag_list.map { |t| [t.key, t.value] }] 65 | end 66 | 67 | def set_tags(tags) 68 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/RDS/Client.html#add_tags_to_resource-instance_method 69 | # Unlike EC2, RDS tags can have a nil value 70 | tags = tags.map do |k, v| 71 | if v.nil? 72 | { key: k } 73 | else 74 | { key: k, value: v } 75 | end 76 | end 77 | rds_client.add_tags_to_resource( 78 | resource_name: rds_object_arn, 79 | tags: tags 80 | ) 81 | end 82 | 83 | def delete_tags(tag_keys) 84 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/RDS/Client.html#remove_tags_from_resource-instance_method 85 | rds_client.remove_tags_from_resource( 86 | resource_name: rds_object_arn, 87 | tag_keys: tag_keys 88 | ) 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/tagging_strategy/s3.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_tagger" 2 | module Chef::Provisioning::AWSDriver::TaggingStrategy 3 | class S3 4 | attr_reader :s3_client, :bucket_name, :desired_tags 5 | 6 | def initialize(s3_client, bucket_name, desired_tags) 7 | @s3_client = s3_client 8 | @bucket_name = bucket_name 9 | @desired_tags = desired_tags 10 | end 11 | 12 | def current_tags 13 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#get_bucket_tagging-instance_method 14 | resp = s3_client.get_bucket_tagging( 15 | bucket: bucket_name 16 | ) 17 | Hash[resp.tag_set.map { |t| [t.key, t.value] }] 18 | rescue ::Aws::S3::Errors::NoSuchTagSet => e 19 | # Instead of returning nil or empty, AWS raises an error :) 20 | {} 21 | end 22 | 23 | def set_tags(_tags) 24 | return if @is_set_tag 25 | # It will also run from delete_tags to prevent two times execution of same api class variable is defined 26 | @is_set_tag = true 27 | # http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#put_bucket_tagging-instance_method 28 | s3_client.put_bucket_tagging( 29 | bucket: bucket_name, 30 | tagging: { 31 | tag_set: desired_tags.map { |k, v| { key: k.to_s, value: v.to_s } } 32 | } 33 | ) 34 | end 35 | 36 | def delete_tags(_tag_keys) 37 | if desired_tags.empty? 38 | s3_client.delete_bucket_tagging( 39 | bucket: bucket_name 40 | ) 41 | else 42 | set_tags(desired_tags) 43 | end 44 | # S3 doesn't have a client action for deleting individual tags, just ALL tags. But the 45 | # put_bucket_tagging method will set the tags to what is provided so we don't need to 46 | # worry about this 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/chef/provisioning/aws_driver/version.rb: -------------------------------------------------------------------------------- 1 | class Chef 2 | module Provisioning 3 | module AWSDriver 4 | VERSION = "3.0.9".freeze 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/chef/provisioning/driver_init/aws.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver" 2 | 3 | Chef::Provisioning.register_driver_class("aws", Chef::Provisioning::AWSDriver::Driver) 4 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_auto_scaling_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsAutoScalingGroup < Chef::Provisioning::AWSDriver::AWSResource 4 | include Chef::Provisioning::AWSDriver::AWSTaggable 5 | 6 | aws_sdk_type ::Aws::AutoScaling::AutoScalingGroup 7 | 8 | attribute :name, kind_of: String, name_attribute: true 9 | attribute :options, kind_of: Hash, default: {} 10 | attribute :availability_zones, kind_of: Array 11 | attribute :desired_capacity, kind_of: Integer 12 | attribute :launch_configuration, kind_of: String 13 | attribute :min_size, kind_of: Integer 14 | attribute :max_size, kind_of: Integer 15 | attribute :load_balancers, kind_of: Array, coerce: proc { |value| [value].flatten } 16 | attribute :notification_configurations, kind_of: Array, default: [] 17 | attribute :scaling_policies, kind_of: Hash, default: {} 18 | 19 | def aws_object 20 | result = driver.auto_scaling_resource.group(name) 21 | result && result.exists? ? result : nil 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_cache_cluster.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | require "chef/resource/aws_security_group" 3 | 4 | # AWS Elasticache Cluster 5 | # 6 | # @see http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_cache_cluster-instance_method 7 | class Chef::Resource::AwsCacheCluster < Chef::Provisioning::AWSDriver::AWSResource 8 | # Note: There isn't actually an SDK class for Elasticache. 9 | aws_sdk_type ::Aws::ElastiCache 10 | 11 | # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_cache_cluster-instance_method 12 | # for information on possible values for each attribute. Values are passed 13 | # straight through to AWS, with the exception of security_groups, which 14 | # may contain a reference to a Chef aws_security_group resource. 15 | 16 | # Cluster Name 17 | # 18 | # @param :cluster_name [String] unique name for a cluster 19 | attribute :cluster_name, kind_of: String, name_attribute: true 20 | 21 | # Availability Zone 22 | # 23 | # @param :az_mode [String] Specifies whether the nodes in this Memcached node group are created in a single Availability Zone or created across multiple Availability Zones in the cluster's region. This parameter is only supported for Memcached cache clusters. If the AZMode and PreferredAvailabilityZones are not specified, ElastiCache assumes single-az mode. 24 | attribute :az_mode, kind_of: String 25 | 26 | # Preferred Availability Zone 27 | # 28 | # @param :preferred_availability_zone [String] preferred availability zone of the cache cluster 29 | attribute :preferred_availability_zone, kind_of: String 30 | 31 | # Preferred Availability Zones 32 | # 33 | # @param :preferred_availability_zones [String, Array] One or more preferred availability zones 34 | attribute :preferred_availability_zones, 35 | kind_of: [String, Array], 36 | coerce: proc { |v| [v].flatten } 37 | 38 | # Number of Nodes 39 | # 40 | # @param :number_nodes [Integer] Number of nodes in the cache 41 | attribute :number_nodes, kind_of: Integer, default: 1 42 | 43 | # Node type 44 | # 45 | # @param :node_type [String] AWS node type for each cache cluster node 46 | attribute :node_type, kind_of: String, required: true 47 | 48 | # Engine 49 | # 50 | # @param :engine [String] Valid values are `memcached` or `redis` 51 | attribute :engine, kind_of: String, required: true 52 | 53 | # Engine Version 54 | # 55 | # @param :engine_version [String] The version number of the cache engine to be used for this cache cluster. 56 | attribute :engine_version, kind_of: String, required: true 57 | 58 | # Subnet Group Name 59 | # 60 | # @param :subnet_group_name [String] Cache cluster aws_cache_subnet_group 61 | attribute :subnet_group_name, kind_of: String 62 | 63 | # Security Groups 64 | # 65 | # @param :security_groups [String, Array, AwsSecurityGroup, ::Aws::EC2::SecurityGroup] one or more VPC security groups associated with the cache cluster. 66 | attribute :security_groups, 67 | kind_of: [String, Array, AwsSecurityGroup, ::Aws::EC2::SecurityGroup], 68 | required: true, 69 | coerce: proc { |v| [v].flatten } 70 | 71 | def aws_object 72 | driver.elasticache 73 | .describe_cache_clusters(cache_cluster_id: cluster_name) 74 | .data[:cache_clusters].first 75 | rescue ::Aws::ElastiCache::Errors::CacheClusterNotFound 76 | nil 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_cache_replication_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | require "chef/resource/aws_security_group" 3 | 4 | # AWS Elasticache Replication Group 5 | # @see See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_replication_group-instance_method 6 | class Chef::Resource::AwsCacheReplicationGroup < Chef::Provisioning::AWSDriver::AWSResource 7 | # Note: There isn't actually an SDK class for Elasticache. 8 | aws_sdk_type ::Aws::ElastiCache 9 | 10 | # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_replication_group-instance_method 11 | # for information on possible values for each attribute. Values are passed 12 | # straight through to AWS, with the exception of security_groups, which 13 | # may contain a reference to a Chef aws_security_group resource. 14 | 15 | # Group Name 16 | # 17 | # @param :group_name [String] Elasticache replication group name. 18 | attribute :group_name, kind_of: String, name_attribute: true 19 | 20 | # Replication group description 21 | # 22 | # @param :description [String] Elasticache replication group description. 23 | attribute :description, kind_of: String, required: true 24 | 25 | # Automatic failover 26 | # 27 | # @param :automatic_failover [Boolean] Whether a read replica will be automatically promoted to read/write primary if the existing primary encounters a failure. 28 | attribute :automatic_failover, kind_of: [TrueClass, FalseClass], default: false 29 | 30 | # Number of cache clusters 31 | # 32 | # @param :number_cache_clusters [Integer] Number of cache clusters. 33 | attribute :number_cache_clusters, kind_of: Integer, default: 2 34 | 35 | # Node type 36 | # 37 | # @param :node_type [String] AWS node type for each replication group. 38 | attribute :node_type, kind_of: String, required: true 39 | 40 | # Engine 41 | # 42 | # @param :engine [String] Valid values are `memcached` or `redis`. 43 | attribute :engine, kind_of: String, required: true 44 | 45 | # Engine Version 46 | # 47 | # @param :engine_version [String] The version number of the cache engine. 48 | attribute :engine_version, kind_of: String, required: true 49 | 50 | # Subnet group name 51 | # 52 | # @param :subnet_group_name [String] Cache cluster aws_cache_subnet_group. 53 | attribute :subnet_group_name, kind_of: String 54 | 55 | # Security Groups 56 | # 57 | # @param 58 | attribute :security_groups, 59 | kind_of: [String, Array, AwsSecurityGroup, ::Aws::EC2::SecurityGroup], 60 | required: true, 61 | coerce: proc { |v| [v].flatten } 62 | 63 | # Group Name 64 | # 65 | # @param 66 | attribute :preferred_availability_zones, 67 | kind_of: [String, Array], 68 | coerce: proc { |v| [v].flatten } 69 | 70 | def aws_object 71 | driver.elasticache 72 | .describe_replication_groups(replication_group_id: group_name) 73 | .data[:replication_groups].first 74 | rescue ::Aws::ElastiCache::Errors::ReplicationGroupNotFoundFault 75 | nil 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_cache_subnet_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | require "chef/resource/aws_subnet" 3 | 4 | # AWS Elasticache Subnet Group 5 | # @see http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_cache_subnet_group-instance_method 6 | class Chef::Resource::AwsCacheSubnetGroup < Chef::Provisioning::AWSDriver::AWSResource 7 | # Note: There isn't actually an SDK class for Elasticache. 8 | aws_sdk_type ::Aws::ElastiCache, id: :group_name 9 | 10 | # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/ElastiCache/Client/V20140930.html#create_cache_subnet_group-instance_method 11 | # for information on possible values for each attribute. Values are passed 12 | # straight through to AWS, with the exception of subnets, which 13 | # may contain a reference to a Chef aws_subnet resource. 14 | 15 | # Group Name 16 | # 17 | # @param :group_name [String] The name of the cache subnet group to be used for the replication group. 18 | attribute :group_name, kind_of: String, name_attribute: true 19 | 20 | # Description 21 | # 22 | # @param :description [String] Subnet group description. 23 | attribute :description, kind_of: String, required: true 24 | 25 | # Subnets 26 | # 27 | # @param :subnets [ String, Array, AwsSubnet, ::Aws::EC2::Subnet ] One or more subnets in the subnet group. 28 | attribute :subnets, 29 | kind_of: [String, Array, AwsSubnet, ::Aws::EC2::Subnet], 30 | required: true, 31 | coerce: proc { |v| [v].flatten } 32 | 33 | def aws_object 34 | driver.elasticache 35 | .describe_cache_subnet_groups(cache_subnet_group_name: group_name) 36 | .data[:cache_subnet_groups].first 37 | rescue ::Aws::ElastiCache::Errors::CacheSubnetGroupNotFoundFault 38 | nil 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_cloudsearch_domain.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | module AWS 4 | class CloudSearch 5 | class Domain 6 | # The version of the AWS sdk we are using doesn't have a model 7 | # object for CloudSearch Domains. This empty class is here to 8 | # make the reset of chef-provisioning happy. 9 | end 10 | end 11 | end 12 | 13 | class Chef::Resource::AwsCloudsearchDomain < Chef::Provisioning::AWSDriver::AWSResource 14 | aws_sdk_type ::Aws::CloudSearchDomain 15 | attribute :name, kind_of: String, name_attribute: true 16 | attribute :cloudsearch_api_version, equal_to: %w{20130101 20110201}, default: "20130101" 17 | 18 | # Availability Options 19 | attribute :multi_az, kind_of: [TrueClass, FalseClass], default: false 20 | 21 | # Scaling Parameters 22 | attribute :instance_type, equal_to: ["search.m1.small", "search.m3.medium", 23 | "search.m3.large", "search.m3.xlarge", 24 | "search.m3.2xlarge"] 25 | attribute :partition_count, kind_of: Integer 26 | attribute :replication_count, kind_of: Integer 27 | 28 | # Service Access Policies 29 | # TODO(ssd): We need to decide how we want to model access policies 30 | # For now we just allow the user to shove the policy in via a string. 31 | attribute :access_policies, kind_of: String 32 | 33 | # Indexing Options 34 | # TODO(ssd): Like Access Polcies, we should decide 35 | # whether we want a DSL for defining index fields, or just allow the 36 | # user to pass in an array properly formated hash. 37 | attribute :index_fields, kind_of: Array 38 | 39 | # None of the cloudsearch objects actually have instance-specific 40 | # objects in the version of the AWS API we are using. This will 41 | # return a hash with some relevant information about the domain. 42 | def aws_object 43 | driver.cloudsearch.describe_domains(domain_names: [name])[:domain_status_list].find { |d| !d[:deleted] } 44 | end 45 | 46 | def cloudsearch_api_version(arg = nil) 47 | unless arg.nil? 48 | Chef::Log.warn("The ':cloudsearch_api_version' has been deprecated since it has been removed in AWS SDK version 2.") 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_cloudwatch_alarm.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsCloudwatchAlarm < Chef::Provisioning::AWSDriver::AWSResource 4 | include Chef::Provisioning::AWSDriver::AWSTaggable 5 | 6 | aws_sdk_type ::Aws::CloudWatch::Alarm, id: :name 7 | 8 | # This name must be unique within the user's AWS account 9 | attribute :name, kind_of: String, name_attribute: true 10 | attribute :namespace, kind_of: String 11 | attribute :metric_name, kind_of: String 12 | attribute :dimensions, kind_of: Array 13 | attribute :comparison_operator, kind_of: String 14 | attribute :evaluation_periods, kind_of: Integer 15 | attribute :period, kind_of: [Integer, Float], coerce: proc { |v| v.to_f } 16 | attribute :statistic, kind_of: String 17 | attribute :threshold, kind_of: [Integer, Float] 18 | attribute :insufficient_data_actions, kind_of: Array, coerce: proc { |v| [v].flatten } 19 | attribute :ok_actions, kind_of: Array, coerce: proc { |v| [v].flatten } 20 | attribute :alarm_actions, kind_of: Array, coerce: proc { |v| [v].flatten } 21 | attribute :actions_enabled, kind_of: [TrueClass, FalseClass] 22 | attribute :alarm_description, kind_of: String 23 | attribute :unit, kind_of: String 24 | 25 | def aws_object 26 | # TODO: exists? isn't defined yet 27 | # https://github.com/aws/aws-sdk-ruby/issues/1171 28 | a = driver.cloudwatch_resource.alarm(name) 29 | return nil if a.data.nil? 30 | a 31 | rescue ::Aws::CloudWatch::Errors::NoSuchEntity 32 | nil 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_dhcp_options.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | 3 | # 4 | # DHCP options for use by VPCs. 5 | # 6 | # If you specify nothing, the DHCP options set will use 'AmazonProvidedDNS' for its 7 | # domain name servers and all other values will be empty. 8 | # 9 | # API documentation for the AWS Ruby SDK for DHCP Options (and the object returned from `aws_object` can be found here: 10 | # 11 | # - http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_DHCP_Options.html 12 | # 13 | class Chef::Resource::AwsDhcpOptions < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 14 | include Chef::Provisioning::AWSDriver::AWSTaggable 15 | 16 | aws_sdk_type ::Aws::EC2::DhcpOptions 17 | 18 | # 19 | # The Chef "idempotence name" of this DHCP options set. 20 | # 21 | attribute :name, kind_of: String, name_attribute: true 22 | 23 | # 24 | # A domain name of your choice (e.g., example.com). 25 | # 26 | attribute :domain_name, kind_of: String 27 | 28 | # 29 | # The IP addresses of domain name servers. You can specify up to four addresses. 30 | # 31 | # Defaults to "AmazonProvidedDNS" 32 | # 33 | attribute :domain_name_servers, kind_of: Array, coerce: proc { |v| Array[v].flatten } 34 | 35 | # 36 | # The IP addresses of Network Time Protocol (NTP) servers. You can specify up to four addresses. 37 | # 38 | attribute :ntp_servers, kind_of: Array, coerce: proc { |v| Array[v].flatten } 39 | 40 | # 41 | # The IP addresses of NetBIOS name servers. You can specify up to four addresses. 42 | # 43 | attribute :netbios_name_servers, kind_of: Array, coerce: proc { |v| Array[v].flatten } 44 | 45 | # 46 | # Value indicating the NetBIOS node type (1, 2, 4, or 8). For more information about the values, go to RFC 2132. We recommend you only use 2 at this time (broadcast and multicast are currently not supported). 47 | # 48 | attribute :netbios_node_type, kind_of: Integer 49 | 50 | attribute :dhcp_options_id, kind_of: String, aws_id_attribute: true, default: lazy { 51 | name =~ /^dopt-[a-f0-9]+$/ ? name : nil 52 | } 53 | 54 | def aws_object 55 | driver, id = get_driver_and_id 56 | ec2_resource = ::Aws::EC2::Resource.new(driver.ec2) 57 | result = ec2_resource.dhcp_options(id) if id 58 | result && exists?(result) ? result : nil 59 | end 60 | 61 | def exists?(result) 62 | return true if result.data 63 | rescue ::Aws::EC2::Errors::InvalidDhcpOptionIDNotFound 64 | false 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_ebs_volume.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | require "chef/resource/aws_instance" 3 | 4 | class Chef::Resource::AwsEbsVolume < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 5 | include Chef::Provisioning::AWSDriver::AWSTaggable 6 | 7 | aws_sdk_type ::Aws::EC2::Volume, backcompat_data_bag_name: "ebs_volumes" 8 | 9 | attribute :name, kind_of: String, name_attribute: true 10 | 11 | attribute :machine, kind_of: [String, FalseClass, AwsInstance, ::Aws::EC2::Instance, ::Aws::EC2::Instance] 12 | 13 | attribute :availability_zone, kind_of: String, default: "a" 14 | attribute :size, kind_of: Integer, default: 8 15 | attribute :snapshot, kind_of: String 16 | 17 | attribute :iops, kind_of: Integer 18 | attribute :volume_type, kind_of: String 19 | attribute :encrypted, kind_of: [TrueClass, FalseClass] 20 | attribute :device, kind_of: String 21 | 22 | attribute :volume_id, kind_of: String, aws_id_attribute: true, default: lazy { 23 | name =~ /^vol-[a-f0-9]+$/ ? name : nil 24 | } 25 | 26 | def aws_object 27 | driver, id = get_driver_and_id 28 | result = driver.ec2_resource.volume(id) if id 29 | result && exists?(result) && !%i{deleted deleting}.include?(result.state) ? result : nil 30 | end 31 | 32 | def exists?(result) 33 | return true if result.data 34 | rescue ::Aws::EC2::Errors::InvalidVolumeNotFound 35 | false 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_eip_address.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | 3 | class Chef::Resource::AwsEipAddress < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 4 | aws_sdk_type ::Aws::OpsWorks::Types::ElasticIp, option_names: [:public_ip], id: :public_ip, managed_entry_id_name: "public_ip", backcompat_data_bag_name: "eip_addresses" 5 | 6 | attribute :name, kind_of: String, name_attribute: true 7 | 8 | # TODO: network interface 9 | attribute :machine, kind_of: [String, FalseClass] 10 | attribute :associate_to_vpc, kind_of: [TrueClass, FalseClass] 11 | 12 | # Like other aws_id_attributes, this is read-only - you cannot provide it and expect 13 | # aws to honor it 14 | attribute :public_ip, kind_of: String, aws_id_attribute: true, 15 | default: lazy { name =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/ ? name : nil } 16 | 17 | def aws_object 18 | driver, public_ip = get_driver_and_id 19 | result = driver.ec2.describe_addresses.addresses.find { |b| b.public_ip == public_ip } 20 | result && !result.empty? ? result : nil 21 | end 22 | 23 | def action(*args) 24 | # Backcompat for associate and disassociate 25 | if args == [:associate] 26 | super(:create) 27 | elsif args == [:disassociate] 28 | machine false 29 | super(:create) 30 | else 31 | super 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_elasticsearch_domain.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | module AWS 4 | class Elasticsearch 5 | class Domain 6 | end 7 | end 8 | end 9 | 10 | class Chef::Resource::AwsElasticsearchDomain < Chef::Provisioning::AWSDriver::AWSResource 11 | include Chef::Provisioning::AWSDriver::AWSTaggable 12 | 13 | aws_sdk_type ::Aws::CloudSearchDomain 14 | 15 | attribute :domain_name, kind_of: String, name_attribute: true 16 | 17 | # Cluster Config 18 | attribute :instance_type, kind_of: String 19 | attribute :instance_count, kind_of: Integer 20 | attribute :dedicated_master_enabled, kind_of: [TrueClass, FalseClass] 21 | attribute :dedicated_master_type, kind_of: String 22 | attribute :dedicated_master_count, kind_of: Integer 23 | attribute :zone_awareness_enabled, kind_of: [TrueClass, FalseClass] 24 | 25 | # EBS Options 26 | attribute :ebs_enabled, kind_of: [TrueClass, FalseClass] 27 | attribute :volume_type, equal_to: %w{standard gp2 io1} 28 | attribute :volume_size, kind_of: Integer 29 | attribute :iops, kind_of: Integer 30 | 31 | # Snapshot Options 32 | attribute :automated_snapshot_start_hour, kind_of: Integer 33 | 34 | # Access Policies 35 | attribute :access_policies, kind_of: String 36 | 37 | def aws_object 38 | driver.elasticsearch_client 39 | .describe_elasticsearch_domains(domain_names: [domain_name])[:domain_status_list] 40 | .find { |d| !d[:deleted] } 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_iam_instance_profile.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | # 4 | # An AWS IAM instance profile, a container for an IAM role that you can use to 5 | # pass role information to an EC2 instance when the instance starts.. 6 | # 7 | # `name` is unique for an AWS account. 8 | # 9 | # API documentation for the AWS Ruby SDK for IAM instance profiles (and the object returned from `aws_object`) can be found here: 10 | # 11 | # - http://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/InstanceProfile.html 12 | # 13 | class Chef::Resource::AwsIamInstanceProfile < Chef::Provisioning::AWSDriver::AWSResource 14 | # We don't want any lookup_options to try and build a resource from a :iam_instance_profile string, 15 | # its either a name or an ARN 16 | aws_sdk_type ::Aws::IAM::InstanceProfile, option_names: [] 17 | 18 | # 19 | # The name of the instance profile to create. 20 | # 21 | attribute :name, kind_of: String, name_attribute: true 22 | 23 | # 24 | # The path to the instance profile. For more information about paths, see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html 25 | # 26 | attribute :path, kind_of: String 27 | 28 | attribute :role, kind_of: [String, AwsIamRole, ::Aws::IAM::Role] 29 | 30 | def aws_object 31 | result = driver.iam_resource.instance_profile(name) 32 | result && result.exists? ? result : nil 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_iam_role.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | # 4 | # An AWS IAM role, specifying set of policies for acessing other AWS services. 5 | # 6 | # `name` is unique for an AWS account. 7 | # 8 | # API documentation for the AWS Ruby SDK for IAM roles (and the object returned from `aws_object`) can be found here: 9 | # 10 | # - http://docs.aws.amazon.com/sdkforruby/api/Aws/IAM.html 11 | # 12 | class Chef::Resource::AwsIamRole < Chef::Provisioning::AWSDriver::AWSResource 13 | aws_sdk_type ::Aws::IAM::Role 14 | 15 | # 16 | # The name of the role to create. 17 | # 18 | attribute :name, kind_of: String, name_attribute: true 19 | 20 | # 21 | # The path to the role. For more information about paths, see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html 22 | # 23 | attribute :path, kind_of: String 24 | 25 | # 26 | # The policy that grants an entity permission to assume the role. 27 | # 28 | attribute :assume_role_policy_document, kind_of: String 29 | 30 | # 31 | # Inline policies which _only_ apply to this role, unlike managed_policies 32 | # which can be shared between users, groups and roles. Maps to the 33 | # [RolePolicy](http://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/RolePolicy.html) 34 | # SDK object. 35 | # 36 | # Hash keys are the inline policy name and the value is the policy document. 37 | # 38 | attribute :inline_policies, kind_of: Hash, callbacks: { 39 | "inline_policies must be a hash maping policy names to policy documents" => proc do |policies| 40 | policies.all? { |policy_name, policy| (policy_name.is_a?(String) || policy_name.is_a?(Symbol)) && policy.is_a?(String) } 41 | end 42 | } 43 | 44 | # 45 | # TODO: add when we get a policy resource 46 | # 47 | # attribute :managed_policies, kind_of: [Array, String, ::Aws::IAM::Policy, AwsIamPolicy], coerce: proc { |value| [value].flatten } 48 | 49 | def aws_object 50 | driver.iam_resource.role(name).load 51 | rescue ::Aws::IAM::Errors::NoSuchEntity 52 | nil 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_image.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | require "chef/provisioning/aws_driver/aws_taggable" 3 | 4 | class Chef::Resource::AwsImage < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 5 | include Chef::Provisioning::AWSDriver::AWSTaggable 6 | 7 | aws_sdk_type ::Aws::EC2::Image, 8 | managed_entry_type: :machine_image, 9 | managed_entry_id_name: "image_id" 10 | 11 | attribute :name, kind_of: String, name_attribute: true 12 | 13 | attribute :image_id, kind_of: String, aws_id_attribute: true, default: lazy { 14 | name =~ /^ami-[a-f0-9]+$/ ? name : nil 15 | } 16 | 17 | def aws_object 18 | driver, id = get_driver_and_id 19 | result = driver.ec2_resource.image(id) if id 20 | result && result.exists? ? result : nil 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_instance.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | require "chef/provisioning/aws_driver/aws_taggable" 3 | 4 | class Chef::Resource::AwsInstance < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 5 | include Chef::Provisioning::AWSDriver::AWSTaggable 6 | 7 | # The require needs to be inside this class otherwise it gets loaded before the rest of the SDK 8 | # and starts causing issues - AWS expects to load all this stuff itself 9 | aws_sdk_type ::Aws::EC2::Instance, 10 | managed_entry_type: :machine, 11 | managed_entry_id_name: "instance_id" 12 | 13 | attribute :name, kind_of: String, name_attribute: true 14 | 15 | attribute :instance_id, kind_of: String, aws_id_attribute: true, default: lazy { 16 | name =~ /^i-[a-f0-9]+$/ ? name : nil 17 | } 18 | 19 | def aws_object 20 | driver, id = get_driver_and_id 21 | result = driver.ec2_resource.instance(id) if id 22 | result && result.exists? ? result : nil 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_internet_gateway.rb: -------------------------------------------------------------------------------- 1 | # 2 | # An AWS internet gateway, allowing communication between instances inside a VPC and the internet. 3 | # 4 | # `name` is not guaranteed unique for an AWS account; therefore, Chef will 5 | # store the internet gateway ID associated with this name in your Chef server in the 6 | # data bag `data/aws_internet_gateway/`. 7 | # 8 | # API documentation for the AWS Ruby SDK for VPCs (and the object returned from `aws_object` can be found here: 9 | # 10 | # - http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/InternetGateway.html 11 | # 12 | class Chef::Resource::AwsInternetGateway < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 13 | include Chef::Provisioning::AWSDriver::AWSTaggable 14 | 15 | aws_sdk_type ::Aws::EC2::InternetGateway, id: :id 16 | 17 | require "chef/resource/aws_vpc" 18 | 19 | # 20 | # Extend actions for the internet gateway 21 | # 22 | actions :create, :destroy, :detach, :purge 23 | 24 | # 25 | # The name of this internet gateway. 26 | # 27 | attribute :name, kind_of: String, name_attribute: true 28 | 29 | # 30 | # A vpc to attach to the internet gateway. 31 | # 32 | # May be one of: 33 | # - The name of an `aws_vpc` Chef resource. 34 | # - An actual `aws_vpc` resource. 35 | # - An AWS `VPC` object. 36 | # 37 | attribute :vpc, kind_of: [String, AwsVpc, ::Aws::EC2::Vpc] 38 | 39 | attribute :internet_gateway_id, kind_of: String, aws_id_attribute: true, default: lazy { 40 | name =~ /^igw-[a-f0-9]+$/ ? name : nil 41 | } 42 | 43 | def aws_object 44 | driver, id = get_driver_and_id 45 | ec2_resource = ::Aws::EC2::Resource.new(driver.ec2) 46 | result = ec2_resource.internet_gateway(id) if id 47 | result && exists?(result) ? result : nil 48 | end 49 | 50 | def exists?(result) 51 | return true if result.data 52 | rescue ::Aws::EC2::Errors::InvalidInternetGatewayIDNotFound 53 | false 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_key_pair.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsKeyPair < Chef::Provisioning::AWSDriver::AWSResource 4 | aws_sdk_type ::Aws::EC2::KeyPair, id: :name 5 | 6 | # Private key to use as input (will be generated if it does not exist) 7 | attribute :private_key_path, kind_of: String 8 | # Public key to use as input (will be generated if it does not exist) 9 | attribute :public_key_path, kind_of: String 10 | # List of parameters to the private_key resource used for generation of the key 11 | attribute :private_key_options, kind_of: Hash 12 | 13 | # TODO: what is the right default for this? 14 | attribute :allow_overwrite, kind_of: [TrueClass, FalseClass], default: false 15 | 16 | def aws_object 17 | resource = ::Aws::EC2::Resource.new(driver.ec2) 18 | result = resource.key_pairs.find { |b| b.name == name } 19 | result 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_launch_configuration.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsLaunchConfiguration < Chef::Provisioning::AWSDriver::AWSResource 4 | aws_sdk_type ::Aws::AutoScaling::LaunchConfiguration, id: :name 5 | 6 | attribute :name, kind_of: String, name_attribute: true 7 | attribute :image, kind_of: [String, ::Aws::EC2::Image, ::Aws::EC2::Image] 8 | attribute :instance_type, kind_of: String 9 | attribute :options, kind_of: Hash, default: {} 10 | 11 | def aws_object 12 | launchconfig = ::Aws::AutoScaling::LaunchConfiguration.new(name, client: driver.auto_scaling_client) 13 | result = launchconfig.data 14 | result 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_load_balancer.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | require "chef/provisioning/aws_driver/aws_taggable" 3 | 4 | class Chef::Resource::AwsLoadBalancer < Chef::Provisioning::AWSDriver::AWSResource 5 | include Chef::Provisioning::AWSDriver::AWSTaggable 6 | 7 | aws_sdk_type ::Aws::AutoScaling::LoadBalancer 8 | 9 | attribute :name, kind_of: String, name_attribute: true 10 | 11 | attribute :load_balancer_id, kind_of: String, aws_id_attribute: true, default: lazy { 12 | name =~ /^elb-[a-f0-9]+$/ ? name : nil 13 | } 14 | 15 | def aws_object 16 | result = nil 17 | begin 18 | result = driver.elb.describe_load_balancers(load_balancer_names: [name]).load_balancer_descriptions 19 | if result.length == 1 20 | result = result[0] 21 | else 22 | raise "Must have 0 or 1 load balancers which match name!" 23 | end 24 | rescue ::Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound 25 | Chef::Log.debug("No loadbalancer named #{name} - returning nil!") 26 | result = nil 27 | end 28 | result 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_nat_gateway.rb: -------------------------------------------------------------------------------- 1 | # 2 | # An AWS nat gateway, enable instances in a private subnet to connect to 3 | # the Internet or other AWS services, but prevent the Internet from 4 | # initiating a connection with those instances 5 | # 6 | # `name` is not guaranteed unique for an AWS account; therefore, Chef will 7 | # store the nat gateway ID associated with this name in your Chef server in the 8 | # data bag `data/aws_nat_gateway/`. 9 | # 10 | # API documentation for the AWS Ruby SDK for Nat gateway can be found here: 11 | # 12 | # - http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Types/NatGateway.html 13 | # 14 | 15 | # We provide this class because the AWS SDK V2 does not provide it (as of 16 | # May 2016). We copied the pattern in their SDK so when they do add a real 17 | # resource there shouldn't be a need for much translation. 18 | class Aws::EC2::NatGateway < ::Aws::Resources::Resource 19 | attr_reader :resource, :id, :nat_gateway_id, :vpc_id, :subnet_id, :nat_gateway_addresses 20 | 21 | def initialize(id, options = {}) 22 | @id = id 23 | @nat_gateway_id = id 24 | @client = options[:client] 25 | nat_gateway_struct = get_nat_gateway_struct 26 | @vpc_id = nat_gateway_struct.vpc_id 27 | @subnet_id = nat_gateway_struct.subnet_id 28 | @nat_gateway_addresses = nat_gateway_struct.nat_gateway_addresses 29 | end 30 | 31 | def state 32 | get_nat_gateway_struct.state 33 | end 34 | 35 | def delete 36 | @client.delete_nat_gateway(nat_gateway_id: @id) 37 | end 38 | 39 | private 40 | 41 | def get_nat_gateway_struct 42 | @client.describe_nat_gateways(nat_gateway_ids: [@id]).nat_gateways.first 43 | end 44 | end 45 | 46 | # See comment on class above as to why we add these methods to the AWS SDK 47 | class Aws::EC2::Resource 48 | def create_nat_gateway(options) 49 | nat_gateway_struct = client.create_nat_gateway(options).nat_gateway 50 | nat_gateway(nat_gateway_struct.nat_gateway_id) 51 | end 52 | 53 | def nat_gateway(nat_gateway_id) 54 | ::Aws::EC2::NatGateway.new(nat_gateway_id, client: client) 55 | end 56 | end 57 | 58 | class Chef::Resource::AwsNatGateway < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 59 | aws_sdk_type ::Aws::EC2::NatGateway, id: :nat_gateway_id, managed_entry_id_name: "nat_gateway_id" 60 | 61 | require "chef/resource/aws_subnet" 62 | require "chef/resource/aws_eip_address" 63 | 64 | # 65 | # The name of this nat gateway. 66 | # 67 | attribute :name, kind_of: String, name_attribute: true 68 | 69 | # 70 | # A subnet to attach to the internet gateway. 71 | # 72 | # May be one of: 73 | # - The name of an `aws_subnet` Chef resource. 74 | # - An actual `aws_subnet` resource. 75 | # - An Aws `Subnet` object. 76 | # 77 | attribute :subnet, kind_of: [String, AwsSubnet, ::Aws::EC2::Subnet] 78 | 79 | # 80 | # A elastic ip address for the nat gateway. 81 | # 82 | # May be one of: 83 | # - The name of an `aws_eip_address` Chef resource. 84 | # - An actual `aws_eip_address` resource. 85 | # - nil, meaning that no EIP exists yet and needs to be created. 86 | # 87 | attribute :eip_address, kind_of: [String, ::Aws::OpsWorks::Types::ElasticIp, AwsEipAddress, nil], default: nil 88 | 89 | attribute :nat_gateway_id, kind_of: String, aws_id_attribute: true, default: lazy { 90 | name =~ /^nat-[A-Fa-f0-9]+$/ ? name : nil 91 | } 92 | 93 | def aws_object 94 | driver, nat_gateway_id = get_driver_and_id 95 | result = driver.ec2_resource.nat_gateway(nat_gateway_id) if nat_gateway_id 96 | result && result.id ? result : nil 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_network_acl.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | require "chef/resource/aws_vpc" 3 | require "chef/resource/aws_subnet" 4 | 5 | class Chef::Resource::AwsNetworkAcl < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 6 | include Chef::Provisioning::AWSDriver::AWSTaggable 7 | 8 | aws_sdk_type ::Aws::EC2::NetworkAcl 9 | 10 | # 11 | # The name of this network acl. 12 | # 13 | attribute :name, kind_of: String, name_attribute: true 14 | 15 | # 16 | # The VPC of this network acl. 17 | # 18 | # May be one of: 19 | # - The name of an `aws_vpc` Chef resource. 20 | # - An actual `aws_vpc` resource. 21 | # - An AWS `VPC` object. 22 | # 23 | attribute :vpc, kind_of: [String, AwsVpc, ::Aws::EC2::Vpc] 24 | 25 | # 26 | # Accepts rules in the format: 27 | # [ 28 | # { rule_number: 100, action: <:deny|:allow>, protocol: -1, cidr_block: '0.0.0.0/0', port_range: 80..80 } 29 | # ] 30 | # 31 | # `cidr_block` will be a source if it is an inbound rule, or a destination if it is an outbound rule 32 | # 33 | # If `inbound_rules` or `outbound_rules` is unset, respective current rules will not be changed. 34 | # However, if either is set to `[]` all respective current rules will be removed. 35 | # 36 | attribute :inbound_rules, 37 | kind_of: [Array, Hash], 38 | coerce: proc { |v| v && [v].flatten } 39 | 40 | attribute :outbound_rules, 41 | kind_of: [Array, Hash], 42 | coerce: proc { |v| v && [v].flatten } 43 | 44 | attribute :network_acl_id, 45 | kind_of: String, 46 | aws_id_attribute: true, 47 | default: lazy { 48 | name =~ /^acl-[a-f0-9]+$/ ? name : nil 49 | } 50 | 51 | def aws_object 52 | driver, id = get_driver_and_id 53 | result = driver.ec2_resource.network_acl(id) if id 54 | result && exists?(result) ? result : nil 55 | end 56 | 57 | def exists?(result) 58 | return true if result.data 59 | rescue ::Aws::EC2::Errors::InvalidNetworkAclIDNotFound 60 | false 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_network_interface.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | require "chef/resource/aws_subnet" 3 | require "chef/resource/aws_eip_address" 4 | 5 | class Chef::Resource::AwsNetworkInterface < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 6 | include Chef::Provisioning::AWSDriver::AWSTaggable 7 | 8 | aws_sdk_type ::Aws::EC2::NetworkInterface, option_names: [] 9 | 10 | attribute :name, kind_of: String, name_attribute: true 11 | 12 | attribute :network_interface_id, kind_of: String, aws_id_attribute: true, default: lazy { 13 | name =~ /^eni-[a-f0-9]+$/ ? name : nil 14 | } 15 | 16 | attribute :subnet, kind_of: [String, ::Aws::EC2::Subnet, AwsSubnet] 17 | 18 | attribute :private_ip_address, kind_of: String 19 | 20 | attribute :description, kind_of: String 21 | 22 | attribute :security_groups, kind_of: Array # (Array, Array) 23 | 24 | attribute :machine, kind_of: [String, FalseClass, AwsInstance, ::Aws::EC2::Instance, ::Aws::EC2::Instance] 25 | 26 | attribute :device_index, kind_of: Integer 27 | 28 | # TODO: implement eip address association 29 | # attribute :elastic_ip_address, kind_of: [ String, ::Aws::OpsWorks::Types::ElasticIp, AwsEipAddress, FalseClass ] 30 | 31 | def aws_object 32 | driver, id = get_driver_and_id 33 | result = driver.ec2_resource.network_interface(id) if id 34 | result && exists?(result) ? result : nil 35 | end 36 | 37 | def exists?(result) 38 | return true if result.data 39 | rescue ::Aws::EC2::Errors::InvalidNetworkInterfaceIDNotFound 40 | false 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_rds_instance.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_rds_resource" 2 | require "chef/provisioning/aws_driver/aws_taggable" 3 | 4 | class Chef::Resource::AwsRdsInstance < Chef::Provisioning::AWSDriver::AWSRDSResource 5 | include Chef::Provisioning::AWSDriver::AWSTaggable 6 | 7 | aws_sdk_type ::Aws::RDS::DBInstance, id: :db_instance_identifier 8 | 9 | attribute :db_instance_identifier, kind_of: String, name_attribute: true 10 | 11 | attribute :db_snapshot_identifier, kind_of: String 12 | attribute :engine, kind_of: String 13 | attribute :engine_version, kind_of: String 14 | attribute :db_instance_class, kind_of: String 15 | attribute :multi_az, default: false, kind_of: [TrueClass, FalseClass] 16 | attribute :allocated_storage, kind_of: Integer 17 | attribute :iops, kind_of: Integer 18 | attribute :publicly_accessible, kind_of: [TrueClass, FalseClass], default: false 19 | attribute :master_username, kind_of: String 20 | attribute :master_user_password, kind_of: String 21 | attribute :db_name, kind_of: String 22 | attribute :port, kind_of: Integer 23 | # We cannot pass the resource or an AWS object because there is no AWS model 24 | # and that causes lookup_options to fail 25 | attribute :db_subnet_group_name, kind_of: String 26 | # We cannot pass the resource or an AWS object because there is no AWS model 27 | # and that causes lookup_options to fail 28 | attribute :db_parameter_group_name, kind_of: String 29 | 30 | # RDS has a ton of options, allow users to set any of them via a 31 | # custom Hash 32 | attribute :additional_options, kind_of: Hash, default: {} 33 | 34 | def aws_object 35 | result = driver.rds_resource.db_instance(name) 36 | return nil unless result && result.db_instance_status != "deleting" 37 | result 38 | rescue ::Aws::RDS::Errors::DBInstanceNotFound 39 | nil 40 | end 41 | 42 | def db_instance_status 43 | aws_object.db_instance_status if aws_object 44 | rescue ::Aws::RDS::Errors::DBInstanceNotFound 45 | nil 46 | end 47 | 48 | def rds_tagging_type 49 | "db" 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_rds_parameter_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_rds_resource" 2 | require "chef/provisioning/aws_driver/aws_taggable" 3 | 4 | class Chef::Resource::AwsRdsParameterGroup < Chef::Provisioning::AWSDriver::AWSRDSResource 5 | include Chef::Provisioning::AWSDriver::AWSTaggable 6 | 7 | # there is no class for a parameter group specifically 8 | aws_sdk_type ::Aws::RDS 9 | 10 | attribute :name, kind_of: String, name_attribute: true 11 | attribute :db_parameter_group_family, kind_of: String, required: true 12 | attribute :description, kind_of: String, required: true 13 | attribute :parameters, kind_of: Array, default: [] 14 | 15 | def aws_object 16 | object = driver.rds.describe_db_parameter_groups(db_parameter_group_name: name)[:db_parameter_groups].first 17 | 18 | # use paginated API to get all options 19 | initial_request = driver.rds.describe_db_parameters(db_parameter_group_name: name, max_records: 100) 20 | marker = initial_request[:marker] 21 | parameters = initial_request[:parameters] 22 | until marker.nil? 23 | more_results = driver.rds.describe_db_parameters(db_parameter_group_name: name, max_records: 100, marker: marker) 24 | parameters += more_results[:parameters] 25 | marker = more_results[:marker] 26 | end 27 | driver.rds.reset_db_parameter_group(db_parameter_group_name: name, parameters: parameters) 28 | 29 | object 30 | rescue ::Aws::RDS::Errors::DBParameterGroupNotFound 31 | nil 32 | end 33 | 34 | def rds_tagging_type 35 | "pg" 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_rds_subnet_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_rds_resource" 2 | require "chef/provisioning/aws_driver/aws_taggable" 3 | require "chef/resource/aws_subnet" 4 | 5 | class Chef::Resource::AwsRdsSubnetGroup < Chef::Provisioning::AWSDriver::AWSRDSResource 6 | include Chef::Provisioning::AWSDriver::AWSTaggable 7 | 8 | aws_sdk_type ::Aws::RDS 9 | 10 | attribute :name, kind_of: String, name_attribute: true 11 | attribute :description, kind_of: String, required: true 12 | attribute :subnets, 13 | kind_of: [String, Array, AwsSubnet, ::Aws::EC2::Subnet], 14 | required: true, 15 | coerce: proc { |v| [v].flatten } 16 | 17 | def aws_object 18 | driver.rds.describe_db_subnet_groups(db_subnet_group_name: name)[:db_subnet_groups].first 19 | rescue ::Aws::RDS::Errors::DBSubnetGroupNotFoundFault 20 | # triggered by describe_db_subnet_groups when the group can't 21 | # be found 22 | nil 23 | end 24 | 25 | def rds_tagging_type 26 | "subgrp" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_s3_bucket.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsS3Bucket < Chef::Provisioning::AWSDriver::AWSResource 4 | include Chef::Provisioning::AWSDriver::AWSTaggable 5 | 6 | aws_sdk_type ::Aws::S3::Bucket, id: :name 7 | 8 | attribute :name, kind_of: String, name_attribute: true 9 | attribute :options, kind_of: Hash, default: {} 10 | attribute :enable_website_hosting, kind_of: [TrueClass, FalseClass], default: false 11 | attribute :website_options, kind_of: Hash, default: {} 12 | attribute :recursive_delete, kind_of: [TrueClass, FalseClass], default: false 13 | 14 | def aws_object 15 | resource = ::Aws::S3::Resource.new(driver.s3_client) 16 | result = resource.buckets.find { |b| b.name == name } 17 | result && result.exists? ? result : nil 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_security_group.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | require "chef/resource/aws_vpc" 3 | require "chef/provisioning/aws_driver/exceptions" 4 | 5 | class Chef::Resource::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSResource 6 | include Chef::Provisioning::AWSDriver::AWSTaggable 7 | 8 | aws_sdk_type ::Aws::EC2::SecurityGroup, 9 | id: :id, 10 | option_names: %i{security_group security_group_id security_group_name} 11 | 12 | attribute :name, kind_of: String, name_attribute: true 13 | attribute :vpc, kind_of: [String, AwsVpc, ::Aws::EC2::Vpc] 14 | attribute :description, kind_of: String 15 | 16 | # 17 | # Accepts rules in the format: 18 | # [ 19 | # { port: 22, protocol: :tcp, sources: [, , ...] } 20 | # ] 21 | # 22 | # Or: 23 | # { 24 | # => , 25 | # ... 26 | # } 27 | # 28 | # Where is one of: 29 | # - : the port number. e.g. `80`; or a port range: `1024..2048` 30 | # - [ , ] or [ , ], e.g. `[ 80, :http ]` 31 | # - { port: , protocol: }, e.g. { port: 80, protocol: :http } 32 | # 33 | # And is one of: 34 | # - : An IP or CIDR of IPs to talk to. 35 | # - `inbound_rules '1.2.3.4' => 80` 36 | # - `inbound_rules '1.2.3.4/24' => 80` 37 | # - : A security group to authorize. 38 | # - `inbound_rules 'mysecuritygroup'` 39 | # - `inbound_rules { security_group: 'mysecuritygroup' }` 40 | # - `inbound_rules 'sg-1234abcd' => 80` 41 | # - `inbound_rules aws_security_group('mysecuritygroup') => 80` 42 | # - `inbound_rules AWS.ec2.security_groups.first => 80` 43 | # - : A load balancer to authorize. 44 | # - `inbound_rules { load_balancer: 'myloadbalancer' } => 80` 45 | # - `inbound_rules 'elb-1234abcd' => 80` 46 | # - `inbound_rules load_balancer('myloadbalancer') => 80` 47 | # - `inbound_rules AWS.ec2.security_groups.first => 80` 48 | # 49 | attribute :inbound_rules, kind_of: [Array, Hash] 50 | attribute :outbound_rules, kind_of: [Array, Hash] 51 | 52 | attribute :security_group_id, kind_of: String, aws_id_attribute: true, default: lazy { 53 | name =~ /^sg-[a-f0-9]+$/ ? name : nil 54 | } 55 | 56 | def aws_object 57 | if security_group_id 58 | result = driver.ec2_resource.security_group(security_group_id) 59 | else 60 | # Names are unique within a VPC. Try to search by name and narroy by VPC, if 61 | # provided 62 | if vpc 63 | vpc_object = Chef::Resource::AwsVpc.get_aws_object(vpc, resource: self) 64 | results = vpc_object.security_groups.to_a.select { |s| (s.group_name == name) || (s.id == name) } 65 | else 66 | results = driver.ec2_resource.security_groups.to_a.select { |s| (s.group_name == name) || (s.id == name) } 67 | end 68 | if results.size >= 2 69 | raise ::Chef::Provisioning::AWSDriver::Exceptions::MultipleSecurityGroupError.new(name, results) 70 | end 71 | result = results.first 72 | end 73 | result || nil 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_server_certificate.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsServerCertificate < Chef::Provisioning::AWSDriver::AWSResource 4 | aws_sdk_type ::Aws::IAM::ServerCertificate 5 | 6 | attribute :name, kind_of: String, name_attribute: true 7 | 8 | attribute :certificate_body, kind_of: String 9 | attribute :certificate_chain, kind_of: String 10 | attribute :private_key, kind_of: String 11 | 12 | def aws_object 13 | cert = ::Aws::IAM::ServerCertificate.new(name, client: driver.iam) 14 | cert.data 15 | cert 16 | rescue ::Aws::IAM::Errors::NoSuchEntity 17 | nil 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_sns_topic.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsSnsTopic < Chef::Provisioning::AWSDriver::AWSResource 4 | aws_sdk_type ::Aws::SNS::Topic 5 | 6 | attribute :name, kind_of: String, name_attribute: true 7 | attribute :arn, kind_of: String, default: lazy { driver.build_arn(service: "sns", resource: name) } 8 | 9 | def aws_object 10 | begin 11 | # Test whether it exists or not by asking for a property 12 | result = driver.sns.get_topic_attributes(topic_arn: arn) 13 | result = result.data 14 | rescue ::Aws::SNS::Errors::NotFound 15 | result = nil 16 | end 17 | result 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_sqs_queue.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource" 2 | 3 | class Chef::Resource::AwsSqsQueue < Chef::Provisioning::AWSDriver::AWSResource 4 | aws_sdk_type ::Aws::SQS::Queue 5 | 6 | attribute :name, kind_of: String, name_attribute: true 7 | attribute :options, kind_of: Hash 8 | 9 | def aws_object 10 | driver.sqs.get_queue_url(queue_name: name) 11 | rescue ::Aws::SQS::Errors::NonExistentQueue 12 | nil 13 | end 14 | 15 | protected 16 | 17 | def self.get_aws_object_id(value, **options) 18 | aws_object = get_aws_object(value, **options) 19 | aws_object.arn.split(":")[-1] if aws_object 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_subnet.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | 3 | # 4 | # An AWS subnet is a sub-section of a VPC, walled gardens within the walled garden; 5 | # they share a space of IP addresses with other subnets in the VPC but can otherwise 6 | # be walled off from each other. 7 | # 8 | # `name` is not guaranteed unique for an AWS account; therefore, Chef will 9 | # store the subnet ID associated with this name in your Chef server in the 10 | # data bag `data/aws_subnet/`. 11 | # 12 | # API documentation for the AWS Ruby SDK for VPCs (and the object returned from `aws_object` can be found here: 13 | # 14 | # - http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Subnet.html 15 | # 16 | class Chef::Resource::AwsSubnet < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 17 | include Chef::Provisioning::AWSDriver::AWSTaggable 18 | 19 | aws_sdk_type ::Aws::EC2::Subnet, id: :id 20 | 21 | require "chef/resource/aws_vpc" 22 | require "chef/resource/aws_network_acl" 23 | require "chef/resource/aws_route_table" 24 | 25 | # 26 | # The name of this subnet. 27 | # 28 | attribute :name, kind_of: String, name_attribute: true 29 | 30 | # 31 | # The VPC of this subnet. 32 | # 33 | # May be one of: 34 | # - The name of an `aws_vpc` Chef resource. 35 | # - An actual `aws_vpc` resource. 36 | # - An AWS `VPC` object. 37 | # 38 | attribute :vpc, kind_of: [String, AwsVpc, ::Aws::EC2::Vpc] 39 | 40 | # 41 | # The CIDR block of IP addresses allocated to this subnet. 42 | # Must be a subset of the IP addresses in the VPC, and must not overlap the 43 | # IP addresses of any other subnet in the VPC. 44 | # 45 | # For example: 46 | # - `'10.0.0.0/24'` gives you 256 addresses. 47 | # - `'10.0.0.0/16'` gives you 65536 addresses. 48 | # 49 | # This defaults to taking all IP addresses in the VPC. 50 | # 51 | attribute :cidr_block, kind_of: String 52 | 53 | # 54 | # The availability zone of this subnet. 55 | # 56 | # e.g. us-east-1a or us-east-1b. 57 | # 58 | # By default, AWS will pick an AZ for a given subnet. 59 | # 60 | attribute :availability_zone, kind_of: String 61 | 62 | # 63 | # Whether to give public IP addresses to new instances in this subnet by default. 64 | # 65 | attribute :map_public_ip_on_launch, kind_of: [TrueClass, FalseClass] 66 | 67 | # 68 | # The route table to associate with this subnet. 69 | # 70 | # May be one of: 71 | # - The name of an `aws_route_table` Chef resource. 72 | # - An actual `aws_route_table` resource. 73 | # - An AWS `route_table` object. 74 | # - `:default_to_main`, which will detach any explicit route tables associated 75 | # with the subnet, causing it to use the default (main) route table for the VPC. 76 | # 77 | # By default, an implicit association with the main route table is made (`:default_to_main`) 78 | # 79 | attribute :route_table # , kind_of: [ String, AwsRouteTable, ::Aws::EC2::RouteTable ], equal_to: [ :default_to_main ] 80 | 81 | # 82 | # The Network ACL to associate with this subnet. Subnets may only 83 | # be associated with a single Network ACL. 84 | # 85 | # TODO: See if it's possible to disassociate a Network ACL. 86 | # 87 | attribute :network_acl, kind_of: [String, AwsNetworkAcl, ::Aws::EC2::NetworkAcl] 88 | 89 | attribute :subnet_id, kind_of: String, aws_id_attribute: true, default: lazy { 90 | name =~ /^subnet-[a-f0-9]+$/ ? name : nil 91 | } 92 | 93 | def aws_object 94 | driver, id = get_driver_and_id 95 | result = driver.ec2_resource.subnet(id) if id 96 | if result 97 | begin 98 | # Try to access it to see if it exists (no `exists?` method) 99 | result.vpc_id 100 | rescue ::Aws::EC2::Errors::InvalidSubnetIDNotFound 101 | result = nil 102 | end 103 | end 104 | result 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/chef/resource/aws_vpc_peering_connection.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/aws_resource_with_entry" 2 | 3 | # 4 | # An AWS peering connection, specifying which VPC to peer. 5 | # 6 | # `name` is not guaranteed unique for an AWS account; therefore, Chef will 7 | # store the vpc peering connection ID associated with this name in your Chef server in the 8 | # data bag `data/aws_vpc_peering_connection/`. 9 | # 10 | # API documentation for the AWS Ruby SDK for VPC Peering Connections can be found here: 11 | # 12 | # - http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Types/VpcPeeringConnectionVpcInfo.html 13 | # 14 | class Chef::Resource::AwsVpcPeeringConnection < Chef::Provisioning::AWSDriver::AWSResourceWithEntry 15 | aws_sdk_type ::Aws::EC2::VpcPeeringConnection 16 | actions :accept, :create, :destroy, :purge, :nothing 17 | 18 | require "chef/resource/aws_vpc" 19 | 20 | # 21 | # The name of this peering connection. 22 | # 23 | attribute :name, kind_of: String, name_attribute: true 24 | 25 | # 26 | # The Local VPC to peer 27 | # 28 | # May be one of: 29 | # - The name of an `aws_vpc` Chef resource. 30 | # - An actual `aws_vpc` resource. 31 | # - An AWS `VPC` object. 32 | # 33 | # This is required for new peering connections. 34 | # 35 | attribute :vpc, kind_of: [String, AwsVpc, ::Aws::EC2::Vpc] 36 | 37 | # 38 | # The VPC to peer 39 | # 40 | # May be one of: 41 | # - The name of an `aws_vpc` Chef resource. 42 | # - An actual `aws_vpc` resource. 43 | # - An AWS `VPC` object. 44 | # - The id of an AWS `VPC`. 45 | # 46 | # This is required for new peering connections. 47 | # 48 | attribute :peer_vpc, kind_of: [String, AwsVpc, ::Aws::EC2::Vpc] 49 | 50 | # 51 | # The target VPC account id to peer 52 | # 53 | # If not specified, will be assumed that the target VPC belongs to the current account. 54 | # 55 | attribute :peer_owner_id, kind_of: String 56 | 57 | attribute :vpc_peering_connection_id, kind_of: String, aws_id_attribute: true, default: lazy { 58 | name =~ /^pcx-[a-f0-9]+$/ ? name : nil 59 | } 60 | 61 | def aws_object 62 | driver, id = get_driver_and_id 63 | result = driver.ec2_resource.vpc_peering_connection(id) if id 64 | 65 | begin 66 | # try accessing it to find out if it exists 67 | result.requester_vpc if result 68 | rescue ::Aws::EC2::Errors::InvalidVpcPeeringConnectionIDNotFound 69 | result = nil 70 | end 71 | result 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/aws_support/aws_resource_run_wrapper.rb: -------------------------------------------------------------------------------- 1 | require "cheffish/rspec/recipe_run_wrapper" 2 | 3 | module AWSSupport 4 | class AWSResourceRunWrapper < Cheffish::RSpec::RecipeRunWrapper 5 | def initialize(example, resource_type, name, &properties) 6 | super(example.chef_config) do 7 | if properties && !properties.parameters.empty? 8 | public_send(resource_type, name) { instance_exec(example, &properties) } 9 | else 10 | public_send(resource_type, name, &properties) 11 | end 12 | end 13 | @example = example 14 | @resource_type = resource_type 15 | @name = name 16 | @properties = properties 17 | end 18 | 19 | attr_reader :example 20 | attr_reader :resource_type 21 | attr_reader :name 22 | 23 | def resource 24 | resources.first 25 | end 26 | 27 | def to_s 28 | "#{resource_type}[#{name}]" 29 | end 30 | 31 | def destroy 32 | resource_type = self.resource_type 33 | name = self.name 34 | example.recipe do 35 | public_send(resource_type, name) do 36 | if allowed_actions.include?(:purge) 37 | action :purge 38 | else 39 | action :destroy 40 | end 41 | end 42 | end.converge 43 | end 44 | 45 | def aws_object 46 | resource.aws_object 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/aws_support/deep_matcher.rb: -------------------------------------------------------------------------------- 1 | module AWSSupport 2 | # 3 | # Include this and override `match_failure_messages`, and your class becomes 4 | # a matcher which will have `matches?` call `match_failure_messages` and 5 | # cache the result, which is then returned verbatim from failure_message. 6 | # 7 | module DeepMatcher 8 | require "aws_support/deep_matcher/match_values_failure_messages" 9 | 10 | include MatchValuesFailureMessages 11 | 12 | def matches?(actual) 13 | @failure_messages = match_failure_messages(actual) 14 | @failure_messages.empty? 15 | end 16 | 17 | def failure_message 18 | @failure_messages.flat_map { |message| message.split("\n") }.join("\n") 19 | end 20 | 21 | def failure_message_when_negated 22 | "expected #{@actual.inspect} not to #{description}" 23 | end 24 | 25 | # 26 | # Return the failure message resulting from matching `actual`. Meant to be 27 | # overridden in implementors. 28 | # 29 | # @param actual The actual value to compare against 30 | # @param identifier The name of the thing being compared (may not be passed, 31 | # in which case the class will choose an appropriate default.) 32 | # 33 | # @return A failure message, or empty string if it does not fail. 34 | # 35 | def match_failure_messages(_actual, _identifier = "value") 36 | raise NotImplementedError, :match_failure_messages 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/aws_support/deep_matcher/fuzzy_match_objects.rb: -------------------------------------------------------------------------------- 1 | module AWSSupport 2 | module DeepMatcher 3 | # 4 | # This gets mixed into RSpec::Support::FuzzyMatch, adding the ability to 5 | # fuzzy match objects against hashes, a la: 6 | # 7 | # values_match({ a: 1, b: 2, 'c.d.e' => 3 }, 8 | # 9 | # end 10 | # 11 | module FuzzyMatchObjects 12 | require "rspec/support/fuzzy_matcher" 13 | require "aws_support/deep_matcher/matchable_object" 14 | require "aws_support/deep_matcher/matchable_array" 15 | 16 | def values_match?(expected, actual) 17 | if Hash === expected 18 | return hash_and_object_match?(expected, actual) if MatchableObject === actual 19 | elsif Array === expected 20 | return arrays_match?(expected, actual) if MatchableArray === actual 21 | end 22 | 23 | super 24 | end 25 | 26 | def hash_and_object_match?(expected, actual) 27 | expected_hash.all? do |expected_key, expected_value| 28 | # 'a.b.c' => 1 -> { 'a' => { 'b' => { 'c' => 1 } } } 29 | # :"a.b.c" => 1 -> { :a => { :b => { :c => 1 } } } 30 | names = expected_key.to_s.split(".") 31 | if names.size > 1 32 | expected_key = expected_key.is_a?(Symbol) ? names.shift.to_sym : names.shift 33 | expected_value = { names.pop => expected_value } until names.empty? 34 | end 35 | 36 | # Grab the actual value from the object 37 | begin 38 | actual_value = actual_object.send(expected_key) 39 | rescue NoMethodError 40 | if !actual_value.respond_to?(expected_key) 41 | return false 42 | else 43 | raise 44 | end 45 | end 46 | 47 | return false unless values_match?(expected, actual) 48 | end 49 | 50 | true 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/aws_support/deep_matcher/matchable_array.rb: -------------------------------------------------------------------------------- 1 | module AWSSupport 2 | module DeepMatcher 3 | # 4 | # If a module implements this, it signifies that 5 | # 6 | module MatchableArray 7 | # 8 | # TODO allow the user to return a new object that actually implements the 9 | # enumerable, in case the class in question is non-standard. 10 | # 11 | 12 | def self.matchable_classes 13 | @matchable_classes ||= [] 14 | end 15 | 16 | def self.===(other) 17 | return true if matchable_classes.any? { |c| c === other } 18 | return true if Enumerable === other && !(Struct === other) 19 | super 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/aws_support/deep_matcher/matchable_object.rb: -------------------------------------------------------------------------------- 1 | module AWSSupport 2 | module DeepMatcher 3 | # 4 | # If a module implements this, or is added to `matchable_classes`, 5 | # RSpec's `match` will match its attributes up to hashes a la: 6 | # 7 | # ```ruby 8 | # expect(my_object).to match({ a: 1, b: 2 }) 9 | # ``` 10 | # 11 | # Which will compare my_object.a to 1 and my_object.b to 2. 12 | # 13 | module MatchableObject 14 | def self.matchable_classes 15 | @matchable_classes ||= [] 16 | end 17 | 18 | def self.===(other) 19 | return true if matchable_classes.any? { |c| c === other } 20 | super 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/aws_support/deep_matcher/rspec_monkeypatches.rb: -------------------------------------------------------------------------------- 1 | # FIRST thing we do in a desperate attempt to get our module in before RSpec loads 2 | 3 | module AWSSupport 4 | module DeepMatcher 5 | module MatchValuesFailureMessages 6 | end 7 | end 8 | end 9 | 10 | module RSpec 11 | module Matchers 12 | module Composable 13 | include AWSSupport::DeepMatcher::MatchValuesFailureMessages 14 | end 15 | end 16 | end 17 | 18 | require "aws_support/deep_matcher/match_values_failure_messages" 19 | require "rspec/matchers/composable" 20 | require "rspec/support/fuzzy_matcher" 21 | require "aws_support/deep_matcher/fuzzy_match_objects" 22 | 23 | module RSpec::Support::FuzzyMatcher 24 | prepend AWSSupport::DeepMatcher::FuzzyMatchObjects 25 | end 26 | -------------------------------------------------------------------------------- /spec/aws_support/delayed_stream.rb: -------------------------------------------------------------------------------- 1 | require "timeout" 2 | 3 | module AWSSupport 4 | class DelayedStream 5 | def initialize(delay_before_streaming, *streams) 6 | @streams = streams.flatten.reject(&:nil?) 7 | if delay_before_streaming > 0 8 | @buffer = StringIO.new 9 | @thread = Thread.new do 10 | sleep delay_before_streaming 11 | start_streaming 12 | end 13 | end 14 | end 15 | 16 | attr_reader :streams 17 | attr_reader :buffer 18 | 19 | def start_streaming 20 | if @buffer 21 | buffer = @buffer 22 | @buffer = nil 23 | streams.each { |s| s.write(buffer.string) } 24 | end 25 | end 26 | 27 | def write(*args, &block) 28 | if buffer.nil? 29 | streams.each { |s| s.write(*args, &block) } 30 | else 31 | buffer.write(*args, &block) 32 | end 33 | end 34 | 35 | def close 36 | @streams = [] 37 | @thread.kill if @thread 38 | @thread = nil 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/aws_support/matchers/create_an_aws_object.rb: -------------------------------------------------------------------------------- 1 | require "rspec/matchers" 2 | require "chef/provisioning" 3 | require "aws_support/deep_matcher" 4 | 5 | module AWSSupport 6 | module Matchers 7 | class CreateAnAWSObject 8 | include RSpec::Matchers::Composable 9 | include AWSSupport::DeepMatcher 10 | 11 | # @param custom_matcher [Block] A block with 1 argument that will be provided the aws_object 12 | def initialize(example, resource_class, name, expected_values, custom_matcher) 13 | @example = example 14 | @resource_class = resource_class 15 | @name = name 16 | @expected_values = expected_values 17 | @custom_matcher = custom_matcher 18 | end 19 | 20 | attr_reader :example 21 | attr_reader :resource_class 22 | attr_reader :name 23 | attr_reader :expected_values 24 | attr_reader :custom_matcher 25 | 26 | def resource_name 27 | @resource_class.resource_name 28 | end 29 | 30 | def match_failure_messages(recipe) 31 | differences = [] 32 | 33 | # We want to record that it was created BEFORE the converge, so that 34 | # even if it fails, we destroy it. 35 | example.created_during_test << [resource_name, name] 36 | 37 | # Converge 38 | begin 39 | recipe.converge unless recipe.converged? 40 | rescue StandardError 41 | differences += ["error trying to create #{resource_name}[#{name}]!\n#{($ERROR_INFO.backtrace.map { |line| "- #{line}\n" } + [recipe.output_for_failure_message]).join('')}"] 42 | end 43 | 44 | # Check whether the recipe caused an update or not 45 | differences += match_values_failure_messages(example.be_updated, recipe, "recipe") 46 | 47 | # Check for object existence and properties 48 | resource = resource_class.new(name, nil) 49 | resource.driver example.driver 50 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 51 | aws_object = resource.aws_object 52 | 53 | example.instance_exec aws_object, &custom_matcher if custom_matcher 54 | 55 | # We get response as a Struct for aws_cache_subnet_group resource. 56 | # Hence converting it into a hash. 57 | aws_object = aws_object.to_hash if resource_name == :aws_cache_subnet_group || resource_name == :aws_cache_cluster 58 | 59 | # Check existence 60 | if aws_object.nil? 61 | differences << "#{resource_name}[#{name}] was not created!" 62 | else 63 | differences += match_values_failure_messages(expected_values, aws_object, "#{resource_name}[#{name}]") 64 | end 65 | 66 | differences 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/aws_support/matchers/destroy_an_aws_object.rb: -------------------------------------------------------------------------------- 1 | require "rspec/matchers" 2 | require "chef/provisioning" 3 | require "aws_support/deep_matcher" 4 | 5 | module AWSSupport 6 | module Matchers 7 | class DestroyAnAWSObject 8 | include RSpec::Matchers::Composable 9 | include AWSSupport::DeepMatcher 10 | 11 | def initialize(example, resource_class, name) 12 | @example = example 13 | @resource_class = resource_class 14 | @name = name 15 | 16 | # Grab the "before" value 17 | resource = resource_class.new(name, nil) 18 | resource.driver example.driver 19 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 20 | @had_initial_value = !resource.aws_object.nil? 21 | end 22 | 23 | attr_reader :example 24 | attr_reader :resource_class 25 | attr_reader :name 26 | def resource_name 27 | @resource_class.resource_name 28 | end 29 | attr_reader :had_initial_value 30 | 31 | def match_failure_messages(recipe) 32 | differences = [] 33 | 34 | unless had_initial_value 35 | differences << "expected recipe to delete #{resource_name}[#{name}], but the AWS object did not exist before recipe ran!" 36 | return differences 37 | end 38 | 39 | # Converge 40 | begin 41 | recipe.converge unless recipe.converged? 42 | rescue StandardError 43 | differences += ["error trying to delete #{resource_name}[#{name}]!\n#{($ERROR_INFO.backtrace.map { |line| "- #{line}\n" } + [recipe.output_for_failure_message]).join('')}"] 44 | end 45 | 46 | # Check whether the recipe caused an update or not 47 | differences += match_values_failure_messages(example.be_updated, recipe, "recipe") 48 | 49 | # Check for object existence and properties 50 | resource = resource_class.new(name, nil) 51 | resource.driver example.driver 52 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 53 | aws_object = resource.aws_object 54 | 55 | # Check existence 56 | differences << "#{resource_name}[#{name}] was not deleted!" unless aws_object.nil? 57 | 58 | differences 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/aws_support/matchers/have_aws_object_tags.rb: -------------------------------------------------------------------------------- 1 | require "rspec/matchers" 2 | require "chef/provisioning" 3 | require "aws_support/deep_matcher" 4 | 5 | module AWSSupport 6 | module Matchers 7 | class HaveAWSObjectTags 8 | include RSpec::Matchers::Composable 9 | include AWSSupport::DeepMatcher 10 | 11 | def initialize(example, resource_class, name, expected_tags) 12 | @example = example 13 | @resource_class = resource_class 14 | @name = name 15 | @expected_tags = expected_tags 16 | end 17 | 18 | attr_reader :example 19 | attr_reader :resource_class 20 | attr_reader :name 21 | attr_reader :expected_tags 22 | def resource_name 23 | @resource_class.resource_name 24 | end 25 | 26 | def match_failure_messages(recipe) 27 | differences = [] 28 | 29 | # Check for object existence and properties 30 | resource = resource_class.new(name, recipe.client.run_context) 31 | resource.driver example.driver 32 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 33 | @aws_object = resource.aws_object 34 | 35 | # Check existence 36 | if @aws_object.nil? 37 | differences << "#{resource} did not exist!" 38 | else 39 | differences += match_hashes_failure_messages(expected_tags, aws_object_tags(resource), resource.to_s) 40 | end 41 | 42 | differences 43 | end 44 | 45 | private 46 | 47 | def aws_object_tags(resource) 48 | # Okay, its annoying to have to lookup the provider for a resource and duplicate a bunch of code here. 49 | # But I don't want to move the `converge_tags` method into the resource and until the resource & provider 50 | # are combined, this is my best idea. 51 | provider = resource.provider_for_action(:create) 52 | provider.aws_tagger.current_tags 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/aws_support/matchers/match_an_aws_object.rb: -------------------------------------------------------------------------------- 1 | require "rspec/matchers" 2 | require "chef/provisioning" 3 | require "aws_support/deep_matcher" 4 | 5 | module AWSSupport 6 | module Matchers 7 | # This matcher doesn't try to validate that an example was created/updated/destroyed 8 | # it just checks that the object exists and posses the attributes you specify 9 | # It also doesn't clean up any aws objects so only use if the resource is defined outside 10 | # of an example block 11 | class MatchAnAWSObject 12 | include RSpec::Matchers::Composable 13 | include AWSSupport::DeepMatcher 14 | 15 | # @param custom_matcher [Block] A block with 1 argument that will be provided the aws_obect 16 | def initialize(example, resource_class, name, expected_values, custom_matcher) 17 | @example = example 18 | @resource_class = resource_class 19 | @name = name 20 | @expected_values = expected_values 21 | @custom_matcher = custom_matcher 22 | end 23 | 24 | attr_reader :example 25 | attr_reader :resource_class 26 | attr_reader :name 27 | attr_reader :expected_values 28 | attr_reader :custom_matcher 29 | 30 | def resource_name 31 | @resource_class.resource_name 32 | end 33 | 34 | def match_failure_messages(recipe) 35 | differences = [] 36 | 37 | # Converge 38 | begin 39 | recipe.converge unless recipe.converged? 40 | rescue StandardError 41 | differences += ["error trying to converge #{resource_name}[#{name}]!\n#{($ERROR_INFO.backtrace.map { |line| "- #{line}\n" } + [recipe.output_for_failure_message]).join('')}"] 42 | end 43 | 44 | # Check for object existence and properties 45 | resource = resource_class.new(name, nil) 46 | resource.driver example.driver 47 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 48 | aws_object = resource.aws_object 49 | 50 | example.instance_exec aws_object, &custom_matcher if custom_matcher 51 | 52 | # Check existence 53 | if aws_object.nil? 54 | differences << "#{resource_name}[#{name}] AWS object did not exist!" 55 | else 56 | differences += match_values_failure_messages(expected_values, aws_object, "#{resource_name}[#{name}]") 57 | end 58 | 59 | differences 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/aws_support/matchers/update_an_aws_object.rb: -------------------------------------------------------------------------------- 1 | require "rspec/matchers" 2 | require "aws_support/deep_matcher" 3 | 4 | module AWSSupport 5 | module Matchers 6 | class UpdateAnAWSObject 7 | include RSpec::Matchers::Composable 8 | include AWSSupport::DeepMatcher 9 | 10 | require "chef/provisioning" 11 | 12 | # @param custom_matcher [Block] A block with 1 argument that will be provided the aws_obect 13 | def initialize(example, resource_class, name, expected_updates, custom_matcher) 14 | @example = example 15 | @resource_class = resource_class 16 | @name = name 17 | @expected_updates = expected_updates 18 | @custom_matcher = custom_matcher 19 | 20 | # Grab the "before" value 21 | resource = resource_class.new(name, nil) 22 | resource.driver example.driver 23 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 24 | @had_initial_value = !resource.aws_object.nil? 25 | end 26 | 27 | attr_reader :example 28 | attr_reader :resource_class 29 | attr_reader :name 30 | attr_reader :expected_updates 31 | attr_reader :custom_matcher 32 | attr_reader :had_initial_value 33 | 34 | def resource_name 35 | @resource_class.resource_name 36 | end 37 | 38 | def match_failure_messages(recipe) 39 | differences = [] 40 | 41 | unless had_initial_value 42 | differences << "expected recipe to update #{resource_name}[#{name}], but the AWS object did not exist before recipe ran!" 43 | return differences 44 | end 45 | 46 | # Converge 47 | begin 48 | recipe.converge unless recipe.converged? 49 | rescue StandardError 50 | differences += ["error trying to update #{resource_name}[#{name}]!\n#{($ERROR_INFO.backtrace.map { |line| "- #{line}\n" } + [recipe.output_for_failure_message]).join('')}"] 51 | end 52 | 53 | # Check if the recipe actually caused an update 54 | differences += match_values_failure_messages(example.be_updated, recipe, "recipe") 55 | 56 | # Check if any properties that should have been updated, weren't 57 | resource = resource_class.new(name, nil) 58 | resource.driver example.driver 59 | resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store 60 | aws_object = resource.aws_object 61 | 62 | example.instance_exec aws_object, &custom_matcher if custom_matcher 63 | 64 | # Check to see if properties have the expected values 65 | differences += match_values_failure_messages(expected_updates, aws_object, "#{resource_name}[#{name}]") 66 | 67 | differences 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/integration/aws_cache_cluster_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::AwsCacheCluster, :super_slow do 4 | extend AWSSupport 5 | 6 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 7 | with_aws "with a VPC, subnet, subnet_group and security_group" do 8 | after(:context) do 9 | # Cache Cluster takes around 7 minutes to get deleted 10 | # and all the dependent resources can be deleted after that only. 11 | # Hence waiting for 8 minutes. 12 | sleep(480) 13 | converge do 14 | aws_cache_subnet_group "test-subnet-group" do 15 | action :destroy 16 | end 17 | 18 | aws_vpc "test_vpc" do 19 | action :purge 20 | end 21 | end 22 | end 23 | 24 | it "aws_cache_cluster 'TestRedisCluster' creates a cache cluster" do 25 | converge do 26 | aws_vpc "test_vpc" do 27 | cidr_block "10.0.0.0/24" 28 | internet_gateway true 29 | end 30 | 31 | aws_subnet "test_subnet" do 32 | vpc "test_vpc" 33 | cidr_block "10.0.0.0/24" 34 | end 35 | 36 | aws_cache_subnet_group "test-subnet-group" do 37 | description "Test Subnet Group" 38 | subnets ["test_subnet"] 39 | end 40 | 41 | aws_security_group "test_sg" do 42 | vpc "test_vpc" 43 | end 44 | end 45 | 46 | expect_recipe do 47 | aws_cache_cluster "TestRedisCluster" do 48 | az_mode "single-az" 49 | engine "redis" 50 | engine_version "3.2.6" 51 | node_type "cache.t2.micro" 52 | number_nodes 1 53 | security_groups "test_sg" 54 | subnet_group_name "test-subnet-group" 55 | end 56 | end.to create_an_aws_cache_cluster("TestRedisCluster", 57 | cache_cluster_id: "testrediscluster", 58 | cache_node_type: "cache.t2.micro", 59 | engine: "redis", 60 | engine_version: "3.2.6", 61 | num_cache_nodes: 1, 62 | pending_modified_values: {}, 63 | cache_security_groups: [], 64 | cache_parameter_group: 65 | { cache_parameter_group_name: "default.redis3.2", parameter_apply_status: "in-sync", cache_node_ids_to_reboot: [] }, 66 | cache_subnet_group_name: "test-subnet-group").and be_idempotent 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/integration/aws_cache_subnet_group_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::AwsCacheSubnetGroup do 4 | extend AWSSupport 5 | 6 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 7 | with_aws "with a VPC with an internet gateway and subnet" do 8 | aws_vpc "test_vpc" do 9 | cidr_block "10.0.0.0/24" 10 | internet_gateway true 11 | end 12 | 13 | aws_subnet "test_subnet" do 14 | vpc "test_vpc" 15 | cidr_block "10.0.0.0/24" 16 | end 17 | 18 | it "aws_cache_subnet_group 'test-subnet-group' creates a cache subnet group" do 19 | expect_recipe do 20 | aws_cache_subnet_group "test-subnet-group" do 21 | description "Test Subnet Group" 22 | subnets ["test_subnet"] 23 | end 24 | end.to create_an_aws_cache_subnet_group("test-subnet-group", 25 | vpc_id: test_vpc.aws_object.id, 26 | subnets: [ 27 | { subnet_identifier: test_subnet.aws_object.id } 28 | ]).and be_idempotent 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/integration/aws_cloudsearch_domain_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::AwsCloudsearchDomain do 4 | extend AWSSupport 5 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 6 | with_aws "when connected to AWS" do 7 | # Cloudsearch can take forevvvver to delete so we need to randomize our names 8 | time = DateTime.now.strftime("%Q") 9 | 10 | it "aws_cloudsearch_domain 'test-#{time}' creates a cloudsearch domain" do 11 | expect_recipe do 12 | aws_cloudsearch_domain "test-#{time}" do 13 | multi_az false 14 | end 15 | end.to create_an_aws_cloudsearch_domain("test-#{time}", {}).and be_idempotent 16 | end 17 | 18 | it "returns nil when aws_object is called for something that does not exist" do 19 | r = nil 20 | converge do 21 | r = aws_cloudsearch_domain "wont-exist" do 22 | action :nothing 23 | end 24 | end 25 | expect(r.aws_object).to eq(nil) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/integration/aws_dhcp_options_spec.rb: -------------------------------------------------------------------------------- 1 | describe "AwsDhcpOptions" do 2 | extend AWSSupport 3 | 4 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 5 | with_aws "when connected to AWS" do 6 | it "creates an aws_dhcp_options resource with maximum attributes" do 7 | expect_recipe do 8 | aws_dhcp_options "test-dhcp-options" do 9 | domain_name "example.com" 10 | domain_name_servers %w{8.8.8.8 8.8.4.4} 11 | ntp_servers %w{8.8.8.8 8.8.4.4} 12 | netbios_name_servers %w{8.8.8.8 8.8.4.4} 13 | netbios_node_type 2 14 | end 15 | end.to create_an_aws_dhcp_options("test-dhcp-options", dhcp_configurations: [ 16 | { key: "domain-name", values: [{ value: "example.com" }] }, 17 | { key: "domain-name-servers", values: [{ value: "8.8.8.8" }, { value: "8.8.4.4" }] }, 18 | { key: "ntp-servers", values: [{ value: "8.8.8.8" }, { value: "8.8.4.4" }] }, 19 | { key: "netbios-node-type", values: [{ value: "2" }] }, 20 | { key: "netbios-name-servers", values: [{ value: "8.8.8.8" }, { value: "8.8.4.4" }] } 21 | ]).and be_idempotent 22 | end 23 | 24 | it "creates aws_dhcp_options tags" do 25 | expect_recipe do 26 | aws_dhcp_options "test-dhcp-options" do 27 | aws_tags key1: "value" 28 | end 29 | end.to create_an_aws_dhcp_options("test-dhcp-options") 30 | .and have_aws_dhcp_options_tags("test-dhcp-options", 31 | "Name" => "test-dhcp-options", 32 | "key1" => "value").and be_idempotent 33 | end 34 | 35 | context "with existing tags" do 36 | aws_dhcp_options "test-dhcp-options" do 37 | aws_tags key1: "value" 38 | end 39 | 40 | it "updates aws_dhcp_options tags" do 41 | expect_recipe do 42 | aws_dhcp_options "test-dhcp-options" do 43 | aws_tags key1: "value2", key2: nil 44 | end 45 | end.to have_aws_dhcp_options_tags("test-dhcp-options", 46 | "Name" => "test-dhcp-options", 47 | "key1" => "value2", 48 | "key2" => "").and be_idempotent 49 | end 50 | 51 | it "removes all aws_dhcp_options tags except Name" do 52 | expect_recipe do 53 | aws_dhcp_options "test-dhcp-options" do 54 | aws_tags({}) 55 | end 56 | end.to have_aws_dhcp_options_tags("test-dhcp-options", 57 | "Name" => "test-dhcp-options").and be_idempotent 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/integration/aws_eip_address_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::AwsEipAddress do 4 | extend AWSSupport 5 | 6 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 7 | with_aws "when connected to AWS" do 8 | it "aws_eip_address 'test_eip' creates an elastic ip" do 9 | expect_recipe do 10 | aws_eip_address "test_eip" 11 | end.to create_an_aws_eip_address("test_eip").and be_idempotent 12 | end 13 | 14 | it "raises an error trying to reference an eip that does not exist" do 15 | r = recipe do 16 | aws_eip_address "0.0.0.0" 17 | end 18 | expect { r.converge }.to raise_error(/Chef::Resource::AwsEipAddress\[0.0.0.0\] does not exist!/) 19 | end 20 | 21 | context "with an existing aws_eip_address" do 22 | aws_eip_address "test_eip" 23 | 24 | it "can reference the ip address by id in the name field" do 25 | expect_recipe do 26 | aws_eip_address test_eip.aws_object.public_ip 27 | end.to match_an_aws_eip_address(test_eip.aws_object.public_ip, 28 | public_ip: test_eip.aws_object.public_ip).and be_idempotent 29 | end 30 | 31 | it "can reference the ip address in the public_ip field" do 32 | expect_recipe do 33 | aws_eip_address "random_identifier" do 34 | public_ip test_eip.aws_object.public_ip 35 | end 36 | end.to match_an_aws_eip_address("random_identifier", 37 | public_ip: test_eip.aws_object.public_ip).and be_idempotent 38 | end 39 | end 40 | 41 | describe "action :delete" do 42 | aws_eip_address "test_eip" 43 | 44 | it "deletes the elastic ip" do 45 | # TODO: all the `with_*` and `expect_*` methods from Cheffish 46 | # automatically converge the block - we don't want to do that, 47 | # we want to let the `destroy_an*` matcher do that 48 | r = recipe do 49 | aws_eip_address "test_eip" do 50 | action :destroy 51 | end 52 | end 53 | expect(r).to destroy_an_aws_eip_address("test_eip").and be_idempotent 54 | end 55 | end 56 | 57 | context "with existing machines", :super_slow do 58 | purge_all 59 | setup_public_vpc 60 | 61 | machine "test_machine" do 62 | machine_options bootstrap_options: { 63 | subnet_id: "test_public_subnet", 64 | key_name: "test_key_pair" 65 | } 66 | action :ready # The box has to be online for AWS to accept it as routable 67 | end 68 | 69 | it "associates an EIP with a machine" do 70 | expect_recipe do 71 | aws_eip_address "test_eip" do 72 | associate_to_vpc true 73 | machine "test_machine" 74 | end 75 | end.to create_an_aws_eip_address("test_eip", 76 | instance_id: test_machine.aws_object.id).and be_idempotent 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/integration/aws_elasticsearch_domain_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | def policy(user) 4 | < "value") 99 | end 100 | 101 | it "removes all aws_elasticsearch_domain tags" do 102 | expect_recipe do 103 | aws_elasticsearch_domain "test-#{time}-2" do 104 | aws_tags({}) 105 | end 106 | end.to have_aws_elasticsearch_domain_tags("test-#{time}-2", {}).and be_idempotent 107 | end 108 | 109 | it "destroys an elasticsearch domain" do 110 | r = recipe do 111 | aws_elasticsearch_domain "test-#{time}-2" do 112 | action :destroy 113 | end 114 | end 115 | expect(r).to destroy_an_aws_elasticsearch_domain("test-#{time}-2") 116 | end 117 | end 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /spec/integration/aws_key_pair_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::AwsKeyPair do 4 | extend AWSSupport 5 | 6 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 7 | with_aws "when connected to AWS" do 8 | before :each do 9 | driver.ec2.delete_key_pair(key_name: "test_key_pair") 10 | end 11 | 12 | it "aws_key_pair 'test_key_pair' creates a key pair" do 13 | expect(recipe do 14 | aws_key_pair "test_key_pair" do 15 | private_key_options format: :pem, type: :rsa, regenerate_if_different: true 16 | allow_overwrite true 17 | end 18 | end).to create_an_aws_key_pair("test_key_pair").and be_idempotent 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/integration/aws_launch_configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::AwsLaunchConfiguration do 4 | extend AWSSupport 5 | 6 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 7 | with_aws "when connected to AWS" do 8 | let(:image_filters) do 9 | { 10 | filters: [ 11 | { 12 | name: "image-type", 13 | values: ["machine"] 14 | }, 15 | { 16 | name: "state", 17 | values: ["available"] 18 | }, 19 | { 20 | name: "is-public", 21 | values: ["true"] 22 | }, 23 | { 24 | name: "owner-alias", 25 | values: ["amazon"] 26 | } 27 | ] 28 | } 29 | end 30 | 31 | it "creates a minimum aws_launch_configuration" do 32 | expect_recipe do 33 | ami = driver.ec2_client.describe_images(image_filters).images[0].image_id 34 | aws_launch_configuration "my-launch-configuration" do 35 | image ami 36 | instance_type "t2.micro" 37 | end 38 | end.to create_an_aws_launch_configuration("my-launch-configuration").and be_idempotent 39 | end 40 | 41 | it "accepts base64 encoded user data" do 42 | expect_recipe do 43 | ami = driver.ec2_client.describe_images(image_filters).images[0].image_id 44 | aws_launch_configuration "my-launch-configuration" do 45 | image ami 46 | instance_type "t2.micro" 47 | options( 48 | user_data: Base64.encode64("echo 1") 49 | ) 50 | end 51 | end.to create_an_aws_launch_configuration("my-launch-configuration").and be_idempotent 52 | end 53 | 54 | it "accepts regular user data" do 55 | expect_recipe do 56 | ami = driver.ec2_client.describe_images(image_filters).images[0].image_id 57 | aws_launch_configuration "my-launch-configuration" do 58 | image ami 59 | instance_type "t2.micro" 60 | options( 61 | user_data: "echo 1" 62 | ) 63 | end 64 | end.to create_an_aws_launch_configuration("my-launch-configuration").and be_idempotent 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/integration/aws_nat_gateway_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "chef/resource/aws_nat_gateway" 3 | 4 | describe Chef::Resource::AwsNatGateway do 5 | extend AWSSupport 6 | 7 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 8 | with_aws "with a VPC" do 9 | purge_all 10 | setup_public_vpc 11 | 12 | aws_eip_address "test_eip" 13 | 14 | describe "action :create" do # , :super_slow do 15 | it "creates an aws_nat_gateway in the specified subnet" do 16 | expect_recipe do 17 | sub_id = test_public_subnet.aws_object.id 18 | aws_nat_gateway "test_nat_gateway" do 19 | subnet sub_id 20 | eip_address "test_eip" 21 | end 22 | end.to create_an_aws_nat_gateway("test_nat_gateway", 23 | subnet_id: test_public_subnet.aws_object.id).and be_idempotent 24 | end 25 | end 26 | 27 | describe "action :delete" do 28 | context "when there is a nat_gateway" do 29 | aws_nat_gateway "test_nat_gateway" do 30 | subnet "test_public_subnet" 31 | eip_address "test_eip" 32 | end 33 | 34 | it "deletes the nat gateway and does not delete the eip address" do 35 | r = recipe do 36 | aws_nat_gateway "test_nat_gateway" do 37 | action :destroy 38 | end 39 | end 40 | expect(r).to destroy_an_aws_nat_gateway("test_nat_gateway").and match_an_aws_eip_address("test_eip").and be_idempotent 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/integration/aws_network_interface_spec.rb: -------------------------------------------------------------------------------- 1 | describe "AwsNetworkInterface" do 2 | extend AWSSupport 3 | 4 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 5 | with_aws "when connected to AWS" do 6 | context "setting up public VPC" do 7 | purge_all 8 | setup_public_vpc 9 | 10 | context "with machines", :super_slow do 11 | machine "test_machine" do 12 | machine_options bootstrap_options: { 13 | subnet_id: "test_public_subnet", 14 | security_group_ids: ["test_security_group"] 15 | } 16 | action :ready 17 | end 18 | 19 | it "creates an aws_network_interface resource with maximum attributes" do 20 | expect_recipe do 21 | sub_id = test_public_subnet.aws_object.id 22 | sg_id = test_security_group.aws_object.id 23 | machine_id = test_machine.aws_object.id 24 | aws_network_interface "test_network_interface" do 25 | subnet sub_id 26 | private_ip_address "10.0.0.25" 27 | description "test_network_interface" 28 | security_groups [sg_id] 29 | machine machine_id 30 | device_index 1 31 | end 32 | end.to create_an_aws_network_interface("test_network_interface").and be_idempotent 33 | end 34 | end 35 | 36 | it "creates aws_network_interface tags" do 37 | expect_recipe do 38 | aws_network_interface "test_network_interface" do 39 | subnet "test_public_subnet" 40 | aws_tags key1: "value" 41 | end 42 | end.to create_an_aws_network_interface("test_network_interface") 43 | .and have_aws_network_interface_tags("test_network_interface", 44 | "Name" => "test_network_interface", 45 | "key1" => "value").and be_idempotent 46 | end 47 | 48 | context "with existing tags" do 49 | aws_network_interface "test_network_interface" do 50 | subnet "test_public_subnet" 51 | aws_tags key1: "value" 52 | end 53 | 54 | it "updates aws_network_interface tags" do 55 | expect_recipe do 56 | aws_network_interface "test_network_interface" do 57 | subnet "test_public_subnet" 58 | aws_tags key1: "value2", key2: nil 59 | end 60 | end.to have_aws_network_interface_tags("test_network_interface", 61 | "Name" => "test_network_interface", 62 | "key1" => "value2", 63 | "key2" => "").and be_idempotent 64 | end 65 | 66 | it "removes all aws_network_interface tags except Name" do 67 | expect_recipe do 68 | aws_network_interface "test_network_interface" do 69 | subnet "test_public_subnet" 70 | aws_tags({}) 71 | end 72 | end.to have_aws_network_interface_tags("test_network_interface", 73 | "Name" => "test_network_interface").and be_idempotent 74 | end 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/integration/aws_s3_bucket_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "securerandom" 3 | 4 | def mk_bucket_name 5 | bucket_postfix = SecureRandom.hex(8) 6 | "chef.provisioning.test.#{bucket_postfix}" 7 | end 8 | 9 | describe Chef::Resource::AwsS3Bucket do 10 | extend AWSSupport 11 | 12 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 13 | with_aws "when connected to AWS" do 14 | bucket_name = mk_bucket_name 15 | 16 | it "aws_s3_bucket '#{bucket_name}' creates a bucket" do 17 | expect_recipe do 18 | aws_s3_bucket bucket_name 19 | end.to create_an_aws_s3_bucket(bucket_name).and be_idempotent 20 | end 21 | 22 | it "creates aws_s3_bucket tags" do 23 | expect_recipe do 24 | aws_s3_bucket bucket_name do 25 | aws_tags key1: "value" 26 | end 27 | end.to create_an_aws_s3_bucket(bucket_name) 28 | .and have_aws_s3_bucket_tags(bucket_name, 29 | "key1" => "value").and be_idempotent 30 | end 31 | 32 | context "with existing tags" do 33 | aws_s3_bucket bucket_name do 34 | aws_tags key1: "value" 35 | end 36 | 37 | it "updates aws_s3_bucket tags" do 38 | expect_recipe do 39 | aws_s3_bucket bucket_name do 40 | aws_tags key1: "value2", key2: nil 41 | end 42 | end.to have_aws_s3_bucket_tags(bucket_name, 43 | "key1" => "value2", 44 | "key2" => "").and be_idempotent 45 | end 46 | 47 | it "removes all aws_s3_bucket tags" do 48 | expect_recipe do 49 | aws_s3_bucket bucket_name do 50 | aws_tags({}) 51 | end 52 | end.to have_aws_s3_bucket_tags(bucket_name, {}).and be_idempotent 53 | end 54 | end 55 | end 56 | 57 | with_aws "when a bucket with content exists" do 58 | bucket_name = mk_bucket_name 59 | with_converge do 60 | aws_s3_bucket bucket_name 61 | 62 | ruby_block "upload s3 object" do 63 | block do 64 | ::Aws::S3::Resource.new(driver.s3_client).buckets.find { |b| b.name == bucket_name }.object("test-object").put(body: "test-content") 65 | end 66 | end 67 | end 68 | 69 | it "aws_s3_bucket '#{bucket_name}' with recursive_delete set to true, deletes the bucket" do 70 | r = recipe do 71 | aws_s3_bucket bucket_name do 72 | recursive_delete true 73 | action :delete 74 | end 75 | end 76 | expect(r).to destroy_an_aws_s3_bucket(bucket_name) 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/integration/machine_batch_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Chef::Resource::MachineBatch do 4 | extend AWSSupport 5 | 6 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context do 7 | with_aws "with a VPC and a public subnet" do 8 | before :all do 9 | chef_config[:log_level] = :warn 10 | end 11 | 12 | purge_all 13 | setup_public_vpc 14 | 15 | azs = [] 16 | driver.ec2.availability_zones.each { |az| azs << az } 17 | az = azs[1].name 18 | aws_subnet "test_subnet2" do 19 | vpc "test_vpc" 20 | cidr_block "10.0.1.0/24" 21 | availability_zone az 22 | map_public_ip_on_launch true 23 | end 24 | 25 | it "machine_batch creates multiple machines", :super_slow do 26 | expect_recipe do 27 | machine_batch "test_machines" do 28 | action :allocate 29 | (1..3).each do |i| 30 | machine "test_machine#{i}" do 31 | machine_options bootstrap_options: { 32 | subnet_id: "test_public_subnet", 33 | key_name: "test_key_pair" 34 | }, source_dest_check: false 35 | end 36 | end 37 | action :allocate 38 | end 39 | end.to create_an_aws_instance("test_machine1", 40 | source_dest_check: false).and create_an_aws_instance("test_machine2", 41 | source_dest_check: false).and create_an_aws_instance("test_machine3", 42 | source_dest_check: false).and be_idempotent 43 | end 44 | 45 | it "machine_batch supports runtime machine_options", :super_slow do 46 | expect_recipe do 47 | subnets = %w{test_public_subnet test_subnet2} 48 | 49 | machine_batch "test_machines" do 50 | action :allocate 51 | (1..2).each do |i| 52 | machine "test_machine#{i}" do 53 | machine_options bootstrap_options: { 54 | subnet_id: subnets[i - 1], 55 | key_name: "test_key_pair" 56 | }, source_dest_check: (i == 1) 57 | end 58 | end 59 | end 60 | end.to create_an_aws_instance("test_machine1", 61 | subnet_id: test_public_subnet.aws_object.id, 62 | source_dest_check: true).and create_an_aws_instance("test_machine2", 63 | subnet_id: test_subnet2.aws_object.id, 64 | source_dest_check: false).and be_idempotent 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "simplecov" 3 | SimpleCov.start 4 | rescue LoadError; end 5 | # Bring in the RSpec monkeypatch before we do *anything*, so that builtin matchers 6 | # will get the module. Not strictly necessary, but cleaner that way. 7 | require "aws_support/deep_matcher/rspec_monkeypatches" 8 | 9 | require "chef/mixin/shell_out" 10 | require "chef/dsl/recipe" 11 | require "chef/provisioning" 12 | require "chef/provisioning/aws_driver" 13 | require "chef/platform" 14 | require "chef/run_context" 15 | require "chef/event_dispatch/dispatcher" 16 | require "aws_support" 17 | require "rspec" 18 | 19 | RSpec.configure do |rspec| 20 | rspec.run_all_when_everything_filtered = true 21 | rspec.filter_run :focus 22 | rspec.filter_run_excluding super_slow: true 23 | # rspec.order = 'random' 24 | rspec.expect_with(:rspec) { |c| c.syntax = :expect } 25 | # rspec.before { allow($stdout).to receive(:write) } 26 | rspec.example_status_persistence_file_path = "spec/persistence_file.txt" 27 | end 28 | 29 | # Chef::Log.level = :debug 30 | Chef::Config[:log_level] = :warn 31 | 32 | require "cheffish/rspec/matchers" 33 | -------------------------------------------------------------------------------- /spec/unit/chef/provisioning/aws_driver/driver_spec.rb: -------------------------------------------------------------------------------- 1 | require "chef/provisioning/aws_driver/driver" 2 | require "chef/provisioning/aws_driver/credentials2" 3 | 4 | describe Chef::Provisioning::AWSDriver::Driver do 5 | let(:driver) { Chef::Provisioning::AWSDriver::Driver.new("aws::us-east-1", {}) } 6 | let(:aws_credentials) do 7 | double("credentials", default: { 8 | aws_access_key_id: "id", 9 | aws_secret_access_key: "secret" 10 | }) 11 | end 12 | let(:credentials2) { double("credentials2", get_credentials: {}) } 13 | 14 | before do 15 | expect_any_instance_of(Chef::Provisioning::AWSDriver::Driver).to receive(:aws_credentials).and_return(aws_credentials) 16 | expect(Aws.config).to receive(:update) do |h| 17 | expect(h).to include( 18 | access_key_id: "id", 19 | secret_access_key: "secret", 20 | region: "us-east-1" 21 | ) 22 | end 23 | expect(Chef::Provisioning::AWSDriver::Credentials2).to receive(:new).and_return(credentials2) 24 | end 25 | 26 | describe "#determine_remote_host" do 27 | let(:machine_spec) { double("machine_spec", reference: reference, name: "name") } 28 | let(:instance) { double("instance", private_ip_address: "private", dns_name: "dns", public_ip_address: "public") } 29 | 30 | context "when 'use_private_ip_for_ssh' is specified in the machine_spec.reference" do 31 | let(:reference) { { "use_private_ip_for_ssh" => true } } 32 | it "returns the private ip" do 33 | expect(driver.determine_remote_host(machine_spec, instance)).to eq("private") 34 | expect(reference).to eq("transport_address_location" => :private_ip) 35 | end 36 | end 37 | 38 | context "when 'transport_address_location' is set to :private_ip" do 39 | let(:reference) { { "transport_address_location" => :private_ip } } 40 | it "returns the private ip" do 41 | expect(driver.determine_remote_host(machine_spec, instance)).to eq("private") 42 | end 43 | end 44 | 45 | context "when 'transport_address_location' is set to :dns" do 46 | let(:reference) { { "transport_address_location" => :dns } } 47 | it "returns the dns name" do 48 | expect(driver.determine_remote_host(machine_spec, instance)).to eq("dns") 49 | end 50 | end 51 | 52 | context "when 'transport_address_location' is set to :public_ip" do 53 | let(:reference) { { "transport_address_location" => :public_ip } } 54 | it "returns the public ip" do 55 | expect(driver.determine_remote_host(machine_spec, instance)).to eq("public") 56 | end 57 | end 58 | 59 | context "when machine_spec.reference does not specify the transport type" do 60 | let(:reference) { {} } 61 | 62 | context "when the machine does not have a public_ip_address" do 63 | let(:instance) { double("instance", private_ip_address: "private", public_ip_address: nil) } 64 | 65 | it "returns the private ip" do 66 | expect(driver.determine_remote_host(machine_spec, instance)).to eq("private") 67 | end 68 | end 69 | 70 | context "when the machine has a public_ip_address" do 71 | let(:instance) { double("instance", private_ip_address: "private", public_ip_address: "public") } 72 | 73 | it "returns the public ip" do 74 | expect(driver.determine_remote_host(machine_spec, instance)).to eq("public") 75 | end 76 | end 77 | 78 | context "when the machine does not have a public_ip_address or private_ip_address" do 79 | let(:instance) { double("instance", private_ip_address: nil, public_ip_address: nil, id: "id") } 80 | 81 | it "raises an error" do 82 | expect { driver.determine_remote_host(machine_spec, instance) }.to raise_error("Server #{instance.id} has no private or public IP address!") 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /tools/purge_zone.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "json" 4 | require "pp" 5 | require "aws-sdk" 6 | 7 | # it happens that you end up with a HostedZone with no Chef Server entry, and destroying it manually is 8 | # tedious, because you have to delete all the ResourceRecordSets. this script will handle that for you. 9 | 10 | doomed_zones = ARGV 11 | doomed_zones.each do |doomed_zone| 12 | zones = JSON.parse(`aws route53 list-hosted-zones`)["HostedZones"] 13 | 14 | # requires an exact match, including the trailing dot. not such a bad thing, given the level of unrecoverable 15 | # deletion we're enabling. 16 | winner = zones.find { |z| z["Name"] == doomed_zone } 17 | 18 | if winner.nil? 19 | puts "Couldn't find zone '#{doomed_zone}'; candidates were #{zones.map { |z| z['Name'] }}" 20 | exit 21 | end 22 | 23 | zone_id = winner["Id"] 24 | 25 | rrsets = JSON.parse(`aws route53 list-resource-record-sets --hosted-zone-id=#{zone_id}`)["ResourceRecordSets"] 26 | 27 | rrsets.reject! { |rr| %w{SOA NS}.include?(rr["Type"]) } 28 | 29 | changes = rrsets.map do |rr| 30 | { 31 | action: "DELETE", 32 | resource_record_set: { 33 | name: rr["Name"], 34 | type: rr["Type"], 35 | ttl: rr["TTL"], 36 | resource_records: rr["ResourceRecords"].map { |o| { value: o["Value"] } } 37 | } 38 | } 39 | end 40 | 41 | req = { 42 | hosted_zone_id: winner["Id"], 43 | change_batch: { changes: changes } 44 | } 45 | 46 | client = Aws::Route53::Client.new 47 | client.change_resource_record_sets(req) unless rrsets.empty? 48 | client.delete_hosted_zone(id: zone_id) 49 | 50 | puts "Success! '#{doomed_zone}' deleted." 51 | end 52 | -------------------------------------------------------------------------------- /tools/travis_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "${TRAVIS_SECURE_ENV_VARS}" == "true" ]; then 3 | #export AWS_TEST_DRIVER=$AWS_TRAVIS_DRIVER 4 | echo "AWS_TEST_DRIVER set to ${AWS_TEST_DRIVER}" 5 | else 6 | unset AWS_TEST_DRIVER 7 | echo "Unset AWS_TEST_DRIVER" 8 | fi 9 | --------------------------------------------------------------------------------