├── documentation └── .gitkeep ├── .envrc ├── .gitattributes ├── .mdlrc ├── .rspec ├── .github ├── CODEOWNERS ├── lock.yml ├── workflows │ ├── md-links.yml │ ├── stale.yml │ └── ci.yml └── CONTRIBUTING.md ├── .foodcritic ├── test ├── integration │ ├── spec_helper.rb │ ├── default │ │ ├── acl_spec.rb │ │ └── default_spec.rb │ ├── client │ │ └── default_spec.rb │ ├── windows_client │ │ └── default_spec.rb │ └── windows │ │ └── windows_spec.rb └── fixtures │ ├── cookbooks │ └── consul_spec │ │ ├── metadata.rb │ │ └── recipes │ │ ├── default.rb │ │ ├── consul_watch.rb │ │ ├── consul_definition.rb │ │ └── consul_acl.rb │ ├── policies │ └── default.rb │ └── data_bags │ └── secrets │ └── consul.json ├── Policyfile.rb ├── CODE_OF_CONDUCT.md ├── spec ├── spec_helper.rb ├── libraries │ ├── consul_service_linux_spec.rb │ ├── consul_execute_spec.rb │ ├── consul_watch_spec.rb │ ├── consul_acl_spec.rb │ ├── consul_service_windows_spec.rb │ ├── consul_definition_spec.rb │ ├── consul_config_v0_spec.rb │ └── consul_config_v1_spec.rb └── recipes │ └── default_spec.rb ├── .vscode └── extensions.json ├── TESTING.md ├── Berksfile ├── .chef └── accepted_licenses │ ├── inspec │ ├── chef_infra_client │ └── chef_workstation ├── .circleci └── config.yml ├── CONTRIBUTING.md ├── TODO.md ├── .delivery └── project.toml ├── .yamllint ├── recipes ├── client_gem.rb └── default.rb ├── .editorconfig ├── .overcommit.yml ├── templates └── default │ ├── systemd.service.erb │ ├── upstart.service.erb │ └── sysvinit.service.erb ├── Gemfile ├── .gitignore ├── metadata.rb ├── libraries ├── consul_installation.rb ├── consul_execute.rb ├── helpers.rb ├── consul_installation_package.rb ├── consul_watch.rb ├── consul_service_windows.rb ├── consul_installation_git.rb ├── consul_definition.rb ├── consul_acl.rb ├── consul_service.rb ├── consul_role.rb ├── consul_policy.rb ├── consul_token.rb ├── consul_config_v0.rb ├── consul_config_v1.rb └── consul_installation_binary.rb ├── Dangerfile ├── attributes └── default.rb ├── chefignore ├── kitchen.yml ├── kitchen.dokken.yml ├── LICENSE └── README.md /documentation/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use chefworkstation 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | rules "~MD013", "~MD024" 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --default-path spec --color 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sous-chefs/consul 2 | -------------------------------------------------------------------------------- /.foodcritic: -------------------------------------------------------------------------------- 1 | ~FC019 2 | ~FC028 3 | ~FC044 4 | ~FC109 5 | ~FC121 6 | -------------------------------------------------------------------------------- /test/integration/spec_helper.rb: -------------------------------------------------------------------------------- 1 | def consul_version 2 | '1.0.7' 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/consul_spec/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'consul_spec' 2 | 3 | depends 'consul' 4 | -------------------------------------------------------------------------------- /Policyfile.rb: -------------------------------------------------------------------------------- 1 | name 'consul' 2 | default_source :community 3 | cookbook 'consul', path: '.' 4 | run_list 'consul::default' 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Guidelines 2 | 3 | This project follows the Chef Community Guidelines 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | # require 'poise_boiler/spec_helper' 4 | require_relative '../../libraries/helpers' 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "chef-software.chef", 4 | "rebornix.ruby", 5 | "editorconfig.editorconfig" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Please refer to [the community cookbook documentation on testing](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/TESTING.MD). 4 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | 3 | metadata 4 | 5 | group :integration do 6 | cookbook 'selinux' 7 | cookbook 'consul_spec', path: 'test/fixtures/cookbooks/consul_spec' 8 | end 9 | -------------------------------------------------------------------------------- /.chef/accepted_licenses/inspec: -------------------------------------------------------------------------------- 1 | --- 2 | id: inspec 3 | name: Chef InSpec 4 | date_accepted: '2019-08-08T00:05:08+01:00' 5 | accepting_product: chef-workstation 6 | accepting_product_version: 4.2.0 7 | user: jfield 8 | file_format: 1 9 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | orbs: 4 | kitchen: sous-chefs/kitchen@2 5 | workflows: 6 | danger: 7 | jobs: 8 | - kitchen/danger: 9 | name: danger 10 | context: Danger-Minimal 11 | -------------------------------------------------------------------------------- /.chef/accepted_licenses/chef_infra_client: -------------------------------------------------------------------------------- 1 | --- 2 | id: infra-client 3 | name: Chef Infra Client 4 | date_accepted: '2019-08-08T00:05:08+01:00' 5 | accepting_product: chef-workstation 6 | accepting_product_version: 4.2.0 7 | user: jfield 8 | file_format: 1 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please refer to 4 | [https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD) 5 | -------------------------------------------------------------------------------- /.chef/accepted_licenses/chef_workstation: -------------------------------------------------------------------------------- 1 | --- 2 | id: chef-workstation 3 | name: Chef Workstation 4 | date_accepted: '2019-08-08T00:05:08+01:00' 5 | accepting_product: chef-workstation 6 | accepting_product_version: 4.2.0 7 | user: jfield 8 | file_format: 1 9 | -------------------------------------------------------------------------------- /test/fixtures/policies/default.rb: -------------------------------------------------------------------------------- 1 | name 'default' 2 | default_source :supermarket 3 | default_source :chef_repo, '..' 4 | cookbook 'consul', path: '../../..' 5 | cookbook 'apt' 6 | run_list 'apt', 'consul_spec::default' 7 | named_run_list :client, 'consul::default' 8 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | --- 2 | daysUntilLock: 365 3 | exemptLabels: [] 4 | lockLabel: false 5 | lockComment: > 6 | This thread has been automatically locked since there has not been 7 | any recent activity after it was closed. Please open a new issue for 8 | related bugs. 9 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] Update cookbook to use the [12.5 custom resource][1] format instead of Poise helpers. 2 | - The config, definition, execute and watch resources. 3 | - The installation resources (binary, git, package) 4 | - [ ] Ensure cookbook is compatible with Chef 13 and 14. 5 | - 4.0 release 6 | -------------------------------------------------------------------------------- /.delivery/project.toml: -------------------------------------------------------------------------------- 1 | [local_phases] 2 | unit = "rspec spec/" 3 | lint = 'cookstyle --display-cop-names --extra-details' 4 | syntax = "echo skipping" 5 | provision = "echo skipping" 6 | deploy = "echo skipping" 7 | smoke = "echo skipping" 8 | functional = "echo skipping" 9 | cleanup = "echo skipping" 10 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | rules: 4 | line-length: 5 | max: 256 6 | level: warning 7 | document-start: disable 8 | braces: 9 | forbid: false 10 | min-spaces-inside: 0 11 | max-spaces-inside: 1 12 | min-spaces-inside-empty: -1 13 | max-spaces-inside-empty: -1 14 | -------------------------------------------------------------------------------- /recipes/client_gem.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | 8 | chef_gem node[cookbook_name]['diplomat_gem'] do 9 | version node['consul']['diplomat_version'] if node['consul']['diplomat_version'] 10 | compile_time true 11 | end 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root=true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # Avoid issues parsing cookbook files later 16 | charset = utf-8 17 | 18 | # Avoid cookstyle warnings 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | PreCommit: 3 | TrailingWhitespace: 4 | enabled: true 5 | YamlLint: 6 | enabled: true 7 | Rspec: 8 | enabled: true 9 | required_executable: 'rspec' 10 | Cookstyle: 11 | enabled: true 12 | required_executable: 'cookstyle' 13 | command: ["cookstyle"] 14 | Delivery: 15 | enabled: true 16 | required_executable: 'delivery' 17 | flags: ['local', 'all'] 18 | CommitMsg: 19 | HardTabs: 20 | enabled: true 21 | -------------------------------------------------------------------------------- /spec/libraries/consul_service_linux_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_service' 3 | 4 | # describe ConsulCookbook::Resource::ConsulService do 5 | # step_into(:consul_service) 6 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 7 | 8 | # context 'with default properties' do 9 | # recipe 'consul::default' 10 | 11 | # it { is_expected.to create_directory('/var/lib/consul') } 12 | # end 13 | # end 14 | -------------------------------------------------------------------------------- /.github/workflows/md-links.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: md-links 3 | 4 | "on": 5 | pull_request: 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | md-links: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v2 15 | - name: markdown-link-check 16 | uses: gaurav-nelson/github-action-markdown-link-check@v1 17 | with: 18 | use-verbose-mode: "yes" 19 | folder-path: "documentation" 20 | -------------------------------------------------------------------------------- /templates/default/systemd.service.erb: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=<%= @name %> 3 | Wants=network.target 4 | After=network.target 5 | 6 | [Service] 7 | Environment=<%= @environment.map {|key, val| %Q{"#{key}=#{val}"} }.join(' ') %> 8 | ExecStart=<%= @command %> 9 | ExecReload=/bin/kill -<%= @reload_signal %> $MAINPID 10 | KillSignal=<%= @stop_signal %> 11 | User=<%= @user %> 12 | WorkingDirectory=<%= @directory %> 13 | <%= "LimitNOFILE=#{node['consul']['service_nofile']}" if node['consul']['service_nofile'] %> 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/consul_spec/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # since Consul 0.9.0 enable_script_checks is required to allow scripts to run 2 | # https://www.consul.io/docs/agent/options.html#_enable_script_checks 3 | # this allows the consul_definition recipe to run correctly 4 | node.default['consul']['config']['enable_script_checks'] = true 5 | 6 | include_recipe 'consul::default' 7 | include_recipe 'consul_spec::consul_definition' 8 | include_recipe 'consul_spec::consul_watch' 9 | include_recipe 'consul_spec::consul_acl' unless node.platform_family?('windows') 10 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/consul_spec/recipes/consul_watch.rb: -------------------------------------------------------------------------------- 1 | # The ruby interpreter is guaranteed to exist since it's currently running. 2 | file '/consul_watch_handler.rb' do 3 | content <<-EOF.gsub(/^ */, '') 4 | /bin/sh -c 'echo "Consul watch handler invoked"' 5 | EOF 6 | unless node.platform?('windows') 7 | owner 'root' 8 | mode '0755' 9 | end 10 | end 11 | 12 | consul_watch 'consul_watch_check' do 13 | type 'event' 14 | user 'root' 15 | parameters(handler: '/consul_watch_handler.rb') 16 | notifies :reload, 'consul_service[consul]', :delayed 17 | end 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # This gemfile provides additional gems for testing and releasing this cookbook 2 | # It is meant to be installed on top of ChefDK which provides the majority 3 | # of the necessary gems for testing this cookbook 4 | # 5 | # Run 'chef exec bundle install' to install these dependencies 6 | 7 | source 'https://rubygems.org' 8 | 9 | gem 'diplomat', '~> 2.2.6' 10 | gem 'poise', '~> 2.2' 11 | gem 'poise-boiler' 12 | gem 'poise-service', '~> 1.0' 13 | gem 'rb-readline' 14 | 15 | group :development do 16 | gem 'github_changelog_generator', require: false 17 | gem 'stove', require: false 18 | gem 'webmock', '~> 3.1' 19 | end 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | .config 3 | InstalledFiles 4 | pkg 5 | test/tmp 6 | test/version_tmp 7 | tmp 8 | _Store 9 | *~ 10 | *# 11 | .#* 12 | \#*# 13 | *.un~ 14 | *.tmp 15 | *.bk 16 | *.bkup 17 | 18 | # editor files 19 | .idea 20 | .*.sw[a-z] 21 | 22 | # ruby/bundler/rspec files 23 | .ruby-version 24 | .ruby-gemset 25 | .rvmrc 26 | Gemfile.lock 27 | .bundle 28 | *.gem 29 | coverage 30 | spec/reports 31 | 32 | # YARD / rdoc artifacts 33 | .yardoc 34 | _yardoc 35 | doc/ 36 | rdoc 37 | 38 | # chef infra stuff 39 | Berksfile.lock 40 | .kitchen 41 | kitchen.local.yml 42 | vendor/ 43 | .coverage/ 44 | .zero-knife.rb 45 | Policyfile.lock.json 46 | 47 | # vagrant stuff 48 | .vagrant/ 49 | .vagrant.d/ 50 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/consul_spec/recipes/consul_definition.rb: -------------------------------------------------------------------------------- 1 | # The ruby interpreter is guaranteed to exist since it's currently running. 2 | file '/consul_definition_check.rb' do 3 | content <<-EOF.gsub(/^ */, '') 4 | /bin/sh -c 'echo "Consul check script invoked"' 5 | EOF 6 | unless node.platform?('windows') 7 | owner 'root' 8 | mode '0755' 9 | end 10 | end 11 | 12 | consul_definition 'consul_definition_check' do 13 | type 'check' 14 | user 'root' 15 | parameters(id: 'consul_definition_check', 16 | script: '/consul_definition_check.rb', 17 | interval: '10s', 18 | timeout: '10s') 19 | notifies :reload, 'consul_service[consul]', :delayed 20 | end 21 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'consul' 2 | maintainer 'Sous Chefs' 3 | maintainer_email 'help@sous-chefs.org' 4 | license 'Apache-2.0' 5 | description 'Application cookbook which installs and configures Consul.' 6 | source_url 'https://github.com/sous-chefs/consul' 7 | issues_url 'https://github.com/sous-chefs/consul/issues' 8 | chef_version '>= 13.4' 9 | version '4.5.1' 10 | 11 | supports 'centos', '>= 7.0' 12 | supports 'redhat', '>= 7.0' 13 | supports 'debian', '>= 9.0' 14 | supports 'ubuntu', '>= 16.04' 15 | supports 'windows' 16 | 17 | depends 'build-essential', '>= 5.0.0' # cookstyle: disable ChefModernize/UnnecessaryDependsChef14 18 | depends 'nssm', '>= 4.0.0' 19 | depends 'golang' 20 | depends 'poise', '~> 2.2' 21 | depends 'poise-archive', '~> 1.3' 22 | depends 'poise-service', '~> 1.4' 23 | -------------------------------------------------------------------------------- /libraries/consul_installation.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # A `consul_installation` resource which manages the Consul installation. 12 | # @action create 13 | # @action remove 14 | # @since 2.0 15 | class ConsulInstallation < Chef::Resource 16 | include Poise(inversion: true) 17 | provides(:consul_installation) 18 | default_action(:create) 19 | 20 | # @!attribute version 21 | # The version of Consul to install. 22 | # @return [String] 23 | attribute(:version, kind_of: String, name_attribute: true) 24 | 25 | def consul_program 26 | @program ||= provider_for_action(:consul_program).consul_program 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mark stale issues and pull requests 3 | 4 | "on": 5 | schedule: [cron: "0 0 * * *"] 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v3 12 | with: 13 | repo-token: ${{ secrets.GITHUB_TOKEN }} 14 | close-issue-message: > 15 | Closing due to inactivity. 16 | If this is still an issue please reopen or open another issue. 17 | Alternatively drop by the #sous-chefs channel on the [Chef Community Slack](http://community-slack.chef.io/) and we'll be happy to help! 18 | Thanks, Sous-Chefs. 19 | days-before-close: 7 20 | days-before-stale: 365 21 | stale-issue-message: > 22 | Marking stale due to inactivity. 23 | Remove stale label or comment or this will be closed in 7 days. 24 | Alternatively drop by the #sous-chefs channel on the [Chef Community Slack](http://community-slack.chef.io/) and we'll be happy to help! 25 | Thanks, Sous-Chefs. 26 | -------------------------------------------------------------------------------- /spec/libraries/consul_execute_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_execute' 3 | 4 | # describe ConsulCookbook::Resource::ConsulExecute do 5 | # step_into(:consul_execute) 6 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 7 | 8 | # context 'without options' do 9 | # recipe do 10 | # consul_execute 'uptime' 11 | # end 12 | # it { is_expected.to run_execute('/usr/bin/env consul exec uptime') } 13 | # end 14 | 15 | # context 'with verbose=true' do 16 | # recipe do 17 | # consul_execute 'uptime' do 18 | # options(verbose: true, service: 'web') 19 | # end 20 | # end 21 | # it { is_expected.to run_execute('/usr/bin/env consul exec -verbose -service=web uptime') } 22 | # end 23 | 24 | # context 'with verbose=false' do 25 | # recipe do 26 | # consul_execute 'uptime' do 27 | # options(verbose: false, service: 'web') 28 | # end 29 | # end 30 | # it { is_expected.to run_execute('/usr/bin/env consul exec -service=web uptime') } 31 | # end 32 | # end 33 | -------------------------------------------------------------------------------- /test/integration/default/acl_spec.rb: -------------------------------------------------------------------------------- 1 | describe http 'http://localhost:8500/v1/acl/info/anonymous' do 2 | its('status') { should eq 200 } 3 | its('body') { should match('"ID":"anonymous"') } 4 | its('body') { should match('"Name":"Anonymous Token"') } 5 | its('body') { should match('"Type":"client"') } 6 | its('body') { should include '"Rules":""' } 7 | end 8 | 9 | describe file '/tmp/anonymous-notified' do 10 | it { should_not exist } 11 | end 12 | 13 | describe http 'http://localhost:8500/v1/acl/info/management_token' do 14 | its('status') { should eq 200 } 15 | its('body') { should match('[]') } 16 | end 17 | 18 | # describe file('/tmp/management_token-notified') do 19 | # it { should exist } 20 | # end 21 | # 22 | # describe command('curl -s "http://localhost:8500/v1/acl/info/reader_token"') do 23 | # its(:exit_status) { should eq 0 } 24 | # its(:stdout) { should match('"ID":"reader_token"') } 25 | # its(:stdout) { should match('"Name":""') } 26 | # its(:stdout) { should match('"Type":"client"') } 27 | # its(:stdout) { should include '"Rules":"key \"dummykey\"' } 28 | # end 29 | -------------------------------------------------------------------------------- /spec/libraries/consul_watch_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_watch' 3 | 4 | # describe ConsulCookbook::Resource::ConsulWatch do 5 | # step_into(:consul_watch) 6 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 7 | # before do 8 | # default_attributes['consul'] = { 9 | # 'service_user' => 'consul', 10 | # 'service_group' => 'consul', 11 | # 'service' => { 12 | # 'config_dir' => '/etc/consul/conf.d', 13 | # }, 14 | # } 15 | # end 16 | 17 | # context 'key watch' do 18 | # recipe do 19 | # consul_watch 'foo' do 20 | # type 'key' 21 | # user 'root' 22 | # parameters(key: 'foo/bar/baz', handler: '/bin/false') 23 | # end 24 | # end 25 | 26 | # it { is_expected.to create_directory('/etc/consul/conf.d') } 27 | # it do 28 | # is_expected.to create_file('/etc/consul/conf.d/foo.json') 29 | # .with(user: 'root', group: 'consul', mode: '0640') 30 | # .with(content: JSON.pretty_generate( 31 | # watches: [ 32 | # { 33 | # type: 'key', 34 | # key: 'foo/bar/baz', 35 | # handler: '/bin/false', 36 | # }, 37 | # ] 38 | # )) 39 | # end 40 | # end 41 | # end 42 | -------------------------------------------------------------------------------- /libraries/consul_execute.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # Resource for providing `consul exec` functionality. 12 | # @since 1.0.0 13 | class ConsulExecute < Chef::Resource 14 | include Poise(fused: true) 15 | provides(:consul_execute) 16 | default_action(:run) 17 | 18 | attribute(:command, kind_of: String, name_attribute: true) 19 | attribute(:environment, kind_of: Hash, default: { 'PATH' => '/usr/local/bin:/usr/bin:/bin' }) 20 | attribute(:options, option_collector: true, default: {}) 21 | 22 | action(:run) do 23 | options = new_resource.options.map do |k, v| 24 | next if v.is_a?(NilClass) || v.is_a?(FalseClass) 25 | if v.is_a?(TrueClass) 26 | "-#{k}" 27 | else 28 | ["-#{k}", v].join('=') 29 | end 30 | end 31 | 32 | command = ['/usr/bin/env consul exec', 33 | options, 34 | new_resource.command].flatten.compact 35 | 36 | notifying_block do 37 | execute command.join(' ') do 38 | environment new_resource.environment 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | poise_service_user node['consul']['service_user'] do 8 | group node['consul']['service_group'] 9 | shell node['consul']['service_shell'] unless node['consul']['service_shell'].nil? 10 | not_if { node.platform_family?('windows') } 11 | not_if { node['consul']['service_user'] == 'root' } 12 | not_if { node['consul']['create_service_user'] == false } 13 | end 14 | 15 | service_name = node['consul']['service_name'] 16 | config = consul_config service_name do |r| 17 | node['consul']['config'].each_pair { |k, v| r.send(k, v) } 18 | notifies :reload, "consul_service[#{service_name}]", :delayed 19 | end 20 | 21 | install = consul_installation node['consul']['version'] do |r| 22 | if node['consul']['installation'] 23 | node['consul']['installation'].each_pair { |k, v| r.send(k, v) } 24 | end 25 | end 26 | 27 | consul_service service_name do |r| 28 | config_file config.path 29 | program install.consul_program 30 | 31 | if node.platform_family?('windows') 32 | acl_token node['consul']['config']['acl_master_token'] 33 | else 34 | user node['consul']['service_user'] 35 | group node['consul']['service_group'] 36 | end 37 | if node['consul']['service'] 38 | node['consul']['service'].each_pair { |k, v| r.send(k, v) } 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/consul_spec/recipes/consul_acl.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'consul::client_gem' 2 | 3 | package 'curl' 4 | 5 | consul_acl 'anonymous' do 6 | acl_name 'Anonymous Token' 7 | type 'client' 8 | url "http://localhost:#{node['consul']['config']['ports']['http']}" 9 | auth_token node['consul']['config']['acl_master_token'] 10 | notifies :create, 'file[/tmp/anonymous-notified]', :immediately 11 | end 12 | 13 | file '/tmp/anonymous-notified' do 14 | action :nothing 15 | end 16 | 17 | consul_acl 'management_token' do 18 | acl_name 'Management Token' 19 | type 'management' 20 | auth_token node['consul']['config']['acl_master_token'] 21 | notifies :create, 'file[/tmp/management_token-notified]', :immediately 22 | end 23 | 24 | file '/tmp/management_token-notified' do 25 | action :nothing 26 | end 27 | 28 | consul_acl 'delete_management_token' do 29 | id 'management_token' 30 | auth_token node['consul']['config']['acl_master_token'] 31 | action :delete 32 | end 33 | 34 | consul_acl 'non_existing_token' do 35 | auth_token node['consul']['config']['acl_master_token'] 36 | action :delete 37 | end 38 | 39 | consul_acl 'reader_token' do 40 | type 'client' 41 | rules <<-EOS.gsub(/^\s{4}/, '') 42 | key "dummykey" { 43 | policy = "read" 44 | } 45 | service "dummyservice" { 46 | policy = "write" 47 | } 48 | EOS 49 | auth_token node['consul']['config']['acl_master_token'] 50 | end 51 | -------------------------------------------------------------------------------- /test/integration/client/default_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | consul_executable = "/opt/consul/#{consul_version}/consul" 4 | config_file = '/etc/consul/consul.json' 5 | config_dir = '/etc/consul' 6 | 7 | describe file(consul_executable) do 8 | it { should be_file } 9 | it { should be_executable } 10 | end 11 | 12 | describe group('consul') do 13 | it { should exist } 14 | end 15 | 16 | describe user('consul') do 17 | it { should exist } 18 | its('group') { should eq 'consul' } 19 | end 20 | 21 | describe command("su - consul -c 'echo successfully logged in'") do 22 | its(:stdout) { should_not include 'successfully logged in' } 23 | its(:exit_status) { should_not eq 0 } 24 | end 25 | 26 | describe service('consul') do 27 | it { should be_enabled } 28 | it { should be_running } 29 | end 30 | 31 | [8500, 8600].each do |p| 32 | describe port(p) do 33 | it { should be_listening } 34 | end 35 | end 36 | 37 | describe command("#{consul_executable} members -detailed -token=doublesecret") do 38 | its(:exit_status) { should eq 0 } 39 | its(:stdout) { should include 'alive' } 40 | its(:stdout) { should include 'role=node' } 41 | end 42 | 43 | describe file('/usr/local/bin/consul') do 44 | it { should be_symlink } 45 | end 46 | 47 | describe file(config_file) do 48 | it { should be_file } 49 | its('mode') { should cmp '0640' } 50 | end 51 | 52 | describe file(config_dir) do 53 | it { should be_directory } 54 | its('mode') { should cmp '0755' } 55 | end 56 | -------------------------------------------------------------------------------- /spec/libraries/consul_acl_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require 'webmock/rspec' 3 | # require 'diplomat' 4 | # require_relative '../../libraries/consul_acl' 5 | 6 | # describe ConsulCookbook::Resource::ConsulAcl do 7 | # step_into(:consul_acl) 8 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 9 | 10 | # context 'when the Consul agent is not ready' do 11 | # let(:info_body) do 12 | # [ 13 | # { 14 | # 'CreateIndex' => 3, 15 | # 'ModifyIndex' => 3, 16 | # 'ID' => 'client_token', 17 | # 'Name' => 'Client Token', 18 | # 'Type' => 'client', 19 | # 'Rules' => { 20 | # 'service' => { 21 | # '' => { 22 | # 'policy' => 'write', 23 | # }, 24 | # }, 25 | # }, 26 | # }, 27 | # ] 28 | # end 29 | # before do 30 | # json = JSON.generate(info_body.first) 31 | # stub_request(:get, %r{/v1/acl/info/}) 32 | # .to_return(body: 'No cluster leader', status: 500).then 33 | # .to_return(body: json, status: 200) 34 | # stub_request(:put, %r{/v1/acl/create}) 35 | # .to_return(body: json, status: 200) 36 | # end 37 | 38 | # recipe do 39 | # consul_acl 'client_token' do 40 | # acl_name 'Client Token' 41 | # type 'client' 42 | # auth_token 'ABC123' 43 | # end 44 | # end 45 | 46 | # it do 47 | # expect { chef_run }.to_not raise_error 48 | # end 49 | # end 50 | # end 51 | -------------------------------------------------------------------------------- /test/integration/windows_client/default_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | consul_root = 'C:\Program Files\consul' 4 | 5 | consul_executable = File.join(consul_root, consul_version, 'consul.exe') 6 | symlink_path = File.join(consul_root, 'consul.exe') 7 | consul_command = "& '#{consul_executable}'" 8 | 9 | config_file = 'C:\Program Files\consul\consul.json' 10 | config_dir = 'C:\Program Files\consul\conf.d' 11 | 12 | describe file(consul_executable) do 13 | it { should be_file } 14 | end 15 | 16 | describe service('consul') do 17 | it { should be_enabled } 18 | it { should be_running } 19 | end 20 | 21 | [8500, 8600].each do |p| 22 | describe port(p) do 23 | it { should be_listening } 24 | end 25 | end 26 | 27 | describe file(symlink_path) do 28 | it { should be_file } 29 | end 30 | # Because we can't use be_symlink for Windows 31 | describe command("(Get-Item \"#{symlink_path}\").Attributes -match 'ReparsePoint'") do 32 | its(:exit_status) { should eq 0 } 33 | its('stdout.strip') { should eq 'True' } 34 | end 35 | 36 | # Commented out, because reloading the path doesn't get picked up on the first run 37 | # describe os_env('PATH') do 38 | # its('split') { should include consul_root } 39 | # end 40 | 41 | describe command("#{consul_command} members -detailed -token=doublesecret") do 42 | its(:exit_status) { should eq 0 } 43 | its(:stdout) { should include 'alive' } 44 | its(:stdout) { should include 'role=node' } 45 | end 46 | 47 | describe file(config_file) do 48 | it { should be_file } 49 | end 50 | 51 | describe file(config_dir) do 52 | it { should be_directory } 53 | end 54 | -------------------------------------------------------------------------------- /templates/default/upstart.service.erb: -------------------------------------------------------------------------------- 1 | # <%= @name %> generated by poise-service for <%= @new_resource.to_s %> 2 | 3 | description "<%= @name %>" 4 | 5 | start on runlevel [2345] 6 | stop on runlevel [!2345] 7 | 8 | <%= "limit nofile #{node['consul']['service_nofile']}" if node['consul']['service_nofile'] %> 9 | 10 | respawn 11 | respawn limit 10 5 12 | umask 022 13 | chdir <%= @directory %> 14 | <%- @environment.each do |key, val| -%> 15 | env <%= key %>="<%= val %>" 16 | <%- end -%> 17 | <%- if @upstart_features[:setuid] -%> 18 | setuid <%= @user %> 19 | <%- end -%> 20 | <%- if @upstart_features[:kill_signal] -%> 21 | kill signal <%= @stop_signal %> 22 | <%- end -%> 23 | <%- if @upstart_features[:reload_signal] -%> 24 | reload signal <%= @reload_signal %> 25 | <%- end -%> 26 | 27 | <%- if @upstart_features[:setuid] -%> 28 | exec <%= @command %> 29 | <%- else -%> 30 | script 31 | exec /opt/chef/embedded/bin/ruby <) 34 | if Process.euid != ent.uid || Process.egid != ent.gid 35 | Process.initgroups(ent.name, ent.gid) 36 | Process::GID.change_privilege(ent.gid) if Process.egid != ent.gid 37 | Process::UID.change_privilege(ent.uid) if Process.euid != ent.uid 38 | end 39 | ENV["HOME"] = Dir.home(<%= @user.inspect %>) rescue nil 40 | exec(*<%= Shellwords.split(@command).inspect %>) 41 | EOH 42 | end script 43 | <%- end -%> 44 | <%- if !@upstart_features[:kill_signal] && @stop_signal != 'TERM' -%> 45 | pre-stop script 46 | PID=`initctl status <%= @name %> | sed 's/^.*process \([0-9]*\)$/\1/'` 47 | if [ -n "$PID" ]; then 48 | kill -<%= @stop_signal %> "$PID" 49 | fi 50 | end script 51 | <%- end -%> 52 | -------------------------------------------------------------------------------- /libraries/helpers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | 8 | module ConsulCookbook 9 | module Helpers 10 | include Chef::Mixin::ShellOut 11 | 12 | extend self 13 | 14 | def arch_64? 15 | node['kernel']['machine'] =~ /x86_64/ ? true : false 16 | end 17 | 18 | def windows? 19 | platform_family?('windows') ? true : false 20 | end 21 | 22 | # returns windows friendly version of the provided path, 23 | # ensures backslashes are used everywhere 24 | # Gently plucked from https://github.com/chef-cookbooks/windows 25 | def win_friendly_path(path) 26 | path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR || '\\') if path 27 | end 28 | 29 | # Simply using ::File.join was causing several attributes 30 | # to return strange values in the resources (e.g. "C:/Program Files/\\consul\\data") 31 | def join_path(*path) 32 | windows? ? win_friendly_path(::File.join(path)) : ::File.join(path) 33 | end 34 | 35 | def program_files 36 | join_path('C:', 'Program Files') + (arch_64? ? '' : ' x(86)') 37 | end 38 | 39 | def config_prefix_path 40 | windows? ? join_path(program_files, 'consul') : join_path('/etc', 'consul') 41 | end 42 | 43 | def data_path 44 | windows? ? join_path(program_files, 'consul', 'data') : join_path('/var/lib', 'consul') 45 | end 46 | 47 | def command(config_file, config_dir) 48 | if windows? 49 | %(agent -config-file="#{config_file}" -config-dir="#{config_dir}") 50 | else 51 | "/usr/local/bin/consul agent -config-file=#{config_file} -config-dir=#{config_dir}" 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Reference: http://danger.systems/reference.html 2 | 3 | # A pull request summary is required. Add a description of the pull request purpose. 4 | # Changelog must be updated for each pull request that changes code. 5 | # Warnings will be issued for: 6 | # Pull request with more than 400 lines of code changed 7 | # Pull reqest that change more than 5 lines without test changes 8 | # Failures will be issued for: 9 | # Pull request without summary 10 | # Pull requests with code changes without changelog entry 11 | 12 | def code_changes? 13 | code = %w(libraries attributes recipes resources files templates) 14 | code.each do |location| 15 | return true unless git.modified_files.grep(/#{location}/).empty? 16 | end 17 | false 18 | end 19 | 20 | def test_changes? 21 | tests = %w(spec test kitchen.yml kitchen.dokken.yml) 22 | tests.each do |location| 23 | return true unless git.modified_files.grep(/#{location}/).empty? 24 | end 25 | false 26 | end 27 | 28 | failure 'Please provide a summary of your Pull Request.' if github.pr_body.length < 10 29 | 30 | warn 'This is a big Pull Request.' if git.lines_of_code > 400 31 | 32 | warn 'This is a Table Flip.' if git.lines_of_code > 2000 33 | 34 | # Require a CHANGELOG entry for non-test changes. 35 | if !git.modified_files.include?('CHANGELOG.md') && code_changes? 36 | failure 'Please include a CHANGELOG entry.' 37 | end 38 | 39 | # Require Major Minor Patch version labels 40 | unless github.pr_labels.grep /minor|major|patch/i 41 | warn 'Please add a release label to this pull request' 42 | end 43 | 44 | # A sanity check for tests. 45 | if git.lines_of_code > 5 && code_changes? && !test_changes? 46 | warn 'This Pull Request is probably missing tests.' 47 | end 48 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | 8 | extend ConsulCookbook::Helpers 9 | 10 | default['consul']['service_name'] = 'consul' 11 | default['consul']['service_user'] = 'consul' 12 | default['consul']['service_group'] = 'consul' 13 | # To set the open file limit for the service. If unset system default is used 14 | default['consul']['service_nofile'] = nil 15 | default['consul']['create_service_user'] = true 16 | 17 | default['consul']['config']['path'] = join_path config_prefix_path, 'consul.json' 18 | default['consul']['config']['data_dir'] = data_path 19 | default['consul']['config']['ca_file'] = join_path config_prefix_path, 'ssl', 'CA', 'ca.crt' 20 | default['consul']['config']['cert_file'] = join_path config_prefix_path, 'ssl', 'certs', 'consul.crt' 21 | default['consul']['config']['key_file'] = join_path config_prefix_path, 'ssl', 'private', 'consul.key' 22 | 23 | default['consul']['config']['client_addr'] = '0.0.0.0' 24 | default['consul']['config']['ports'] = { 25 | 'dns' => 8600, 26 | 'http' => 8500, 27 | 'serf_lan' => 8301, 28 | 'serf_wan' => 8302, 29 | 'server' => 8300, 30 | } 31 | 32 | default['consul']['diplomat_gem'] = 'diplomat' 33 | default['consul']['diplomat_version'] = nil 34 | 35 | default['consul']['service']['config_dir'] = join_path config_prefix_path, 'conf.d' 36 | 37 | default['consul']['version'] = '1.0.7' 38 | 39 | # Windows only 40 | default['consul']['service']['nssm_params'] = { 41 | 'AppDirectory' => data_path, 42 | 'AppStdout' => join_path(config_prefix_path, 'stdout.log'), 43 | 'AppStderr' => join_path(config_prefix_path, 'error.log'), 44 | 'AppRotateFiles' => 1, 45 | 'AppRotateOnline' => 1, 46 | 'AppRotateBytes' => 20_000_000, 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | 4 | "on": 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | delivery: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v2 16 | - name: Run Chef Delivery 17 | uses: actionshub/chef-delivery@main 18 | env: 19 | CHEF_LICENSE: accept-no-persist 20 | 21 | yamllint: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Check out code 25 | uses: actions/checkout@v2 26 | - name: Run yaml Lint 27 | uses: actionshub/yamllint@main 28 | 29 | mdl: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Check out code 33 | uses: actions/checkout@v2 34 | - name: Run Markdown Lint 35 | uses: actionshub/markdownlint@main 36 | 37 | integration: 38 | needs: [mdl, yamllint, delivery] 39 | runs-on: ubuntu-latest 40 | strategy: 41 | matrix: 42 | os: 43 | - 'centos-7' 44 | - 'centos-8' 45 | - 'debian-9' 46 | - 'debian-10' 47 | - 'ubuntu-1604' 48 | - 'ubuntu-1804' 49 | - 'ubuntu-2004' 50 | - 'amazonlinux-2' 51 | suite: 52 | - 'client' 53 | - 'default' 54 | # - 'git' 55 | fail-fast: false 56 | 57 | steps: 58 | - name: Check out code 59 | uses: actions/checkout@v2 60 | - name: Install Chef 61 | uses: actionshub/chef-install@main 62 | - name: Dokken 63 | uses: actionshub/test-kitchen@main 64 | env: 65 | CHEF_LICENSE: accept-no-persist 66 | KITCHEN_LOCAL_YAML: kitchen.dokken.yml 67 | with: 68 | suite: ${{ matrix.suite }} 69 | os: ${{ matrix.os }} 70 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # to a Chef Infra Server or Supermarket. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | ehthumbs.db 9 | Icon? 10 | nohup.out 11 | Thumbs.db 12 | .envrc 13 | 14 | # EDITORS # 15 | ########### 16 | .#* 17 | .project 18 | .settings 19 | *_flymake 20 | *_flymake.* 21 | *.bak 22 | *.sw[a-z] 23 | *.tmproj 24 | *~ 25 | \#* 26 | REVISION 27 | TAGS* 28 | tmtags 29 | .vscode 30 | .editorconfig 31 | 32 | ## COMPILED ## 33 | ############## 34 | *.class 35 | *.com 36 | *.dll 37 | *.exe 38 | *.o 39 | *.pyc 40 | *.so 41 | */rdoc/ 42 | a.out 43 | mkmf.log 44 | 45 | # Testing # 46 | ########### 47 | .circleci/* 48 | .codeclimate.yml 49 | .delivery/* 50 | .foodcritic 51 | .kitchen* 52 | .mdlrc 53 | .overcommit.yml 54 | .rspec 55 | .rubocop.yml 56 | .travis.yml 57 | .watchr 58 | .yamllint 59 | azure-pipelines.yml 60 | Dangerfile 61 | examples/* 62 | features/* 63 | Guardfile 64 | kitchen.yml* 65 | mlc_config.json 66 | Procfile 67 | Rakefile 68 | spec/* 69 | test/* 70 | 71 | # SCM # 72 | ####### 73 | .git 74 | .gitattributes 75 | .gitconfig 76 | .github/* 77 | .gitignore 78 | .gitkeep 79 | .gitmodules 80 | .svn 81 | */.bzr/* 82 | */.git 83 | */.hg/* 84 | */.svn/* 85 | 86 | # Berkshelf # 87 | ############# 88 | Berksfile 89 | Berksfile.lock 90 | cookbooks/* 91 | tmp 92 | 93 | # Bundler # 94 | ########### 95 | vendor/* 96 | Gemfile 97 | Gemfile.lock 98 | 99 | # Policyfile # 100 | ############## 101 | Policyfile.rb 102 | Policyfile.lock.json 103 | 104 | # Documentation # 105 | ############# 106 | CODE_OF_CONDUCT* 107 | CONTRIBUTING* 108 | documentation/* 109 | TESTING* 110 | UPGRADING* 111 | 112 | # Vagrant # 113 | ########### 114 | .vagrant 115 | Vagrantfile 116 | -------------------------------------------------------------------------------- /kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | 5 | provisioner: 6 | name: chef_zero 7 | product_name: chef 8 | enforce_idempotency: true 9 | multiple_converge: 2 10 | deprecations_as_errors: true 11 | 12 | verifier: 13 | name: inspec 14 | 15 | platforms: 16 | - name: amazonlinux-2 17 | driver_config: 18 | box: ywatase/amzn2 19 | - name: ubuntu-16.04 20 | - name: ubuntu-18.04 21 | - name: ubuntu-20.04 22 | - name: centos-7 23 | - name: centos-8 24 | - name: debian-9 25 | - name: debian-10 26 | - name: windows-2012r2 27 | driver: 28 | box: mwrock/Windows2012R2 29 | gui: false 30 | provisioner: 31 | product_version: 13 32 | - name: windows-2016 33 | driver: 34 | box: mwrock/Windows2016 35 | gui: false 36 | provisioner: 37 | product_version: 13 38 | 39 | suites: 40 | - name: default 41 | attributes: 42 | consul: 43 | config: &default-config 44 | owner: root 45 | ui: true 46 | bootstrap: true 47 | server: true 48 | datacenter: FortMeade 49 | encrypt: CGXC2NsXW4AvuB4h5ODYzQ== 50 | acl_master_token: doublesecret 51 | acl_datacenter: FortMeade 52 | acl_default_policy: deny 53 | service_nofile: 9001 54 | excludes: 55 | - windows-2012r2 56 | - windows-2016 57 | 58 | - name: client 59 | excludes: 60 | - windows-2012r2 61 | - windows-2016 62 | 63 | - name: git 64 | attributes: 65 | go: 66 | version: '1.10' 67 | consul: 68 | config: *default-config 69 | installation: 70 | provider: git 71 | excludes: 72 | - windows-2012r2 73 | - windows-2016 74 | 75 | - name: windows 76 | attributes: 77 | consul: 78 | config: *default-config 79 | includes: 80 | - windows-2012r2 81 | - windows-2016 82 | 83 | - name: windows_client 84 | named_run_list: client 85 | includes: 86 | - windows-2012r2 87 | - windows-2016 88 | -------------------------------------------------------------------------------- /libraries/consul_installation_package.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Provider 11 | # A `consul_installation` provider which installs Consul from 12 | # package source. 13 | # @action create 14 | # @action remove 15 | # @provides consul_installation 16 | # @example 17 | # consul_installation '0.5.0' do 18 | # provider 'package' 19 | # end 20 | # @since 2.0 21 | class ConsulInstallationPackage < Chef::Provider 22 | include Poise(inversion: :consul_installation) 23 | provides(:package) 24 | inversion_attribute 'consul' 25 | 26 | # The built-in resource "package" has it's own attribute named "options", 27 | # so we should use an alias to access Poise inversion options. 28 | alias res_options options 29 | 30 | # Set the default inversion options. 31 | # @return [Hash] 32 | # @api private 33 | def self.default_inversion_options(node, resource) 34 | super.merge( 35 | version: resource.version, 36 | package_name: 'consul' 37 | ) 38 | end 39 | 40 | action :create do 41 | notifying_block do 42 | package res_options[:package_name] do 43 | source res_options[:package_source] 44 | provider res_options[:package_provider] 45 | version res_options[:version] 46 | action :upgrade 47 | end 48 | end 49 | end 50 | 51 | action :remove do 52 | notifying_block do 53 | package res_options[:package_name] do 54 | source res_options[:package_source] 55 | provider res_options[:package_provider] 56 | version res_options[:version] 57 | action :remove 58 | end 59 | end 60 | end 61 | 62 | def consul_program 63 | options.fetch(:program, '/usr/local/bin/consul') 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/recipes/default_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | 3 | # describe 'consul::default' do 4 | # let(:service_user) {} 5 | # let(:create_service_user) { true } 6 | # let(:platform) { { platform: 'ubuntu', version: '14.04' } } 7 | 8 | # let(:chef_run) do 9 | # runner = ChefSpec::SoloRunner.new(platform) do |node| 10 | # node.normal['consul']['service_user'] = service_user if service_user 11 | # node.normal['consul']['create_service_user'] = create_service_user 12 | # end 13 | # runner.converge(described_recipe) 14 | # end 15 | 16 | # context 'with default service_user' do 17 | # it 'creates the user without a login shell' do 18 | # expect(chef_run).to create_poise_service_user('consul') 19 | # end 20 | # end 21 | 22 | # context 'with johnny5 service_user' do 23 | # let(:service_user) { 'johnny5' } 24 | 25 | # it 'creates the requested user' do 26 | # expect(chef_run).to create_poise_service_user('johnny5') 27 | # end 28 | # it 'does not try to create the default user' do 29 | # expect(chef_run).to_not create_poise_service_user('consul') 30 | # end 31 | # end 32 | 33 | # context 'with root service_user' do 34 | # let(:service_user) { 'root' } 35 | 36 | # it 'does not try to create the root user' do 37 | # expect(chef_run).to_not create_poise_service_user('root') 38 | # end 39 | # it 'does not try to create the default user' do 40 | # expect(chef_run).to_not create_poise_service_user('consul') 41 | # end 42 | # end 43 | 44 | # context 'with create_service_user disabled' do 45 | # let(:create_service_user) { false } 46 | 47 | # it 'does not try to create the user' do 48 | # expect(chef_run).to_not create_poise_service_user('consul') 49 | # end 50 | # end 51 | 52 | # context 'on Windows' do 53 | # let(:platform) { { platform: 'windows', version: '2012R2' } } 54 | 55 | # it 'does not try to create the user' do 56 | # expect(chef_run).to_not create_poise_service_user('consul') 57 | # end 58 | # end 59 | # end 60 | -------------------------------------------------------------------------------- /test/integration/windows/windows_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | consul_root = 'C:\Program Files\consul' 4 | 5 | consul_executable = File.join(consul_root, consul_version, 'consul.exe') 6 | symlink_path = File.join(consul_root, 'consul.exe') 7 | consul_command = "& '#{consul_executable}'" 8 | 9 | config_file = 'C:\Program Files\consul\consul.json' 10 | confd_dir = 'C:\Program Files\consul\conf.d' 11 | data_dir = 'C:\Program Files\consul\data' 12 | 13 | describe file(consul_executable) do 14 | it { should be_file } 15 | end 16 | 17 | describe service('consul') do 18 | it { should be_enabled } 19 | it { should be_running } 20 | end 21 | 22 | [8300, 8500, 8600].each do |p| 23 | describe port(p) do 24 | it { should be_listening } 25 | end 26 | end 27 | 28 | describe command("#{consul_command} members -detailed -token=doublesecret") do 29 | its(:exit_status) { should eq 0 } 30 | its(:stdout) { should include 'alive' } 31 | its(:stdout) { should include 'role=consul' } 32 | its(:stdout) { should include 'bootstrap=1' } 33 | its(:stdout) { should include 'dc=fortmeade' } 34 | end 35 | 36 | describe file(symlink_path) do 37 | it { should be_file } 38 | end 39 | # Because we can't use be_symlink for Windows 40 | describe command("(Get-Item \"#{symlink_path}\").Attributes -match 'ReparsePoint'") do 41 | its(:exit_status) { should eq 0 } 42 | its('stdout.strip') { should eq 'True' } 43 | end 44 | 45 | # Commented out, because reloading the path doesn't get picked up on the first run 46 | # describe os_env('PATH') do 47 | # its('split') { should include consul_root } 48 | # end 49 | 50 | describe file(config_file) do 51 | it { should be_file } 52 | end 53 | 54 | describe directory(confd_dir) do 55 | it { should be_directory } 56 | end 57 | 58 | describe directory(data_dir) do 59 | it { should be_directory } 60 | end 61 | 62 | describe file("#{confd_dir}\\consul_definition_check.json") do 63 | it { should be_file } 64 | end 65 | 66 | describe file("#{confd_dir}\\consul_watch_check.json") do 67 | it { should be_file } 68 | end 69 | -------------------------------------------------------------------------------- /libraries/consul_watch.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # @since 1.0.0 12 | class ConsulWatch < Chef::Resource 13 | include Poise(fused: true) 14 | include ConsulCookbook::Helpers 15 | provides(:consul_watch) 16 | default_action(:create) 17 | 18 | # @!attribute path 19 | # @return [String] 20 | attribute(:path, kind_of: String, default: lazy { join_path node['consul']['service']['config_dir'], "#{name}.json" }) 21 | 22 | # @!attribute user 23 | # @return [String] 24 | attribute(:user, kind_of: String, default: lazy { node['consul']['service_user'] }) 25 | 26 | # @!attribute group 27 | # @return [String] 28 | attribute(:group, kind_of: String, default: lazy { node['consul']['service_group'] }) 29 | 30 | # @!attribute type 31 | # @return [String] 32 | attribute(:type, equal_to: %w(checks event key keyprefix nodes service services)) 33 | 34 | # @!attribute parameters 35 | # @return [Hash] 36 | attribute(:parameters, option_collector: true, default: {}) 37 | 38 | def params_to_json 39 | JSON.pretty_generate(watches: [{ type: type }.merge(parameters)]) 40 | end 41 | 42 | action(:create) do 43 | notifying_block do 44 | directory ::File.dirname(new_resource.path) do 45 | recursive true 46 | unless platform?('windows') 47 | owner new_resource.user 48 | group new_resource.group 49 | mode '0755' 50 | end 51 | end 52 | 53 | file new_resource.path do 54 | content new_resource.params_to_json 55 | unless platform?('windows') 56 | owner new_resource.user 57 | group new_resource.group 58 | mode '0640' 59 | end 60 | end 61 | end 62 | end 63 | 64 | action(:delete) do 65 | notifying_block do 66 | file new_resource.path do 67 | action :delete 68 | end 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /libraries/consul_service_windows.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | require_relative 'helpers' 9 | 10 | module ConsulCookbook 11 | module Provider 12 | # Provider for managing the Consul service on a Windows instance. 13 | # @since 1.0.0 14 | class ConsulServiceWindows < Chef::Provider 15 | include Poise 16 | provides(:consul_service, os: %w(windows)) 17 | include ConsulCookbook::Helpers 18 | 19 | def application_path 20 | { 'Application' => new_resource.program } 21 | end 22 | 23 | def action_enable 24 | notifying_block do 25 | directories = %W(#{new_resource.data_dir} 26 | #{new_resource.config_dir} 27 | #{::File.dirname(new_resource.nssm_params['AppStdout'])} 28 | #{::File.dirname(new_resource.nssm_params['AppStderr'])}).uniq.compact 29 | directories.delete_if { |i| i.eql? '.' }.each do |dirname| 30 | directory dirname do 31 | recursive true 32 | end 33 | end 34 | 35 | nssm 'consul' do 36 | extend ConsulCookbook::Helpers 37 | 38 | program new_resource.program 39 | args command(new_resource.config_file, new_resource.config_dir) 40 | parameters new_resource.nssm_params.select { |_k, v| v != '' } 41 | action :install 42 | end 43 | end 44 | end 45 | 46 | def action_reload 47 | notifying_block do 48 | execute 'Reload consul' do 49 | command 'consul.exe reload' + (new_resource.acl_token ? " -token=#{new_resource.acl_token}" : '') 50 | cwd ::File.dirname(new_resource.program) 51 | action :run 52 | end 53 | end 54 | end 55 | 56 | def action_restart 57 | notifying_block do 58 | powershell_script 'Restart consul' do 59 | code 'restart-service consul' 60 | end 61 | end 62 | end 63 | 64 | def action_disable 65 | notifying_block do 66 | nssm 'consul' do 67 | action %i(stop remove) 68 | end 69 | 70 | file new_resource.config_file do 71 | action :delete 72 | end 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /libraries/consul_installation_git.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Provider 11 | # A `consul_installation` provider which manages Consul installation 12 | # from the Go source builds. 13 | # @action create 14 | # @action remove 15 | # @provides consul_installation 16 | # @example 17 | # consul_installation '0.5.0' do 18 | # provider 'git' 19 | # end 20 | # @since 2.0 21 | class ConsulInstallationGit < Chef::Provider 22 | include Poise(inversion: :consul_installation) 23 | provides(:git) 24 | inversion_attribute('consul') 25 | 26 | # Set the default inversion options. 27 | # @return [Hash] 28 | # @api private 29 | def self.default_inversion_options(node, resource) 30 | super.merge( 31 | version: resource.version, 32 | git_url: 'https://github.com/hashicorp/consul', 33 | git_path: '/usr/local/go/src/github.com/hashicorp/consul' 34 | ) 35 | end 36 | 37 | action :create do 38 | notifying_block do 39 | include_recipe 'golang::default' 40 | build_essential 'build-essential' 41 | golang_package 'github.com/mitchellh/gox' 42 | golang_package 'github.com/tools/godep' 43 | directory options[:git_path] do 44 | recursive true 45 | end 46 | 47 | package 'zip' do 48 | action :install 49 | end 50 | 51 | git options[:git_path] do 52 | repository options[:git_url] 53 | reference options.fetch(:git_ref, "v#{new_resource.version}") 54 | action :checkout 55 | end 56 | 57 | execute 'make' do 58 | cwd options[:git_path] 59 | environment(PATH: "#{node['go']['install_dir']}/go/bin:#{node['go']['gobin']}:/usr/bin:/bin", 60 | GOPATH: node['go']['gopath']) 61 | end 62 | end 63 | end 64 | 65 | action :remove do 66 | notifying_block do 67 | directory options[:git_path] do 68 | recursive true 69 | action :delete 70 | end 71 | end 72 | end 73 | 74 | def consul_program 75 | ::File.join(options[:git_path], 'bin', 'consul') 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /kitchen.dokken.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: dokken 4 | privileged: true 5 | chef_version: <%= ENV['CHEF_VERSION'] || 'current' %> 6 | env: [CHEF_LICENSE=accept] 7 | 8 | transport: 9 | name: dokken 10 | 11 | provisioner: 12 | name: dokken 13 | 14 | platforms: 15 | - name: centos-7 16 | driver: 17 | image: dokken/centos-7 18 | pid_one_command: /usr/lib/systemd/systemd 19 | attributes: 20 | poise-service: 21 | consul: 22 | provider: systemd 23 | 24 | - name: centos-8 25 | driver: 26 | image: dokken/centos-8 27 | pid_one_command: /usr/lib/systemd/systemd 28 | attributes: 29 | poise-service: 30 | consul: 31 | provider: systemd 32 | 33 | - name: ubuntu-16.04 34 | driver: 35 | image: dokken/ubuntu-16.04 36 | pid_one_command: /bin/systemd 37 | intermediate_instructions: 38 | - RUN /usr/bin/apt-get update 39 | attributes: 40 | poise-service: 41 | consul: 42 | provider: systemd 43 | 44 | - name: ubuntu-18.04 45 | driver: 46 | image: dokken/ubuntu-18.04 47 | pid_one_command: /bin/systemd 48 | intermediate_instructions: 49 | - RUN /usr/bin/apt-get update 50 | attributes: 51 | poise-service: 52 | consul: 53 | provider: systemd 54 | 55 | - name: ubuntu-20.04 56 | driver: 57 | image: dokken/ubuntu-20.04 58 | pid_one_command: /bin/systemd 59 | intermediate_instructions: 60 | - RUN /usr/bin/apt-get update 61 | attributes: 62 | poise-service: 63 | consul: 64 | provider: systemd 65 | 66 | - name: debian-9 67 | driver: 68 | image: dokken/debian-9 69 | pid_one_command: /bin/systemd 70 | intermediate_instructions: 71 | - RUN /usr/bin/apt-get update 72 | attributes: 73 | poise-service: 74 | consul: 75 | provider: systemd 76 | 77 | - name: debian-10 78 | driver: 79 | image: dokken/debian-10 80 | pid_one_command: /bin/systemd 81 | intermediate_instructions: 82 | - RUN /usr/bin/apt-get update 83 | attributes: 84 | poise-service: 85 | consul: 86 | provider: systemd 87 | 88 | - name: amazonlinux-2 89 | driver: 90 | image: dokken/amazonlinux-2 91 | pid_one_command: /usr/lib/systemd/systemd 92 | attributes: 93 | poise-service: 94 | consul: 95 | provider: systemd 96 | -------------------------------------------------------------------------------- /test/integration/default/default_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | consul_executable = "/opt/consul/#{consul_version}/consul" 4 | symlink_path = '/usr/local/bin/consul' 5 | consul_command = symlink_path 6 | 7 | config_file = '/etc/consul/consul.json' 8 | confd_dir = '/etc/consul/conf.d' 9 | data_dir = '/var/lib/consul' 10 | 11 | describe file(consul_executable) do 12 | it { should be_file } 13 | it { should be_executable } 14 | end 15 | 16 | describe group('consul') do 17 | it { should exist } 18 | end 19 | 20 | describe user('consul') do 21 | it { should exist } 22 | its('group') { should eq 'consul' } 23 | end 24 | 25 | describe command("su - consul -c 'echo successfully logged in'") do 26 | its(:stdout) { should_not include 'successfully logged in' } 27 | its(:exit_status) { should_not eq 0 } 28 | end 29 | 30 | describe service('consul') do 31 | it { should be_enabled } 32 | it { should be_running } 33 | end 34 | 35 | [8300, 8500, 8600].each do |p| 36 | describe port(p) do 37 | it { should be_listening } 38 | end 39 | end 40 | 41 | describe command("#{consul_command} members -detailed -token=doublesecret") do 42 | its(:exit_status) { should eq 0 } 43 | its(:stdout) { should include 'alive' } 44 | its(:stdout) { should include 'role=consul' } 45 | its(:stdout) { should include 'bootstrap=1' } 46 | its(:stdout) { should include 'dc=fortmeade' } 47 | end 48 | 49 | describe file(symlink_path) do 50 | it { should be_symlink } 51 | end 52 | 53 | describe file(config_file) do 54 | it { should be_file } 55 | it { should be_owned_by 'root' } 56 | it { should be_grouped_into 'consul' } 57 | its('mode') { should cmp '0640' } 58 | end 59 | 60 | describe file(confd_dir) do 61 | it { should be_directory } 62 | it { should be_owned_by 'root' } 63 | it { should be_grouped_into 'consul' } 64 | its('mode') { should cmp '0755' } 65 | end 66 | 67 | describe directory(data_dir) do 68 | it { should be_directory } 69 | it { should be_owned_by 'consul' } 70 | it { should be_grouped_into 'consul' } 71 | its('mode') { should cmp '0750' } 72 | end 73 | 74 | describe command('grep files /proc/$(pgrep consul)/limits') do 75 | its(:stdout) { should include '9001' } 76 | end 77 | 78 | # describe file("#{confd_dir}/consul_definition_check.json") do 79 | # it { should be_file } 80 | # it { should be_owned_by 'root' } 81 | # it { should be_grouped_into 'consul' } 82 | # its('mode') { should cmp '0644' } 83 | # end 84 | # 85 | # describe file("#{confd_dir}/consul_watch_check.json") do 86 | # it { should be_file } 87 | # it { should be_owned_by 'root' } 88 | # it { should be_grouped_into 'consul' } 89 | # its('mode') { should cmp '0640' } 90 | # end 91 | -------------------------------------------------------------------------------- /libraries/consul_definition.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # @since 1.0.0 12 | class ConsulDefinition < Chef::Resource 13 | include Poise(fused: true) 14 | include ConsulCookbook::Helpers 15 | provides(:consul_definition) 16 | default_action(:create) 17 | 18 | # @!attribute path 19 | # @return [String] 20 | attribute(:path, kind_of: String, default: lazy { join_path node['consul']['service']['config_dir'], "#{name}.json" }) 21 | 22 | # @!attribute user 23 | # @return [String] 24 | attribute(:user, kind_of: String, default: lazy { node['consul']['service_user'] }) 25 | 26 | # @!attribute group 27 | # @return [String] 28 | attribute(:group, kind_of: String, default: lazy { node['consul']['service_group'] }) 29 | 30 | # @!attribute mode 31 | # @return [String] 32 | attribute(:mode, kind_of: String, default: '0640') 33 | 34 | # @!attribute type 35 | # @return [String] 36 | attribute(:type, equal_to: %w(check service checks services)) 37 | 38 | # @!attribute parameters 39 | # @return [Hash] 40 | attribute(:parameters, option_collector: true, default: {}) 41 | 42 | def params_to_json 43 | final_parameters = parameters 44 | final_parameters = final_parameters.merge(name: name) if %w(check service).include?(type) && final_parameters[:name].nil? 45 | JSON.pretty_generate(type => final_parameters) 46 | end 47 | 48 | action(:create) do 49 | notifying_block do 50 | directory ::File.dirname(new_resource.path) do 51 | recursive true 52 | unless platform?('windows') 53 | owner new_resource.user 54 | group new_resource.group 55 | mode '0755' 56 | # Prevent clobbering permissions on the directory since the intent 57 | # in this context is to set the permissions of the definition file 58 | not_if { Dir.exist? path } 59 | end 60 | end 61 | 62 | file new_resource.path do 63 | content new_resource.params_to_json 64 | unless platform?('windows') 65 | owner new_resource.user 66 | group new_resource.group 67 | mode new_resource.mode 68 | end 69 | end 70 | end 71 | end 72 | 73 | action(:delete) do 74 | notifying_block do 75 | file new_resource.path do 76 | action :delete 77 | end 78 | end 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/libraries/consul_service_windows_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_service' 3 | # require_relative '../../libraries/consul_service_windows' 4 | 5 | # describe ConsulCookbook::Resource::ConsulService do 6 | # step_into(:consul_service) 7 | # let(:chefspec_options) { { platform: 'windows', version: '2012R2' } } 8 | # let(:shellout) { double('shellout') } 9 | 10 | # context 'with default properties' do 11 | # before do 12 | # allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) 13 | # allow(shellout).to receive(:live_stream) 14 | # allow(shellout).to receive(:live_stream=) 15 | # allow(shellout).to receive(:error!) 16 | # allow(shellout).to receive(:stderr) 17 | # allow(shellout).to receive(:run_command) 18 | # allow(shellout).to receive(:exitstatus) 19 | # allow(shellout).to receive(:stdout).and_return("Consul v1.0.7\nProtocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)\n") 20 | 21 | # # Stub admin_user method since we are testing a Windows host via Linux 22 | # # Fixed in https://github.com/poise/poise/commit/2f42850c82e295af279d060155bcd5c7ebb31d6a but not released yet 23 | # allow(Poise::Utils::Win32).to receive(:admin_user).and_return('Administrator') 24 | # end 25 | 26 | # recipe 'consul::default' 27 | 28 | # it do 29 | # is_expected.to create_directory('C:\Program Files\consul\conf.d') 30 | # end 31 | 32 | # it do 33 | # is_expected.to create_directory('C:\Program Files\consul\data') 34 | # end 35 | 36 | # it do 37 | # expect(chef_run).to install_nssm('consul').with( 38 | # program: 'C:\Program Files\consul\1.0.7\consul.exe', 39 | # args: 'agent -config-file="C:\Program Files\consul\consul.json" -config-dir="C:\Program Files\consul\conf.d"' 40 | # ) 41 | # end 42 | # end 43 | 44 | # describe 'reload' do 45 | # before do 46 | # default_attributes['consul'] = { 47 | # 'config' => config, 48 | # } 49 | # end 50 | 51 | # recipe do 52 | # consul_service 'consul' do 53 | # action :reload 54 | # end 55 | # end 56 | 57 | # context 'with no ACL token' do 58 | # let(:config) { {} } 59 | # it do 60 | # is_expected.to run_execute('Reload consul').with( 61 | # command: 'consul.exe reload' 62 | # ) 63 | # end 64 | # end 65 | 66 | # context 'with an ACL token' do 67 | # let(:config) { { 'acl_master_token' => 'my_token' } } 68 | # it do 69 | # is_expected.to run_execute('Reload consul').with( 70 | # command: 'consul.exe reload -token=my_token' 71 | # ) 72 | # end 73 | # end 74 | # end 75 | # end 76 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Bloomberg's Chef Cookbooks 2 | First off, thanks for taking the time to contribute! 3 | 4 | The following is a set of guidelines for contributing to Bloomberg's 5 | Chef cookbooks. These are guidelines, not rules, so please use your 6 | best judgment and feel free to propose changes to this document in a 7 | _separate_ pull-request from one which contains code. 8 | 9 | ## Table of Contents 10 | 11 | - [What should I know before I get started?](#what-should-i-know-before-i-get-started) 12 | - [Code of Conduct](#code-of-conduct) 13 | - [The Apache License](#the-apache-license) 14 | - [Quick Contributing Steps](#quick-contributing-steps) 15 | - [How Can I Contribute?](#how-can-i-contribute) 16 | - [Contribution Process](#contribution-process) 17 | - [Reporting Bugs](#reporting-bugs) 18 | - [Suggesting Enhancements](#suggesting-enhancements) 19 | - [Your First Code Contribution](#your-first-code-contribution) 20 | - [Pull Requests](#pull-requests) 21 | - [Release Cycle](#release-cycle) 22 | - [Best Practices](#best-practices) 23 | - [Git Commit Messages](#git-commit-messages) 24 | - [Ruby Style Guide](#ruby-style-guide) 25 | - [Chef Style Guide](#chef-style-guide) 26 | - [Additional Notes](#additional-notes) 27 | - [Issue and Pull Request Labels](#issue-and-pull-request-labels) 28 | 29 | ## What should I know before I get started? 30 | 31 | ### Code of Conduct 32 | This project adheres to Contributor Covenant [code of conduct][0]. By 33 | participating, you are expected to uphold this code. Please report 34 | unacceptable behavior immediately. 35 | 36 | ### The Apache License 37 | Bloomberg believes that licensing is very important to open source 38 | projects. It helps ensure that software continues to be available 39 | under the terms that the authors desired. Bloomberg uses the 40 | [Apache 2.0 license][1] for its cookbooks. 41 | 42 | This license strikes a balance between open contribution and allowing 43 | you to use software however you like. The license tells you what 44 | rights you have that are provided by the copyright holder. 45 | 46 | ### Quick Contributing Steps 47 | 48 | 1. Create an account on [GitHub](https://github.com). 49 | 1. Fork the repository. 50 | 1. Create a named feature branch (i.e. `add-new-provider`). 51 | 1. Write your changes, unit and integration tests (if applicable). 52 | 1. Run the tests, ensuring that they are all passing. 53 | 1. Submit a Pull Request. 54 | 55 | ## How Can I Contribute? 56 | 57 | ### Contribution Process 58 | 59 | ### Reporting Bugs 60 | 61 | ### Suggesting Enhancements 62 | 63 | ### Your First Code Contribution 64 | 65 | ### Pull Requests 66 | 67 | ### Release Cycle 68 | The scheme for versions of Bloomberg cookbooks is X.Y.Z, where: 69 | 70 | - X is a major release, which may not be fully compatible with prior 71 | major releases 72 | - Y is a minor release, which adds both new features and bug fixes 73 | - Z is a patch release, which adds just bug fixes 74 | 75 | For more information, take a look at the Chef community 76 | [cookbook versioning policy][2]. 77 | 78 | ## Best Practices 79 | 80 | ### Git Commit Messages 81 | 82 | ### Ruby Style Guide 83 | 84 | ### Chef Style Guide 85 | 86 | ## Additional Notes 87 | 88 | ### Issue and Pull Request Labels 89 | 90 | [0]: http://contributor-covenant.org/version/1/4/ 91 | [1]: https://www.apache.org/licenses/LICENSE-2.0 92 | [2]: https://chef-community.github.io/cvp/ 93 | -------------------------------------------------------------------------------- /templates/default/sysvinit.service.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # <%= @name %> - this script manages the consul agent 4 | # 5 | # chkconfig: 345 99 70 6 | # description: this script manages the consul agent 7 | # 8 | ### BEGIN INIT INFO 9 | # Provides: <%= @name %> 10 | # Required-Start: $local_fs $network 11 | # Required-Stop: $local_fs $network 12 | # Default-Start: 2 3 4 5 13 | # Default-Stop: 0 1 6 14 | # Short-Description: Manage the consul agent 15 | ### END INIT INFO 16 | 17 | <%= "limit nofile #{node['consul']['service_nofile']}" if node['consul']['service_nofile'] %> 18 | 19 | prog="<%= File.basename(@daemon) %>" 20 | user="<%= @user %>" 21 | exec="<%= @daemon %>" 22 | pidfile="<%= @pid_file %>" 23 | logfile="/var/log/$prog.log" 24 | lockfile="/var/lock/subsys/$prog" 25 | 26 | <%- @environment.each do |key, val| -%> 27 | export <%= key %>="<%= val %>" 28 | <%- end -%> 29 | export PATH="${PATH:+$PATH:}/usr/sbin:/sbin" 30 | 31 | <%- if @platform_family == 'debian' -%> 32 | . /lib/lsb/init-functions 33 | 34 | _start() { 35 | touch $logfile 36 | chown $user $logfile 37 | echo "Starting consul" 38 | start-stop-daemon --start --quiet --background \ 39 | --pidfile $pidfile<% unless @pid_file_external %> --make-pidfile<% end %> \ 40 | --chuid $user --chdir "<%= @directory %>" \ 41 | --startas /bin/bash -- -c "exec $exec <%= @daemon_options %> >> $logfile 2>&1" 42 | } 43 | 44 | _stop() { 45 | start-stop-daemon --stop --quiet --pidfile $pidfile --user $user --retry="<%= @stop_signal %>"/30/KILL/5 46 | } 47 | 48 | _status() { 49 | status_of_proc -p $pidfile $exec $prog 50 | } 51 | 52 | _reload() { 53 | start-stop-daemon --stop --quiet --pidfile $pidfile --user $user --signal "<%= @reload_signal %>" 54 | } 55 | 56 | <%- else -%> 57 | . /etc/rc.d/init.d/functions 58 | 59 | _start() { 60 | [ -x $exec ] || exit 5 61 | 62 | umask 077 63 | touch $pidfile $logfile 64 | chown $user $pidfile $logfile 65 | 66 | echo -n $"Starting <%= @name %>: " 67 | daemon \ 68 | --pidfile=$pidfile \ 69 | --user=$user \ 70 | " { $exec <%= @daemon_options %> >> $logfile 2>&1 & } ; echo \$! >| $pidfile " 71 | RETVAL=$? 72 | echo 73 | [ $RETVAL -eq 0 ] && touch $lockfile 74 | return $RETVAL 75 | } 76 | 77 | _stop() { 78 | echo -n $"Stopping <%= @name %>: " 79 | killproc -p $pidfile $exec -<%= @stop_signal %> 80 | RETVAL=$? 81 | echo 82 | [ $RETVAL -eq 0 ] && rm -f $lockfile $pidfile 83 | return $RETVAL 84 | } 85 | 86 | _status() { 87 | status -p $pidfile -l $prog $exec 88 | } 89 | 90 | _reload() { 91 | echo -n $"Reloading <%= @name %>: " 92 | killproc -p $pidfile $exec -<%= @reload_signal %> 93 | echo 94 | } 95 | <%- end -%> 96 | 97 | _restart() { 98 | _stop 99 | while : 100 | do 101 | ss -pl | fgrep "((\"$prog\"," > /dev/null 102 | [ $? -ne 0 ] && break 103 | sleep 0.1 104 | done 105 | _start 106 | } 107 | 108 | _status_q() { 109 | _status >/dev/null 2>&1 110 | } 111 | 112 | case "$1" in 113 | start) 114 | _status_q && exit 0 115 | _start 116 | ;; 117 | stop) 118 | _status_q || exit 0 119 | _stop 120 | ;; 121 | restart|force-reload) 122 | _restart 123 | ;; 124 | reload) 125 | _status_q || exit 7 126 | _reload 127 | ;; 128 | status) 129 | _status 130 | ;; 131 | condrestart|try-restart) 132 | _status_q || exit 0 133 | _restart 134 | ;; 135 | *) 136 | echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" 137 | exit 2 138 | esac 139 | 140 | exit $? 141 | -------------------------------------------------------------------------------- /test/fixtures/data_bags/secrets/consul.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "consul", 3 | "certificate": "-----BEGIN CERTIFICATE-----\nMIIDjzCCAnegAwIBAgIBCjANBgkqhkiG9w0BAQUFADB8MQswCQYDVQQGEwJVUzER\nMA8GA1UECBMITmV3IFlvcmsxFjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxHzAdBgNV\nBAoTFkJsb29tYmVyZyBGaW5hbmNlIEwuUC4xITAfBgNVBAsUGFImRCBQbGF0Zm9y\nbSBFbmdpbmVlcmluZzAeFw0xNTA2MTUxMTM1MDhaFw0yNTA2MTIxMTM1MDhaMIGi\nMRUwEwYDVQQDEwxKb2huIEJlbGxvbmUxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD\nVQQGEwJVUzElMCMGCSqGSIb3DQEJARYWamJlbGxvbmVAYmxvb21iZXJnLm5ldDEf\nMB0GA1UEChMWQmxvb21iZXJnIEZpbmFuY2UgTC5QLjEhMB8GA1UECxQYUiZEIFBs\nYXRmb3JtIEVuZ2luZWVyaW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO\nJTDt2WDTCKeB2ubzvBbE5ziCQZwGj1T4/uzlXlrybQ9q5M+oQDD44WRzSANx3Vx+\nI6ao57z6QtA5qOG9cw3csFgsMn6lVLzoegm2wQ7UhvqfcD2SOOWHBBsXp8VmX+ay\nOXpKRT5UOdLx3AczoUTua7aVsXDk/FJ9D4NV8NOF5wIDAQABo3kwdzAJBgNVHRME\nAjAAMB0GA1UdDgQWBBRo/0cnJ6coZaWuE+M7oqSsZ52vBjAfBgNVHSMEGDAWgBS5\nzSJ2wMBbmqDs6IlOdrUynXj8gTALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB\nBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQCzqVLJ+v+yzvA0RFif\nf6j4DMj8MT/bxyaaX6SwYoC4ILIZjJ9Pn/1LwxZUfOkpEXwOQnT4G1epvuJIhXL7\nkILMoewMxseY6laHW4vCo3w4UJsKKlSnGinSkUun+CtI2CpPaIZ4bRnKsYGdqzeN\nShbn7xVc+oVS4tQA72T4g7WltVGTM+At6EKhKCvbEUiZ3OznFHEmvxYzrGZGii18\nyamF6DtSIS4a6E1G5zDGr2ba4THcHBuV9JsZeQhg9UlGdyA30n9zz5M8QJpUr34Z\nfRGmyGq+fIwpH639F/I2sLNIcZKsPbjfVojK+4e6d4t4rQiqo1jv9YZUvgvDIiu8\nWatv\n-----END CERTIFICATE-----\n", 4 | "ca_certificate": "-----BEGIN CERTIFICATE-----\nMIIEXjCCA0agAwIBAgIJAJ4alkWMxGdeMA0GCSqGSIb3DQEBBQUAMHwxCzAJBgNV\nBAYTAlVTMREwDwYDVQQIEwhOZXcgWW9yazEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0\neTEfMB0GA1UEChMWQmxvb21iZXJnIEZpbmFuY2UgTC5QLjEhMB8GA1UECxQYUiZE\nIFBsYXRmb3JtIEVuZ2luZWVyaW5nMB4XDTE1MDYxNTExMzEyMVoXDTI1MDYxMjEx\nMzEyMVowfDELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMRYwFAYDVQQH\nEw1OZXcgWW9yayBDaXR5MR8wHQYDVQQKExZCbG9vbWJlcmcgRmluYW5jZSBMLlAu\nMSEwHwYDVQQLFBhSJkQgUGxhdGZvcm0gRW5naW5lZXJpbmcwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDnEeVEYXozw1TPU15hyGN89eD07MOLHF4eAU7n\neZXNNHNYuuX4Ub2nbpAbekO7Muq2s9I3x+CzGfMSkKSxSh8aT+S1Fu9xxA91VE8L\nMIFZTy+O4CFWYzYaRXI+hJ5Hf0JF/pHPyfZbYX2gSUIWmxjBDnpUNeCfr/LrWRpn\nXL8GoIeKrINXGwJJas7ZLuW/TmaOIlwFD+mRl6hAlyYbekozSnCLL8PfE+F5iVK2\nRbra1z3WFiHNvMnBRaZIiUdci0JRmccdc3IEM301ZJjD0PbXpoW+4r/bAiJxT3Gw\n6X2rjES6du/MnlApr3yUuGhF46Eb5RrTRZSRRWDiqOqWNSy/AgMBAAGjgeIwgd8w\nHQYDVR0OBBYEFLnNInbAwFuaoOzoiU52tTKdePyBMIGvBgNVHSMEgacwgaSAFLnN\nInbAwFuaoOzoiU52tTKdePyBoYGApH4wfDELMAkGA1UEBhMCVVMxETAPBgNVBAgT\nCE5ldyBZb3JrMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MR8wHQYDVQQKExZCbG9v\nbWJlcmcgRmluYW5jZSBMLlAuMSEwHwYDVQQLFBhSJkQgUGxhdGZvcm0gRW5naW5l\nZXJpbmeCCQCeGpZFjMRnXjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IB\nAQBGyM0eOlHYyGFyYs/mPZv+UMifwITnUqnDBpbMZmoWUFBQMqAbc/XRbrBH5bpm\nXuEoV4PqEnkzSsMZN2bHulyor2wrgh5AoO1rEW6QghGlMG0dbwcqtA0D+JPzb13c\nw3TLD6LOo8O+1lymNe18nhdye3xxN+IYzxwAvQwszbupFqQ1f8EiNcTgxTfiVr56\n84ca5/3YRtV12CiFy0RICjw062UeJ03ki15HNp0bLWXnZFcK+MqL7sH9/ZoKBfKq\n3WXH3r45LJlMpNxZJYIAODMKe8WGj8Y61cfyYEed3wszEYqe7u6L+2Hee5XWDSi4\n91WN5l6KnBpBpYOERH1BfEie\n-----END CERTIFICATE-----\n", 5 | "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDOJTDt2WDTCKeB2ubzvBbE5ziCQZwGj1T4/uzlXlrybQ9q5M+o\nQDD44WRzSANx3Vx+I6ao57z6QtA5qOG9cw3csFgsMn6lVLzoegm2wQ7UhvqfcD2S\nOOWHBBsXp8VmX+ayOXpKRT5UOdLx3AczoUTua7aVsXDk/FJ9D4NV8NOF5wIDAQAB\nAoGBAKmI+KaD4hdsxKYM62eERo2FQ3oMj07tzgpBTX6NjOpXOxjEOOu8bwogA8az\ncPHSBWFP3J6Ih2iiTjE9bPmrh7d/feEArjNUAL0ntcM3VFDX3IcZis8ZjgVqQG1J\nwvpn2o1qP37FJE1JSVdzN0MvWzFzgmhewxLqJcnFbUJysbaBAkEA8oW5EEd5beZO\nZ7V71C6EfOQw3HeCo34M5ndhI7rIa4MeLnLMVYBYodIezNdWHsFV7lqSkJamrKH9\nfLzjrsvMYQJBANmZ8d9QmfYJfBlJqZQziUqmEXQ8a7A0zW8WnFWgQrpLojs+eEMm\nx0Xb1IdyQT3Irl9+cE8lVoAiF8RxWd+FN0cCQQCR2IE2nQUVZk74Z1eUfnUGdmQ7\n8VMK5x7y6g/s4MLuhOd9n2Pqd0jV5/rFzSnpTPNUZ/uEIFUTtEcw4Jc74yuBAkBh\n15KmMvvHYWRninOxq6qj4iAe/7v8MwHcXXJWHgVi9vcvZFt29kzL4JijfoBPY5jk\nX1nofIV0f9/n+H/MvX2pAkBtiZ0gtW35QOmWdhqnOqpToOcNdzw6ON8d2bAx2mA1\nQm5P2pmsxEBJ3cSElRvOK+36OMWr0uMjUz5IogI6Fas7\n-----END RSA PRIVATE KEY-----\n" 6 | } 7 | -------------------------------------------------------------------------------- /libraries/consul_acl.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # Resource for managing Consul ACLs. 12 | class ConsulAcl < Chef::Resource 13 | include Poise 14 | provides(:consul_acl) 15 | default_action(:create) 16 | 17 | # @!attribute url 18 | # @return [String] 19 | attribute(:url, kind_of: String, default: 'http://localhost:8500') 20 | 21 | # @!attribute auth_token 22 | # @return [String] 23 | attribute(:auth_token, kind_of: String, required: true) 24 | 25 | # @!attribute id 26 | # @return [String] 27 | attribute(:id, kind_of: String, name_attribute: true) 28 | 29 | # @!attribute acl_name 30 | # @return [String] 31 | attribute(:acl_name, kind_of: String, default: '') 32 | 33 | # @!attribute type 34 | # @return [String] 35 | attribute(:type, equal_to: %w(client management), default: 'client') 36 | 37 | # @!attribute rules 38 | # @return [String] 39 | attribute(:rules, kind_of: String, default: '') 40 | 41 | # @!attribute ssl 42 | # @return [Hash] 43 | attribute(:ssl, kind_of: Hash, default: {}) 44 | 45 | def to_acl 46 | { 'ID' => id, 'Type' => type, 'Name' => acl_name, 'Rules' => rules } 47 | end 48 | end 49 | end 50 | 51 | module Provider 52 | # Provider for managing Consul ACLs. 53 | class ConsulAcl < Chef::Provider 54 | include Poise 55 | provides(:consul_acl) 56 | 57 | def action_create 58 | configure_diplomat 59 | unless up_to_date? 60 | converge_by 'creating ACL' do 61 | Diplomat::Acl.create(new_resource.to_acl) 62 | end 63 | end 64 | end 65 | 66 | def action_delete 67 | configure_diplomat 68 | unless Diplomat::Acl.info(new_resource.id).empty? 69 | converge_by 'destroying ACL' do 70 | Diplomat::Acl.destroy(new_resource.id) 71 | end 72 | end 73 | end 74 | 75 | protected 76 | 77 | def configure_diplomat 78 | begin 79 | require 'diplomat' 80 | rescue LoadError => e 81 | raise 'The diplomat gem is required; ' \ 82 | "include recipe[consul::client_gem] to install, details: #{e}" 83 | end 84 | Diplomat.configure do |config| 85 | config.url = new_resource.url 86 | config.acl_token = new_resource.auth_token 87 | config.options = { ssl: new_resource.ssl, request: { timeout: 10 } } 88 | end 89 | end 90 | 91 | def up_to_date? 92 | retry_block(max_tries: 3, sleep: 0.5) do 93 | old_acl = Diplomat::Acl.info(new_resource.to_acl['ID'], {}, :return) 94 | return false if old_acl.nil? || old_acl.empty? 95 | old_acl.first.select! { |k, _v| %w(ID Type Name Rules).include?(k) } 96 | old_acl.first == new_resource.to_acl 97 | end 98 | end 99 | 100 | def retry_block(opts = {}, &_block) 101 | opts = { 102 | max_tries: 3, # Number of tries 103 | sleep: 0, # Seconds to sleep between tries 104 | }.merge(opts) 105 | 106 | try_count = 1 107 | 108 | begin 109 | yield try_count 110 | rescue Diplomat::UnknownStatus 111 | try_count += 1 112 | 113 | # If we've maxed out our attempts, raise the exception to the calling code 114 | raise if try_count > opts[:max_tries] 115 | 116 | # Sleep before the next retry if the option was given 117 | sleep opts[:sleep] 118 | 119 | retry 120 | end 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /spec/libraries/consul_definition_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_definition' 3 | 4 | # describe ConsulCookbook::Resource::ConsulDefinition do 5 | # step_into(:consul_definition) 6 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 7 | # before do 8 | # default_attributes['consul'] = { 9 | # 'service_user' => 'consul', 10 | # 'service_group' => 'consul', 11 | # 'service' => { 12 | # 'config_dir' => '/etc/consul/conf.d', 13 | # }, 14 | # } 15 | # end 16 | 17 | # context 'service definition' do 18 | # recipe do 19 | # consul_definition 'redis' do 20 | # type 'service' 21 | # user 'root' 22 | # parameters(tags: %w(master), address: '127.0.0.1', port: 6379, interval: '10s') 23 | # end 24 | # end 25 | 26 | # it { is_expected.to create_directory('/etc/consul/conf.d') } 27 | # it do 28 | # is_expected.to create_file('/etc/consul/conf.d/redis.json') 29 | # .with(user: 'root', group: 'consul', mode: '0640') 30 | # .with(content: JSON.pretty_generate( 31 | # service: { 32 | # tags: ['master'], 33 | # address: '127.0.0.1', 34 | # port: 6379, 35 | # interval: '10s', 36 | # name: 'redis', 37 | # } 38 | # )) 39 | # end 40 | # end 41 | 42 | # context 'service definition with explicit name' do 43 | # recipe do 44 | # consul_definition 'redis' do 45 | # type 'service' 46 | # user 'root' 47 | # parameters(name: 'myredis', tags: %w(master), address: '127.0.0.1', port: 6379, interval: '10s') 48 | # end 49 | # end 50 | 51 | # it { is_expected.to create_directory('/etc/consul/conf.d') } 52 | # it do 53 | # is_expected.to create_file('/etc/consul/conf.d/redis.json') 54 | # .with(user: 'root', group: 'consul', mode: '0640') 55 | # .with(content: JSON.pretty_generate( 56 | # service: { 57 | # name: 'myredis', 58 | # tags: ['master'], 59 | # address: '127.0.0.1', 60 | # port: 6379, 61 | # interval: '10s', 62 | # } 63 | # )) 64 | # end 65 | # end 66 | 67 | # context 'service definition with owner and group from node config' do 68 | # before do 69 | # default_attributes['consul'] = { 70 | # 'service_user' => 'root', 71 | # 'service_group' => 'root', 72 | # 'service' => { 73 | # 'config_dir' => '/etc/consul/conf.d', 74 | # }, 75 | # } 76 | # end 77 | 78 | # recipe do 79 | # consul_definition 'redis' do 80 | # type 'service' 81 | # parameters(name: 'myredis', tags: %w(master), address: '127.0.0.1', port: 6379, interval: '10s') 82 | # end 83 | # end 84 | 85 | # it { is_expected.to create_directory('/etc/consul/conf.d') } 86 | # it do 87 | # is_expected.to create_file('/etc/consul/conf.d/redis.json') 88 | # .with(user: 'root', group: 'root', mode: '0640') 89 | # .with(content: JSON.pretty_generate( 90 | # service: { 91 | # name: 'myredis', 92 | # tags: ['master'], 93 | # address: '127.0.0.1', 94 | # port: 6379, 95 | # interval: '10s', 96 | # } 97 | # )) 98 | # end 99 | # end 100 | 101 | # context 'check definition' do 102 | # recipe do 103 | # consul_definition 'web-api' do 104 | # type 'check' 105 | # user 'root' 106 | # parameters(http: 'http://localhost:5000/health', ttl: '30s') 107 | # end 108 | # end 109 | 110 | # it { is_expected.to create_directory('/etc/consul/conf.d') } 111 | # it do 112 | # is_expected.to create_file('/etc/consul/conf.d/web-api.json') 113 | # .with(user: 'root', group: 'consul', mode: '0640') 114 | # .with(content: JSON.pretty_generate( 115 | # check: { 116 | # http: 'http://localhost:5000/health', 117 | # ttl: '30s', 118 | # name: 'web-api', 119 | # } 120 | # )) 121 | # end 122 | # end 123 | # end 124 | -------------------------------------------------------------------------------- /libraries/consul_service.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise_service/service_mixin' 8 | require_relative 'helpers' 9 | 10 | module ConsulCookbook 11 | module Resource 12 | # A `consul_service` resource for use with `poise_service`. This 13 | # resource manages the Consul service. 14 | # @since 1.0 15 | class ConsulService < Chef::Resource 16 | include Poise 17 | provides(:consul_service) 18 | include PoiseService::ServiceMixin 19 | include ConsulCookbook::Helpers 20 | 21 | # @!attribute config_file 22 | # @return [String] 23 | attribute(:config_file, kind_of: String, default: lazy { node['consul']['config']['path'] }) 24 | # @!attribute user 25 | # The service user the Consul process runs as. 26 | # @return [String] 27 | attribute(:user, kind_of: String, default: lazy { node['consul']['service_user'] }) 28 | # @!attribute group 29 | # The service group the Consul process runs as. 30 | # @return [String] 31 | attribute(:group, kind_of: String, default: lazy { node['consul']['service_group'] }) 32 | # @!attribute environment 33 | # The environment that the Consul process starts with. 34 | # @return [String] 35 | attribute(:environment, kind_of: Hash, default: lazy { default_environment }) 36 | # @!attribute data_dir 37 | # @return [String] 38 | attribute(:data_dir, kind_of: String, default: lazy { node['consul']['config']['data_dir'] }) 39 | # @!attribute config_dir 40 | # @return [String] 41 | attribute(:config_dir, kind_of: String, default: lazy { node['consul']['service']['config_dir'] }) 42 | # @!attribute nssm_params 43 | # @return [String] 44 | attribute(:nssm_params, kind_of: Hash, default: lazy { node['consul']['service']['nssm_params'] }) 45 | # @!attribute program 46 | # The location of the Consul executable. 47 | # @return [String] 48 | attribute(:program, kind_of: String, default: '/usr/local/bin/consul') 49 | # @!attribute acl_token 50 | # The ACL token. Needed to reload the Consul service on Windows 51 | # @return [String] 52 | attribute(:acl_token, kind_of: String, default: lazy { node['consul']['config']['acl_master_token'] }) 53 | 54 | def command 55 | "#{program} agent -config-file=#{config_file} -config-dir=#{config_dir}" 56 | end 57 | 58 | def shell_environment 59 | shell = node['consul']['service_shell'] 60 | shell.nil? ? {} : { 'SHELL' => shell } 61 | end 62 | 63 | def default_environment 64 | { 65 | 'GOMAXPROCS' => [node['cpu']['total'], 2].max.to_s, 66 | 'PATH' => '/usr/local/bin:/usr/bin:/bin', 67 | }.merge(shell_environment) 68 | end 69 | end 70 | end 71 | 72 | module Provider 73 | # A provider for managing the Consul service. 74 | # @since 1.0 75 | class ConsulService < Chef::Provider 76 | include Poise 77 | provides(:consul_service) 78 | include PoiseService::ServiceMixin 79 | 80 | def action_enable 81 | notifying_block do 82 | directory new_resource.data_dir do 83 | recursive true 84 | owner new_resource.user 85 | group new_resource.group 86 | mode '0750' 87 | end 88 | end 89 | super 90 | end 91 | 92 | def service_options(service) 93 | service.command(new_resource.command) 94 | service.directory(new_resource.data_dir) 95 | service.user(new_resource.user) 96 | service.environment(new_resource.environment) 97 | service.restart_on_update(false) 98 | service.options(:systemd, template: 'consul:systemd.service.erb') 99 | service.options(:sysvinit, template: 'consul:sysvinit.service.erb') 100 | service.options(:upstart, template: 'consul:upstart.service.erb', executable: new_resource.program) 101 | 102 | if node.platform_family?('rhel') && node['platform_version'].to_i == 6 103 | service.provider(:sysvinit) 104 | end 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /libraries/consul_role.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # Resource for managing Consul ACL roles. 12 | class ConsulRole < Chef::Resource 13 | include Poise 14 | provides(:consul_role) 15 | actions(:create, :delete) 16 | default_action(:create) 17 | 18 | # @!attribute url 19 | # @return [String] 20 | attribute(:url, kind_of: String, default: 'http://localhost:8500') 21 | 22 | # @!attribute auth_token 23 | # @return [String] 24 | attribute(:auth_token, kind_of: String, required: true) 25 | 26 | # @!attribute role_name 27 | # @return [String] 28 | attribute(:role_name, kind_of: String, name_attribute: true) 29 | 30 | # @!attribute description 31 | # @return [String] 32 | attribute(:description, kind_of: String, default: '') 33 | 34 | # @!attribute policies 35 | # @return [Array] 36 | attribute(:policies, kind_of: Array, default: []) 37 | 38 | # @!attribute service_identities 39 | # @return [Array] 40 | attribute(:service_identities, kind_of: Array, default: []) 41 | 42 | # @!attribute ssl 43 | # @return [Hash] 44 | attribute(:ssl, kind_of: Hash, default: {}) 45 | 46 | def to_acl 47 | { 'Name' => role_name, 48 | 'Description' => description, 49 | 'Policies' => policies, 50 | 'ServiceIdentities' => service_identities } 51 | end 52 | end 53 | end 54 | 55 | module Provider 56 | # Provider for managing Consul ACL roles. 57 | class ConsulRole < Chef::Provider 58 | include Poise 59 | provides(:consul_role) 60 | 61 | def action_create 62 | configure_diplomat 63 | unless up_to_date? 64 | role = Diplomat::Role.list.select { |p| p['Name'] == new_resource.role_name } 65 | if role.empty? 66 | converge_by %(creating ACL role "#{new_resource.role_name}") do 67 | Diplomat::Role.create(new_resource.to_acl) 68 | end 69 | else 70 | converge_by %(updating ACL role "#{new_resource.role_name}") do 71 | Diplomat::Role.update(new_resource.to_acl.merge('ID' => role.first['ID'])) 72 | end 73 | end 74 | end 75 | end 76 | 77 | def action_delete 78 | configure_diplomat 79 | converge_by %(deleting ACL role "#{new_resource.role_name}") do 80 | role = Diplomat::Role.list.select { |p| p['Name'] == new_resource.role_name } 81 | Diplomat::Role.delete(role['ID']) unless role.empty? 82 | end 83 | end 84 | 85 | protected 86 | 87 | def configure_diplomat 88 | begin 89 | require 'diplomat' 90 | rescue LoadError => e 91 | raise 'The diplomat gem is required; ' \ 92 | "include recipe[consul::client_gem] to install, details: #{e}" 93 | end 94 | Diplomat.configure do |config| 95 | config.url = new_resource.url 96 | config.acl_token = new_resource.auth_token 97 | config.options = { ssl: new_resource.ssl, request: { timeout: 10 } } 98 | end 99 | end 100 | 101 | def up_to_date? 102 | retry_block(max_tries: 3, sleep: 0.5) do 103 | old_role = Diplomat::Role.list.select { |p| p['Name'] == new_resource.role_name }.first 104 | return false if old_role.nil? 105 | old_role.select! { |k, _v| %w(Name Description Policies ServiceIdentities).include?(k) } 106 | old_role == new_resource.to_acl 107 | end 108 | end 109 | 110 | def retry_block(opts = {}, &_block) 111 | opts = { 112 | max_tries: 3, # Number of tries 113 | sleep: 0, # Seconds to sleep between tries 114 | }.merge(opts) 115 | 116 | try_count = 1 117 | 118 | begin 119 | yield try_count 120 | rescue Diplomat::UnknownStatus 121 | try_count += 1 122 | 123 | # If we've maxed out our attempts, raise the exception to the calling code 124 | raise if try_count > opts[:max_tries] 125 | 126 | # Sleep before the next retry if the option was given 127 | sleep opts[:sleep] 128 | 129 | retry 130 | end 131 | end 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /libraries/consul_policy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # Resource for managing Consul ACL policies. 12 | class ConsulPolicy < Chef::Resource 13 | include Poise 14 | provides(:consul_policy) 15 | actions(:create, :delete) 16 | default_action(:create) 17 | 18 | # @!attribute url 19 | # @return [String] 20 | attribute(:url, kind_of: String, default: 'http://localhost:8500') 21 | 22 | # @!attribute auth_token 23 | # @return [String] 24 | attribute(:auth_token, kind_of: String, required: true) 25 | 26 | # @!attribute policy_name 27 | # @return [String] 28 | attribute(:policy_name, kind_of: String, name_attribute: true) 29 | 30 | # @!attribute description 31 | # @return [String] 32 | attribute(:description, kind_of: String, default: '') 33 | 34 | # @!attribute type 35 | # @return [Array] 36 | attribute(:datacenters, kind_of: Array, default: []) 37 | 38 | # @!attribute rules 39 | # @return [String] 40 | attribute(:rules, kind_of: String, default: '') 41 | 42 | # @!attribute ssl 43 | # @return [Hash] 44 | attribute(:ssl, kind_of: Hash, default: {}) 45 | 46 | def to_acl 47 | { 'Name' => policy_name, 48 | 'Description' => description, 49 | 'Datacenters' => datacenters, 50 | 'Rules' => rules } 51 | end 52 | end 53 | end 54 | 55 | module Provider 56 | # Provider for managing Consul ACL policies. 57 | class ConsulPolicy < Chef::Provider 58 | include Poise 59 | provides(:consul_policy) 60 | 61 | def action_create 62 | configure_diplomat 63 | unless up_to_date? 64 | policy = Diplomat::Policy.list.select { |p| p['Name'].downcase == new_resource.policy_name.downcase } 65 | if policy.empty? 66 | converge_by %(creating ACL policy "#{new_resource.policy_name.downcase}") do 67 | Diplomat::Policy.create(new_resource.to_acl) 68 | end 69 | else 70 | converge_by %(updating ACL policy "#{new_resource.policy_name.downcase}") do 71 | Diplomat::Policy.update(new_resource.to_acl.merge('ID' => policy.first['ID'])) 72 | end 73 | end 74 | end 75 | end 76 | 77 | def action_delete 78 | configure_diplomat 79 | converge_by %(deleting ACL policy "#{new_resource.policy_name.downcase}") do 80 | policy = Diplomat::Policy.list.select { |p| p['Name'].downcase == new_resource.policy_name.downcase } 81 | Diplomat::Policy.delete(policy['ID']) unless policy.empty? 82 | end 83 | end 84 | 85 | protected 86 | 87 | def configure_diplomat 88 | begin 89 | require 'diplomat' 90 | rescue LoadError => e 91 | raise 'The diplomat gem is required; ' \ 92 | "include recipe[consul::client_gem] to install, details: #{e}" 93 | end 94 | Diplomat.configure do |config| 95 | config.url = new_resource.url 96 | config.acl_token = new_resource.auth_token 97 | config.options = { ssl: new_resource.ssl, request: { timeout: 10 } } 98 | end 99 | end 100 | 101 | def up_to_date? 102 | retry_block(max_tries: 3, sleep: 0.5) do 103 | old_policy_id = Diplomat::Policy.list.select { |p| p['Name'].downcase == new_resource.policy_name.downcase } 104 | return false if old_policy_id.empty? 105 | old_policy = Diplomat::Policy.read(old_policy_id.first['ID'], {}, :return) 106 | return false if old_policy.nil? 107 | old_policy.first.select! { |k, _v| %w(Name Description Rules).include?(k) } 108 | old_policy['Description'].downcase! 109 | old_policy.first == new_resource.to_acl 110 | end 111 | end 112 | 113 | def retry_block(opts = {}, &_block) 114 | opts = { 115 | max_tries: 3, # Number of tries 116 | sleep: 0, # Seconds to sleep between tries 117 | }.merge(opts) 118 | 119 | try_count = 1 120 | 121 | begin 122 | yield try_count 123 | rescue Diplomat::UnknownStatus 124 | try_count += 1 125 | 126 | # If we've maxed out our attempts, raise the exception to the calling code 127 | raise if try_count > opts[:max_tries] 128 | 129 | # Sleep before the next retry if the option was given 130 | sleep opts[:sleep] 131 | 132 | retry 133 | end 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /libraries/consul_token.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | 9 | module ConsulCookbook 10 | module Resource 11 | # Resource for managing Consul ACL tokens. 12 | class ConsulToken < Chef::Resource 13 | include Poise 14 | provides(:consul_token) 15 | actions(:create, :delete) 16 | default_action(:create) 17 | 18 | # @!attribute url 19 | # @return [String] 20 | attribute(:url, kind_of: String, default: 'http://localhost:8500') 21 | 22 | # @!attribute auth_token 23 | # @return [String] 24 | attribute(:auth_token, kind_of: String, required: true) 25 | 26 | # @!attribute secret_id 27 | # @return [String] 28 | attribute(:secret_id, kind_of: String, default: nil) 29 | 30 | # @!attribute description 31 | # @return [String] 32 | attribute(:description, kind_of: String, name_attribute: true) 33 | 34 | # @!attribute policies 35 | # @return [Array] 36 | attribute(:policies, kind_of: Array, default: []) 37 | 38 | # @!attribute roles 39 | # @return [Array] 40 | attribute(:roles, kind_of: Array, default: []) 41 | 42 | # @!attribute service_identities 43 | # @return [Array] 44 | attribute(:service_identities, kind_of: Array, default: []) 45 | 46 | # @!attribute expiration_time 47 | # @return [String] 48 | attribute(:expiration_time, kind_of: String, default: '') 49 | 50 | # @!attribute expiration_ttl 51 | # @return [String] 52 | attribute(:expiration_ttl, kind_of: String, default: nil) 53 | 54 | # @!attribute local 55 | # @return [Bool] 56 | attribute(:local, kind_of: [TrueClass, FalseClass], default: false) 57 | 58 | # @!attribute ssl 59 | # @return [Hash] 60 | attribute(:ssl, kind_of: Hash, default: {}) 61 | 62 | def to_acl 63 | { 'SecretID' => secret_id, 64 | 'Description' => description.downcase, 65 | 'Local' => local, 66 | 'Policies' => [ policies.each_with_object({}) { |k, h| h['Name'] = k } ], 67 | } 68 | end 69 | end 70 | end 71 | 72 | module Provider 73 | # Provider for managing Consul ACL tokens. 74 | class ConsulToken < Chef::Provider 75 | include Poise 76 | provides(:consul_token) 77 | 78 | def action_create 79 | configure_diplomat 80 | unless up_to_date? 81 | old_token = Diplomat::Token.list.select { |p| p['Description'].downcase == new_resource.description.downcase } 82 | if old_token.empty? 83 | converge_by %(creating ACL token "#{new_resource.description.downcase}") do 84 | Diplomat::Token.create(new_resource.to_acl) 85 | end 86 | else 87 | converge_by %(updating ACL token "#{new_resource.description.downcase}") do 88 | Diplomat::Token.update(new_resource.to_acl.merge('AccessorID' => old_token.first['AccessorID'])) 89 | end 90 | end 91 | end 92 | end 93 | 94 | def action_delete 95 | configure_diplomat 96 | converge_by %(deleting ACL token "#{new_resource.description.downcase}") do 97 | token = Diplomat::Token.list.select { |p| p['Description'].downcase == new_resource.description.downcase } 98 | Diplomat::Token.delete(token['AccessorID']) unless token.empty? 99 | end 100 | end 101 | 102 | protected 103 | 104 | def configure_diplomat 105 | begin 106 | require 'diplomat' 107 | rescue LoadError => e 108 | raise 'The diplomat gem is required; ' \ 109 | "include recipe[consul::client_gem] to install, details: #{e}" 110 | end 111 | Diplomat.configure do |config| 112 | config.url = new_resource.url 113 | config.acl_token = new_resource.auth_token 114 | config.options = { ssl: new_resource.ssl, request: { timeout: 10 } } 115 | end 116 | end 117 | 118 | def up_to_date? 119 | retry_block(max_tries: 3, sleep: 0.5) do 120 | old_token_id = Diplomat::Token.list.select { |p| p['Description'].downcase == new_resource.description.downcase } 121 | if old_token_id.empty? 122 | Chef::Log.warn %(Token with description "#{new_resource.description.downcase}" was not found. Will create.) 123 | return false 124 | end 125 | old_token = Diplomat::Token.read(old_token_id.first['AccessorID'], {}, :return) 126 | old_token.select! { |k, _v| %w(SecretID Description Policies Local).include?(k) } 127 | old_token['Description'].downcase! 128 | old_token == new_resource.to_acl 129 | end 130 | end 131 | 132 | def retry_block(opts = {}, &_block) 133 | opts = { 134 | max_tries: 3, # Number of tries 135 | sleep: 0, # Seconds to sleep between tries 136 | }.merge(opts) 137 | 138 | try_count = 1 139 | 140 | begin 141 | yield try_count 142 | rescue Diplomat::UnknownStatus 143 | try_count += 1 144 | 145 | # If we've maxed out our attempts, raise the exception to the calling code 146 | raise if try_count > opts[:max_tries] 147 | 148 | # Sleep before the next retry if the option was given 149 | sleep opts[:sleep] 150 | 151 | retry 152 | end 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /spec/libraries/consul_config_v0_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_config_v0' 3 | 4 | # shared_examples 'a simple field' do |field_name, field_value| 5 | # it "sets `#{field_name}`" do 6 | # expect(config[field_name]).to eq field_value 7 | # end 8 | # end 9 | 10 | # describe ConsulCookbook::Resource::ConsulConfigV0 do 11 | # # We have to specify the class here, because `poise_boiler/spec_helper` can't 12 | # # resolve providers with node attributes 13 | # step_into(ConsulCookbook::Resource::ConsulConfigV0) 14 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 15 | 16 | # before do 17 | # recipe = double('Chef::Recipe') 18 | # allow_any_instance_of(Chef::RunContext).to receive(:include_recipe).and_return([recipe]) 19 | # default_attributes['consul'] = { 20 | # 'service_user' => 'consul', 21 | # 'service_group' => 'consul', 22 | # 'service' => { 23 | # 'config_dir' => '/etc/consul/conf.d', 24 | # }, 25 | # 'version' => '0.9.3', 26 | # } 27 | # end 28 | 29 | # context 'sets options directly' do 30 | # recipe do 31 | # consul_config '/etc/consul/default.json' do 32 | # owner 'root' 33 | # options do 34 | # recursor 'foo' 35 | # translate_wan_addrs true 36 | # end 37 | # end 38 | # end 39 | 40 | # it do 41 | # is_expected.to create_directory('/etc/consul/conf.d') 42 | # .with(user: 'root', group: 'consul', mode: '0755') 43 | # end 44 | 45 | # it do 46 | # is_expected.to create_file('/etc/consul/default.json') 47 | # .with(user: 'root', group: 'consul', mode: '0640') 48 | # .with(content: <<-EOH.chomp.gsub(/^ /, '')) 49 | # { 50 | # "recursor": "foo", 51 | # "translate_wan_addrs": true 52 | # } 53 | # EOH 54 | # end 55 | # end 56 | 57 | # context 'deletes configuration' do 58 | # recipe do 59 | # consul_config '/etc/consul/default.json' do 60 | # action :delete 61 | # end 62 | # end 63 | 64 | # it { is_expected.to delete_file('/etc/consul/default.json') } 65 | # end 66 | 67 | # describe 'parameters' do 68 | # let(:config) { JSON.parse(subject.find_resource('consul_config', '/etc/consul/default.json').params_to_json) } 69 | 70 | # describe 'retry_join_ec2' do 71 | # recipe do 72 | # consul_config '/etc/consul/default.json' do 73 | # retry_join_ec2( 74 | # 'region' => 'ca-central-1', 75 | # 'tag_key' => 'foo', 76 | # 'tag_value' => 'bar', 77 | # 'access_key_id' => 'KEY_ID', 78 | # 'secret_access_key' => 'SECRETS' 79 | # ) 80 | # end 81 | # end 82 | # it 'sets the `retry_join_ec2` field' do 83 | # expect(config['retry_join_ec2']).to include( 84 | # 'region' => 'ca-central-1', 85 | # 'tag_key' => 'foo', 86 | # 'tag_value' => 'bar', 87 | # 'access_key_id' => 'KEY_ID', 88 | # 'secret_access_key' => 'SECRETS' 89 | # ) 90 | # end 91 | # end 92 | 93 | # describe 'retry_join_azure' do 94 | # recipe do 95 | # consul_config '/etc/consul/default.json' do 96 | # retry_join_azure( 97 | # 'tag_name' => 'foo', 98 | # 'tag_value' => 'bar', 99 | # 'subscription_id' => 'SUBSCRIPTION_ID', 100 | # 'tenant_id' => 'TENANT_ID', 101 | # 'client_id' => 'CLIENT_ID', 102 | # 'secret_access_key' => 'SECRETS' 103 | # ) 104 | # end 105 | # end 106 | # it 'sets the `retry_join_azure` field' do 107 | # expect(config['retry_join_azure']).to include( 108 | # 'tag_name' => 'foo', 109 | # 'tag_value' => 'bar', 110 | # 'subscription_id' => 'SUBSCRIPTION_ID', 111 | # 'tenant_id' => 'TENANT_ID', 112 | # 'client_id' => 'CLIENT_ID', 113 | # 'secret_access_key' => 'SECRETS' 114 | # ) 115 | # end 116 | # end 117 | 118 | # describe 'atlas_infrastructure' do 119 | # recipe do 120 | # consul_config '/etc/consul/default.json' do 121 | # atlas_infrastructure 'infra' 122 | # end 123 | # end 124 | # it_should_behave_like 'a simple field', 'atlas_infrastructure', 'infra' 125 | # end 126 | # describe 'atlas_token' do 127 | # recipe do 128 | # consul_config '/etc/consul/default.json' do 129 | # atlas_token 'token' 130 | # end 131 | # end 132 | # it_should_behave_like 'a simple field', 'atlas_token', 'token' 133 | # end 134 | # describe 'atlas_acl_token' do 135 | # recipe do 136 | # consul_config '/etc/consul/default.json' do 137 | # atlas_acl_token 'acl_token' 138 | # end 139 | # end 140 | # it_should_behave_like 'a simple field', 'atlas_acl_token', 'acl_token' 141 | # end 142 | # describe 'atlas_join' do 143 | # recipe do 144 | # consul_config '/etc/consul/default.json' do 145 | # atlas_join true 146 | # end 147 | # end 148 | # it_should_behave_like 'a simple field', 'atlas_join', true 149 | # end 150 | # describe 'atlas_endpoint' do 151 | # recipe do 152 | # consul_config '/etc/consul/default.json' do 153 | # atlas_endpoint 'endpoint' 154 | # end 155 | # end 156 | # it_should_behave_like 'a simple field', 'atlas_endpoint', 'endpoint' 157 | # end 158 | 159 | # describe 'http_api_response_headers' do 160 | # recipe do 161 | # consul_config '/etc/consul/default.json' do 162 | # http_api_response_headers( 163 | # 'Access-Control-Allow-Origin' => '*' 164 | # ) 165 | # end 166 | # end 167 | # it 'sets the `http_api_response_headers` field' do 168 | # expect(config['http_api_response_headers']).to include( 169 | # 'Access-Control-Allow-Origin' => '*' 170 | # ) 171 | # end 172 | # end 173 | 174 | # describe 'recursor' do 175 | # recipe do 176 | # consul_config '/etc/consul/default.json' do 177 | # recursor '127.0.0.1' 178 | # end 179 | # end 180 | # it_should_behave_like 'a simple field', 'recursor', '127.0.0.1' 181 | # end 182 | 183 | # describe 'statsd_addr' do 184 | # recipe do 185 | # consul_config '/etc/consul/default.json' do 186 | # statsd_addr '127.0.0.1:123' 187 | # end 188 | # end 189 | # it_should_behave_like 'a simple field', 'statsd_addr', '127.0.0.1:123' 190 | # end 191 | 192 | # describe 'statsite_addr' do 193 | # recipe do 194 | # consul_config '/etc/consul/default.json' do 195 | # statsite_addr '127.0.0.1:123' 196 | # end 197 | # end 198 | # it_should_behave_like 'a simple field', 'statsite_addr', '127.0.0.1:123' 199 | # end 200 | 201 | # describe 'statsite_prefix' do 202 | # recipe do 203 | # consul_config '/etc/consul/default.json' do 204 | # statsite_prefix 'prefix' 205 | # end 206 | # end 207 | # it_should_behave_like 'a simple field', 'statsite_prefix', 'prefix' 208 | # end 209 | 210 | # describe 'discovery_max_stale_unsupported_old_version' do 211 | # before do 212 | # recipe = double('Chef::Recipe') 213 | # allow_any_instance_of(Chef::RunContext).to receive(:include_recipe).and_return([recipe]) 214 | # end 215 | # recipe do 216 | # consul_config '/etc/consul/default.json' do 217 | # discovery_max_stale '72h' 218 | # end 219 | # end 220 | # it 'does not set the [`discovery_max_stale`] field since unsupported' do 221 | # expect(config['discovery_max_stale']).to be_nil 222 | # end 223 | # end 224 | # end 225 | # end 226 | -------------------------------------------------------------------------------- /libraries/consul_config_v0.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | require_relative 'helpers' 9 | 10 | module ConsulCookbook 11 | module Resource 12 | # @since 1.0 13 | class ConsulConfigV0 < Chef::Resource 14 | include Poise(fused: true) 15 | include ConsulCookbook::Helpers 16 | provides :consul_config do |node| 17 | node['consul']['version'].to_i < 1 18 | end 19 | 20 | # @!attribute path 21 | # @return [String] 22 | attribute(:path, kind_of: String, name_attribute: true) 23 | # @!attribute owner 24 | # @return [String] 25 | attribute(:owner, kind_of: String, default: lazy { node['consul']['service_user'] }) 26 | # @!attribute group 27 | # @return [String] 28 | attribute(:group, kind_of: String, default: lazy { node['consul']['service_group'] }) 29 | # @!attribute config_dir 30 | # @return [String] 31 | attribute(:config_dir, kind_of: String, default: lazy { node['consul']['service']['config_dir'] }) 32 | # @!attribute config_dir_mode 33 | # @return [String] 34 | attribute(:config_dir_mode, kind_of: String, default: '0755') 35 | # @!attribute options 36 | # @return [Hash] 37 | attribute(:options, option_collector: true) 38 | 39 | # @see: http://www.consul.io/docs/agent/options.html 40 | attribute(:acl_agent_token, kind_of: String) 41 | attribute(:acl_agent_master_token, kind_of: String) 42 | attribute(:acl_datacenter, kind_of: String) 43 | attribute(:acl_default_policy, kind_of: String) 44 | attribute(:acl_down_policy, kind_of: String) 45 | attribute(:acl_enforce_version_8, equal_to: [true, false]) 46 | attribute(:acl_master_token, kind_of: String) 47 | attribute(:acl_replication_token, kind_of: String) 48 | attribute(:acl_token, kind_of: String) 49 | attribute(:acl_ttl, kind_of: String) 50 | attribute(:addresses, kind_of: [Hash, Mash]) 51 | attribute(:advertise_addr, kind_of: String) 52 | attribute(:advertise_addr_wan, kind_of: String) 53 | attribute(:atlas_acl_token, kind_of: String) 54 | attribute(:atlas_infrastructure, kind_of: String) 55 | attribute(:atlas_join, equal_to: [true, false]) 56 | attribute(:atlas_token, kind_of: String) 57 | attribute(:atlas_endpoint, kind_of: String) 58 | attribute(:autopilot, kind_of: [Hash, Mash]) 59 | attribute(:bind_addr, kind_of: String) 60 | attribute(:bootstrap, equal_to: [true, false]) 61 | attribute(:bootstrap_expect, kind_of: Integer) 62 | attribute(:ca_file, kind_of: String) 63 | attribute(:ca_path, kind_of: String) 64 | attribute(:cert_file, kind_of: String) 65 | attribute(:check_update_interval, kind_of: String) 66 | attribute(:client_addr, kind_of: String) 67 | attribute(:data_dir, kind_of: String) 68 | attribute(:datacenter, kind_of: String) 69 | attribute(:disable_anonymous_signature, equal_to: [true, false]) 70 | attribute(:disable_host_node_id, equal_to: [true, false]) 71 | attribute(:disable_keyring_file, equal_to: [true, false]) 72 | attribute(:disable_remote_exec, equal_to: [true, false]) 73 | attribute(:disable_update_check, equal_to: [true, false]) 74 | attribute(:discard_check_output, equal_to: [true, false]) 75 | # Not supported, but allow same attribute for v0.x and 1.0.6+ 76 | attribute(:discovery_max_stale, kind_of: String) 77 | attribute(:dns_config, kind_of: [Hash, Mash]) 78 | attribute(:domain, kind_of: String) 79 | attribute(:enable_acl_replication, equal_to: [true, false]) 80 | attribute(:enable_debug, equal_to: [true, false]) 81 | attribute(:enable_script_checks, equal_to: [true, false]) 82 | attribute(:enable_syslog, equal_to: [true, false]) 83 | attribute(:encrypt, kind_of: String) 84 | attribute(:encrypt_verify_incoming, equal_to: [true, false]) 85 | attribute(:encrypt_verify_outgoing, equal_to: [true, false]) 86 | attribute(:http_api_response_headers, kind_of: [Hash, Mash]) 87 | attribute(:http_config, kind_of: [Hash, Mash]) 88 | attribute(:key_file, kind_of: String) 89 | attribute(:leave_on_terminate, equal_to: [true, false]) 90 | attribute(:limits, kind_of: [Hash, Mash]) 91 | attribute(:log_level, equal_to: %w(INFO DEBUG WARN ERR)) 92 | attribute(:node_id, kind_of: String) 93 | attribute(:node_name, kind_of: String) 94 | attribute(:node_meta, kind_of: [Hash, Mash]) 95 | attribute(:non_voting_server, equal_to: [true, false]) 96 | attribute(:performance, kind_of: [Hash, Mash]) 97 | attribute(:ports, kind_of: [Hash, Mash]) 98 | attribute(:protocol, kind_of: String) 99 | attribute(:raft_protocol, kind_of: Integer) 100 | attribute(:reap, equal_to: [true, false]) 101 | attribute(:reconnect_timeout, kind_of: String) 102 | attribute(:reconnect_timeout_wan, kind_of: String) 103 | attribute(:recursor, kind_of: String) 104 | attribute(:recursors, kind_of: Array) 105 | attribute(:retry_interval, kind_of: String) 106 | attribute(:retry_interval_wan, kind_of: String) 107 | attribute(:retry_join, kind_of: Array) 108 | attribute(:retry_join_azure, kind_of: [Hash, Mash]) 109 | attribute(:retry_join_ec2, kind_of: [Hash, Mash]) 110 | attribute(:retry_join_wan, kind_of: Array) 111 | attribute(:retry_max, kind_of: Integer) 112 | attribute(:rejoin_after_leave, equal_to: [true, false]) 113 | attribute(:segment, kind_of: String) 114 | attribute(:segments, kind_of: Array) 115 | attribute(:serf_lan_bind, kind_of: String) 116 | attribute(:serf_wan_bind, kind_of: String) 117 | attribute(:server, equal_to: [true, false]) 118 | attribute(:server_name, kind_of: String) 119 | attribute(:session_ttl_min, kind_of: String) 120 | attribute(:skip_leave_on_interrupt, equal_to: [true, false]) 121 | attribute(:start_join, kind_of: Array) 122 | attribute(:start_join_wan, kind_of: Array) 123 | attribute(:statsd_addr, kind_of: String) 124 | attribute(:statsite_addr, kind_of: String) 125 | attribute(:statsite_prefix, kind_of: String) 126 | attribute(:telemetry, kind_of: [Hash, Mash]) 127 | attribute(:syslog_facility, kind_of: String) 128 | attribute(:tls_cipher_suites, kind_of: String) 129 | attribute(:tls_min_version, equal_to: %w(tls10 tls11 tls12)) 130 | attribute(:tls_prefer_server_cipher_suites, equal_to: [true, false]) 131 | attribute(:translate_wan_addrs, equal_to: [true, false]) 132 | attribute(:ui, equal_to: [true, false]) 133 | attribute(:ui_dir, kind_of: String) 134 | attribute(:unix_sockets, kind_of: [Hash, Mash]) 135 | attribute(:verify_incoming, equal_to: [true, false]) 136 | attribute(:verify_incoming_https, equal_to: [true, false]) 137 | attribute(:verify_incoming_rpc, equal_to: [true, false]) 138 | attribute(:verify_outgoing, equal_to: [true, false]) 139 | attribute(:verify_server_hostname, equal_to: [true, false]) 140 | attribute(:watches, kind_of: [Hash, Mash]) 141 | 142 | # Transforms the resource into a JSON format which matches the 143 | # Consul service's configuration format. 144 | def params_to_json 145 | for_keeps = %i( 146 | acl_agent_token 147 | acl_agent_master_token 148 | acl_datacenter 149 | acl_default_policy 150 | acl_down_policy 151 | acl_enforce_version_8 152 | acl_master_token 153 | acl_replication_token 154 | acl_token 155 | acl_ttl 156 | addresses 157 | advertise_addr 158 | advertise_addr_wan 159 | atlas_acl_token 160 | atlas_endpoint 161 | atlas_infrastructure 162 | atlas_join 163 | atlas_token 164 | autopilot 165 | bind_addr 166 | check_update_interval 167 | client_addr 168 | data_dir 169 | datacenter 170 | disable_anonymous_signature 171 | disable_host_node_id 172 | disable_keyring_file 173 | disable_remote_exec 174 | disable_update_check 175 | discard_check_output 176 | dns_config 177 | domain 178 | enable_acl_replication 179 | enable_debug 180 | enable_script_checks 181 | enable_syslog 182 | encrypt 183 | encrypt_verify_incoming 184 | encrypt_verify_outgoing 185 | http_api_response_headers 186 | http_config 187 | leave_on_terminate 188 | limits 189 | log_level 190 | node_id 191 | node_meta 192 | non_voting_server 193 | node_name 194 | performance 195 | ports 196 | protocol 197 | reap 198 | raft_protocol 199 | reconnect_timeout 200 | reconnect_timeout_wan 201 | recursor 202 | recursors 203 | rejoin_after_leave 204 | retry_interval 205 | retry_interval_wan 206 | retry_join 207 | retry_join_azure 208 | retry_join_ec2 209 | retry_join_wan 210 | retry_max 211 | segment 212 | segments 213 | serf_lan_bind 214 | serf_wan_bind 215 | server 216 | server_name 217 | session_ttl_min 218 | skip_leave_on_interrupt 219 | start_join 220 | start_join_wan 221 | statsd_addr 222 | statsite_addr 223 | statsite_prefix 224 | syslog_facility 225 | telemetry 226 | tls_cipher_suites 227 | tls_min_version 228 | tls_prefer_server_cipher_suites 229 | translate_wan_addrs 230 | ui 231 | ui_dir 232 | unix_sockets 233 | verify_incoming 234 | verify_incoming_https 235 | verify_incoming_rpc 236 | verify_outgoing 237 | verify_server_hostname 238 | watches 239 | ) 240 | 241 | for_keeps << %i(bootstrap bootstrap_expect) if server 242 | for_keeps << %i(ca_file ca_path cert_file key_file) if tls? 243 | for_keeps = for_keeps.flatten 244 | 245 | # Filter out undefined attributes and keep only those listed above 246 | config = to_hash.keep_if do |k, v| 247 | !v.nil? && for_keeps.include?(k.to_sym) 248 | end.merge(options) 249 | JSON.pretty_generate(Hash[config.sort_by { |k, _| k.to_s }], quirks_mode: true) 250 | end 251 | 252 | def tls? 253 | verify_incoming || verify_incoming_https || verify_incoming_rpc || verify_outgoing 254 | end 255 | 256 | action(:create) do 257 | notifying_block do 258 | [::File.dirname(new_resource.path), new_resource.config_dir].each do |dir| 259 | directory dir do 260 | recursive true 261 | unless platform?('windows') 262 | owner new_resource.owner 263 | group new_resource.group 264 | mode new_resource.config_dir_mode 265 | end 266 | not_if { dir == '/etc' } 267 | end 268 | end 269 | 270 | file new_resource.path do 271 | unless platform?('windows') 272 | owner new_resource.owner 273 | group new_resource.group 274 | mode '0640' 275 | end 276 | content new_resource.params_to_json 277 | sensitive true 278 | end 279 | end 280 | end 281 | 282 | action(:delete) do 283 | notifying_block do 284 | file new_resource.path do 285 | action :delete 286 | end 287 | end 288 | end 289 | end 290 | end 291 | end 292 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014-2017, Bloomberg Finance L.P. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Consul Cookbook 2 | 3 | [![Cookbook Version](https://img.shields.io/cookbook/v/consul.svg)](https://supermarket.chef.io/cookbooks/consul) 4 | [![CI State](https://github.com/sous-chefs/consul/workflows/ci/badge.svg)](https://github.com/sous-chefs/consul/actions?query=workflow%3Aci) 5 | [![OpenCollective](https://opencollective.com/sous-chefs/backers/badge.svg)](#backers) 6 | [![OpenCollective](https://opencollective.com/sous-chefs/sponsors/badge.svg)](#sponsors) 7 | [![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) 8 | 9 | [Application cookbook][0] which installs and configures [Consul][1]. 10 | 11 | Consul is a tool for discovering and configuring services within your 12 | infrastructure. This is an application cookbook which takes a 13 | simplified approach to configuring and installing 14 | Consul. Additionally, it provides Chef primitives for more advanced 15 | configuration. 16 | 17 | ## Maintainers 18 | 19 | This cookbook is maintained by the Sous Chefs. The Sous Chefs are a community of Chef cookbook maintainers working together to maintain important cookbooks. If you’d like to know more please visit [sous-chefs.org](https://sous-chefs.org/) or come chat with us on the Chef Community Slack in [#sous-chefs](https://chefcommunity.slack.com/messages/C2V7B88SF). 20 | 21 | ## Basic Usage 22 | 23 | For most infrastructure we suggest first starting with the default 24 | recipe. This installs and configures Consul from the latest supported 25 | release. It is also what is used to certify platform support through 26 | the use of our integration tests. 27 | 28 | This cookbook provides node attributes which are used to fine tune 29 | the default recipe which installs and configures Consul. These values 30 | are passed directly into the Chef resource/providers which are exposed 31 | for more advanced configuration. 32 | 33 | Out of the box the following platforms are certified to work and are 34 | tested using our [Test Kitchen][8] configuration. Additional platforms 35 | _may_ work, but your mileage may vary. 36 | 37 | - RHEL/CentOS 7 & 8 38 | - Ubuntu 16.04, 18.04 & 20.04 39 | - Debian 9 & 10 40 | - Windows Server 2012 R2 41 | 42 | ### Client 43 | 44 | Out of the box the default recipe installs and configures the Consul 45 | agent to run as a service in _client mode_. The intent here is that 46 | your infrastructure already has a [quorum of servers][13]. In order 47 | to configure Consul to connect to your cluster you would supply an 48 | array of addresses for the Consul agent to join. This would be done 49 | in your [wrapper cookbook][2]: 50 | 51 | ```ruby 52 | node.default['consul']['config']['start_join'] = %w{c1.internal.corporate.com c2.internal.corporate.com c3.internal.corporate.com} 53 | ``` 54 | 55 | ### Server 56 | 57 | This cookbook is designed to allow for the flexibility to bootstrap a 58 | new cluster. The best way to do this is through the use of a 59 | [wrapper cookbook][2] which tunes specific node attributes for a 60 | production server deployment. 61 | 62 | The [Consul cluster cookbook][14] is provided as an example. 63 | 64 | ## Advanced Usage 65 | 66 | As explained above this cookbook provides Chef primitives in the form 67 | of resource/provider to further manage the install and configuration 68 | of Consul. These primitives are what is used in the default recipe, 69 | and should be used in your own [wrapper cookbooks][2] for more 70 | advanced configurations. 71 | 72 | ### Configuration 73 | 74 | It is very important to understand that each resource/provider has 75 | defaults for some properties. Any changes to a resource's default 76 | properties may need to be also changed in other resources. The best 77 | example is the Consul configuration directory. 78 | 79 | In the example below we're going to change the configuration file from 80 | the default (/etc/consul.json) to one that may be on a special volume. 81 | It is obvious that we need to change the path where `consul_config` 82 | writes its file to, but it is less obvious that this needs to be 83 | passed into `consul_service`. 84 | 85 | Inside of a recipe in your [wrapper cookbook][2] you'll want to do 86 | something like the following block of code. It uses the validated 87 | input from the configuration resource and passes it into the service 88 | resource. This ensures that we're using the _same data_. 89 | 90 | ```ruby 91 | config = consul_config '/data/consul/default.json' 92 | consul_service 'consul' do 93 | config_file config.path 94 | end 95 | ``` 96 | 97 | ### Security 98 | 99 | The default recipe makes the Consul configuration writable by the consul service 100 | user to avoid breaking existing implementations. You can make this more secure 101 | by setting the `node['consul']['config']['owner']` attribute to `root`, or set 102 | the `owner` property of `consul_config` explicitly: 103 | 104 | ```ruby 105 | # attributes file 106 | default['consul']['config']['owner'] = 'root' 107 | ``` 108 | 109 | or 110 | 111 | ```ruby 112 | # recipe file 113 | consul_config '/etc/consul/consul.json' do 114 | owner 'root' 115 | end 116 | ``` 117 | 118 | ### Watches/Definitions 119 | 120 | In order to provide an idempotent implementation of Consul 121 | watches and definitions. We write these out as 122 | a separate configuration file in the JSON file format. The provider 123 | for both of these resources are identical in functionality. 124 | 125 | Below is an example of writing a [Consul service definition][10] for 126 | the master instance of Redis. We pass in several parameters and tell 127 | the resource to notify the proper instance of the Consul service to 128 | reload. 129 | 130 | ```ruby 131 | consul_definition 'redis' do 132 | type 'service' 133 | parameters(tags: %w{master}, address: '127.0.0.1', port: 6379) 134 | notifies :reload, 'consul_service[consul]', :delayed 135 | end 136 | ``` 137 | 138 | A [check definition][11] can easily be added as well. You simply have 139 | to change the type and pass in the correct parameters. The definition 140 | below checks memory utilization using a script on a ten second interval. 141 | 142 | ```ruby 143 | consul_definition 'mem-util' do 144 | type 'check' 145 | parameters(script: '/usr/local/bin/check_mem.py', interval: '10s') 146 | notifies :reload, 'consul_service[consul]', :delayed 147 | end 148 | ``` 149 | 150 | A service definition with an integrated check can also be created. You will have to define a regular service and then add a check as a an additional parameter. The definition below checks if the vault service is healthy on a 10 second interval and 5 second timeout. 151 | 152 | ```ruby 153 | consul_definition 'vault' do 154 | type 'service' 155 | parameters( 156 | port: 8200, 157 | address: '127.0.0.1', 158 | tags: ['vault', 'http'], 159 | check: { 160 | interval: '10s', 161 | timeout: '5s', 162 | http: 'http://127.0.0.1:8200/v1/sys/health' 163 | } 164 | ) 165 | notifies :reload, 'consul_service[consul]', :delayed 166 | end 167 | ``` 168 | 169 | Finally, a [watch][9] is created below to tell the agent to monitor to 170 | see if an application has been deployed. Once that application is 171 | deployed a script is run locally. This can be used, for example, as a 172 | lazy way to clear a HTTP disk cache. 173 | 174 | ```ruby 175 | consul_watch 'app-deploy' do 176 | type 'event' 177 | parameters(handler: '/usr/local/bin/clear-disk-cache.sh') 178 | notifies :reload, 'consul_service[consul]', :delayed 179 | end 180 | ``` 181 | 182 | A keen eye would notice that we are _delaying the reload of the Consul 183 | service instance_. The reason we do this is to minimize the number of 184 | times we need to tell Consul to actually reload configurations. If 185 | there are several definitions this may save a little time off your 186 | Chef run. 187 | 188 | ### ACLs 189 | 190 | The `consul_acl` resource allows management of [Consul ACL rules][15]. Supported 191 | actions are `:create` and `:delete`. The `:create` action will update/insert 192 | as necessary. 193 | 194 | The `consul_acl` resource requires the [Diplomat Ruby API][16] gem to be 195 | installed and available to Chef before using the resource. This can be 196 | accomplished by including `consul::client_gem` recipe in your run list. If you 197 | are using Chef Infra Client 15.8+ you will need to make sure you are using at 198 | least version 2.2.6 of the diplomat gem. 199 | 200 | In order to make the resource idempotent and only notify when necessary, the 201 | `id` field is always required (defaults to the name of the resource). 202 | If `type` is not provided, it will default to "client". The `acl_name` 203 | and `rules` attributes are also optional; if not included they will be empty 204 | in the resulting ACL. 205 | 206 | The example below will create a client ACL token with an `ID` of the given UUID, 207 | `Name` of "AwesomeApp Token", and `Rules` of the given string. 208 | 209 | ```ruby 210 | consul_acl '49f06aa9-782f-465a-becf-44f0aaefd335' do 211 | acl_name 'AwesomeApp Token' 212 | type 'client' 213 | rules <<-EOS.gsub(/^\s{4}/, '') 214 | key "" { 215 | policy = "read" 216 | } 217 | service "" { 218 | policy = "write" 219 | } 220 | EOS 221 | auth_token node['consul']['config']['acl_master_token'] 222 | end 223 | ``` 224 | 225 | ### Execute 226 | 227 | The command-line agent provides a mechanism to facilitate remote 228 | execution. For example, this can be used to run the `uptime` command 229 | across your fleet of nodes which are hosting a particular API service. 230 | 231 | ```ruby 232 | consul_execute 'uptime' do 233 | options(service: 'api') 234 | end 235 | ``` 236 | 237 | ### Warning on git based installs 238 | 239 | Consul v1.0 states that Go 1.9 is a requirement. The default go installation uses 240 | 1.5, so you may need to override a `['go']['version']` attribute to allow the 241 | git installation to work reliably. 242 | 243 | All of the [options available on the command-line][12] can be passed 244 | into the resource. This could potentially be a *very dangerous* 245 | operation. You should absolutely understand what you are doing. By the 246 | nature of this command it is _impossible_ for it to be idempotent. 247 | 248 | [0]: http://blog.vialstudios.com/the-environment-cookbook-pattern/#theapplicationcookbook 249 | [1]: http://consul.io 250 | [2]: http://blog.vialstudios.com/the-environment-cookbook-pattern#thewrappercookbook 251 | [3]: http://blog.vialstudios.com/the-environment-cookbook-pattern#thelibrarycookbook 252 | [4]: https://github.com/johnbellone/libartifact-cookbook 253 | [5]: https://github.com/poise/poise 254 | [6]: https://github.com/poise/poise-service 255 | [7]: https://github.com/skottler/selinux 256 | [8]: https://github.com/test-kitchen/test-kitchen 257 | [9]: https://consul.io/docs/agent/watches.html 258 | [10]: https://consul.io/docs/agent/services.html 259 | [11]: https://consul.io/docs/agent/checks.html 260 | [12]: https://consul.io/docs/commands/exec.html 261 | [13]: https://en.wikipedia.org/wiki/Quorum_(distributed_computing) 262 | [14]: https://github.com/johnbellone/consul-cluster-cookbook 263 | [15]: https://www.consul.io/docs/internals/acl.html 264 | [16]: https://github.com/WeAreFarmGeek/diplomat 265 | 266 | ## Contributors 267 | 268 | This project exists thanks to all the people who [contribute.](https://opencollective.com/sous-chefs/contributors.svg?width=890&button=false) 269 | 270 | ### Backers 271 | 272 | Thank you to all our backers! 273 | 274 | ![https://opencollective.com/sous-chefs#backers](https://opencollective.com/sous-chefs/backers.svg?width=600&avatarHeight=40) 275 | 276 | ### Sponsors 277 | 278 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. 279 | 280 | ![https://opencollective.com/sous-chefs/sponsor/0/website](https://opencollective.com/sous-chefs/sponsor/0/avatar.svg?avatarHeight=100) 281 | ![https://opencollective.com/sous-chefs/sponsor/1/website](https://opencollective.com/sous-chefs/sponsor/1/avatar.svg?avatarHeight=100) 282 | ![https://opencollective.com/sous-chefs/sponsor/2/website](https://opencollective.com/sous-chefs/sponsor/2/avatar.svg?avatarHeight=100) 283 | ![https://opencollective.com/sous-chefs/sponsor/3/website](https://opencollective.com/sous-chefs/sponsor/3/avatar.svg?avatarHeight=100) 284 | ![https://opencollective.com/sous-chefs/sponsor/4/website](https://opencollective.com/sous-chefs/sponsor/4/avatar.svg?avatarHeight=100) 285 | ![https://opencollective.com/sous-chefs/sponsor/5/website](https://opencollective.com/sous-chefs/sponsor/5/avatar.svg?avatarHeight=100) 286 | ![https://opencollective.com/sous-chefs/sponsor/6/website](https://opencollective.com/sous-chefs/sponsor/6/avatar.svg?avatarHeight=100) 287 | ![https://opencollective.com/sous-chefs/sponsor/7/website](https://opencollective.com/sous-chefs/sponsor/7/avatar.svg?avatarHeight=100) 288 | ![https://opencollective.com/sous-chefs/sponsor/8/website](https://opencollective.com/sous-chefs/sponsor/8/avatar.svg?avatarHeight=100) 289 | ![https://opencollective.com/sous-chefs/sponsor/9/website](https://opencollective.com/sous-chefs/sponsor/9/avatar.svg?avatarHeight=100) 290 | -------------------------------------------------------------------------------- /spec/libraries/consul_config_v1_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | # require_relative '../../libraries/consul_config_v1' 3 | 4 | # shared_examples 'a removed field' do |field_name| 5 | # it "does not set `#{field_name}`" do 6 | # expect(config[field_name]).to be_nil 7 | # end 8 | # it 'logs a warning' do 9 | # expect(Chef::Log).to receive(:warn).with("Parameter '#{field_name}' is deprecated") 10 | # chef_run 11 | # end 12 | # end 13 | 14 | # describe ConsulCookbook::Resource::ConsulConfigV1 do 15 | # # We have to specify the class here, because `poise_boiler/spec_helper` can't 16 | # # resolve providers with node attributes 17 | # step_into(ConsulCookbook::Resource::ConsulConfigV1) 18 | # let(:chefspec_options) { { platform: 'ubuntu', version: '14.04' } } 19 | 20 | # before do 21 | # recipe = double('Chef::Recipe') 22 | # allow_any_instance_of(Chef::RunContext).to receive(:include_recipe).and_return([recipe]) 23 | # default_attributes['consul'] = { 24 | # 'service_user' => 'consul', 25 | # 'service_group' => 'consul', 26 | # 'service' => { 27 | # 'config_dir' => '/etc/consul/conf.d', 28 | # }, 29 | # 'version' => '1.0', 30 | # } 31 | # end 32 | 33 | # context 'sets options directly' do 34 | # recipe do 35 | # consul_config '/etc/consul/default.json' do 36 | # owner 'root' 37 | # options do 38 | # translate_wan_addrs true 39 | # end 40 | # end 41 | # end 42 | 43 | # it do 44 | # is_expected.to create_directory('/etc/consul/conf.d') 45 | # .with(user: 'root', group: 'consul', mode: '0755') 46 | # end 47 | 48 | # it do 49 | # is_expected.to create_file('/etc/consul/default.json') 50 | # .with(user: 'root', group: 'consul', mode: '0640') 51 | # .with(content: <<-EOH.chomp.gsub(/^ /, '')) 52 | # { 53 | # "translate_wan_addrs": true 54 | # } 55 | # EOH 56 | # end 57 | # end 58 | 59 | # context 'deletes configuration' do 60 | # recipe do 61 | # consul_config '/etc/consul/default.json' do 62 | # action :delete 63 | # end 64 | # end 65 | 66 | # it { is_expected.to delete_file('/etc/consul/default.json') } 67 | # end 68 | 69 | # describe 'parameters' do 70 | # let(:config) { JSON.parse(subject.find_resource('consul_config', '/etc/consul/default.json').params_to_json) } 71 | 72 | # context 'no retry_join parameters' do 73 | # describe 'retry_join_ec2' do 74 | # recipe do 75 | # consul_config '/etc/consul/default.json' do 76 | # retry_join_ec2( 77 | # 'region' => 'ca-central-1', 78 | # 'tag_key' => 'foo', 79 | # 'tag_value' => 'bar', 80 | # 'access_key_id' => 'KEY_ID', 81 | # 'secret_access_key' => 'SECRETS' 82 | # ) 83 | # end 84 | # end 85 | # it 'sets the `retry_join` field' do 86 | # expect( 87 | # config['retry_join'].collect do |item| 88 | # Hash[item.split.map { |pair| pair.split('=') }] 89 | # end 90 | # ).to contain_exactly( 91 | # 'provider' => 'aws', 92 | # 'region' => 'ca-central-1', 93 | # 'tag_key' => 'foo', 94 | # 'tag_value' => 'bar', 95 | # 'access_key_id' => 'KEY_ID', 96 | # 'secret_access_key' => 'SECRETS' 97 | # ) 98 | # end 99 | # it 'does not set the `retry_join_ec2` field' do 100 | # expect(config['retry_join_ec2']).to be_nil 101 | # end 102 | # it 'logs a warning' do 103 | # expect(Chef::Log).to receive(:warn).with('Parameter \'retry_join_ec2\' is deprecated') 104 | # chef_run 105 | # end 106 | # end 107 | # describe 'retry_join_azure' do 108 | # recipe do 109 | # consul_config '/etc/consul/default.json' do 110 | # retry_join_azure( 111 | # 'tag_name' => 'foo', 112 | # 'tag_value' => 'bar', 113 | # 'subscription_id' => 'SUBSCRIPTION_ID', 114 | # 'tenant_id' => 'TENANT_ID', 115 | # 'client_id' => 'CLIENT_ID', 116 | # 'secret_access_key' => 'SECRETS' 117 | # ) 118 | # end 119 | # end 120 | # it 'sets the `retry_join` field' do 121 | # expect( 122 | # config['retry_join'].collect do |item| 123 | # Hash[item.split.map { |pair| pair.split('=') }] 124 | # end 125 | # ).to contain_exactly( 126 | # 'provider' => 'azure', 127 | # 'tag_name' => 'foo', 128 | # 'tag_value' => 'bar', 129 | # 'subscription_id' => 'SUBSCRIPTION_ID', 130 | # 'tenant_id' => 'TENANT_ID', 131 | # 'client_id' => 'CLIENT_ID', 132 | # 'secret_access_key' => 'SECRETS' 133 | # ) 134 | # end 135 | # it 'does not set the `retry_join_azure` field' do 136 | # expect(config['retry_join_azure']).to be_nil 137 | # end 138 | # it 'logs a warning' do 139 | # expect(Chef::Log).to receive(:warn).with('Parameter \'retry_join_azure\' is deprecated') 140 | # chef_run 141 | # end 142 | # end 143 | # end 144 | 145 | # context 'with another retry_join parameter' do 146 | # describe 'retry_join_ec2' do 147 | # recipe do 148 | # consul_config '/etc/consul/default.json' do 149 | # retry_join ['127.0.0.1'] 150 | # retry_join_ec2( 151 | # 'region' => 'ca-central-1', 152 | # 'tag_key' => 'foo', 153 | # 'tag_value' => 'bar' 154 | # ) 155 | # end 156 | # end 157 | # it 'sets the `retry_join` field' do 158 | # expect( 159 | # config['retry_join'].collect do |item| 160 | # Hash[item.split.map { |pair| pair.split('=') }] 161 | # end 162 | # ).to contain_exactly( 163 | # { '127.0.0.1' => nil }, 164 | # { # rubocop:disable Style/BracesAroundHashParameters 165 | # 'provider' => 'aws', 166 | # 'region' => 'ca-central-1', 167 | # 'tag_key' => 'foo', 168 | # 'tag_value' => 'bar', 169 | # } 170 | # ) 171 | # end 172 | # end 173 | # describe 'retry_join_azure' do 174 | # recipe do 175 | # consul_config '/etc/consul/default.json' do 176 | # retry_join ['127.0.0.1'] 177 | # retry_join_azure( 178 | # 'tag_name' => 'foo', 179 | # 'tag_value' => 'bar' 180 | # ) 181 | # end 182 | # end 183 | # it 'sets the `retry_join` field' do 184 | # expect( 185 | # config['retry_join'].collect do |item| 186 | # Hash[item.split.map { |pair| pair.split('=') }] 187 | # end 188 | # ).to contain_exactly( 189 | # { '127.0.0.1' => nil }, 190 | # { # rubocop:disable Style/BracesAroundHashParameters 191 | # 'provider' => 'azure', 192 | # 'tag_name' => 'foo', 193 | # 'tag_value' => 'bar', 194 | # } 195 | # ) 196 | # end 197 | # end 198 | 199 | # describe 'atlas_infrastructure' do 200 | # recipe do 201 | # consul_config '/etc/consul/default.json' do 202 | # atlas_infrastructure 'infra' 203 | # end 204 | # end 205 | # it_should_behave_like 'a removed field', 'atlas_infrastructure' 206 | # end 207 | # describe 'atlas_token' do 208 | # recipe do 209 | # consul_config '/etc/consul/default.json' do 210 | # atlas_token 'token' 211 | # end 212 | # end 213 | # it_should_behave_like 'a removed field', 'atlas_token' 214 | # end 215 | # describe 'atlas_acl_token' do 216 | # recipe do 217 | # consul_config '/etc/consul/default.json' do 218 | # atlas_acl_token 'acl_token' 219 | # end 220 | # end 221 | # it_should_behave_like 'a removed field', 'atlas_acl_token' 222 | # end 223 | # describe 'atlas_join' do 224 | # recipe do 225 | # consul_config '/etc/consul/default.json' do 226 | # atlas_join true 227 | # end 228 | # end 229 | # it_should_behave_like 'a removed field', 'atlas_join' 230 | # end 231 | # describe 'atlas_endpoint' do 232 | # recipe do 233 | # consul_config '/etc/consul/default.json' do 234 | # atlas_endpoint 'endpoint' 235 | # end 236 | # end 237 | # it_should_behave_like 'a removed field', 'atlas_endpoint' 238 | # end 239 | 240 | # describe 'http_api_response_headers' do 241 | # recipe do 242 | # consul_config '/etc/consul/default.json' do 243 | # http_api_response_headers( 244 | # 'Access-Control-Allow-Origin' => '*' 245 | # ) 246 | # end 247 | # end 248 | # it_should_behave_like 'a removed field', 'http_api_response_headers' 249 | # it 'sets the [`http_config`][`response_headers`] field' do 250 | # expect(config['http_config']['response_headers']).to include( 251 | # 'Access-Control-Allow-Origin' => '*' 252 | # ) 253 | # end 254 | # end 255 | 256 | # describe 'recursor' do 257 | # context 'when no `recursors` are set' do 258 | # recipe do 259 | # consul_config '/etc/consul/default.json' do 260 | # recursor '127.0.0.1' 261 | # end 262 | # end 263 | # it_should_behave_like 'a removed field', 'recursor' 264 | # it 'sets the `recursors` field' do 265 | # expect(config['recursors']).to contain_exactly( 266 | # '127.0.0.1' 267 | # ) 268 | # end 269 | # end 270 | # context 'when other `recursors` are set' do 271 | # recipe do 272 | # consul_config '/etc/consul/default.json' do 273 | # recursor '127.0.0.1' 274 | # recursors ['168.192.1.1'] 275 | # end 276 | # end 277 | # it_should_behave_like 'a removed field', 'recursor' 278 | # it 'sets the `recursors` field' do 279 | # expect(config['recursors']).to contain_exactly( 280 | # '127.0.0.1', 281 | # '168.192.1.1' 282 | # ) 283 | # end 284 | # end 285 | # end 286 | 287 | # describe 'statsd_addr' do 288 | # recipe do 289 | # consul_config '/etc/consul/default.json' do 290 | # statsd_addr '127.0.0.1:123' 291 | # end 292 | # end 293 | # it_should_behave_like 'a removed field', 'statsd_addr' 294 | # it 'sets the [`telemetry`][`statsd_address`] field' do 295 | # expect(config['telemetry']['statsd_address']).to eq '127.0.0.1:123' 296 | # end 297 | # end 298 | 299 | # describe 'statsite_addr' do 300 | # recipe do 301 | # consul_config '/etc/consul/default.json' do 302 | # statsite_addr '127.0.0.1:123' 303 | # end 304 | # end 305 | # it_should_behave_like 'a removed field', 'statsite_addr' 306 | # it 'sets the [`telemetry`][`statsite_address`] field' do 307 | # expect(config['telemetry']['statsite_address']).to eq '127.0.0.1:123' 308 | # end 309 | # end 310 | 311 | # describe 'statsite_prefix' do 312 | # recipe do 313 | # consul_config '/etc/consul/default.json' do 314 | # statsite_prefix 'prefix' 315 | # end 316 | # end 317 | # it_should_behave_like 'a removed field', 'statsite_prefix' 318 | # it 'sets the [`telemetry`][`metrics_prefix`] field' do 319 | # expect(config['telemetry']['metrics_prefix']).to eq 'prefix' 320 | # end 321 | # end 322 | 323 | # describe 'discovery_max_stale_unsupported_old_version' do 324 | # before do 325 | # recipe = double('Chef::Recipe') 326 | # allow_any_instance_of(Chef::RunContext).to receive(:include_recipe).and_return([recipe]) 327 | # default_attributes['consul']['version'] = '1.0.4' 328 | # end 329 | # recipe do 330 | # consul_config '/etc/consul/default.json' do 331 | # discovery_max_stale '72h' 332 | # end 333 | # end 334 | # it 'does not set the [`discovery_max_stale`] field since unsupported' do 335 | # expect(config['discovery_max_stale']).to be_nil 336 | # end 337 | # end 338 | 339 | # describe 'discovery_max_stale_supported' do 340 | # before do 341 | # recipe = double('Chef::Recipe') 342 | # allow_any_instance_of(Chef::RunContext).to receive(:include_recipe).and_return([recipe]) 343 | # default_attributes['consul']['version'] = '1.0.7' 344 | # end 345 | # recipe do 346 | # consul_config '/etc/consul/default.json' do 347 | # discovery_max_stale '72h' 348 | # end 349 | # end 350 | # it 'sets the [`discovery_max_stale`] field' do 351 | # expect(config['discovery_max_stale']).to eq '72h' 352 | # end 353 | # end 354 | # end 355 | # end 356 | # end 357 | -------------------------------------------------------------------------------- /libraries/consul_config_v1.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | require_relative 'helpers' 9 | 10 | module ConsulCookbook 11 | module Resource 12 | # @since 1.0 13 | class ConsulConfigV1 < Chef::Resource 14 | include Poise(fused: true) 15 | include ConsulCookbook::Helpers 16 | provides :consul_config do |node| 17 | node['consul']['version'].to_i >= 1 18 | end 19 | 20 | # @!attribute path 21 | # @return [String] 22 | attribute(:path, kind_of: String, name_attribute: true) 23 | # @!attribute owner 24 | # @return [String] 25 | attribute(:owner, kind_of: String, default: lazy { node['consul']['service_user'] }) 26 | # @!attribute group 27 | # @return [String] 28 | attribute(:group, kind_of: String, default: lazy { node['consul']['service_group'] }) 29 | # @!attribute config_dir 30 | # @return [String] 31 | attribute(:config_dir, kind_of: String, default: lazy { node['consul']['service']['config_dir'] }) 32 | # @!attribute config_dir_mode 33 | # @return [String] 34 | attribute(:config_dir_mode, kind_of: String, default: '0755') 35 | # @!attribute options 36 | # @return [Hash] 37 | attribute(:options, option_collector: true) 38 | 39 | # @see: http://www.consul.io/docs/agent/options.html 40 | attribute(:acl, kind_of: [Hash, Mash]) 41 | attribute(:acl_agent_token, kind_of: String) 42 | attribute(:acl_agent_master_token, kind_of: String) 43 | attribute(:acl_datacenter, kind_of: String) 44 | attribute(:acl_default_policy, kind_of: String) 45 | attribute(:acl_down_policy, kind_of: String) 46 | attribute(:acl_enforce_version_8, equal_to: [true, false]) 47 | attribute(:acl_master_token, kind_of: String) 48 | attribute(:acl_replication_token, kind_of: String) 49 | attribute(:acl_token, kind_of: String) 50 | attribute(:acl_ttl, kind_of: String) 51 | attribute(:addresses, kind_of: [Hash, Mash]) 52 | attribute(:advertise_addr, kind_of: String) 53 | attribute(:advertise_addr_ipv4, kind_of: String) 54 | attribute(:advertise_addr_ipv6, kind_of: String) 55 | attribute(:advertise_addr_wan, kind_of: String) 56 | attribute(:atlas_acl_token, kind_of: String) 57 | attribute(:atlas_infrastructure, kind_of: String) 58 | attribute(:atlas_join, equal_to: [true, false]) 59 | attribute(:atlas_token, kind_of: String) 60 | attribute(:atlas_endpoint, kind_of: String) 61 | attribute(:autopilot, kind_of: [Hash, Mash]) 62 | attribute(:auto_encrypt, kind_of: [Hash, Mash]) 63 | attribute(:bind_addr, kind_of: String) 64 | attribute(:bootstrap, equal_to: [true, false]) 65 | attribute(:bootstrap_expect, kind_of: Integer) 66 | attribute(:ca_file, kind_of: String) 67 | attribute(:ca_path, kind_of: String) 68 | attribute(:cert_file, kind_of: String) 69 | attribute(:check_update_interval, kind_of: String) 70 | attribute(:client_addr, kind_of: String) 71 | attribute(:config_entries, kind_of: [Hash, Mash]) 72 | attribute(:connect, kind_of: [Hash, Mash]) 73 | attribute(:data_dir, kind_of: String) 74 | attribute(:datacenter, kind_of: String) 75 | attribute(:disable_anonymous_signature, equal_to: [true, false]) 76 | attribute(:disable_host_node_id, equal_to: [true, false]) 77 | attribute(:disable_keyring_file, equal_to: [true, false]) 78 | attribute(:disable_remote_exec, equal_to: [true, false]) 79 | attribute(:disable_update_check, equal_to: [true, false]) 80 | attribute(:discard_check_output, equal_to: [true, false]) 81 | attribute(:discovery_max_stale, kind_of: String) 82 | attribute(:dns_config, kind_of: [Hash, Mash]) 83 | attribute(:domain, kind_of: String) 84 | attribute(:enable_acl_replication, equal_to: [true, false]) 85 | attribute(:enable_agent_tls_for_checks, equal_to: [true, false]) 86 | attribute(:enable_central_service_config, equal_to: [true, false]) 87 | attribute(:enable_debug, equal_to: [true, false]) 88 | attribute(:enable_local_script_checks, equal_to: [true, false]) 89 | attribute(:enable_script_checks, equal_to: [true, false]) 90 | attribute(:enable_syslog, equal_to: [true, false]) 91 | attribute(:encrypt, kind_of: String) 92 | attribute(:encrypt_verify_incoming, equal_to: [true, false]) 93 | attribute(:encrypt_verify_outgoing, equal_to: [true, false]) 94 | attribute(:gossip_lan, kind_of: [Hash, Mash]) 95 | attribute(:gossip_wan, kind_of: [Hash, Mash]) 96 | attribute(:http_api_response_headers, kind_of: [Hash, Mash]) 97 | attribute(:http_config, kind_of: [Hash, Mash]) 98 | attribute(:key_file, kind_of: String) 99 | attribute(:leave_on_terminate, equal_to: [true, false]) 100 | attribute(:limits, kind_of: [Hash, Mash]) 101 | attribute(:log_file, kind_of: String) 102 | attribute(:log_level, equal_to: %w(INFO DEBUG WARN ERR)) 103 | attribute(:log_rotate_duration, kind_of: String) 104 | attribute(:log_rotate_bytes, kind_of: Integer) 105 | attribute(:log_rotate_max_files, kind_of: Integer) 106 | attribute(:metrics_prefix, kind_of: String) 107 | attribute(:node_id, kind_of: String) 108 | attribute(:node_name, kind_of: String) 109 | attribute(:node_meta, kind_of: [Hash, Mash]) 110 | attribute(:non_voting_server, equal_to: [true, false]) 111 | attribute(:performance, kind_of: [Hash, Mash]) 112 | attribute(:ports, kind_of: [Hash, Mash]) 113 | attribute(:primary_datacenter, kind_of: String) 114 | attribute(:protocol, kind_of: String) 115 | attribute(:raft_protocol, kind_of: Integer) 116 | attribute(:raft_snapshot_interval, kind_of: String) 117 | attribute(:raft_snapshot_threshold, kind_of: Integer) 118 | attribute(:raft_trailing_logs, kind_of: Integer) 119 | attribute(:reap, equal_to: [true, false]) 120 | attribute(:reconnect_timeout, kind_of: String) 121 | attribute(:reconnect_timeout_wan, kind_of: String) 122 | attribute(:recursor, kind_of: String) 123 | attribute(:recursors, kind_of: Array) 124 | attribute(:retry_interval, kind_of: String) 125 | attribute(:retry_interval_wan, kind_of: String) 126 | attribute(:retry_join, kind_of: Array) 127 | attribute(:retry_join_azure, kind_of: [Hash, Mash]) 128 | attribute(:retry_join_ec2, kind_of: [Hash, Mash]) 129 | attribute(:retry_join_wan, kind_of: Array) 130 | attribute(:retry_max, kind_of: Integer) 131 | attribute(:rejoin_after_leave, equal_to: [true, false]) 132 | attribute(:segment, kind_of: String) 133 | attribute(:segments, kind_of: Array) 134 | attribute(:serf_lan, kind_of: String) 135 | attribute(:serf_wan, kind_of: String) 136 | attribute(:serf_lan_bind, kind_of: String) 137 | attribute(:serf_wan_bind, kind_of: String) 138 | attribute(:server, equal_to: [true, false]) 139 | attribute(:server_name, kind_of: String) 140 | attribute(:session_ttl_min, kind_of: String) 141 | attribute(:skip_leave_on_interrupt, equal_to: [true, false]) 142 | attribute(:start_join, kind_of: Array) 143 | attribute(:start_join_wan, kind_of: Array) 144 | attribute(:statsd_addr, kind_of: String) 145 | attribute(:statsd_address, kind_of: String) 146 | attribute(:statsite_addr, kind_of: String) 147 | attribute(:statsite_address, kind_of: String) 148 | attribute(:statsite_prefix, kind_of: String) 149 | attribute(:telemetry, kind_of: [Hash, Mash]) 150 | attribute(:syslog_facility, kind_of: String) 151 | attribute(:tls_cipher_suites, kind_of: String) 152 | attribute(:tls_min_version, equal_to: %w(tls10 tls11 tls12)) 153 | attribute(:tls_prefer_server_cipher_suites, equal_to: [true, false]) 154 | attribute(:translate_wan_addrs, equal_to: [true, false]) 155 | attribute(:ui, equal_to: [true, false]) 156 | attribute(:ui_dir, kind_of: String) 157 | attribute(:unix_sockets, kind_of: [Hash, Mash]) 158 | attribute(:verify_incoming, equal_to: [true, false]) 159 | attribute(:verify_incoming_https, equal_to: [true, false]) 160 | attribute(:verify_incoming_rpc, equal_to: [true, false]) 161 | attribute(:verify_outgoing, equal_to: [true, false]) 162 | attribute(:verify_server_hostname, equal_to: [true, false]) 163 | attribute(:watches, kind_of: [Hash, Mash]) 164 | 165 | # Transforms the resource into a JSON format which matches the 166 | # Consul service's configuration format. 167 | def params_to_json 168 | for_keeps = %i( 169 | acl 170 | acl_agent_token 171 | acl_agent_master_token 172 | acl_datacenter 173 | acl_default_policy 174 | acl_down_policy 175 | acl_enforce_version_8 176 | acl_master_token 177 | acl_replication_token 178 | acl_token 179 | acl_ttl 180 | addresses 181 | advertise_addr 182 | advertise_addr_ipv4 183 | advertise_addr_ipv6 184 | advertise_addr_wan 185 | autopilot 186 | auto_encrypt 187 | bind_addr 188 | check_update_interval 189 | client_addr 190 | config_entries 191 | connect 192 | data_dir 193 | datacenter 194 | disable_anonymous_signature 195 | disable_host_node_id 196 | disable_keyring_file 197 | disable_remote_exec 198 | disable_update_check 199 | discard_check_output 200 | dns_config 201 | domain 202 | enable_acl_replication 203 | enable_central_service_config 204 | enable_debug 205 | enable_local_script_checks 206 | enable_script_checks 207 | enable_syslog 208 | encrypt 209 | encrypt_verify_incoming 210 | encrypt_verify_outgoing 211 | gossip_lan 212 | gossip_wan 213 | http_config 214 | leave_on_terminate 215 | limits 216 | log_file 217 | log_level 218 | log_rotate_duration 219 | log_rotate_bytes 220 | log_rotate_max_files 221 | node_id 222 | node_meta 223 | node_name 224 | non_voting_server 225 | performance 226 | ports 227 | primary_datacenter 228 | protocol 229 | reap 230 | raft_protocol 231 | raft_snapshot_interval 232 | raft_snapshot_threshold 233 | raft_trailing_logs 234 | reconnect_timeout 235 | reconnect_timeout_wan 236 | recursors 237 | rejoin_after_leave 238 | retry_interval 239 | retry_interval_wan 240 | retry_join 241 | retry_join_wan 242 | retry_max 243 | segment 244 | segments 245 | serf_lan 246 | serf_wan 247 | serf_lan_bind 248 | serf_wan_bind 249 | server 250 | server_name 251 | session_ttl_min 252 | skip_leave_on_interrupt 253 | start_join 254 | start_join_wan 255 | syslog_facility 256 | telemetry 257 | tls_cipher_suites 258 | tls_min_version 259 | tls_prefer_server_cipher_suites 260 | translate_wan_addrs 261 | ui 262 | ui_dir 263 | unix_sockets 264 | verify_incoming 265 | verify_incoming_https 266 | verify_incoming_rpc 267 | verify_outgoing 268 | verify_server_hostname 269 | watches 270 | ) 271 | 272 | for_keeps << %i(discovery_max_stale) if node['consul']['version'] > '1.0.6' 273 | for_keeps << %i(bootstrap bootstrap_expect) if server 274 | for_keeps << %i(ca_file ca_path cert_file enable_agent_tls_for_checks key_file) if tls? 275 | for_keeps = for_keeps.flatten 276 | 277 | raw_config = to_hash 278 | 279 | if raw_config[:retry_join_ec2] 280 | Chef::Log.warn("Parameter 'retry_join_ec2' is deprecated") 281 | join_string = consul_cloud_join_string('aws', retry_join_ec2) 282 | existing_retry_join = raw_config[:retry_join] 283 | raw_config[:retry_join] = if existing_retry_join.nil? 284 | [join_string] 285 | else 286 | existing_retry_join.clone << join_string 287 | end 288 | end 289 | if raw_config[:retry_join_azure] 290 | Chef::Log.warn("Parameter 'retry_join_azure' is deprecated") 291 | join_string = consul_cloud_join_string('azure', retry_join_azure) 292 | existing_retry_join = raw_config[:retry_join] 293 | raw_config[:retry_join] = if existing_retry_join.nil? 294 | [join_string] 295 | else 296 | existing_retry_join.clone << join_string 297 | end 298 | end 299 | [:atlas_infrastructure, :atlas_token, :atlas_acl_token, :atlas_join, :atlas_endpoint].each do |field| 300 | if raw_config[field] 301 | Chef::Log.warn("Parameter '#{field}' is deprecated") 302 | end 303 | end 304 | if raw_config[:http_api_response_headers] 305 | Chef::Log.warn("Parameter 'http_api_response_headers' is deprecated") 306 | raw_config[:http_config] = { 307 | 'response_headers' => raw_config[:http_api_response_headers], 308 | } 309 | end 310 | if raw_config[:recursor] 311 | Chef::Log.warn("Parameter 'recursor' is deprecated") 312 | existing_recursors = raw_config[:recursors] 313 | raw_config[:recursors] = if existing_recursors.nil? 314 | [raw_config[:recursor]] 315 | else 316 | existing_recursors.clone << raw_config[:recursor] 317 | end 318 | end 319 | { 320 | statsd_addr: :statsd_address, 321 | statsite_addr: :statsite_address, 322 | statsite_prefix: :metrics_prefix, 323 | }.each do |field, replacement| 324 | next unless raw_config[field] 325 | Chef::Log.warn("Parameter '#{field}' is deprecated") 326 | raw_config[:telemetry] ||= {} 327 | raw_config[:telemetry][replacement] = raw_config[field] 328 | end 329 | 330 | # Filter out undefined attributes and keep only those listed above 331 | config = raw_config.keep_if do |k, v| 332 | !v.nil? && for_keeps.include?(k.to_sym) 333 | end.merge(options) 334 | JSON.pretty_generate(Hash[config.sort_by { |k, _| k.to_s }], quirks_mode: true) 335 | end 336 | 337 | def tls? 338 | verify_incoming || verify_outgoing 339 | end 340 | 341 | def consul_cloud_join_string(provider, values) 342 | "provider=#{provider} " << values.collect { |k, v| "#{k}=#{v}" }.join(' ') 343 | end 344 | 345 | action(:create) do 346 | notifying_block do 347 | [::File.dirname(new_resource.path), new_resource.config_dir].each do |dir| 348 | directory dir do 349 | recursive true 350 | unless platform?('windows') 351 | owner new_resource.owner 352 | group new_resource.group 353 | mode new_resource.config_dir_mode 354 | end 355 | not_if { dir == '/etc' } 356 | end 357 | end 358 | 359 | file new_resource.path do 360 | unless platform?('windows') 361 | owner new_resource.owner 362 | group new_resource.group 363 | mode '0640' 364 | end 365 | content new_resource.params_to_json 366 | sensitive true 367 | end 368 | end 369 | end 370 | 371 | action(:delete) do 372 | notifying_block do 373 | file new_resource.path do 374 | action :delete 375 | end 376 | end 377 | end 378 | end 379 | end 380 | end 381 | -------------------------------------------------------------------------------- /libraries/consul_installation_binary.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook: consul 3 | # License: Apache 2.0 4 | # 5 | # Copyright:: 2014-2016, Bloomberg Finance L.P. 6 | # 7 | require 'poise' 8 | require_relative './helpers' 9 | 10 | module ConsulCookbook 11 | module Provider 12 | # A `consul_installation` provider which manages Consul binary 13 | # installation from remote source URL. 14 | # @action create 15 | # @action remove 16 | # @provides consul_installation 17 | # @example 18 | # consul_installation '0.5.0' 19 | # @since 2.0 20 | class ConsulInstallationBinary < Chef::Provider 21 | include Poise(inversion: :consul_installation) 22 | include ::ConsulCookbook::Helpers 23 | provides(:binary) 24 | inversion_attribute('consul') 25 | 26 | # @api private 27 | def self.provides_auto?(_node, _resource) 28 | true 29 | end 30 | 31 | # Set the default inversion options. 32 | # @return [Hash] 33 | # @api private 34 | def self.default_inversion_options(node, resource) 35 | extract_path = node.platform_family?('windows') ? node.config_prefix_path : '/opt/consul' 36 | super.merge(extract_to: extract_path, 37 | version: resource.version, 38 | archive_url: 'https://releases.hashicorp.com/consul/%{version}/%{basename}', 39 | archive_basename: binary_basename(node, resource), 40 | archive_checksum: binary_checksum(node, resource)) 41 | end 42 | 43 | action :create do 44 | notifying_block do 45 | directory join_path(options[:extract_to], new_resource.version) do 46 | mode '0755' 47 | recursive true 48 | end 49 | 50 | url = format(options[:archive_url], version: options[:version], basename: options[:archive_basename]) 51 | poise_archive url do # cookstyle: disable ChefDeprecations/PoiseArchiveUsage 52 | destination join_path(options[:extract_to], new_resource.version) 53 | source_properties checksum: options[:archive_checksum] 54 | strip_components 0 55 | not_if { ::File.exist?(consul_program) } 56 | end 57 | 58 | link '/usr/local/bin/consul' do 59 | to ::File.join(options[:extract_to], new_resource.version, 'consul') 60 | not_if { windows? } 61 | end 62 | 63 | link "#{node.config_prefix_path}\\consul.exe" do 64 | to ::File.join(options[:extract_to], new_resource.version, 'consul.exe') 65 | only_if { windows? } 66 | end 67 | windows_path node.config_prefix_path do 68 | action :add 69 | only_if { windows? } 70 | end 71 | end 72 | end 73 | 74 | action :remove do 75 | notifying_block do 76 | directory join_path(options[:extract_to], new_resource.version) do 77 | action :delete 78 | recursive true 79 | end 80 | end 81 | end 82 | 83 | def consul_program 84 | @program ||= join_path(options[:extract_to], new_resource.version, 'consul') 85 | windows? ? @program + '.exe' : @program 86 | end 87 | 88 | def self.binary_basename(node, resource) 89 | case node['kernel']['machine'] 90 | when 'x86_64', 'amd64' then ['consul', resource.version, node['os'], 'amd64'].join('_') 91 | when /i\d86/ then ['consul', resource.version, node['os'], '386'].join('_') 92 | when /^arm/ then ['consul', resource.version, node['os'], 'arm'].join('_') 93 | when 'aarch64' then ['consul', resource.version, node['os'], 'arm64'].join('_') 94 | else ['consul', resource.version, node['os'], node['kernel']['machine']].join('_') 95 | end.concat('.zip') 96 | end 97 | 98 | def self.binary_checksum(node, resource) 99 | tag = node['kernel']['machine'] =~ /x86_64/ ? 'amd64' : node['kernel']['machine'] 100 | case [node['os'], tag].join('-') 101 | when 'darwin-amd64' 102 | case resource.version 103 | when '0.5.0' then '24d9758c873e9124e0ce266f118078f87ba8d8363ab16c2e59a3cd197b77e964' 104 | when '0.5.1' then '161f2a8803e31550bd92a00e95a3a517aa949714c19d3124c46e56cfdc97b088' 105 | when '0.5.2' then '87be515d7dbab760a61a359626a734f738d46ece367f68422b7dec9197d9eeea' 106 | when '0.6.0' then '29ddff01368458048731afa586cec5426c8033a914b43fc83d6442e0a522c114' 107 | when '0.6.1' then '358654900772b3477497f4a5b5a841f2763dc3062bf29212606a97f5a7a675f3' 108 | when '0.6.2' then '3089f77fcdb922894456ea6d0bc78a2fb60984d1d3687fa9d8f604b266c83446' 109 | when '0.6.3' then '6dff4ffc61d66aacd627a176737b8725624718a9e68cc81460a3df9b241c7932' 110 | when '0.6.4' then '75422bbd26107cfc5dfa7bbb65c1d8540a5193796b5c6b272d8d70b094b26488' 111 | when '0.7.0' then '74111674527c5be0db7a98600df8290395abdd94e2cd86bda7418d748413396d' 112 | when '0.7.1' then '9b3a199779a0d9b92266fae2abd7ed91c18ba437eba46e76114cd1940b3b7741' 113 | when '0.7.2' then '4403357fbfddbcdd0742946cab7856638cb0f15898c75d79d155753621d60b0c' 114 | when '0.7.3' then '08dd9af590a6c6ecd629e532083bd898b42c6425d08aa9f62b8f090a6dd65826' 115 | when '0.7.4' then 'a66cd4efdff376e7fd5c22d2710d7ef6734562c46df80a05e3144222965d9a97' 116 | when '0.7.5' then '60c1685bfbefe55d0ac67f37d4bc88752a204609221d0cc4425452f1ffc2e42b' 117 | when '0.8.1' then 'd4d4e865e760df499a3ea884fa73ff5fc11a42ce23108c5ce0eb0eb80dafcb75' 118 | when '0.8.2' then '63de23c7de3b9b48592353f8427ad40a9dd28da51a66c9f3759f5fdfd6aad136' 119 | when '0.8.3' then '97102020bd3638e98d65633d0e51d425168de17a53f12d566585663a2a19905f' 120 | when '0.8.4' then '913f0e5b2af39f09c6579035d0bb7f002589377bd86e2e886df37f1ecbc25906' 121 | when '0.8.5' then 'cfef78ea3c0b28afe51f7ef0f7663e323398f3311ae4ce760546854991dbdb16' 122 | when '0.9.0' then '0bf2cee6d7c8eb3ce5bb6a868907d845a0b9fd83655146b9dd17dc530e6a7ef5' 123 | when '0.9.1' then 'd035207b84c8bd4cdc252b01053d2126b6194e7961a130b11407e6e3a66e4b0e' 124 | when '0.9.2' then 'e54ef843a82defe4fff9042c5ba953ba6fb510241253fe51d410cf0f95e86d9f' 125 | when '0.9.3' then 'd5ee6bd737463f211c0e99f71b33c5382dcad27ecf13affb82e4c1314bd8656a' 126 | end 127 | when 'darwin-i386' 128 | case resource.version 129 | when '0.6.0' then '95d57bfcc287bc344ec3ae8372cf735651af1158c5b1345e6f30cd9a9c811815' 130 | when '0.6.1' then '41dfcc0aefe0a60bdde413eaa8a4a0c98e396d6b438494f1cf29b32d07759b8e' 131 | when '0.6.2' then '973105816261c8001fcfa76c9fb707fa56325460476fb0daa97b9ece0602a918' 132 | when '0.6.3' then '7fb30756504cd9559c9b23e5d0d8d73a847ee62ed85d39955b5906c2f59a5bc1' 133 | when '0.6.4' then '4cd39e968ca6bed0888f831a2fc438ffe0b48dab863c822e777f5b5219bacf5c' 134 | when '0.7.0' then '16ab91969c9b268ccae532070221b6c4fecaad298e4662a46cdfe9847c80dd3f' 135 | when '0.7.1' then '668b0a5c577fc717de710391fa509a820c5640d73ab3b232023fc351e6084c36' 136 | when '0.7.2' then '23e6e8dd14c2be02fd095a865edd1725f5ccdbce1109ad5f70832866012d1d7f' 137 | when '0.7.3' then 'cf369542e30c5aa22967459b25fec284284d292ff25e801bdcd1a5f37f1a5143' 138 | when '0.7.4' then '7638e80c9db050ef8d63bad3baa338985da1a1bd4657f3b2fc4222d105c673a3' 139 | when '0.7.5' then '9ff8798a94bab99fb2387afae5bc0fc2844a304675abbceb9315292019a8f582' 140 | when '0.8.1' then '7e07930dd7085db838feabc8cdf0a9e6668eddc1b7db6496192a966a9548e447' 141 | when '0.8.2' then '43de9c5f42e548ca821e2a786988ca5f98b506ae8c26e3e3d3159935f775809c' 142 | when '0.8.3' then '6a088c90282c1aa45f727357631c377dd1370169d2baa79f317256caf3fd5230' 143 | when '0.8.4' then '3e635e6d961ebbeaecbd651e22dc009445d5c13402d2c127eb71f8a69eb41cea' 144 | when '0.8.5' then '2f13a389405724597c97d3e738fcd3350512f3417a5520c21f9b36d06765c4e1' 145 | when '0.9.0' then 'a7b9fac9429e35b393d9f7e7b3b65dccb920a5dc3589c600ed6aa552ab30e8c8' 146 | when '0.9.1' then '4d20daad49708f17515d5d2b89c731d88253f53b8aa578fca7e7c135be6603a2' 147 | when '0.9.2' then '0d76287f9ba23b5dca2e449b1fff6fa95ebd4c2420014d6bbb80fb9557a62455' 148 | when '0.9.3' then 'bcca7910f661628baba1ecc44ca90cbb6ed1b8ccad016fa29784f047dcbadda0' 149 | end 150 | when 'solaris-amd64' 151 | case resource.version 152 | when '0.6.2' then 'f5655f0b173e5d51c5b92327d1fc7f24ac0939897a1966da09146e4eb75af9d1' 153 | when '0.6.3' then 'e6a286ff17a2345b8800732850eadb858b3dba9486355e1164a774ccec2f0e98' 154 | when '0.6.4' then 'c26a64310f83c3ba388c78d5b89d640d961ae9eabe221c244bfffcfa753966bd' 155 | when '0.7.0' then '0f1db173a95861bc84940b4dcdb2debfbfbc18f2b50e651d0e23dfda331018ea' 156 | when '0.7.1' then '9c77c5c904dce4832b9e7dede2cdc5f42f5fb360885583bc414fee868aed5cb9' 157 | when '0.7.2' then 'f1ccaf9d9dd62544323e130cee7221df2a6d4b577e9e4a120db357e59782f12d' 158 | when '0.7.3' then '49b13f83f8099537e72adc1bb34b6cb70b3699aa10245db4b8ef1f48c6e0b007' 159 | when '0.7.4' then '0300ffa4d1007b00bca37112cf934d3e281afdc300ce336735bbf3a33ebcfc19' 160 | when '0.7.5' then 'aa3705a958d0403e2ddacaa75c1d3ede5f290b0ef3a60e6e24976f2d8f32d840' 161 | when '0.8.1' then '67862913dae4dd968e7219aec2122d6e7a20dd42537e137c648d0a53f17c4c9e' 162 | when '0.8.2' then '393f11abaa19c39b122049c3d8672d672c23ec6916a11234470aa5002c8cd4f7' 163 | when '0.8.3' then '22a5b2eb89f6492a54e42fafee958f862baa6962d52d45974c77f62951b4ba0e' 164 | when '0.8.4' then '3d54a8af4b7de9d83b49019399ff2f69b45a35b94b5a0f9f82dda944e9ce48be' 165 | when '0.8.5' then '6d05136a9c64a6ec6bffc10f3c8e57e944930eeb609dedd7e6c5f81fc81e1eee' 166 | when '0.9.0' then '859d5282549bdf3e38ff24c3c33c0a2b9a4016f58fc4050ae64e6bf75e110d0d' 167 | when '0.9.1' then '7d93e50bbf0abd1fc2cac141981c3b476c9cba3b303b37f08f35977241b0716a' 168 | when '0.9.2' then '1e07fe2f71afee5c581c9b1e9a836c91ce4bbec5003d9c3fe426995d2233def8' 169 | when '0.9.3' then '75dffd24347397fb4abf82291486ff8773f53dc48725ac5a78accba492f8cf38' 170 | end 171 | when 'windows-amd64' 172 | case resource.version 173 | when '0.6.0' then '182beea0d8d346a9bfd70679621a5542aeeeea1f35be81fa3d3aeec2479bac3d' 174 | when '0.6.1' then '2be6b0f0fdebff00aea202e9846131af570676f52e2936728cbf29ffbb02f57f' 175 | when '0.6.2' then 'df3234fb7def7138b7cb8c73fe7c05942ec1e485925701a7b38fc7e2396a212f' 176 | when '0.6.3' then '04cd1fdc9cd3a27ffc64e312e40142db7af0d240608f8080ec6d238294b20652' 177 | when '0.6.4' then '1ca3cc2943b27ec8968665efce1122d4ea355ccbde5b4807753af71f11190a9b' 178 | when '0.7.0' then 'ac5973a58dd9c6f52c784a7106a29adcf7c94015036538155b6c0ee7efc3a330' 179 | when '0.7.1' then '71a4e073cbab336c0becb5c17a0173fdae56480558564138353dc0b89e989d82' 180 | when '0.7.2' then '7a5ec31018328a3764f22327c940765c9cd99e57c6759fc43fbfed8318d5e379' 181 | when '0.7.3' then 'ec80a931603bf585704e338e6cb497af9aa58ebdae5e3442a3f78f7027d80b66' 182 | when '0.7.4' then 'c2e071ebae166d4cfdf894966b2966026cf9175d394001704f68bcbccaa8e446' 183 | when '0.7.5' then '6cc64b1bb949f926d403e0436d02bf740844cf268076cf6d3d345361c1aa5293' 184 | when '0.8.1' then 'ea5475b9421dc93383480c622936203eb1b457ff6c96a11e10d65f1aaa061bff' 185 | when '0.8.2' then 'e3def6d26c26937a5c33327ff2884322aa12bdd29235335d877864e05a12fb52' 186 | when '0.8.3' then '9fea45cbe7e55bb94b3d7fb4c8f0527ba36c79029eb1369ace0d45d9546d158a' 187 | when '0.8.4' then 'd320721e5e65bbf96d83feeb7f7dead5b498ff21771e31ea4553adb11299b4fa' 188 | when '0.8.5' then '9ccccea7ecbbfedbceb0a6a0e4bbf3c07bcac14334c69faac42e5a9026471161' 189 | when '0.9.0' then 'c2bd9906b61290fa01ac236393cbfe2f3aadeec4b9ceee724c99217ccb1061ff' 190 | when '0.9.1' then '74bc3b131411fbcdc650542b94daa28838619c086617ab75c78504eef507e781' 191 | when '0.9.2' then '1126fc9841e295dfbdf0d4180336452545eee75b3f545d1c6a105ae8a8020a3c' 192 | when '0.9.3' then '7aaf4d944f77f1f4f9efee0b5e2f28fbd89a83f3fc65ee5dd68a998d6ecda08e' 193 | end 194 | when 'windows-i386' 195 | case resource.version 196 | when '0.5.0' then '7fd760ee8a5c2756391cacc1e924ae602b16cdad838db068e564f798383ad714' 197 | when '0.5.1' then 'bb9e1753cf793ad6f9db34bd6e18fb0fa5b0696a8a51a7f1c61484386dfe6682' 198 | when '0.5.2' then '2e866812de16f1a6138a0fd1eebc76143f1314826e3b52597a55ac510ae94be6' 199 | when '0.6.0' then '8379afd07668933c120880bba8228277e380abb14e07a6c45b94562ac19b37bd' 200 | when '0.6.1' then '10197d1f7be0d0087414c9965008ddd88e9fcd9ac9d5bd02d72d65eda36f5834' 201 | when '0.6.2' then 'f072d89c098dde143897e653d5adaf23125b58062344ef4be4029d635f959654' 202 | when '0.6.3' then '55733a730c5055d0ed1dc2656b2b6a27b21c7c361a907919cfae90aab2dff870' 203 | when '0.6.4' then '6555f0fff6c3f9ea310c94a73365d9892afc255efb47c85041ad1c0ede854b87' 204 | when '0.7.0' then 'd0ddfe7d1de9879f02b0d110e45bb74cd5028a2910bcac8b2629d0659367cd96' 205 | when '0.7.1' then 'ad7b76ac8660c7417bbdccbe1905942fa2fcc4c53a093d7b2d64497bdf4fc315' 206 | when '0.7.2' then 'c041dc43995df3505d9146e3a2f532bfc491c49fb644bd1e2ceead7d7dc3011c' 207 | when '0.7.3' then '87a7169bd5298e179a3bbd2f30b3447c09023dc771c97d083779090655bf0a5f' 208 | when '0.7.4' then 'ede957f736758a40fb8e3e33eb423a71226db46085fe1507d880a0ce393e9658' 209 | when '0.7.5' then '7ea88aa53026cb14bab6a68d5b64c43515ea39552594ae399978fc13bcd74707' 210 | when '0.8.1' then '175b63438846fbf800394d00cba1f966c16e967c3ebbf99cf8f3df8fa14ca84f' 211 | when '0.8.2' then '98d840c42e255e1d6011e601bcb1a86b0133e381ce836b4d97e92d9d3c882c8b' 212 | when '0.8.3' then 'c9a6f92b34eab0ceec854830af4c906339737c0df0f4875c03da9ac7031fe56e' 213 | when '0.8.4' then '419d81b6ec7c1f94f495a43becdca7243b33ca9168d6107e3475d5e7c5392f48' 214 | when '0.8.5' then '13b42f20bed1028deec9e272698b65866e187fe26b13b2735f0238b37a1d44b5' 215 | when '0.9.0' then '2ec01564ab08d213169caa1a8e2beb329f7f45339d43e231e0f2b77159568f3c' 216 | when '0.9.1' then '047117cc205b4541162177684fc1b0f1e6f62b7cae7ea0a3c17ce85afd82df97' 217 | when '0.9.2' then 'cdeca9c765e6f43543b229c95c5376b9d60c8ab3b1a790ba94b80cfa1b41ffd5' 218 | when '0.9.3' then '2596b26b084b7a91ca1c165419fe81af5058e82bc1f88bffe23402e61813cdbe' 219 | end 220 | when 'linux-amd64' 221 | case resource.version 222 | when '0.5.0' then '161f2a8803e31550bd92a00e95a3a517aa949714c19d3124c46e56cfdc97b088' 223 | when '0.5.1' then '967ad75865b950698833eaf26415ba48d8a22befb5d4e8c77630ad70f251b100' 224 | when '0.5.2' then '171cf4074bfca3b1e46112105738985783f19c47f4408377241b868affa9d445' 225 | when '0.6.0' then '307fa26ae32cb8732aed2b3320ed8daf02c28b50d952cbaae8faf67c79f78847' 226 | when '0.6.1' then 'dbb3c348fdb7cdfc03e5617956b243c594a399733afee323e69ef664cdadb1ac' 227 | when '0.6.2' then '7234eba9a6d1ce169ff8d7af91733e63d8fc82193d52d1b10979d8be5c959095' 228 | when '0.6.3' then 'b0532c61fec4a4f6d130c893fd8954ec007a6ad93effbe283a39224ed237e250' 229 | when '0.6.4' then 'abdf0e1856292468e2c9971420d73b805e93888e006c76324ae39416edcf0627' 230 | when '0.7.0' then 'b350591af10d7d23514ebaa0565638539900cdb3aaa048f077217c4c46653dd8' 231 | when '0.7.1' then '5dbfc555352bded8a39c7a8bf28b5d7cf47dec493bc0496e21603c84dfe41b4b' 232 | when '0.7.2' then 'aa97f4e5a552d986b2a36d48fdc3a4a909463e7de5f726f3c5a89b8a1be74a58' 233 | when '0.7.3' then '901a3796b645c3ce3853d5160080217a10ad8d9bd8356d0b73fcd6bc078b7f82' 234 | when '0.7.4' then '23a61773bee9b29198cc1f8fe2e62c320f82f95006ff70840c15c1e58eead73b' 235 | when '0.7.5' then '40ce7175535551882ecdff21fdd276cef6eaab96be8a8260e0599fadb6f1f5b8' 236 | when '0.8.1' then '74cdd7ad458aa63192222ad2bd14178fc3596d4fd64d12a80520d4e6f93eaf34' 237 | when '0.8.2' then '6409336d15baea0b9f60abfcf7c28f7db264ba83499aa8e7f608fb0e273514d9' 238 | when '0.8.3' then 'f894383eee730fcb2c5936748cc019d83b220321efd0e790dae9a3266f5d443a' 239 | when '0.8.4' then 'c8859a0a34c50115cdff147f998b2b63226f5f052e50f342209142420d1c2668' 240 | when '0.8.5' then '35dc317c80862c306ea5b1d9bc93709483287f992fd0797d214d1cc1848e7b62' 241 | when '0.9.0' then '33e54c7d9a93a8ce90fc87f74c7f787068b7a62092b7c55a945eea9939e8577f' 242 | when '0.9.1' then 'e997b87e70dc0f4996d7c5ac89f4776a8569ca99c00e5c8b8a0e0eb1042a9d30' 243 | when '0.9.2' then '0a2921fc7ca7e4702ef659996476310879e50aeeecb5a205adfdbe7bd8524013' 244 | when '0.9.3' then '9c6d652d772478d9ff44b6decdd87d980ae7e6f0167ad0f7bd408de32482f632' 245 | end 246 | when 'linux-i386' 247 | case resource.version 248 | when '0.5.0' then '4b6147c30596a30361d4753d409f8a1af9518f54f5ed473a4c4ac973738ac0fd' 249 | when '0.5.1' then 'dad93a02c01de885daee191bcc5a05ca2bf106200da61db33694a658432d8399' 250 | when '0.5.2' then '29306ce398109f954ceeea3af79878be4fb0d949f8af3a27c95ccef2101e8f60' 251 | when '0.6.0' then 'f58f3f03a8b48d89bb8be94a6d1767393ad2a410c920b064066e01c7fa24f06c' 252 | when '0.6.1' then '34b8d4a2a9ec85082b6e93c6785ba9c54663fec414062e45dd4386db46a533c4' 253 | when '0.6.2' then '500ac8c75768b7f2d63521d2501ff8cc5fb7f9ddf6c550e9449364810c96f419' 254 | when '0.6.3' then '2afb65383ab913344daaa9af827c1e8576c7cae16e93798048122929b6e4cc92' 255 | when '0.6.4' then 'dbaf5ad1c95aa7dce1625d61b6686d3775e53cb3e7d6c426d29ea96622d248a8' 256 | when '0.7.0' then 'babf618b1f10455b4ab65b91bdf5d5a7be5bfbb874ce41e8051caca884c43378' 257 | when '0.7.1' then '7a391a9adc251a5889405eab5512668b77e6ac0f7d818852928735fa82e8abad' 258 | when '0.7.2' then '43b22bcd04e74445c3ea6c143b3acbfe5546d6792c28d123ef5832cd8f96162f' 259 | when '0.7.3' then 'b15e96a1b5833b08d785d67b8f2465a9a0185e34149855943717dd818b347750' 260 | when '0.7.4' then '7fe40af0825b2c6ab6c7e4e3e7d68471cccbd54f9a1513ad622b832cfda5fa07' 261 | when '0.7.5' then '8abf0189776ecc5c8746e12021b6cfe6d96e0b4689ce4a4948b7e3faa07f3025' 262 | when '0.8.1' then '76b4a6a39a3299ceb9228bc5e37a6b8a968dc2635a9d72030a047ccff0388886' 263 | when '0.8.2' then 'f60237e24e4f03d8f7fd8a4e31cb246c701c41beb7cb7d1735320a5aa0b331c8' 264 | when '0.8.3' then 'f4c6cdf82de7aacbac1590d46f755ddb4861894cc78753a9b29ef351abaa748c' 265 | when '0.8.4' then 'e58abbfedc4bebb66476448ec0fccda37be1c911c05017d7cd597db6384cd531' 266 | when '0.8.5' then 'b1a7c51834178e1cdc38e7377789c23452b77e1861cfedfc2601fa78e914e46d' 267 | when '0.9.0' then '7f4c537ef333ed93d934b7e0bdb3b16949ae50f4071df16894530e24b9e9d927' 268 | when '0.9.1' then '5143f419b4ba67b6c4fdd023a26a6563f0a7cf47909e648e490ef552caf03f65' 269 | when '0.9.2' then '966b5ae4f149aa1982b037fb5343f4c43f6167b64d09fd01cbd0b5aa13db421a' 270 | when '0.9.3' then 'a39eb7e844cbd5daff04f7aff88c18daa1db7a555d476b1475904bc5f5c7036d' 271 | end 272 | when 'linux-arm' 273 | case resource.version 274 | when '0.6.0' then '425e7332789deb446a486ac25f7143aba5f16453ac46ede39b71ab6a361d8726' 275 | when '0.6.1' then '5b61e9ed10e02990aa8a2a0116c398c61608bc7f5051cb5a13750ffd47a54d51' 276 | when '0.6.2' then 'b6b4f66f6dd8b1d4ebbd0339f4ed78c4853c7bd0d42fd15af70179b5bc65482e' 277 | when '0.6.3' then 'c5fd5278be2757d2468bc7e263af15bc9a9e80fc5108fec658755804ea9bca56' 278 | when '0.6.4' then '81200fc8b7965dfc6048c336925211eaf2c7247be5d050946a5dd4d53ec9817e' 279 | when '0.7.0' then '7c9ee149d66d14cc8aa81b8d86e7df5a27876216578ab841ab3921e7f4a0ce4b' 280 | when '0.7.1' then 'e7b6846fb338c31e238f9b70cc42bd35f7de804cc31d2d91fe23cbe5de948aae' 281 | when '0.7.2' then 'e18934a3a38b980bc0cfaa8d74379a6bfe58cf1ecf4b164e28ff37dd6c7198b0' 282 | when '0.7.3' then 'a2d2d2cf194e3768aae7c3cdf140a056bf2534f4c83fb7a66cfbd4090c98773e' 283 | when '0.7.4' then 'bfd9cbef9c7c9f2128704940323d1727d8edbbd595c8d82aba923e04f04b266d' 284 | when '0.7.5' then 'df4bc38eff4305632d29c5650fbb7e7ff97b8ef12a964fd8ee5f691849c51711' 285 | when '0.8.1' then '552aa077ffbe6a52bf38d8feca5803a813a7a3986e4cb6efda61dad4480642c1' 286 | when '0.8.2' then '02b63410a8c46bab0713615c126eb1530945ebfac3340bcb748d12cb1ab6db8c' 287 | when '0.8.3' then 'a650c9a973fb34c23328f717a6bd5fe6bc22ac3b9e15013649c720d87dce90d4' 288 | when '0.8.4' then 'ce9914f75df068930fc78cb99ddcad7e4c298a385a5fa3686b2d9d4ea1044945' 289 | when '0.8.5' then '59d14c76f808a1d41647e2d772ce1fe0d4a522836af238faa4894f7e076b1b03' 290 | when '0.9.0' then '35a35db51af51cfbdf6ee67e3d0311c7823cfe17a55f0babccb0d58ee6a344ff' 291 | when '0.9.1' then '393347d27cfc9362b17c92099296f09acfc7ac2b7671b05350d1aa960dd85086' 292 | when '0.9.2' then '15c556b1b66d3fe80837898c85628e7ee5eadcb3a86cb4814a4eda072e6d7c29' 293 | when '0.9.3' then '068c9c20f15fe348e9ac36081e0c87d12fcc650c60638ea3f87e299b24b1cd6a' 294 | end 295 | end 296 | end 297 | end 298 | end 299 | end 300 | --------------------------------------------------------------------------------