├── .gitignore ├── .kitchen.yml ├── .rubocop.yml ├── .travis.yml ├── Berksfile ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── attributes ├── default.rb ├── ppa.rb └── source.rb ├── chefignore ├── files └── default │ ├── haproxy.conf │ └── haproxy.default.cfg ├── libraries ├── chef_haproxy_backend.rb ├── chef_haproxy_defaults.rb ├── chef_haproxy_frontend.rb ├── chef_haproxy_instance.rb ├── chef_haproxy_listen.rb ├── chef_haproxy_peers.rb ├── chef_haproxy_proxy.rb ├── chef_haproxy_userlist.rb ├── haproxy.rb ├── haproxy_helpers.rb ├── haproxy_instance.rb ├── haproxy_proxy.rb ├── haproxy_proxy_all.rb ├── haproxy_proxy_backend.rb ├── haproxy_proxy_defaults_backend.rb ├── haproxy_proxy_defaults_frontend.rb ├── haproxy_proxy_frontend.rb ├── haproxy_proxy_non_defaults.rb └── matchers.rb ├── metadata.rb ├── recipes ├── default.rb ├── install.rb └── service.rb ├── spec ├── spec_helper.rb └── unit │ ├── all_spec.rb │ ├── backend_spec.rb │ ├── defaults_backend_spec.rb │ ├── defaults_frontend_spec.rb │ ├── frontend_spec.rb │ ├── helpers_spec.rb │ ├── instance_spec.rb │ ├── non_defaults_spec.rb │ ├── proxy_spec.rb │ └── recipes │ ├── default_spec.rb │ ├── install_spec.rb │ ├── service_spec.rb │ └── test_spec.rb ├── templates └── default │ └── haproxy.cfg.erb └── test ├── fixtures └── cookbooks │ ├── my-consul-lb │ ├── attributes │ │ └── default.rb │ ├── metadata.rb │ └── recipes │ │ ├── consul.rb │ │ ├── consul_template.rb │ │ ├── default.rb │ │ └── haproxy.rb │ └── my-lb │ ├── attributes │ └── default.rb │ ├── metadata.rb │ └── recipes │ └── default.rb └── integration ├── consul └── serverspec │ ├── default_spec.rb │ └── spec_helper.rb ├── nodes ├── app01.json ├── app02.json ├── lb01.json ├── lb02.json ├── mysql01.json └── mysql02.json ├── package └── serverspec │ ├── default_spec.rb │ └── spec_helper.rb ├── ppa └── serverspec │ ├── default_spec.rb │ └── spec_helper.rb └── source └── serverspec ├── default_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | Berksfile.lock 3 | *~ 4 | *# 5 | .#* 6 | \#*# 7 | .*.sw[a-z] 8 | *.un~ 9 | 10 | # Bundler 11 | bin/* 12 | .bundle/* 13 | 14 | .kitchen/ 15 | .kitchen.local.yml 16 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | 5 | provisioner: 6 | name: chef_zero 7 | 8 | platforms: 9 | - name: ubuntu-12.04 10 | - name: ubuntu-14.04 11 | - name: centos-6.7 12 | - name: centos-7.1 13 | - name: fedora-21 14 | 15 | suites: 16 | - name: package 17 | run_list: 18 | - recipe[my-lb] 19 | attributes: 20 | haproxy: 21 | install_method: package 22 | - name: source 23 | run_list: 24 | - recipe[my-lb] 25 | attributes: 26 | haproxy: 27 | install_method: source 28 | - name: ppa 29 | run_list: 30 | - recipe[my-lb] 31 | attributes: 32 | haproxy: 33 | install_method: ppa 34 | excludes: 35 | - centos-6.7 36 | - centos-7.1 37 | - fedora-21 38 | - name: consul 39 | run_list: 40 | - recipe[my-consul-lb] 41 | excludes: 42 | - ubuntu-12.04 43 | - ubuntu-14.04 44 | - centos-6.7 45 | - fedora-21 46 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - Rakefile 4 | - Berksfile 5 | - metadata.rb 6 | - vendor/**/* 7 | - spec/**/* 8 | - test/**/* 9 | Documentation: 10 | Enabled: false 11 | ClassAndModuleChildren: 12 | Enabled: false 13 | HashSyntax: 14 | EnforcedStyle: hash_rockets 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: ruby 4 | cache: bundler 5 | rvm: 6 | - 2.2.0 7 | 8 | script: bundle exec rake default 9 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source "https://supermarket.chef.io" 2 | 3 | metadata 4 | 5 | group :integration do 6 | cookbook 'my-lb', path: 'test/fixtures/cookbooks/my-lb' 7 | cookbook 'my-consul-lb', path: 'test/fixtures/cookbooks/my-consul-lb' 8 | end 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.2.1 / 2016-10-12 2 | 3 | * Add an attribute to allow restart or reload service control 4 | 5 | # 1.2.0 / 2015-12-03 6 | 7 | * fix issue with using attributes as proxy config arg by reading proxy-provider generated stub (thanks @wktmeow) 8 | 9 | # 1.1.1 / 2015-11-11 10 | 11 | * add config_tail option for adding config after acls (thanks @balexx!) 12 | 13 | # 1.1.0 / 2015-09-25 14 | 15 | * continuation of config merge fix to handle attributes (thanks @kwilczynski!) 16 | * updated init system handling (added systemd cookbook dependency) 17 | 18 | # 1.0.2 / 2015-08-25 19 | 20 | * fix config merge when passing an attribute instead of an array (thanks @kwilczynski and @andrewdutton!) 21 | 22 | # 1.0.1 / 2015-07-04 23 | 24 | * update to haproxy 1.5.14 25 | 26 | # 1.0.0 / 2015-06-26 27 | 28 | * remove world-readability from config templates 29 | * update to latest source release 30 | 31 | # 0.5.2 / 2015-06-24 32 | 33 | * add missing requires to libraries 34 | * fix service provider for upstart service with package-install 35 | * update version matching for ark resource 36 | 37 | # 0.5.1 / 2015-06-24 38 | 39 | * fix compile-time constant initialization warnings 40 | * fix upstart service on EL6 41 | 42 | # 0.5.0 / 2015-05-28 43 | 44 | * break up the hwrp-supporting modules into smaller pieces 45 | * update source installation to use the ark cookbook 46 | 47 | # 0.4.1 / 2015-05-22 48 | 49 | * doc updates related to 0.4.0 50 | * fix disabling verification for proxy sub-resources 51 | * demo using consul-template with haproxy-ng 52 | 53 | # 0.4.0 / 2015-05-17 54 | 55 | * rename validate_at_compile option to 'verify' to adhere to chef norms 56 | * skip instance config verification if 'verify' attribute is false 57 | 58 | # 0.3.0 / 2015-05-15 59 | 60 | * add ability to disable compile-time validation of proxy/instance 61 | resources with the "validate_at_compile" resource attribute 62 | * add new verify attribute to instance template when chef > 12; 63 | replaces validating execute resource 64 | * updated testing/documentation 65 | 66 | # 0.2.12 / 2015-05-09 67 | 68 | * add extra keyword 69 | 70 | # 0.2.11 / 2015-05-07 71 | 72 | * explicitly list supported service actions (thanks @alefend) 73 | 74 | # 0.2.10 / 2015-05-06 75 | 76 | * fix cops 77 | * bump to latest stable haproxy for source build 78 | 79 | # 0.2.9 / 2015-04-03 80 | 81 | * misc. doc updates 82 | * misc. testing improvements 83 | * backport upstream improvements to systemd service file 84 | * sort servers by name to reduce unnecessary restart/reload 85 | 86 | # 0.2.8 / 2015-02-27 87 | 88 | * doc updates 89 | 90 | # 0.2.7 / 2015-02-27 91 | 92 | * unit testing improvements 93 | * fix bind keyword matrix entry 94 | 95 | # 0.2.6 / 2015-02-25 96 | 97 | * add ppa install method (thanks @elementai!) 98 | 99 | # 0.2.5 / 2015-02-25 100 | 101 | * fix service setup on fedora when doing source install 102 | 103 | # 0.2.4 / 2015-02-23 104 | 105 | * fix stick-table entry 106 | 107 | # 0.2.3 / 2015-02-19 108 | 109 | * add peers resource 110 | * add userlist resource 111 | 112 | # 0.2.2 / 2015-02-17 113 | 114 | * enable source install 115 | * docs and testing updates 116 | 117 | # 0.2.1 / 2015-02-13 118 | 119 | * permit abuse of proxy resource for configuration of peers, userlists 120 | * various testing improvements 121 | 122 | # 0.2.0 / 2015-02-11 123 | 124 | * set type as required attribute for haproxy_proxy resource 125 | * remove default proxy list, proxies recipe 126 | * various and sundry documentation and testing improvements 127 | * add negated keyword equivalents where appropriate 128 | 129 | # 0.1.22 / 2015-02-10 130 | 131 | * fix Haproxy::Proxy::NonDefaults.merged_config source merge 132 | 133 | # 0.1.20 / 2015-02-10 134 | 135 | * instance resource filters on actionable proxies 136 | * remove peer/usergroups attrs from instance resource pending actual build-out 137 | * extract default instance config into attributes to make it easier to consume default recipe 138 | 139 | # 0.1.18 / 2015-02-09 140 | 141 | * add timeout options to redis listen proxy 142 | * move mode attr back into modules 143 | 144 | # 0.1.16 / 2015-02-09 145 | 146 | * fix balance keyword for DefaultsBackend 147 | 148 | # 0.1.14 / 2015-02-09 149 | 150 | * add listen resource to default recipe for testing 151 | * move mode attribute under general proxy resource 152 | 153 | # 0.1.12 / 2015-02-09 154 | 155 | * fix listen provider 156 | * add dummy listen resource to default recipe 157 | 158 | # 0.1.10 / 2015-02-09 159 | 160 | * use strings as keys 161 | 162 | # 0.1.8 / 2015-02-09 163 | 164 | * fix option typo 165 | 166 | # 0.1.6 / 2015-02-09 167 | 168 | * fix type for listen resource 169 | 170 | # 0.1.4 / 2015-02-06 171 | 172 | * use the correct resource provider for the listener resource 173 | 174 | # 0.1.2 / 2015-02-05 175 | 176 | * more build-out, consolidation of attributes common to multiple resources 177 | 178 | # 0.1.0 / 2015-02-03 179 | 180 | * initial release 181 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'berkshelf' 4 | 5 | group :test do 6 | gem 'rake' 7 | gem 'chefspec' 8 | gem 'foodcritic' 9 | gem 'rubocop' 10 | end 11 | 12 | group :integration do 13 | gem 'test-kitchen' 14 | gem 'kitchen-vagrant' 15 | gem 'chef-zero' 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.3.8) 5 | ast (2.0.0) 6 | astrolabe (1.3.0) 7 | parser (>= 2.2.0.pre.3, < 3.0) 8 | berkshelf (3.2.4) 9 | addressable (~> 2.3.4) 10 | berkshelf-api-client (~> 1.2) 11 | buff-config (~> 1.0) 12 | buff-extensions (~> 1.0) 13 | buff-shell_out (~> 0.1) 14 | celluloid (~> 0.16.0) 15 | celluloid-io (~> 0.16.1) 16 | cleanroom (~> 1.0) 17 | faraday (~> 0.9.0) 18 | minitar (~> 0.5.4) 19 | octokit (~> 3.0) 20 | retryable (~> 2.0) 21 | ridley (~> 4.0) 22 | solve (~> 1.1) 23 | thor (~> 0.19) 24 | berkshelf-api-client (1.2.1) 25 | faraday (~> 0.9.0) 26 | buff-config (1.0.1) 27 | buff-extensions (~> 1.0) 28 | varia_model (~> 0.4) 29 | buff-extensions (1.0.0) 30 | buff-ignore (1.1.1) 31 | buff-ruby_engine (0.1.0) 32 | buff-shell_out (0.2.0) 33 | buff-ruby_engine (~> 0.1.0) 34 | builder (3.2.2) 35 | celluloid (0.16.0) 36 | timers (~> 4.0.0) 37 | celluloid-io (0.16.2) 38 | celluloid (>= 0.16.0) 39 | nio4r (>= 1.1.0) 40 | chef (12.3.0) 41 | chef-zero (~> 4.1) 42 | diff-lcs (~> 1.2, >= 1.2.4) 43 | erubis (~> 2.7) 44 | ffi-yajl (>= 1.2, < 3.0) 45 | highline (~> 1.6, >= 1.6.9) 46 | mixlib-authentication (~> 1.3) 47 | mixlib-cli (~> 1.4) 48 | mixlib-config (~> 2.0) 49 | mixlib-log (~> 1.3) 50 | mixlib-shellout (>= 2.0.0.rc.0, < 3.0) 51 | net-ssh (~> 2.6) 52 | net-ssh-multi (~> 1.1) 53 | ohai (~> 8.0) 54 | plist (~> 3.1.0) 55 | pry (~> 0.9) 56 | rspec-core (~> 3.2) 57 | rspec-expectations (~> 3.2) 58 | rspec-mocks (~> 3.2) 59 | rspec_junit_formatter (~> 0.2.0) 60 | serverspec (~> 2.7) 61 | specinfra (~> 2.10) 62 | chef-zero (4.2.2) 63 | ffi-yajl (>= 1.1, < 3.0) 64 | hashie (~> 2.0) 65 | mixlib-log (~> 1.3) 66 | rack 67 | uuidtools (~> 2.1) 68 | chefspec (4.2.0) 69 | chef (>= 11.14) 70 | fauxhai (~> 2.0) 71 | rspec (~> 3.0) 72 | cleanroom (1.0.0) 73 | coderay (1.1.0) 74 | dep-selector-libgecode (1.0.2) 75 | dep_selector (1.0.3) 76 | dep-selector-libgecode (~> 1.0) 77 | ffi (~> 1.9) 78 | diff-lcs (1.2.5) 79 | erubis (2.7.0) 80 | faraday (0.9.1) 81 | multipart-post (>= 1.2, < 3) 82 | fauxhai (2.3.0) 83 | net-ssh 84 | ohai 85 | ffi (1.9.8) 86 | ffi-yajl (2.2.0) 87 | libyajl2 (~> 1.2) 88 | foodcritic (4.0.0) 89 | erubis 90 | gherkin (~> 2.11) 91 | nokogiri (~> 1.5) 92 | rake 93 | rufus-lru (~> 1.0) 94 | treetop (~> 1.4) 95 | yajl-ruby (~> 1.1) 96 | gherkin (2.12.2) 97 | multi_json (~> 1.3) 98 | hashie (2.1.2) 99 | highline (1.7.2) 100 | hitimes (1.2.2) 101 | ipaddress (0.8.0) 102 | json (1.8.2) 103 | kitchen-vagrant (0.18.0) 104 | test-kitchen (~> 1.4) 105 | libyajl2 (1.2.0) 106 | method_source (0.8.2) 107 | mime-types (2.5) 108 | mini_portile (0.6.2) 109 | minitar (0.5.4) 110 | mixlib-authentication (1.3.0) 111 | mixlib-log 112 | mixlib-cli (1.5.0) 113 | mixlib-config (2.2.1) 114 | mixlib-log (1.6.0) 115 | mixlib-shellout (2.1.0) 116 | multi_json (1.11.0) 117 | multipart-post (2.0.0) 118 | net-http-persistent (2.9.4) 119 | net-scp (1.2.1) 120 | net-ssh (>= 2.6.5) 121 | net-ssh (2.9.2) 122 | net-ssh-gateway (1.2.0) 123 | net-ssh (>= 2.6.5) 124 | net-ssh-multi (1.2.1) 125 | net-ssh (>= 2.6.5) 126 | net-ssh-gateway (>= 1.2.0) 127 | nio4r (1.1.0) 128 | nokogiri (1.6.6.2) 129 | mini_portile (~> 0.6.0) 130 | octokit (3.8.0) 131 | sawyer (~> 0.6.0, >= 0.5.3) 132 | ohai (8.4.0) 133 | ffi (~> 1.9) 134 | ffi-yajl (>= 1.1, < 3.0) 135 | ipaddress 136 | mime-types (~> 2.0) 137 | mixlib-cli 138 | mixlib-config (~> 2.0) 139 | mixlib-log 140 | mixlib-shellout (~> 2.0) 141 | rake (~> 10.1) 142 | systemu (~> 2.6.4) 143 | wmi-lite (~> 1.0) 144 | parser (2.2.2.3) 145 | ast (>= 1.1, < 3.0) 146 | plist (3.1.0) 147 | polyglot (0.3.5) 148 | powerpack (0.1.1) 149 | pry (0.10.1) 150 | coderay (~> 1.1.0) 151 | method_source (~> 0.8.1) 152 | slop (~> 3.4) 153 | rack (1.6.1) 154 | rainbow (2.0.0) 155 | rake (10.4.2) 156 | retryable (2.0.1) 157 | ridley (4.1.2) 158 | addressable 159 | buff-config (~> 1.0) 160 | buff-extensions (~> 1.0) 161 | buff-ignore (~> 1.1) 162 | buff-shell_out (~> 0.1) 163 | celluloid (~> 0.16.0) 164 | celluloid-io (~> 0.16.1) 165 | erubis 166 | faraday (~> 0.9.0) 167 | hashie (>= 2.0.2, < 3.0.0) 168 | json (>= 1.7.7) 169 | mixlib-authentication (>= 1.3.0) 170 | net-http-persistent (>= 2.8) 171 | retryable (>= 2.0.0) 172 | semverse (~> 1.1) 173 | varia_model (~> 0.4) 174 | rspec (3.2.0) 175 | rspec-core (~> 3.2.0) 176 | rspec-expectations (~> 3.2.0) 177 | rspec-mocks (~> 3.2.0) 178 | rspec-core (3.2.3) 179 | rspec-support (~> 3.2.0) 180 | rspec-expectations (3.2.1) 181 | diff-lcs (>= 1.2.0, < 2.0) 182 | rspec-support (~> 3.2.0) 183 | rspec-its (1.2.0) 184 | rspec-core (>= 3.0.0) 185 | rspec-expectations (>= 3.0.0) 186 | rspec-mocks (3.2.1) 187 | diff-lcs (>= 1.2.0, < 2.0) 188 | rspec-support (~> 3.2.0) 189 | rspec-support (3.2.2) 190 | rspec_junit_formatter (0.2.2) 191 | builder (< 4) 192 | rspec-core (>= 2, < 4, != 2.12.0) 193 | rubocop (0.31.0) 194 | astrolabe (~> 1.3) 195 | parser (>= 2.2.2.1, < 3.0) 196 | powerpack (~> 0.1) 197 | rainbow (>= 1.99.1, < 3.0) 198 | ruby-progressbar (~> 1.4) 199 | ruby-progressbar (1.7.5) 200 | rufus-lru (1.0.5) 201 | safe_yaml (1.0.4) 202 | sawyer (0.6.0) 203 | addressable (~> 2.3.5) 204 | faraday (~> 0.8, < 0.10) 205 | semverse (1.2.1) 206 | serverspec (2.17.0) 207 | multi_json 208 | rspec (~> 3.0) 209 | rspec-its 210 | specinfra (~> 2.32) 211 | slop (3.6.0) 212 | solve (1.2.1) 213 | dep_selector (~> 1.0) 214 | semverse (~> 1.1) 215 | specinfra (2.34.1) 216 | net-scp 217 | net-ssh 218 | systemu (2.6.5) 219 | test-kitchen (1.4.0) 220 | mixlib-shellout (>= 1.2, < 3.0) 221 | net-scp (~> 1.1) 222 | net-ssh (~> 2.7) 223 | safe_yaml (~> 1.0) 224 | thor (~> 0.18) 225 | thor (0.19.1) 226 | timers (4.0.1) 227 | hitimes 228 | treetop (1.6.2) 229 | polyglot (~> 0.3) 230 | uuidtools (2.1.5) 231 | varia_model (0.4.0) 232 | buff-extensions (~> 1.0) 233 | hashie (>= 2.0.2, < 3.0.0) 234 | wmi-lite (1.0.0) 235 | yajl-ruby (1.2.1) 236 | 237 | PLATFORMS 238 | ruby 239 | 240 | DEPENDENCIES 241 | berkshelf 242 | chef-zero 243 | chefspec 244 | foodcritic 245 | kitchen-vagrant 246 | rake 247 | rubocop 248 | test-kitchen 249 | -------------------------------------------------------------------------------- /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 2015, Nathan Williams 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 | # haproxy-ng cookbook [![Build Status](https://travis-ci.org/nathwill/chef-haproxy-ng.svg?branch=master)](https://travis-ci.org/nathwill/chef-haproxy-ng) 2 | 3 | This cookbook is deprecated, check out the [haproxy](https://github.com/sous-chefs/haproxy) cookbook instead. 4 | 5 | 6 | A resource-driven cookbook for configuring [HAProxy](http://www.haproxy.org/). 7 | 8 | Cookbook builds on 2 core resources: 9 | 10 | - `haproxy_instance`: the "parent" resource, which maps to a complete configuration and (probably) a running haproxy daemon 11 | - `haproxy_proxy`: the "core" proxy resource, which maps to a specific proxy 12 | 13 | Additional resources `haproxy_peers`, `haproxy_userlist`, `haproxy_frontend`, 14 | `haproxy_backend`, `haproxy_defaults`, and `haproxy_listen` extend the `haproxy_proxy` 15 | resource with additional validation for common configuration keywords for their respective 16 | proxy types. 17 | 18 | Suggested background reading: 19 | 20 | - [The Fine Manual](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html) 21 | - This README, the modules in `libraries/haproxy*.rb`, and the individual resources/providers (`libraries/chef_haproxy*.rb`) 22 | - the test target and example wrapper cookbook: 'test/fixtures/cookbooks/my-lb' 23 | - the consul-template powered example wrapper cookbook: 'test/fixtures/cookbooks/my-consul-lb' 24 | 25 | ## Recipes 26 | 27 | ### haproxy-ng::default 28 | 29 | Configures a default instance, 'haproxy_instance[haproxy]', and corresponding 30 | 'haproxy' service via the `config`, `tuning`, and `proxies` cookbook attributes 31 | (which are mapped onto the corresponding resource attributes). 32 | 33 | This recipe also provides a useful example of using the provided helper, 34 | `Haproxy::Helpers#proxy`, to map a list of proxies to their corresponding 35 | resources in the resource collection. 36 | 37 | See wrapper cookbook example at 'test/fixtures/cookbooks/my-lb'. 38 | 39 | ### haproxy-ng::install 40 | 41 | Installs haproxy via the `node['haproxy']['install_method']` method. 42 | Supports 'package', 'source', and 'ppa'. 43 | 44 | ### haproxy-ng::service 45 | 46 | Configures a default-named ("haproxy") service resource. 47 | 48 | Useful for typical installs running a single haproxy daemon under the default 49 | 'haproxy' service name. Service providers, or those running multiple haproxy 50 | daemons on a single host will most likely want to configure a service instance 51 | per haproxy_instance. 52 | 53 | ## Attributes 54 | 55 | 56 | |Attribute|Description|Default| 57 | |---------|-----------|-------| 58 | |install_method|One of: 'package', 'source', 'ppa'|`package`| 59 | |proxies|Array of proxy names for the default haproxy_instance[haproxy]|[]| 60 | |config|global config of resource haproxy_instance[haproxy]|See `attributes/default.rb`| 61 | |tuning|global tuning of resource haproxy_instance[haproxy]|See `attributes/default.rb`| 62 | 63 | And more! (see `attributes/*.rb`) 64 | 65 | ## Resources 66 | 67 | ### haproxy_instance 68 | 69 | The "parent" resource. Maps 1-to-1 with a generated haproxy config file, 70 | and most likely to a running service. 71 | 72 | |Attribute|Description|Default| 73 | |---------|-----------|-------| 74 | |verify|whether to perform resource whitelist validation|true| 75 | |config|global keywords for process mgmt|`['daemon']`| 76 | |tuning|global keywords for performance|`['maxconn 256']`| 77 | |debug|global keyword for debugging ('debug', 'quiet')|`nil`| 78 | |proxies|array of proxies, see `default` recipe for example|[]| 79 | 80 | ### haproxy_proxy 81 | 82 | The simplest proxy representation and base class for the other 83 | proxy resources (peers, userlist, defaults, frontend, backend, listen). 84 | 85 | |Attribute|Description|Default| 86 | |---------|-----------|-------| 87 | |verify|whether to perform resource whitelist validation|true| 88 | |type|String denoting proxy type. (defaults, frontend, backend, listen, peers, userlist)|nil| 89 | |config|array of keywords, validated against specified type|[]| 90 | 91 | ### haproxy_peers 92 | 93 | Maps to a peers block in haproxy configuration. Not actually a proxy, 94 | but treating it like one is useful for code reusability. Don't judge me. 95 | 96 | |Attribute|Description|Default| 97 | |---------|-----------|-------| 98 | |verify|whether to perform resource whitelist validation|true| 99 | |peers|array of hashes. each hash requires 'name', 'config' keys|[]| 100 | |config|array of peers keywords. validated against whitelist|[]| 101 | 102 | 103 | For example, this resource: 104 | 105 | ```ruby 106 | haproxy_peers 'lb' do 107 | peers [ 108 | { 109 | 'name' => 'lb01', 110 | 'address' => '12.4.56.78', 111 | 'port' => 1_024 112 | }, 113 | { 114 | 'name' => 'lb02', 115 | 'address' => '12.34.56.8', 116 | 'port' => 1_024 117 | }, 118 | ] 119 | end 120 | ``` 121 | 122 | will render this configuration: 123 | 124 | ```text 125 | peers lb 126 | peer lb01 12.4.56.78:1024 127 | peer lb02 12.34.56.8:1024 128 | ``` 129 | 130 | ### haproxy_userlist 131 | 132 | Maps to a userlist block in haproxy configuration. Also not actually a proxy, 133 | as such. 134 | 135 | |Attribute|Description|Default| 136 | |---------|-----------|-------| 137 | |verify|whether to perform resource whitelist validation|true| 138 | |groups|array of hashes. hashes require 'name', 'config' keys|[]| 139 | |users|array of hashes. hashes require 'name', 'config' keys|[]| 140 | |config|array of userlist keywords, validated against whitelist|[]| 141 | 142 | For example, this resource: 143 | 144 | ```ruby 145 | haproxy_userlist 'L1' do 146 | groups [ 147 | { 'name' => 'G1', 'config' => 'users tiger,scott' }, 148 | { 'name' => 'G2', 'config' => 'users xdb,scott' } 149 | ] 150 | users [ 151 | { 'name' => 'tiger', 'config' => 'insecure-password password123' }, 152 | { 'name' => 'scott', 'config' => 'insecure-password pa55word123' }, 153 | { 'name' => 'xdb', 'config' => 'insecure-password hello' } 154 | ] 155 | end 156 | ``` 157 | 158 | will render this configuration: 159 | 160 | ```text 161 | userlist L1 162 | group G1 users tiger,scott 163 | group G2 users xdb,scott 164 | user tiger insecure-password password123 165 | user scott insecure-password pa55word123 166 | user xdb insecure-password hello 167 | 168 | ``` 169 | 170 | ### haproxy_defaults 171 | 172 | Maps to a 'defaults' block in haproxy configuration. Convention 173 | suggests that resource names be capitalized (e.g. haproxy_defaults[HTTP]). 174 | 175 | |Attribute|Description|Default| 176 | |---------|-----------|-------| 177 | |verify|whether to perform resource whitelist validation|true| 178 | |mode|specifies listener mode (http, tcp, health)|nil| 179 | |default_backend|argument to `default_backend` keyword|nil| 180 | |balance|desired balancing algo (see docs for permitted values)|nil| 181 | |source|argument to source keyword|nil| 182 | |config|array of defaults keywords, validated against whitelist|[]| 183 | 184 | For example, this resource: 185 | 186 | ```ruby 187 | haproxy_defaults 'TCP' do 188 | mode 'tcp' 189 | balance 'leastconn' 190 | source node['ipaddress'] 191 | config [ 192 | 'option clitcpka', 193 | 'option srvtcpka', 194 | 'timeout connect 5s', 195 | 'timeout client 300s', 196 | 'timeout server 300s' 197 | ] 198 | end 199 | ``` 200 | 201 | will render this configuration: 202 | 203 | ```text 204 | defaults TCP 205 | balance leastconn 206 | mode tcp 207 | option clitcpka 208 | option srvtcpka 209 | timeout connect 5s 210 | timeout client 300s 211 | timeout server 300s 212 | source 10.0.2.15 213 | ``` 214 | 215 | ### haproxy_frontend 216 | 217 | Maps to a frontend block in the instance configuration, 218 | and typically to one or more listening ports or sockets. 219 | 220 | |Attribute|Description|Default| 221 | |---------|-----------|-------| 222 | |verify|whether to perform resource whitelist validation|true| 223 | |mode|specifies listener mode (http, tcp, health)|nil| 224 | |acls|array of hashes, each requiring 'name', 'criterion' keys|[]| 225 | |description|string describing proxy|nil| 226 | |bind|args to `bind` keyword|nil| 227 | |default_backend|argument to `default_backend` keyword|nil| 228 | |use_backends|array of hashes, each requiring 'backend', 'condition', keys|[]| 229 | |config|array of frontend keywords, validated against whitelist|[]| 230 | |config_tail|same as 'config' only appended after acls|[]| 231 | 232 | For example, this resource: 233 | 234 | ```ruby 235 | haproxy_frontend 'www' do 236 | mode 'http' 237 | acls [ 238 | { 239 | 'name' => 'inside', 240 | 'criterion' => 'src 10.0.0.0/8' 241 | } 242 | ] 243 | description 'http frontend' 244 | bind '*:80' 245 | default_backend 'app' 246 | use_backends [ 247 | { 248 | 'backend' => 'app', 249 | 'condition' => 'if inside' 250 | } 251 | ] 252 | config [ 253 | 'option clitcpka' 254 | ] 255 | config_tail [ 256 | 'http-request allow if inside' 257 | ] 258 | end 259 | ``` 260 | 261 | will render this configuration: 262 | 263 | ```text 264 | frontend www 265 | bind *:80 266 | mode http 267 | option clitcpka 268 | description http frontend 269 | acl inside src 10.0.0.0/8 270 | http-request allow if inside 271 | default_backend app 272 | use_backend app if inside 273 | ``` 274 | 275 | ### haproxy_backend 276 | 277 | Maps to a backend configuration block in haproxy configuration. 278 | 279 | |Attribute|Description|Default| 280 | |---------|-----------|-------| 281 | |verify|whether to perform resource whitelist validation|true| 282 | |mode|specifies listener mode (http, tcp, health)|nil| 283 | |acls|array of hashes, each requiring 'name', 'criterion' keys|[]| 284 | |description|string describing proxy|nil| 285 | |balance|desired balancing algo (see docs for permitted values)|nil| 286 | |source|string specifying args to source keyword|nil| 287 | |servers|array of hashes, each requiring 'name', 'address', 'port' keys. 'config' key optional|[]| 288 | |config|array of backend keywords, validated against whitelist|[]| 289 | |config_tail|same as 'config' only appended after acls|[]| 290 | 291 | For example, this resource: 292 | 293 | ```ruby 294 | haproxy_backend 'app' do 295 | mode 'http' 296 | acls [ 297 | { 298 | 'name' => 'inside', 299 | 'criterion' => 'src 10.0.0.0/8' 300 | } 301 | ] 302 | description 'app pool' 303 | balance 'roundrobin' 304 | source node['ipaddress'] 305 | servers [ 306 | { 307 | 'name' => 'app01', 308 | 'address' => '12.34.56.78', 309 | 'port' => 80, 310 | 'config' => 'check inter 5000 rise 2 fall 5' 311 | }, 312 | { 313 | 'name' => 'app02', 314 | 'address' => '12.4.56.78', 315 | 'port' => 80, 316 | 'config' => 'check inter 5000 rise 2 fall 5' 317 | }, 318 | ] 319 | config [ 320 | 'option httpchk GET /health_check HTTP/1.1\r\nHost:\ localhost' 321 | ] 322 | config_tail [ 323 | 'http-request allow if inside' 324 | ] 325 | end 326 | ``` 327 | 328 | will render this configuration: 329 | 330 | ```text 331 | backend app 332 | balance roundrobin 333 | mode http 334 | option httpchk GET /health_check HTTP/1.1\r\nHost:\ localhost 335 | description app pool 336 | acl inside src 10.0.0.0/8 337 | http-request allow if inside 338 | source 10.0.2.15 339 | server app01 12.34.56.78:80 check inter 5000 rise 2 fall 5 340 | server app02 22.4.56.78:80 check inter 5000 rise 2 fall 5 341 | ``` 342 | 343 | ### haproxy_listen 344 | 345 | Maps to a listen configuration block, combines frontend and backend config 346 | blocks into a single proxy. Less flexible, but more concise. Typically used 347 | for tcp-mode proxies with a 1:1 frontend:backend mapping. 348 | 349 | |Attribute|Description|Default| 350 | |---------|-----------|-------| 351 | |verify|whether to perform resource whitelist validation|true| 352 | |mode|specifies listener mode (http, tcp, health)|nil| 353 | |acls|array of hashes, each requiring 'name', 'criterion' keys|[]| 354 | |description|string describing proxy|nil| 355 | |balance|desired balancing algo (see docs for permitted values)|nil| 356 | |source|string specifying args to source keyword|nil| 357 | |servers|array of hashes, each requiring 'name', 'address', 'port' keys. 'config' key optional|[]| 358 | |bind|args to `bind` keyword|nil| 359 | |default_backend|argument to `default_backend` keyword|nil| 360 | |use_backends|array of hashes, each requiring 'backend', 'condition', keys|[]| 361 | |config|array of listen keywords, validated against whitelist|[]| 362 | |config_tail|same as 'config' only appended after acls|[]| 363 | 364 | For example, this resource: 365 | 366 | ```ruby 367 | haproxy_listen 'mysql' do 368 | mode 'tcp' 369 | acls [ 370 | { 371 | 'name' => 'inside', 372 | 'criterion' => 'src 10.0.0.0/8' 373 | } 374 | ] 375 | description 'mysql pool' 376 | balance 'leastconn' 377 | source node['ipaddress'] 378 | bind '0.0.0.0:3306' 379 | servers [ 380 | { 381 | 'name' => 'mysql01', 382 | 'address' => '12.34.56.89', 383 | 'port' => 3_306, 384 | 'config' => 'maxconn 500 check port 3306 inter 2s backup' 385 | }, 386 | { 387 | 'name' => 'mysql02', 388 | 'address' => '12.34.56.90', 389 | 'port' => 3_306, 390 | 'config' => 'maxconn 500 check port 3306 inter 2s backup' 391 | }, 392 | ] 393 | config [ 394 | 'option mysql-check' 395 | ] 396 | config_tail [ 397 | 'http-request allow if inside' 398 | ] 399 | end 400 | ``` 401 | 402 | will generate this configuration: 403 | 404 | ```text 405 | listen mysql 406 | bind 0.0.0.0:3306 407 | balance leastconn 408 | mode tcp 409 | option mysql-check 410 | description mysql pool 411 | acl inside src 10.0.0.0/8 412 | http-request allow if inside 413 | source 10.0.2.15 414 | server mysql01 12.34.56.89:3306 maxconn 500 check port 3306 inter 2s backup 415 | server mysql02 12.34.56.90:3306 maxconn 500 check port 3306 inter 2s backup 416 | ``` 417 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new(:chefspec) 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new 8 | 9 | require 'foodcritic' 10 | FoodCritic::Rake::LintTask.new 11 | 12 | begin 13 | require 'kitchen/rake_tasks' 14 | Kitchen::RakeTasks.new 15 | rescue 16 | puts '>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] 17 | end 18 | 19 | task :default => %w( rubocop foodcritic chefspec ) 20 | task :all => %w( default kitchen:all ) 21 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Attribute:: default 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | default['haproxy'].tap do |haproxy| 20 | haproxy['install_method'] = 'package' 21 | haproxy['proxies'] = [] 22 | haproxy['config'] = [ 23 | 'daemon', 24 | 'user haproxy', 25 | 'group haproxy', 26 | 'pidfile /var/run/haproxy.pid' 27 | ] 28 | haproxy['tuning'] = [ 29 | 'maxconn 50000' 30 | ] 31 | haproxy['restart'] = false 32 | end 33 | -------------------------------------------------------------------------------- /attributes/ppa.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Attribute:: ppa 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | default['haproxy']['ppa'].tap do |ppa| 20 | ppa['uri'] = 'ppa:vbernat/haproxy-1.5' 21 | ppa['key'] = '1C61B9CD' 22 | end 23 | -------------------------------------------------------------------------------- /attributes/source.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Attribute:: source 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | default['haproxy']['source'].tap do |source| 20 | source['url'] = 21 | 'http://www.haproxy.org/download/1.5/src/haproxy-1.5.14.tar.gz' 22 | 23 | source['checksum'] = 24 | '9565dd38649064d0350a2883fa81ccfe92eb17dcda457ebdc01535e1ab0c8f99' 25 | 26 | source['dependencies'] = value_for_platform_family( 27 | 'debian' => %w( libpcre3-dev libssl-dev gcc make ), 28 | 'default' => %w( pcre-devel openssl-devel gcc make ) 29 | ) 30 | 31 | source['make_args'] = { 32 | 'TARGET' => 'linux2628', 33 | 'CPU' => 'native', 34 | 'USE_LIBCRYPT' => 1, 35 | 'USE_LINUX_SPLICE' => 1, 36 | 'USE_LINUX_TPROXY' => 1, 37 | 'USE_PCRE' => 1, 38 | 'USE_OPENSSL' => 1, 39 | 'USE_ZLIB' => 1 40 | } 41 | end 42 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # or sharing to the community site. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | Guardfile 55 | Procfile 56 | 57 | # SCM # 58 | ####### 59 | .git 60 | */.git 61 | .gitignore 62 | .gitmodules 63 | .gitconfig 64 | .gitattributes 65 | .svn 66 | */.bzr/* 67 | */.hg/* 68 | */.svn/* 69 | 70 | # Berkshelf # 71 | ############# 72 | Berksfile 73 | Berksfile.lock 74 | cookbooks/* 75 | tmp 76 | 77 | # Cookbooks # 78 | ############# 79 | CONTRIBUTING 80 | 81 | # Strainer # 82 | ############ 83 | Colanderfile 84 | Strainerfile 85 | .colander 86 | .strainer 87 | 88 | # Vagrant # 89 | ########### 90 | .vagrant 91 | Vagrantfile 92 | 93 | # Travis # 94 | ########## 95 | .travis.yml 96 | -------------------------------------------------------------------------------- /files/default/haproxy.conf: -------------------------------------------------------------------------------- 1 | # HAProxy 2 | description "HAProxy Load Balancer" 3 | 4 | start on runlevel [2345] 5 | stop on runlevel [016] 6 | 7 | respawn 8 | respawn limit 2 5 9 | 10 | env CONF=/etc/haproxy/haproxy.cfg 11 | 12 | pre-start script 13 | [ -r $CONF ] 14 | end script 15 | 16 | exec /usr/local/sbin/haproxy -db -f $CONF 17 | -------------------------------------------------------------------------------- /files/default/haproxy.default.cfg: -------------------------------------------------------------------------------- 1 | # Configuration managed by Chef 2 | # Changes will be overwritten! 3 | # Set ENABLED to 1 if you want the init script to start haproxy. 4 | ENABLED=1 5 | # Add extra flags here. 6 | #EXTRAOPTS="-de -m 16" 7 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_backend.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyBackend, 4 | # Chef::Provider::HaproxyBackend 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require_relative 'chef_haproxy_proxy' 24 | 25 | class Chef::Resource 26 | class HaproxyBackend < Chef::Resource::HaproxyProxy 27 | identity_attr :name 28 | 29 | include ::Haproxy::Proxy::All 30 | include ::Haproxy::Proxy::NonDefaults 31 | include ::Haproxy::Proxy::DefaultsBackend 32 | include ::Haproxy::Proxy::Backend 33 | 34 | def initialize(name, run_context = nil) 35 | super 36 | @resource_name = :haproxy_backend 37 | @provider = Chef::Provider::HaproxyBackend 38 | end 39 | 40 | def type(_ = nil) 41 | 'backend' 42 | end 43 | end 44 | end 45 | 46 | class Chef::Provider 47 | class HaproxyBackend < Chef::Provider::HaproxyProxy 48 | def load_current_resource 49 | @current_resource ||= 50 | Chef::Resource::HaproxyBackend.new(new_resource.name) 51 | @current_resource.verify new_resource.verify 52 | @current_resource.type new_resource.type 53 | @current_resource.config merged_config(new_resource) 54 | @current_resource 55 | end 56 | 57 | private 58 | 59 | def merged_config(r) 60 | a = Haproxy::Proxy::All.merged_config(r.config, r) 61 | nd_a = Haproxy::Proxy::NonDefaults.merged_config(a, r) 62 | db_nd_a = Haproxy::Proxy::DefaultsBackend.merged_config(nd_a, r) 63 | Haproxy::Proxy::Backend.merged_config(db_nd_a, r) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_defaults.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyDefaults, 4 | # Chef::Provider::HaproxyDefaults 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require_relative 'chef_haproxy_proxy' 24 | 25 | class Chef::Resource 26 | class HaproxyDefaults < Chef::Resource::HaproxyProxy 27 | identity_attr :name 28 | 29 | include ::Haproxy::Proxy::All 30 | include ::Haproxy::Proxy::DefaultsFrontend 31 | include ::Haproxy::Proxy::DefaultsBackend 32 | 33 | def initialize(name, run_context = nil) 34 | super 35 | @resource_name = :haproxy_defaults 36 | @provider = Chef::Provider::HaproxyDefaults 37 | end 38 | 39 | def type(_ = nil) 40 | 'defaults' 41 | end 42 | end 43 | end 44 | 45 | class Chef::Provider 46 | class HaproxyDefaults < Chef::Provider::HaproxyProxy 47 | def load_current_resource 48 | @current_resource ||= 49 | Chef::Resource::HaproxyDefaults.new(new_resource.name) 50 | @current_resource.verify new_resource.verify 51 | @current_resource.type new_resource.type 52 | @current_resource.config merged_config(new_resource) 53 | @current_resource 54 | end 55 | 56 | private 57 | 58 | def merged_config(r) 59 | a = Haproxy::Proxy::All.merged_config(r.config, r) 60 | df_a = Haproxy::Proxy::DefaultsFrontend.merged_config(a, r) 61 | Haproxy::Proxy::DefaultsBackend.merged_config(df_a, r) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_frontend.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyFrontend, 4 | # Chef::Provider::HaproxyFrontend 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require_relative 'chef_haproxy_proxy' 24 | 25 | class Chef::Resource 26 | class HaproxyFrontend < Chef::Resource::HaproxyProxy 27 | identity_attr :name 28 | 29 | include ::Haproxy::Proxy::All 30 | include ::Haproxy::Proxy::NonDefaults 31 | include ::Haproxy::Proxy::DefaultsFrontend 32 | include ::Haproxy::Proxy::Frontend 33 | 34 | def initialize(name, run_context = nil) 35 | super 36 | @resource_name = :haproxy_frontend 37 | @provider = Chef::Provider::HaproxyFrontend 38 | end 39 | 40 | def type(_ = nil) 41 | 'frontend' 42 | end 43 | end 44 | end 45 | 46 | class Chef::Provider 47 | class HaproxyFrontend < Chef::Provider::HaproxyProxy 48 | def load_current_resource 49 | @current_resource ||= 50 | Chef::Resource::HaproxyFrontend.new(new_resource.name) 51 | @current_resource.verify new_resource.verify 52 | @current_resource.type new_resource.type 53 | @current_resource.config merged_config(new_resource) 54 | @current_resource 55 | end 56 | 57 | private 58 | 59 | def merged_config(r) 60 | a = Haproxy::Proxy::All.merged_config(r.config, r) 61 | nd_a = Haproxy::Proxy::NonDefaults.merged_config(a, r) 62 | df_nd_a = Haproxy::Proxy::DefaultsFrontend.merged_config(nd_a, r) 63 | Haproxy::Proxy::Frontend.merged_config(df_nd_a, r) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_instance.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyInstance, 4 | # Chef::Provider::HaproxyInstance 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require_relative 'haproxy' 24 | 25 | class Chef::Resource 26 | class HaproxyInstance < Chef::Resource 27 | include Haproxy 28 | 29 | identity_attr :name 30 | 31 | def initialize(name, run_context = nil) 32 | super 33 | @name = name 34 | @resource_name = :haproxy_instance 35 | @provider = Chef::Provider::HaproxyInstance 36 | @allowed_actions = [:create, :delete] 37 | @action = :create 38 | end 39 | 40 | def cookbook(arg = nil) 41 | set_or_return( 42 | :cookbook, arg, 43 | :kind_of => String, 44 | :default => 'haproxy-ng' 45 | ) 46 | end 47 | 48 | def config(arg = nil) 49 | set_or_return( 50 | :config, arg, 51 | :kind_of => Array, 52 | :default => %w( daemon ), 53 | :callbacks => { 54 | 'is a valid config' => lambda do |spec| 55 | !verify || Haproxy::Instance.valid_config?(spec) 56 | end 57 | } 58 | ) 59 | end 60 | 61 | def tuning(arg = nil) 62 | set_or_return( 63 | :tuning, arg, 64 | :kind_of => Array, 65 | :default => ['maxconn 256'], 66 | :callbacks => { 67 | 'is a valid tuning' => lambda do |spec| 68 | !verify || Haproxy::Instance.valid_tuning?(spec) 69 | end 70 | } 71 | ) 72 | end 73 | 74 | def debug(arg = nil) 75 | set_or_return( 76 | :debug, arg, 77 | :kind_of => String, 78 | :equal_to => %w( debug quiet ) 79 | ) 80 | end 81 | 82 | def proxies(arg = nil) 83 | set_or_return( 84 | :proxies, arg, 85 | :kind_of => Array, 86 | :default => [], 87 | :callbacks => { 88 | 'is a valid proxy list' => lambda do |spec| 89 | spec.all? { |p| p.is_a? Chef::Resource::HaproxyProxy } 90 | end 91 | } 92 | ) 93 | end 94 | end 95 | end 96 | 97 | # 98 | # Cookbook Name:: haproxy-ng 99 | # Provider:: instance 100 | # 101 | 102 | class Chef::Provider 103 | class HaproxyInstance < Chef::Provider 104 | def initialize(*args) 105 | super 106 | @tpl = Chef::Resource::Template.new( 107 | "haproxy-instance-#{new_resource.name}", 108 | run_context 109 | ) 110 | end 111 | 112 | # rubocop: disable AbcSize 113 | def load_current_resource 114 | @current_resource ||= 115 | Chef::Resource::HaproxyInstance.new(new_resource.name) 116 | @current_resource.verify new_resource.verify 117 | @current_resource.cookbook new_resource.cookbook 118 | @current_resource.config new_resource.config 119 | @current_resource.tuning new_resource.tuning 120 | @current_resource.debug new_resource.debug 121 | @current_resource.proxies actionable_proxies(new_resource.proxies) 122 | @current_resource 123 | end 124 | # rubocop: enable AbcSize 125 | 126 | def action_create 127 | new_resource.updated_by_last_action(edit_instance(:create)) 128 | end 129 | 130 | def action_delete 131 | new_resource.updated_by_last_action(edit_instance(:delete)) 132 | end 133 | 134 | private 135 | 136 | def actionable_proxies(proxies) 137 | proxies.select do |p| 138 | p.action == :create && !p.should_skip?(new_resource.action) 139 | end 140 | end 141 | 142 | def edit_instance(exec_action) 143 | @tpl.cookbook @current_resource.cookbook 144 | @tpl.mode '0640' 145 | @tpl.path "/etc/haproxy/#{@current_resource.name}.cfg" 146 | @tpl.source 'haproxy.cfg.erb' 147 | @tpl.variables :instance => @current_resource 148 | if @current_resource.verify && (Chef::VERSION.to_f >= 12) 149 | @tpl.verify { |path| "haproxy -q -c -f #{path}" } 150 | end 151 | @tpl.run_action exec_action 152 | @tpl.updated_by_last_action? 153 | end 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_listen.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyListen, 4 | # Chef::Provider::HaproxyListen 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require_relative 'chef_haproxy_proxy' 24 | 25 | class Chef::Resource 26 | class HaproxyListen < Chef::Resource::HaproxyProxy 27 | identity_attr :name 28 | 29 | include ::Haproxy::Proxy::All 30 | include ::Haproxy::Proxy::NonDefaults 31 | include ::Haproxy::Proxy::DefaultsBackend 32 | include ::Haproxy::Proxy::Backend 33 | include ::Haproxy::Proxy::DefaultsFrontend 34 | include ::Haproxy::Proxy::Frontend 35 | 36 | def initialize(name, run_context = nil) 37 | super 38 | @resource_name = :haproxy_listen 39 | @provider = Chef::Provider::HaproxyListen 40 | end 41 | 42 | def type(_ = nil) 43 | 'listen' 44 | end 45 | end 46 | end 47 | 48 | class Chef::Provider 49 | class HaproxyListen < Chef::Provider::HaproxyProxy 50 | def initialize(*args) 51 | super 52 | end 53 | 54 | def load_current_resource 55 | @current_resource ||= 56 | Chef::Resource::HaproxyListen.new(new_resource.name) 57 | @current_resource.verify new_resource.verify 58 | @current_resource.type new_resource.type 59 | @current_resource.config merged_config(new_resource) 60 | @current_resource 61 | end 62 | 63 | private 64 | 65 | def merged_config(r) 66 | a = Haproxy::Proxy::All.merged_config(r.config, r) 67 | nd_a = Haproxy::Proxy::NonDefaults.merged_config(a, r) 68 | db_nd_a = Haproxy::Proxy::DefaultsBackend.merged_config(nd_a, r) 69 | b_db_nd_a = Haproxy::Proxy::Backend.merged_config(db_nd_a, r) 70 | df_b_db_nd_a = 71 | Haproxy::Proxy::DefaultsFrontend.merged_config(b_db_nd_a, r) 72 | Haproxy::Proxy::Frontend.merged_config(df_b_db_nd_a, r) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_peers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyPeers, 4 | # Chef::Provider::HaproxyPeers 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require_relative 'chef_haproxy_proxy' 24 | 25 | class Chef::Resource 26 | class HaproxyPeers < Chef::Resource::HaproxyProxy 27 | identity_attr :name 28 | 29 | def initialize(name, run_context = nil) 30 | super 31 | @resource_name = :haproxy_peers 32 | @provider = Chef::Provider::HaproxyPeers 33 | end 34 | 35 | def type(_ = nil) 36 | 'peers' 37 | end 38 | 39 | # rubocop: disable MethodLength 40 | def peers(arg = nil) 41 | set_or_return( 42 | :peers, arg, 43 | :kind_of => Array, 44 | :default => [], 45 | :callbacks => { 46 | 'valid peers list' => lambda do |spec| 47 | spec.empty? || spec.all? do |p| 48 | %w( name address port ).all? { |a| p.keys.include? a } 49 | end 50 | end 51 | } 52 | ) 53 | end 54 | # rubocop: enable MethodLength 55 | end 56 | end 57 | 58 | class Chef::Provider 59 | class HaproxyPeers < Chef::Provider::HaproxyProxy 60 | # rubocop: disable AbcSize 61 | def load_current_resource 62 | @current_resource ||= 63 | Chef::Resource::HaproxyPeers.new(new_resource.name) 64 | @current_resource.verify new_resource.verify 65 | @current_resource.type new_resource.type 66 | @current_resource.peers new_resource.peers 67 | @current_resource.config merged_config(new_resource) 68 | @current_resource 69 | end 70 | # rubocop: enable AbcSize 71 | 72 | private 73 | 74 | def merged_config(r) 75 | conf = r.config 76 | r.peers.each do |p| 77 | conf << "peer #{p['name']} #{p['address']}:#{p['port']}" 78 | end 79 | conf 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_proxy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyProxy, 4 | # Chef::Provider::HaproxyProxy 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | require 'chef/resource' 24 | require_relative 'haproxy' 25 | 26 | class Chef::Resource 27 | class HaproxyProxy < Chef::Resource 28 | include Haproxy 29 | 30 | identity_attr :name 31 | 32 | def initialize(name, run_context = nil) 33 | super 34 | @name = name 35 | @resource_name = :haproxy_proxy 36 | @provider = Chef::Provider::HaproxyProxy 37 | @allowed_actions = [:create, :delete] 38 | @action = :create 39 | end 40 | 41 | def type(arg = nil) 42 | set_or_return( 43 | :type, arg, 44 | :required => true, 45 | :kind_of => String, 46 | :equal_to => %w( defaults frontend backend listen peers userlist ) 47 | ) 48 | end 49 | 50 | def config(arg = nil) 51 | set_or_return( 52 | :config, arg, 53 | :kind_of => Array, 54 | :default => [], 55 | :callbacks => { 56 | "is a valid #{type} config" => lambda do |spec| 57 | !verify || Haproxy::Proxy.valid_config?(spec, type) 58 | end 59 | } 60 | ) 61 | end 62 | 63 | def verify(arg = nil) 64 | set_or_return( 65 | :verify, arg, 66 | :kind_of => [TrueClass, FalseClass], 67 | :default => true 68 | ) 69 | end 70 | end 71 | end 72 | 73 | class Chef::Provider 74 | class HaproxyProxy < Chef::Provider 75 | def initialize(*args) 76 | super 77 | @proxy_file = Chef::Resource::File.new( 78 | "haproxy-#{new_resource.type}-#{new_resource.name}", 79 | run_context 80 | ) 81 | end 82 | 83 | def load_current_resource 84 | @current_resource ||= Chef::Resource::HaproxyProxy.new(new_resource.name) 85 | @current_resource.verify new_resource.verify 86 | @current_resource.type new_resource.type 87 | @current_resource.config new_resource.config 88 | @current_resource 89 | end 90 | 91 | def action_create 92 | new_resource.updated_by_last_action(edit_proxy(:create)) 93 | end 94 | 95 | def action_delete 96 | new_resource.updated_by_last_action(edit_proxy(:delete)) 97 | end 98 | 99 | private 100 | 101 | def edit_proxy(exec_action) 102 | @proxy_file.mode '0640' 103 | @proxy_file.path ::File.join( 104 | Chef::Config['file_cache_path'] || '/tmp', 105 | "haproxy.#{@current_resource.type}.#{@current_resource.name}.cfg" 106 | ) 107 | @proxy_file.content Haproxy::Proxy.config_block(@current_resource) 108 | @proxy_file.run_action exec_action 109 | @proxy_file.updated_by_last_action? 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /libraries/chef_haproxy_userlist.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Chef::Resource::HaproxyUserlist, 4 | # Chef::Provider::HaproxyUserlist 5 | # 6 | # Author:: Nathan Williams 7 | # 8 | # Copyright 2015, Nathan Williams 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | class Chef::Resource 24 | class HaproxyUserlist < Chef::Resource::HaproxyProxy 25 | identity_attr :name 26 | 27 | def initialize(name, run_context = nil) 28 | super 29 | @resource_name = :haproxy_userlist 30 | @provider = Chef::Provider::HaproxyUserlist 31 | end 32 | 33 | def type(_ = nil) 34 | 'userlist' 35 | end 36 | 37 | # rubocop: disable MethodLength 38 | def users(arg = nil) 39 | set_or_return( 40 | :user, arg, 41 | :kind_of => Array, 42 | :default => [], 43 | :callbacks => { 44 | 'valid users list' => lambda do |spec| 45 | spec.empty? || spec.all? do |u| 46 | %w( name config ).all? { |a| u.keys.include? a } 47 | end 48 | end 49 | } 50 | ) 51 | end 52 | 53 | def groups(arg = nil) 54 | set_or_return( 55 | :group, arg, 56 | :kind_of => Array, 57 | :default => [], 58 | :callbacks => { 59 | 'valid groups list' => lambda do |spec| 60 | spec.empty? || spec.all? do |g| 61 | %w( name config ).all? { |a| g.keys.include? a } 62 | end 63 | end 64 | } 65 | ) 66 | end 67 | # rubocop: enable MethodLength 68 | end 69 | end 70 | 71 | class Chef::Provider 72 | class HaproxyUserlist < Chef::Provider::HaproxyProxy 73 | # rubocop: disable AbcSize 74 | def load_current_resource 75 | @current_resource ||= 76 | Chef::Resource::HaproxyUserlist.new(new_resource.name) 77 | @current_resource.verify new_resource.verify 78 | @current_resource.type new_resource.type 79 | @current_resource.users new_resource.users 80 | @current_resource.groups new_resource.groups 81 | @current_resource.config merged_config(new_resource) 82 | @current_resource 83 | end 84 | # rubocop: enable AbcSize 85 | 86 | private 87 | 88 | def merged_config(r) 89 | conf = r.config 90 | r.groups.each do |g| 91 | conf << "group #{g['name']} #{g['config']}" 92 | end 93 | r.users.each do |u| 94 | conf << "user #{u['name']} #{u['config']}" 95 | end 96 | conf 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /libraries/haproxy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | require_relative 'haproxy_helpers' 23 | require_relative 'haproxy_instance' 24 | require_relative 'haproxy_proxy' 25 | 26 | module Haproxy 27 | def verify(arg = nil) 28 | set_or_return( 29 | :verify, arg, 30 | :kind_of => [TrueClass, FalseClass], 31 | :default => true 32 | ) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /libraries/haproxy_helpers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Helpers 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | module Haproxy 23 | module Helpers 24 | def self.config_block(declaration, configuration) 25 | "#{declaration}\n #{configuration.join("\n ")}" 26 | end 27 | 28 | def self.proxies(run_context) 29 | resources(Chef::Resource::HaproxyProxy, run_context) 30 | end 31 | 32 | def self.proxy(name, run_context) 33 | proxies(run_context).find { |p| p.name == name } 34 | end 35 | 36 | def self.from_immutable_array(value) 37 | value.is_a?(Chef::Node::ImmutableArray) ? value.to_a : value 38 | end 39 | 40 | private 41 | 42 | def self.resources(resource, run_context) 43 | run_context.resource_collection.select do |r| 44 | r.is_a?(resource) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /libraries/haproxy_instance.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Instance 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | require_relative 'haproxy_helpers' 23 | 24 | module Haproxy 25 | module Instance 26 | CONFIG_KEYWORDS ||= [ 27 | 'ca-base', 28 | 'chroot', 29 | 'cpu-map', 30 | 'crt-base', 31 | 'daemon', 32 | 'description', 33 | 'gid', 34 | 'group', 35 | 'log', 36 | 'log-send-hostname', 37 | 'log-tag', 38 | 'nbproc', 39 | 'node', 40 | 'pidfile', 41 | 'ssl-default-bind-ciphers', 42 | 'ssl-default-bind-options', 43 | 'ssl-default-server-ciphers', 44 | 'ssl-default-server-options', 45 | 'ssl-server-verify', 46 | 'stats bind-process', 47 | 'stats maxconn', 48 | 'stats socket', 49 | 'stats timeout', 50 | 'uid', 51 | 'ulimit-n', 52 | 'unix-bind', 53 | 'user' 54 | ] 55 | 56 | TUNING_KEYWORDS ||= %w( 57 | max-spread-checks 58 | maxconn 59 | maxconnrate 60 | maxcomprate 61 | maxcompcpuusage 62 | maxpipes 63 | maxsessrate 64 | maxsslconn 65 | maxsslrate 66 | maxzlibmem 67 | noepoll 68 | nogetaddrinfo 69 | nokqueue 70 | nopoll 71 | nosplice 72 | spread-checks 73 | tune.bufsize 74 | tune.chksize 75 | tune.comp.maxlevel 76 | tune.http.cookielen 77 | tune.http.maxhdr 78 | tune.idletimer 79 | tune.maxaccept 80 | tune.maxpollevents 81 | tune.maxrewrite 82 | tune.pipesize 83 | tune.rcvbuf.client 84 | tune.rcvbuf.server 85 | tune.sndbuf.client 86 | tune.sndbuf.server 87 | tune.ssl.cachesize 88 | tune.ssl.default-dh-param 89 | tune.ssl.force-private-cache 90 | tune.ssl.lifetime 91 | tune.ssl.maxrecord 92 | tune.zlib.memlevel 93 | tune.zlib.windowsize 94 | ) 95 | 96 | def self.valid_config?(conf) 97 | conf.all? do |c| 98 | CONFIG_KEYWORDS.any? do |kw| 99 | c.start_with? kw 100 | end 101 | end 102 | end 103 | 104 | def self.valid_tuning?(conf) 105 | conf.all? do |c| 106 | TUNING_KEYWORDS.any? do |kw| 107 | c.start_with? kw 108 | end 109 | end 110 | end 111 | 112 | def self.config_block(instance) 113 | Haproxy::Helpers.config_block('global', instance.config + instance.tuning) 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | require_relative 'haproxy_helpers' 23 | require_relative 'haproxy_proxy_all' 24 | require_relative 'haproxy_proxy_backend' 25 | require_relative 'haproxy_proxy_defaults_backend' 26 | require_relative 'haproxy_proxy_frontend' 27 | require_relative 'haproxy_proxy_defaults_frontend' 28 | require_relative 'haproxy_proxy_non_defaults' 29 | 30 | module Haproxy 31 | # rubocop: disable ModuleLength 32 | module Proxy 33 | MODES ||= %w( tcp http health ) 34 | 35 | KEYWORD_PEERS ||= %w( peers ) 36 | KEYWORD_USERLIST ||= %w( userlist ) 37 | KEYWORD_ALL ||= %w( defaults frontend listen backend ) 38 | KEYWORD_DEFAULTS_FRONTEND ||= %w( defaults frontend listen ) 39 | KEYWORD_DEFAULTS_BACKEND ||= %w( defaults backend listen ) 40 | KEYWORD_FRONTEND ||= %w( frontend listen ) 41 | KEYWORD_BACKEND ||= %w( backend listen ) 42 | KEYWORD_NON_DEFAULTS ||= %w( frontend listen backend ) 43 | 44 | KEYWORD_MATRIX ||= { 45 | 'peer' => KEYWORD_PEERS, 46 | 'user' => KEYWORD_USERLIST, 47 | 'group' => KEYWORD_USERLIST, 48 | 'acl' => KEYWORD_NON_DEFAULTS, 49 | 'appsession' => KEYWORD_BACKEND, 50 | 'backlog' => KEYWORD_DEFAULTS_FRONTEND, 51 | 'balance' => KEYWORD_DEFAULTS_BACKEND, 52 | 'bind' => KEYWORD_FRONTEND, 53 | 'bind-process' => KEYWORD_ALL, 54 | 'block' => KEYWORD_NON_DEFAULTS, 55 | 'capture cookie' => KEYWORD_FRONTEND, 56 | 'capture request header' => KEYWORD_FRONTEND, 57 | 'capture response header' => KEYWORD_FRONTEND, 58 | 'clitimeout' => KEYWORD_DEFAULTS_FRONTEND, 59 | 'compression algo' => KEYWORD_ALL, 60 | 'compression type' => KEYWORD_ALL, 61 | 'compression offload' => KEYWORD_ALL, 62 | 'contimeout' => KEYWORD_DEFAULTS_BACKEND, 63 | 'cookie' => KEYWORD_DEFAULTS_BACKEND, 64 | 'default-server' => KEYWORD_DEFAULTS_BACKEND, 65 | 'default_backend' => KEYWORD_DEFAULTS_FRONTEND, 66 | 'description' => KEYWORD_NON_DEFAULTS, 67 | 'disabled' => KEYWORD_ALL, 68 | 'dispatch' => KEYWORD_BACKEND, 69 | 'enabled' => KEYWORD_ALL, 70 | 'errorfile' => KEYWORD_ALL, 71 | 'errorloc' => KEYWORD_ALL, 72 | 'errorloc302' => KEYWORD_ALL, 73 | 'errorloc303' => KEYWORD_ALL, 74 | 'force-persist' => KEYWORD_NON_DEFAULTS, 75 | 'fullconn' => KEYWORD_DEFAULTS_BACKEND, 76 | 'grace' => KEYWORD_ALL, 77 | 'hash-type' => KEYWORD_DEFAULTS_BACKEND, 78 | 'http-check disable-on-404' => KEYWORD_DEFAULTS_BACKEND, 79 | 'http-check expect' => KEYWORD_BACKEND, 80 | 'http-check send-state' => KEYWORD_DEFAULTS_BACKEND, 81 | 'http-request' => KEYWORD_NON_DEFAULTS, 82 | 'http-response' => KEYWORD_NON_DEFAULTS, 83 | 'http-send-name-header' => KEYWORD_BACKEND, 84 | 'id' => KEYWORD_NON_DEFAULTS, 85 | 'ignore-persist' => KEYWORD_NON_DEFAULTS, 86 | 'log' => KEYWORD_ALL, 87 | 'no log' => KEYWORD_ALL, 88 | 'log-format' => KEYWORD_DEFAULTS_FRONTEND, 89 | 'max-keep-alive-queue' => KEYWORD_DEFAULTS_BACKEND, 90 | 'maxconn' => KEYWORD_DEFAULTS_FRONTEND, 91 | 'mode' => KEYWORD_ALL, 92 | 'monitor fail' => KEYWORD_FRONTEND, 93 | 'monitor-net' => KEYWORD_DEFAULTS_FRONTEND, 94 | 'monitor-uri' => KEYWORD_DEFAULTS_FRONTEND, 95 | 'option abortonclose' => KEYWORD_DEFAULTS_BACKEND, 96 | 'no option abortonclose' => KEYWORD_DEFAULTS_BACKEND, 97 | 'option accept-invalid-http-request' => KEYWORD_DEFAULTS_FRONTEND, 98 | 'no option accept-invalid-http-request' => KEYWORD_DEFAULTS_FRONTEND, 99 | 'option accept-invalid-http-response' => KEYWORD_DEFAULTS_BACKEND, 100 | 'no option accept-invalid-http-response' => KEYWORD_DEFAULTS_BACKEND, 101 | 'option allbackups' => KEYWORD_DEFAULTS_BACKEND, 102 | 'no option allbackups' => KEYWORD_DEFAULTS_BACKEND, 103 | 'option checkcache' => KEYWORD_DEFAULTS_BACKEND, 104 | 'no option checkcache' => KEYWORD_DEFAULTS_BACKEND, 105 | 'option clitcpka' => KEYWORD_DEFAULTS_FRONTEND, 106 | 'no option clitcpka' => KEYWORD_DEFAULTS_FRONTEND, 107 | 'option contstats' => KEYWORD_DEFAULTS_FRONTEND, 108 | 'option dontlog-normal' => KEYWORD_DEFAULTS_FRONTEND, 109 | 'no option dontlog-normal' => KEYWORD_DEFAULTS_FRONTEND, 110 | 'option dontlognull' => KEYWORD_DEFAULTS_FRONTEND, 111 | 'no option dontlognull' => KEYWORD_DEFAULTS_FRONTEND, 112 | 'option forceclose' => KEYWORD_ALL, 113 | 'no option forceclose' => KEYWORD_ALL, 114 | 'option forwardfor' => KEYWORD_ALL, 115 | 'option http-ignore-probes' => KEYWORD_DEFAULTS_FRONTEND, 116 | 'no option http-ignore-probes' => KEYWORD_DEFAULTS_FRONTEND, 117 | 'option http-keep-alive' => KEYWORD_ALL, 118 | 'no option http-keep-alive' => KEYWORD_ALL, 119 | 'option http-no-delay' => KEYWORD_ALL, 120 | 'no option http-no-delay' => KEYWORD_ALL, 121 | 'option http-pretend-keepalive' => KEYWORD_ALL, 122 | 'no option http-pretend-keepalive' => KEYWORD_ALL, 123 | 'option http-server-close' => KEYWORD_ALL, 124 | 'no option http-server-close' => KEYWORD_ALL, 125 | 'option http-tunnel' => KEYWORD_ALL, 126 | 'no option http-tunnel' => KEYWORD_ALL, 127 | 'option http-use-proxy-header' => KEYWORD_DEFAULTS_FRONTEND, 128 | 'no option http-use-proxy-header' => KEYWORD_DEFAULTS_FRONTEND, 129 | 'option httpchk' => KEYWORD_DEFAULTS_BACKEND, 130 | 'option httpclose' => KEYWORD_ALL, 131 | 'no option httpclose' => KEYWORD_ALL, 132 | 'option httplog' => KEYWORD_ALL, 133 | 'option http_proxy' => KEYWORD_ALL, 134 | 'no option http_proxy' => KEYWORD_ALL, 135 | 'option independent-streams' => KEYWORD_ALL, 136 | 'no option independent-streams' => KEYWORD_ALL, 137 | 'option ldap-check' => KEYWORD_DEFAULTS_BACKEND, 138 | 'option log-health-checks' => KEYWORD_DEFAULTS_BACKEND, 139 | 'no option log-health-checks' => KEYWORD_DEFAULTS_BACKEND, 140 | 'option log-separate-errors' => KEYWORD_DEFAULTS_FRONTEND, 141 | 'no option log-separate-errors' => KEYWORD_DEFAULTS_FRONTEND, 142 | 'option logasap' => KEYWORD_DEFAULTS_FRONTEND, 143 | 'no option logasap' => KEYWORD_DEFAULTS_FRONTEND, 144 | 'option mysql-check' => KEYWORD_DEFAULTS_BACKEND, 145 | 'option pgsql-check' => KEYWORD_DEFAULTS_BACKEND, 146 | 'option nolinger' => KEYWORD_ALL, 147 | 'no option nolinger' => KEYWORD_ALL, 148 | 'option originalto' => KEYWORD_ALL, 149 | 'option persist' => KEYWORD_DEFAULTS_BACKEND, 150 | 'no option persist' => KEYWORD_DEFAULTS_BACKEND, 151 | 'option prefer-last-server' => KEYWORD_DEFAULTS_BACKEND, 152 | 'no option prefer-last-server' => KEYWORD_DEFAULTS_BACKEND, 153 | 'option redispatch' => KEYWORD_DEFAULTS_BACKEND, 154 | 'no option redispatch' => KEYWORD_DEFAULTS_BACKEND, 155 | 'option redis-check' => KEYWORD_DEFAULTS_BACKEND, 156 | 'option smtpchk' => KEYWORD_DEFAULTS_BACKEND, 157 | 'option socket-stats' => KEYWORD_DEFAULTS_FRONTEND, 158 | 'no option socket-stats' => KEYWORD_DEFAULTS_FRONTEND, 159 | 'option splice-auto' => KEYWORD_ALL, 160 | 'no option splice-auto' => KEYWORD_ALL, 161 | 'option splice-request' => KEYWORD_ALL, 162 | 'no option splice-request' => KEYWORD_ALL, 163 | 'option splice-response' => KEYWORD_ALL, 164 | 'no option splice-response' => KEYWORD_ALL, 165 | 'option srvtcpka' => KEYWORD_DEFAULTS_BACKEND, 166 | 'no option srvtcpka' => KEYWORD_DEFAULTS_BACKEND, 167 | 'option ssl-hello-chk' => KEYWORD_DEFAULTS_BACKEND, 168 | 'option tcp-check' => KEYWORD_DEFAULTS_BACKEND, 169 | 'option tcp-smart-accept' => KEYWORD_DEFAULTS_FRONTEND, 170 | 'no option tcp-smart-accept' => KEYWORD_DEFAULTS_FRONTEND, 171 | 'option tcp-smart-connect' => KEYWORD_DEFAULTS_BACKEND, 172 | 'no option tcp-smart-connect' => KEYWORD_DEFAULTS_BACKEND, 173 | 'option tcpka' => KEYWORD_ALL, 174 | 'option tcplog' => KEYWORD_ALL, 175 | 'option transparent' => KEYWORD_DEFAULTS_BACKEND, 176 | 'no option transparent' => KEYWORD_DEFAULTS_BACKEND, 177 | 'persist rdp-cookie' => KEYWORD_DEFAULTS_BACKEND, 178 | 'rate-limit sessions' => KEYWORD_DEFAULTS_FRONTEND, 179 | 'redirect location' => KEYWORD_NON_DEFAULTS, 180 | 'redirect prefix' => KEYWORD_NON_DEFAULTS, 181 | 'redirect scheme' => KEYWORD_NON_DEFAULTS, 182 | 'redisp' => KEYWORD_DEFAULTS_BACKEND, 183 | 'redispatch' => KEYWORD_DEFAULTS_BACKEND, 184 | 'reqadd' => KEYWORD_NON_DEFAULTS, 185 | 'reqallow' => KEYWORD_NON_DEFAULTS, 186 | 'reqdel' => KEYWORD_NON_DEFAULTS, 187 | 'reqdeny' => KEYWORD_NON_DEFAULTS, 188 | 'reqiallow' => KEYWORD_NON_DEFAULTS, 189 | 'reqidel' => KEYWORD_NON_DEFAULTS, 190 | 'reqideny' => KEYWORD_NON_DEFAULTS, 191 | 'reqipass' => KEYWORD_NON_DEFAULTS, 192 | 'reqirep' => KEYWORD_NON_DEFAULTS, 193 | 'reqisetbe' => KEYWORD_NON_DEFAULTS, 194 | 'reqitarpit' => KEYWORD_NON_DEFAULTS, 195 | 'reqpass' => KEYWORD_NON_DEFAULTS, 196 | 'reqrep' => KEYWORD_NON_DEFAULTS, 197 | 'reqsetbe' => KEYWORD_NON_DEFAULTS, 198 | 'reqtarpit' => KEYWORD_NON_DEFAULTS, 199 | 'retries' => KEYWORD_DEFAULTS_BACKEND, 200 | 'rspadd' => KEYWORD_NON_DEFAULTS, 201 | 'rspdel' => KEYWORD_NON_DEFAULTS, 202 | 'rspdeny' => KEYWORD_NON_DEFAULTS, 203 | 'rspidel' => KEYWORD_NON_DEFAULTS, 204 | 'rspideny' => KEYWORD_NON_DEFAULTS, 205 | 'rspirep' => KEYWORD_NON_DEFAULTS, 206 | 'rsprep' => KEYWORD_NON_DEFAULTS, 207 | 'server' => KEYWORD_BACKEND, 208 | 'source' => KEYWORD_DEFAULTS_BACKEND, 209 | 'srvtimeout' => KEYWORD_DEFAULTS_BACKEND, 210 | 'stats admin' => KEYWORD_BACKEND, 211 | 'stats auth' => KEYWORD_DEFAULTS_BACKEND, 212 | 'stats enable' => KEYWORD_DEFAULTS_BACKEND, 213 | 'stats hide-version' => KEYWORD_DEFAULTS_BACKEND, 214 | 'stats http-request' => KEYWORD_BACKEND, 215 | 'stats realm' => KEYWORD_DEFAULTS_BACKEND, 216 | 'stats refresh' => KEYWORD_DEFAULTS_BACKEND, 217 | 'stats scope' => KEYWORD_DEFAULTS_BACKEND, 218 | 'stats show-desc' => KEYWORD_DEFAULTS_BACKEND, 219 | 'stats show-legends' => KEYWORD_DEFAULTS_BACKEND, 220 | 'stats show-node' => KEYWORD_DEFAULTS_BACKEND, 221 | 'stats uri' => KEYWORD_DEFAULTS_BACKEND, 222 | 'stick match' => KEYWORD_BACKEND, 223 | 'stick on' => KEYWORD_BACKEND, 224 | 'stick store-request' => KEYWORD_BACKEND, 225 | 'stick store-response' => KEYWORD_BACKEND, 226 | 'stick-table type' => KEYWORD_NON_DEFAULTS, 227 | 'tcp-check connect' => KEYWORD_BACKEND, 228 | 'tcp-check expect' => KEYWORD_BACKEND, 229 | 'tcp-check send' => KEYWORD_BACKEND, 230 | 'tcp-check send-binary' => KEYWORD_BACKEND, 231 | 'tcp-request connection' => KEYWORD_FRONTEND, 232 | 'tcp-request content' => KEYWORD_NON_DEFAULTS, 233 | 'tcp-request inspect-delay' => KEYWORD_NON_DEFAULTS, 234 | 'tcp-response content' => KEYWORD_BACKEND, 235 | 'tcp-response inspect-delay' => KEYWORD_BACKEND, 236 | 'timeout check' => KEYWORD_DEFAULTS_BACKEND, 237 | 'timeout client' => KEYWORD_DEFAULTS_FRONTEND, 238 | 'timeout client-fin' => KEYWORD_DEFAULTS_FRONTEND, 239 | 'timeout clitimeout' => KEYWORD_DEFAULTS_FRONTEND, 240 | 'timeout connect' => KEYWORD_DEFAULTS_BACKEND, 241 | 'timeout contimeout' => KEYWORD_DEFAULTS_BACKEND, 242 | 'timeout http-keep-alive' => KEYWORD_ALL, 243 | 'timeout http-request' => KEYWORD_ALL, 244 | 'timeout queue' => KEYWORD_DEFAULTS_BACKEND, 245 | 'timeout server' => KEYWORD_DEFAULTS_BACKEND, 246 | 'timeout server-fin' => KEYWORD_DEFAULTS_BACKEND, 247 | 'timeout srvtimeout' => KEYWORD_DEFAULTS_BACKEND, 248 | 'timeout tarpit' => KEYWORD_ALL, 249 | 'timeout tunnel' => KEYWORD_DEFAULTS_BACKEND, 250 | 'transparent' => KEYWORD_DEFAULTS_BACKEND, 251 | 'unique-id-format' => KEYWORD_DEFAULTS_FRONTEND, 252 | 'unique-id-header' => KEYWORD_DEFAULTS_FRONTEND, 253 | 'use_backend' => KEYWORD_FRONTEND, 254 | 'use-server' => KEYWORD_BACKEND 255 | } 256 | 257 | def self.config_block(proxy) 258 | Haproxy::Helpers.config_block("#{proxy.type} #{proxy.name}", proxy.config) 259 | end 260 | 261 | def self.valid_config?(config = [], type) 262 | valid_keywords = KEYWORD_MATRIX.select do |_, v| 263 | v.include? type 264 | end 265 | 266 | config.all? do |c| 267 | valid_keywords.keys.any? { |kw| c.start_with? kw } 268 | end 269 | end 270 | end 271 | # rubocop: enable ModuleLength 272 | end 273 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy_all.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy::All 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | require_relative 'haproxy_proxy' 23 | 24 | module Haproxy 25 | module Proxy 26 | module All 27 | def mode(arg = nil) 28 | set_or_return( 29 | :mode, arg, 30 | :kind_of => String, 31 | :equal_to => Haproxy::Proxy::MODES 32 | ) 33 | end 34 | 35 | def self.merged_config(config, proxy) 36 | config = Haproxy::Helpers.from_immutable_array(config) 37 | config.unshift("mode #{proxy.mode}") if proxy.mode 38 | config 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy_backend.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy::Backend 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | module Haproxy 23 | module Proxy 24 | module Backend 25 | BALANCE_ALGORITHMS ||= %w( 26 | roundrobin 27 | static-rr 28 | leastconn 29 | first 30 | source 31 | uri 32 | url_param 33 | hdr 34 | rdp-cookie 35 | ) 36 | 37 | # rubocop: disable MethodLength 38 | def servers(arg = nil) 39 | set_or_return( 40 | :servers, arg ? arg.sort_by { |s| s['name'] } : arg, 41 | :kind_of => Array, 42 | :default => [], 43 | :callbacks => { 44 | 'is a valid servers list' => lambda do |spec| 45 | spec.empty? || spec.all? do |s| 46 | %w( name address port ).all? do |a| 47 | s.keys.include? a 48 | end 49 | end 50 | end 51 | } 52 | ) 53 | end 54 | # rubocop: enable MethodLength 55 | 56 | # rubocop: disable LineLength 57 | def self.merged_config(config, backend) 58 | config = Haproxy::Helpers.from_immutable_array(config) 59 | backend.servers.each do |s| 60 | config << "server #{s['name']} #{s['address']}:#{s['port']} #{s['config']}" 61 | end 62 | config 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy_defaults_backend.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy::DefaultsBackend 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | require_relative 'haproxy_proxy_backend' 23 | 24 | module Haproxy 25 | module Proxy 26 | module DefaultsBackend 27 | # rubocop: disable MethodLength 28 | def balance(arg = nil) 29 | set_or_return( 30 | :balance, arg, 31 | :kind_of => String, 32 | :callbacks => { 33 | 'is a valid balance algorithm' => lambda do |spec| 34 | Haproxy::Proxy::Backend::BALANCE_ALGORITHMS.any? do |a| 35 | spec.start_with? a 36 | end 37 | end 38 | } 39 | ) 40 | end 41 | # rubocop: enable MethodLength 42 | 43 | def source(arg = nil) 44 | set_or_return( 45 | :source, arg, 46 | :kind_of => String 47 | ) 48 | end 49 | 50 | def self.merged_config(config, backend) 51 | config = Haproxy::Helpers.from_immutable_array(config) 52 | config.unshift("balance #{backend.balance}") if backend.balance 53 | config << "source #{backend.source}" if backend.source 54 | config 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy_defaults_frontend.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy::DefaultsFrontend 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | require_relative 'haproxy_helpers' 23 | 24 | module Haproxy 25 | module Proxy 26 | module DefaultsFrontend 27 | def default_backend(arg = nil) 28 | set_or_return( 29 | :default_backend, arg, 30 | :kind_of => String, 31 | :callbacks => { 32 | 'backend exists' => lambda do |spec| 33 | Haproxy::Helpers.proxy(spec, run_context) 34 | .is_a? Chef::Resource::HaproxyProxy 35 | end 36 | } 37 | ) 38 | end 39 | 40 | # rubocop: disable LineLength 41 | def self.merged_config(config, frontend) 42 | config = Haproxy::Helpers.from_immutable_array(config) 43 | config << "default_backend #{frontend.default_backend}" if frontend.default_backend 44 | config 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy_frontend.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy::Frontend 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | module Haproxy 23 | module Proxy 24 | module Frontend 25 | def bind(arg = nil) 26 | set_or_return( 27 | :bind, arg, 28 | :kind_of => [String, Array] 29 | ) 30 | end 31 | 32 | # rubocop: disable MethodLength 33 | def use_backends(arg = nil) 34 | set_or_return( 35 | :use_backends, arg, 36 | :kind_of => Array, 37 | :default => [], 38 | :callbacks => { 39 | 'is a valid use_backends list' => lambda do |spec| 40 | spec.empty? || spec.all? do |u| 41 | %w( backend condition ).all? do |a| 42 | u.keys.include? a 43 | end 44 | end 45 | end 46 | } 47 | ) 48 | end 49 | # rubocop: enable MethodLength 50 | 51 | def self.merged_config(config, frontend) 52 | config = Haproxy::Helpers.from_immutable_array(config) 53 | Array(frontend.bind).each do |bind| 54 | config.unshift("bind #{bind}") 55 | end 56 | frontend.use_backends.each do |ub| 57 | config << "use_backend #{ub['backend']} #{ub['condition']}" 58 | end 59 | config 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /libraries/haproxy_proxy_non_defaults.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Library:: Haproxy::Proxy::NonDefaults 4 | # 5 | # Author:: Nathan Williams 6 | # 7 | # Copyright 2015, Nathan Williams 8 | # 9 | # Licensed under the Apache License, Version 2.0 (the "License"); 10 | # you may not use this file except in compliance with the License. 11 | # You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, software 16 | # distributed under the License is distributed on an "AS IS" BASIS, 17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | # See the License for the specific language governing permissions and 19 | # limitations under the License. 20 | # 21 | 22 | module Haproxy 23 | module Proxy 24 | module NonDefaults 25 | # rubocop: disable MethodLength 26 | def acls(arg = nil) 27 | set_or_return( 28 | :acls, arg, 29 | :kind_of => Array, 30 | :default => [], 31 | :callbacks => { 32 | 'is a valid list of acls' => lambda do |spec| 33 | spec.empty? || spec.all? do |a| 34 | %w( name criterion ).all? do |k| 35 | a.keys.include? k 36 | end 37 | end 38 | end 39 | } 40 | ) 41 | end 42 | # rubocop: enable MethodLength 43 | 44 | def config_tail(arg = nil) 45 | set_or_return( 46 | :config_tail, arg, 47 | :kind_of => Array, 48 | :default => [], 49 | :callbacks => { 50 | 'is a valid config' => lambda do |spec| 51 | !verify || Haproxy::Proxy.valid_config?(spec, type) 52 | end 53 | } 54 | ) 55 | end 56 | 57 | def description(arg = nil) 58 | set_or_return( 59 | :description, arg, 60 | :kind_of => String 61 | ) 62 | end 63 | 64 | def self.merged_config(config, non_defaults) 65 | config = Haproxy::Helpers.from_immutable_array(config) 66 | config << "description #{non_defaults.description}" if non_defaults.description # rubocop: disable LineLength 67 | non_defaults.acls.each do |acl| 68 | config << "acl #{acl['name']} #{acl['criterion']}" 69 | end 70 | non_defaults.config_tail.each do |cnf| 71 | config << cnf 72 | end 73 | config 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /libraries/matchers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Matchers for ChefSpec 3 | # 4 | 5 | if defined?(ChefSpec) 6 | %w( 7 | instance 8 | proxy 9 | peers 10 | userlist 11 | backend 12 | defaults 13 | frontend 14 | listen 15 | ).each do |r| 16 | %w( create delete ).each do |a| 17 | define_method("#{a}_haproxy_#{r}") do |n| 18 | ChefSpec::Matchers::ResourceMatcher.new( 19 | "haproxy_#{r}".to_sym, 20 | a.to_sym, 21 | n 22 | ) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'haproxy-ng' 2 | maintainer 'Nathan Williams' 3 | maintainer_email 'nath.e.will@gmail.com' 4 | license 'apache2' 5 | description 'modern, resource-driven cookbook for managing haproxy' 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | source_url 'https://github.com/nathwill/chef-haproxy-ng' 8 | issues_url 'https://github.com/nathwill/chef-haproxy-ng/issues' 9 | version '1.2.1' 10 | 11 | %w( fedora redhat centos scientific ubuntu ).each do |platform| 12 | supports platform 13 | end 14 | 15 | depends 'apt' 16 | depends 'ark' 17 | depends 'systemd' 18 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Recipe:: default 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | include_recipe "#{cookbook_name}::install" 20 | 21 | my_proxies = node['haproxy']['proxies'].map do |p| 22 | Haproxy::Helpers.proxy(p, run_context) 23 | end 24 | 25 | haproxy_instance 'haproxy' do 26 | config node['haproxy']['config'] 27 | tuning node['haproxy']['tuning'] 28 | proxies my_proxies 29 | end 30 | 31 | include_recipe "#{cookbook_name}::service" 32 | -------------------------------------------------------------------------------- /recipes/install.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Recipe:: install 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | case node['haproxy']['install_method'] 20 | when 'package' 21 | package 'haproxy' 22 | when 'ppa' 23 | apt_repository 'haproxy' do 24 | uri node['haproxy']['ppa']['uri'] 25 | distribution node['lsb']['codename'] 26 | components ['main'] 27 | keyserver 'keyserver.ubuntu.com' 28 | key node['haproxy']['ppa']['key'] 29 | end 30 | 31 | package 'haproxy' 32 | when 'source' 33 | src = node['haproxy']['source'] 34 | 35 | src['dependencies'].each do |dep| 36 | package dep 37 | end 38 | 39 | directory '/etc/haproxy' 40 | 41 | user 'haproxy' do 42 | home '/var/lib/haproxy' 43 | shell '/usr/sbin/nologin' 44 | system true 45 | end 46 | 47 | directory '/var/lib/haproxy' do 48 | owner 'haproxy' 49 | group 'haproxy' 50 | end 51 | 52 | ark 'haproxy' do 53 | url src['url'] 54 | version src['url'].match(/(\d+\.?){2}\d+/).to_s 55 | checksum src['checksum'] 56 | make_opts src['make_args'].map { |k, v| "#{k}=#{v}" } 57 | action :install_with_make 58 | end 59 | 60 | cookbook_file '/etc/init/haproxy.conf' do 61 | source 'haproxy.conf' 62 | mode '0644' 63 | only_if { File.executable?('/sbin/initctl') } 64 | end 65 | 66 | systemd_service 'haproxy' do 67 | description 'HAProxy Load Balancer' 68 | documentation 'http://www.haproxy.org/#docs' 69 | install do 70 | wanted_by 'multi-user.target' 71 | end 72 | service do 73 | exec_start_pre '/usr/local/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c -q' 74 | exec_start '/usr/local/sbin/haproxy-systemd-wrapper -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid' # rubocop: disable LineLength 75 | exec_reload '/bin/kill -USR2 $MAINPID' 76 | restart 'always' 77 | end 78 | only_if { IO.read('/proc/1/comm').chomp == 'systemd' } 79 | end 80 | else 81 | Chef::Log.warn 'Unknown install_method for haproxy. Skipping install!' 82 | end 83 | -------------------------------------------------------------------------------- /recipes/service.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Recipe:: service 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | cookbook_file '/etc/default/haproxy' do 20 | source 'haproxy.default.cfg' 21 | only_if { platform?('ubuntu') } 22 | end 23 | 24 | service 'haproxy' do 25 | if File.executable?('/sbin/initctl') && 26 | node['haproxy']['install_method'] == 'source' 27 | provider Chef::Provider::Service::Upstart 28 | end 29 | action [:enable, :start] 30 | supports :status => :true, :restart => :true, :reload => :true 31 | if node['haproxy']['restart'] 32 | subscribes :restart, 'haproxy_instance[haproxy]', :delayed 33 | subscribes :restart, 'cookbook_file[/etc/default/haproxy]', :delayed 34 | else 35 | subscribes :reload, 'haproxy_instance[haproxy]', :delayed 36 | subscribes :reload, 'cookbook_file[/etc/default/haproxy]', :delayed 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | 4 | 5 | # Require all our libraries 6 | Dir.glob('libraries/*.rb').sort.each { |f| require File.expand_path(f) } 7 | 8 | ChefSpec::Coverage.start! 9 | -------------------------------------------------------------------------------- /spec/unit/all_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy::All do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_defaults) do 6 | r = Chef::Resource::HaproxyDefaults.new('http', chef_run.run_context) 7 | r.config ['option clitcpka'] 8 | r.mode 'http' 9 | r 10 | end 11 | 12 | it 'creates a valid merged config' do 13 | expect( 14 | Haproxy::Proxy::All 15 | .merged_config(dummy_defaults.config, dummy_defaults) 16 | ).to match_array ['mode http', 'option clitcpka'] 17 | end 18 | 19 | it 'works with Chef attributes' do 20 | chef_run.node.default['dummy']['attribute'] = [ 21 | 'timeout client 3h', 22 | 'timeout server 6h' 23 | ] 24 | 25 | expect(chef_run.node['dummy']['attribute']).to be_a Chef::Node::ImmutableArray 26 | 27 | expect( 28 | Haproxy::Proxy::All 29 | .merged_config(chef_run.node['dummy']['attribute'], dummy_defaults) 30 | ).to match_array ['mode http', 'timeout client 3h','timeout server 6h'] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/backend_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy::Backend do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_backend) do 6 | r = Chef::Resource::HaproxyBackend.new('web', chef_run.run_context) 7 | r.config ['fullconn 100'] 8 | r.servers [ 9 | { 'name' => 'app02', 'address' => '1.2.3.5', 'port' => 80, 'config' => 'backup' }, 10 | { 'name' => 'app01', 'address' => '1.2.3.4', 'port' => 80, 'config' => 'backup' } 11 | ] 12 | r 13 | end 14 | 15 | it 'returns a valid, server-sorted merged config' do 16 | expect( 17 | Haproxy::Proxy::Backend 18 | .merged_config(dummy_backend.config, dummy_backend) 19 | ).to eq(['fullconn 100', 'server app01 1.2.3.4:80 backup', 'server app02 1.2.3.5:80 backup']) 20 | end 21 | 22 | it 'works with Chef attributes' do 23 | chef_run.node.default['dummy']['attribute'] = [ 24 | 'tcp-check connect', 25 | 'tcp-check expect' 26 | ] 27 | 28 | expect(chef_run.node['dummy']['attribute']).to be_a Chef::Node::ImmutableArray 29 | 30 | expect( 31 | Haproxy::Proxy::Backend 32 | .merged_config(chef_run.node['dummy']['attribute'], dummy_backend) 33 | ).to match_array [ 34 | 'server app01 1.2.3.4:80 backup', 35 | 'server app02 1.2.3.5:80 backup', 36 | 'tcp-check connect', 37 | 'tcp-check expect' 38 | ] 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/unit/defaults_backend_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy::DefaultsBackend do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_backend) do 6 | r = Chef::Resource::HaproxyBackend.new('web', chef_run.run_context) 7 | r.config ['fullconn 100'] 8 | r.balance 'roundrobin' 9 | r.source '1.2.3.4' 10 | r 11 | end 12 | 13 | it 'returns a valid merged config' do 14 | expect( 15 | Haproxy::Proxy::DefaultsBackend 16 | .merged_config(dummy_backend.config, dummy_backend) 17 | ).to match_array ['fullconn 100', 'balance roundrobin', 'source 1.2.3.4'] 18 | end 19 | 20 | it 'works with Chef attributes' do 21 | chef_run.node.default['dummy']['attribute'] = [ 22 | 'timeout connect 10s', 23 | 'timeout server 30s' 24 | ] 25 | 26 | expect(chef_run.node['dummy']['attribute']).to be_a Chef::Node::ImmutableArray 27 | 28 | expect( 29 | Haproxy::Proxy::DefaultsBackend 30 | .merged_config(chef_run.node['dummy']['attribute'], dummy_backend) 31 | ).to match_array [ 32 | 'balance roundrobin', 33 | 'source 1.2.3.4', 34 | 'timeout connect 10s', 35 | 'timeout server 30s' 36 | ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/defaults_frontend_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy::DefaultsFrontend do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_frontend) do 6 | r = Chef::Resource::HaproxyFrontend.new('web', chef_run.run_context) 7 | r.config ['bind *:80'] 8 | r.default_backend 'app' 9 | r 10 | end 11 | 12 | it 'returns a valid merged config' do 13 | expect( 14 | Haproxy::Proxy::DefaultsFrontend 15 | .merged_config(dummy_frontend.config, dummy_frontend) 16 | ).to match_array ['bind *:80', 'default_backend app'] 17 | end 18 | 19 | it 'works with Chef attributes' do 20 | chef_run.node.default['dummy']['attribute'] = [ 21 | 'option tcpka', 22 | 'option tcplog' 23 | ] 24 | 25 | expect(chef_run.node['dummy']['attribute']).to be_a Chef::Node::ImmutableArray 26 | 27 | expect( 28 | Haproxy::Proxy::Frontend 29 | .merged_config(chef_run.node['dummy']['attribute'], dummy_frontend) 30 | ).to match_array ['option tcpka', 'option tcplog'] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/frontend_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy::Frontend do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_frontend) do 6 | r = Chef::Resource::HaproxyFrontend.new('app', chef_run.run_context) 7 | r.config ['option clitcpka'] 8 | r.bind '*:80' 9 | r.use_backends [{'backend' => 'dummy', 'condition' => 'if dummy'}] 10 | r 11 | end 12 | 13 | it 'returns a valid merged config' do 14 | expect( 15 | Haproxy::Proxy::Frontend 16 | .merged_config(dummy_frontend.config, dummy_frontend) 17 | ).to match_array ['bind *:80', 'option clitcpka', 'use_backend dummy if dummy'] 18 | end 19 | 20 | it 'works with Chef attributes' do 21 | chef_run.node.default['dummy']['attribute'] = [ 22 | 'timeout http-keep-alive 10s', 23 | 'timeout http-request 30s' 24 | ] 25 | 26 | expect(chef_run.node['dummy']['attribute']).to be_a Chef::Node::ImmutableArray 27 | 28 | expect( 29 | Haproxy::Proxy::Frontend 30 | .merged_config(chef_run.node['dummy']['attribute'], dummy_frontend) 31 | ).to match_array [ 32 | 'bind *:80', 33 | 'use_backend dummy if dummy', 34 | 'timeout http-keep-alive 10s', 35 | 'timeout http-request 30s' 36 | ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Helpers do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:immutable_array) { Chef::Node::ImmutableArray.new([1, 'a', {}]) } 6 | 7 | it 'returns a valid config block' do 8 | expect( 9 | Haproxy::Helpers.config_block('frontend app', ['bind *:80', 'mode http']) 10 | ).to eq "frontend app\n bind *:80\n mode http" 11 | end 12 | 13 | it 'locates proxies in the resource_collection' do 14 | expect( 15 | Haproxy::Helpers.proxy('app', chef_run.run_context) 16 | ).to be_a(Chef::Resource::HaproxyProxy) 17 | end 18 | 19 | it 'returns an Array given Chef::Node::ImmutableArray' do 20 | expect( 21 | Haproxy::Helpers.from_immutable_array(immutable_array) 22 | ).to be_a(Array) 23 | 24 | expect( 25 | Haproxy::Helpers.from_immutable_array(immutable_array) 26 | ).to match_array [1, 'a', {}] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/instance_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Instance do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_instance) do 6 | Chef::Resource::HaproxyInstance.new('dummy', chef_run.run_context) 7 | end 8 | 9 | it 'identifies valid configs' do 10 | expect(Haproxy::Instance.valid_config?( ['daemon'] ) ).to eq true 11 | expect(Haproxy::Instance.valid_config?( ['demon'] ) ).to eq false 12 | end 13 | 14 | it 'identifies valid tunings' do 15 | expect(Haproxy::Instance.valid_tuning?( ['maxconn 200'] ) ).to eq true 16 | expect(Haproxy::Instance.valid_tuning?( ['maxcnon 200'] ) ).to eq false 17 | end 18 | 19 | it 'returns a valid global config block' do 20 | expect( 21 | Haproxy::Instance.config_block(dummy_instance) 22 | ).to eq "global\n daemon\n maxconn 256" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/non_defaults_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy::NonDefaults do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_listen) do 6 | r = Chef::Resource::HaproxyListen.new('redis', chef_run.run_context) 7 | r.config ['bind *:80'] 8 | r.acls [{ 'name' => 'redis', 'criterion' => 'src 1.2.3.4' }] 9 | r.description 'redis cluster' 10 | r 11 | end 12 | 13 | it 'returns a valid merged config' do 14 | expect( 15 | Haproxy::Proxy::NonDefaults 16 | .merged_config(dummy_listen.config, dummy_listen) 17 | ).to match_array ['bind *:80', 'description redis cluster', 'acl redis src 1.2.3.4'] 18 | end 19 | 20 | it 'works with Chef attributes' do 21 | chef_run.node.default['dummy']['attribute'] = [ 22 | 'hash-type consistent', 23 | 'ignore-persist if redis' 24 | ] 25 | 26 | expect(chef_run.node['dummy']['attribute']).to be_a Chef::Node::ImmutableArray 27 | 28 | expect( 29 | Haproxy::Proxy::NonDefaults 30 | .merged_config(chef_run.node['dummy']['attribute'], dummy_listen) 31 | ).to match_array [ 32 | 'description redis cluster', 33 | 'acl redis src 1.2.3.4', 34 | 'hash-type consistent', 35 | 'ignore-persist if redis' 36 | ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/proxy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Haproxy::Proxy do 4 | let(:chef_run) { ChefSpec::ServerRunner.new.converge('my-lb::default') } 5 | let(:dummy_proxy) do 6 | r = Chef::Resource::HaproxyProxy.new('app', chef_run.run_context) 7 | r.type 'frontend' 8 | r.config ['bind *:80'] 9 | r 10 | end 11 | 12 | it 'returns a valid config block' do 13 | expect( 14 | Haproxy::Proxy.config_block(dummy_proxy) 15 | ).to eq "frontend app\n bind *:80" 16 | end 17 | 18 | it 'validates proxy configuration' do 19 | expect(Haproxy::Proxy.valid_config?(['bind *:80'], 'frontend')).to eq true 20 | expect(Haproxy::Proxy.valid_config?(['bind *:80'], 'backend')).to eq false 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/recipes/default_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Spec:: default 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | 20 | require 'spec_helper' 21 | 22 | describe 'haproxy-ng::default' do 23 | 24 | context 'When all attributes are default, on an unspecified platform' do 25 | 26 | let(:chef_run) do 27 | ChefSpec::ServerRunner.new.converge(described_recipe) 28 | end 29 | 30 | it 'includes the install recipe' do 31 | expect(chef_run).to include_recipe 'haproxy-ng::install' 32 | end 33 | 34 | it 'creates haproxy_instance haproxy' do 35 | expect(chef_run).to create_haproxy_instance 'haproxy' 36 | end 37 | 38 | it 'skips validation' do 39 | expect(chef_run).to_not run_execute 'validate-haproxy_instance-haproxy' 40 | end 41 | 42 | it 'includes the service recipe' do 43 | expect(chef_run).to include_recipe 'haproxy-ng::service' 44 | end 45 | 46 | it 'converges successfully' do 47 | chef_run # This should not raise an error 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/unit/recipes/install_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Spec:: install 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | 20 | require 'spec_helper' 21 | 22 | describe 'haproxy-ng::install' do 23 | 24 | context 'When all attributes are default, on an unspecified platform' do 25 | 26 | let(:chef_run) do 27 | ChefSpec::ServerRunner.new.converge(described_recipe) 28 | end 29 | 30 | it 'installs haproxy' do 31 | expect(chef_run).to install_package 'haproxy' 32 | end 33 | 34 | it 'converges successfully' do 35 | chef_run # This should not raise an error 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/recipes/service_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: haproxy-ng 3 | # Spec:: install 4 | # 5 | # Copyright 2015 Nathan Williams 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | 20 | require 'spec_helper' 21 | 22 | describe 'haproxy-ng::service' do 23 | 24 | context 'When all attributes are default, on an unspecified platform' do 25 | 26 | let(:chef_run) do 27 | ChefSpec::ServerRunner.new.converge(described_recipe) 28 | end 29 | 30 | it 'enables service' do 31 | expect(chef_run).to enable_service 'haproxy' 32 | end 33 | 34 | it 'starts service' do 35 | expect(chef_run).to start_service 'haproxy' 36 | end 37 | 38 | it 'converges successfully' do 39 | chef_run # This should not raise an error 40 | end 41 | end 42 | 43 | context 'Ubuntu' do 44 | let(:chef_run) do 45 | ChefSpec::ServerRunner 46 | .new(platform: 'ubuntu', version: '14.04') 47 | .converge(described_recipe) 48 | end 49 | 50 | it 'enables service to start/disables defaults fuckery' do 51 | expect(chef_run).to create_cookbook_file '/etc/default/haproxy' 52 | end 53 | 54 | it 'converges successfully' do 55 | chef_run 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/unit/recipes/test_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'my-lb::default' do 4 | context 'When all attributes are default, on an unspecified platform' do 5 | 6 | let(:chef_run) do 7 | ChefSpec::ServerRunner.new.converge(described_recipe) 8 | end 9 | 10 | it 'creates haproxy_defaults TCP' do 11 | expect(chef_run).to create_haproxy_defaults 'TCP' 12 | end 13 | 14 | it 'creates haproxy_listen mysql' do 15 | expect(chef_run).to create_haproxy_listen 'mysql' 16 | end 17 | 18 | it 'creates haproxy_defaults HTTP' do 19 | expect(chef_run).to create_haproxy_defaults 'HTTP' 20 | end 21 | 22 | it 'creates haproxy_backend app' do 23 | expect(chef_run).to create_haproxy_backend 'app' 24 | end 25 | 26 | it 'skips haproxy_backend should_not_exist' do 27 | expect(chef_run).to_not create_haproxy_backend 'should_not_exist' 28 | end 29 | 30 | it 'creates haproxy_frontend www' do 31 | expect(chef_run).to create_haproxy_frontend 'www' 32 | end 33 | 34 | it 'converges successfully' do 35 | chef_run # This should not raise an error 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /templates/default/haproxy.cfg.erb: -------------------------------------------------------------------------------- 1 | # Generated by Chef 2 | # Changes will be overwritten! 3 | <%= Haproxy::Instance.config_block(@instance) %> 4 | <% @instance.proxies.each do |p| %> 5 | 6 | <%= ::File.read(::File.join(Chef::Config['file_cache_path'] || '/tmp', "haproxy.#{p.type}.#{p.name}.cfg")) %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-consul-lb/attributes/default.rb: -------------------------------------------------------------------------------- 1 | 2 | default['consul']['init_style'] = 'systemd' 3 | 4 | default['consul_template']['init_style'] = 'systemd' 5 | default['consul_template']['service_user'] = 'root' 6 | default['consul_template']['service_group'] = 'root' 7 | 8 | default['haproxy'].tap do |ha| 9 | ha['proxies'] = %w( lb L1 TCP mysql HTTP www app should_not_exist ) 10 | end 11 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-consul-lb/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'my-consul-lb' 2 | 3 | depends 'haproxy-ng' 4 | depends 'consul' 5 | depends 'consul-template' 6 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-consul-lb/recipes/consul.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'consul' 2 | 3 | consul_definition 'haproxy-peers' do 4 | type 'service' 5 | parameters port: 1024 6 | notifies :reload, 'consul_service[consul]' 7 | end 8 | 9 | consul_definition 'mysql' do 10 | type 'service' 11 | parameters port: 3306 12 | notifies :reload, 'consul_service[consul]' 13 | end 14 | 15 | consul_definition 'my-app' do 16 | type 'service' 17 | parameters port: 8080 18 | notifies :reload, 'consul_service[consul]' 19 | end 20 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-consul-lb/recipes/consul_template.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'consul-template' 2 | 3 | consul_template_config 'haproxy' do 4 | templates [{ 5 | source: '/etc/haproxy/consul-template.cfg', 6 | destination: '/etc/haproxy/haproxy.cfg', 7 | command: 'systemctl restart haproxy.service' 8 | }] 9 | notifies :reload, 'service[consul-template]', :delayed 10 | end 11 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-consul-lb/recipes/default.rb: -------------------------------------------------------------------------------- 1 | 2 | %w( consul haproxy consul_template ).each do |r| 3 | include_recipe "#{cookbook_name}::#{r}" 4 | end 5 | 6 | include_recipe "haproxy-ng::service" 7 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-consul-lb/recipes/haproxy.rb: -------------------------------------------------------------------------------- 1 | # Exercise all resources and their attributes for testing, 2 | # even though this generates a pretty silly configuration. 3 | 4 | haproxy_peers 'lb' do 5 | verify false 6 | config [ 7 | '{{range service "haproxy-peers"}}', 8 | 'peer {{.Node}} {{.Address}}:{{.Port}}', 9 | '{{end}}', 10 | ] 11 | end 12 | 13 | haproxy_userlist 'L1' do 14 | groups [ 15 | { 'name' => 'G1', 'config' => 'users tiger,scott' }, 16 | { 'name' => 'G2', 'config' => 'users xdb,scott' } 17 | ] 18 | users [ 19 | { 'name' => 'tiger', 'config' => 'insecure-password password123' }, 20 | { 'name' => 'scott', 'config' => 'insecure-password pa55word123' }, 21 | { 'name' => 'xdb', 'config' => 'insecure-password hello' } 22 | ] 23 | end 24 | 25 | haproxy_listen 'mysql' do 26 | verify false 27 | mode 'tcp' 28 | acls [ 29 | { 30 | 'name' => 'inside', 31 | 'criterion' => 'src 10.0.0.0/8' 32 | } 33 | ] 34 | description 'mysql pool' 35 | source node['ipaddress'] 36 | bind '0.0.0.0:3306' 37 | config [ 38 | 'option mysql-check', 39 | '{{range service "mysql"}}', 40 | 'server {{.Node}} {{.Address}}:{{.Port}}', 41 | '{{end}}', 42 | ] 43 | end 44 | 45 | haproxy_defaults 'TCP' do 46 | mode 'tcp' 47 | balance 'leastconn' 48 | source node['ipaddress'] 49 | config [ 50 | 'option clitcpka', 51 | 'option srvtcpka', 52 | 'timeout connect 5s', 53 | 'timeout client 300s', 54 | 'timeout server 300s' 55 | ] 56 | end 57 | 58 | # Temporarily disable the validation so we can create a bogus resource. 59 | # Reusing the actionable proxy test lets us confirm disabling compile-time 60 | # validation works, without also screwing up the rendered configuration. 61 | haproxy_backend 'should_not_exist' do 62 | verify false 63 | config [ 64 | 'bind 127.0.0.1:8080' # bogus config 65 | ] 66 | not_if { true } 67 | end 68 | 69 | haproxy_backend 'app' do 70 | verify false 71 | mode 'http' 72 | acls [ 73 | { 74 | 'name' => 'inside', 75 | 'criterion' => 'src 10.0.0.0/8' 76 | } 77 | ] 78 | description 'app pool' 79 | balance 'roundrobin' 80 | source node['ipaddress'] 81 | config [ 82 | 'option httpchk GET /health_check HTTP/1.1\r\nHost:\ localhost', 83 | '{{range service "my-app"}}', 84 | 'server {{.Node}} {{.Address}}:{{.Port}}', 85 | '{{end}}', 86 | ] 87 | end 88 | 89 | haproxy_frontend 'www' do 90 | mode 'http' 91 | acls [ 92 | { 93 | 'name' => 'inside', 94 | 'criterion' => 'src 10.0.0.0/8' 95 | } 96 | ] 97 | description 'http frontend' 98 | bind '*:80' 99 | default_backend 'app' 100 | use_backends [ 101 | { 102 | 'backend' => 'app', 103 | 'condition' => 'if inside' 104 | } 105 | ] 106 | config [ 107 | 'option clitcpka' 108 | ] 109 | end 110 | 111 | haproxy_defaults 'HTTP' do 112 | mode 'http' 113 | default_backend 'app' 114 | balance 'roundrobin' 115 | source node['ipaddress'] 116 | config [ 117 | 'maxconn 2000', 118 | 'timeout connect 5s', 119 | 'timeout client 50s', 120 | 'timeout server 50s' 121 | ] 122 | end 123 | 124 | include_recipe "haproxy-ng::install" 125 | 126 | my_proxies = node['haproxy']['proxies'].map do |p| 127 | Haproxy::Helpers.proxy(p, run_context) 128 | end 129 | 130 | haproxy_instance 'consul-template' do 131 | verify false 132 | config node['haproxy']['config'] 133 | tuning node['haproxy']['tuning'] 134 | proxies my_proxies 135 | notifies :reload, 'service[consul-template]', :delayed 136 | end 137 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-lb/attributes/default.rb: -------------------------------------------------------------------------------- 1 | default['haproxy']['proxies'] = %w( lb L1 TCP mysql HTTP www app should_not_exist ) 2 | default['my-lb']['fe_config'] = ['option clitcpka'] 3 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-lb/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'my-lb' 2 | 3 | depends 'haproxy-ng' 4 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/my-lb/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # Exercise all resources and their attributes for testing, 2 | # even though this generates a pretty silly configuration. 3 | 4 | lb_peers = search(:node, 'role:lb').map do |lb| 5 | { 6 | 'name' => lb.name, 7 | 'address' => lb.ipaddress, 8 | 'port' => 1024 9 | } 10 | end 11 | 12 | lb_peers << { 13 | 'name' => node['machinename'], 14 | 'address' => node.ipaddress, 15 | 'port' => 1024 16 | } 17 | 18 | haproxy_peers 'lb' do 19 | peers lb_peers 20 | not_if { platform?('ubuntu') && node['platform_version'] =~ /1(2|4).04/ } 21 | end 22 | 23 | haproxy_userlist 'L1' do 24 | groups [ 25 | { 'name' => 'G1', 'config' => 'users tiger,scott' }, 26 | { 'name' => 'G2', 'config' => 'users xdb,scott' } 27 | ] 28 | users [ 29 | { 'name' => 'tiger', 'config' => 'insecure-password password123' }, 30 | { 'name' => 'scott', 'config' => 'insecure-password pa55word123' }, 31 | { 'name' => 'xdb', 'config' => 'insecure-password hello' } 32 | ] 33 | end 34 | 35 | mysql_members = search(:node, 'role:mysql').map do |s| 36 | { 37 | 'name' => s.name, 38 | 'address' => s.ipaddress, 39 | 'port' => 3306, 40 | 'config' => 'maxconn 500 check port 3306 inter 2s backup' 41 | } 42 | end 43 | 44 | haproxy_listen 'mysql' do 45 | mode 'tcp' 46 | acls [ 47 | { 48 | 'name' => 'inside', 49 | 'criterion' => 'src 10.0.0.0/8' 50 | } 51 | ] 52 | description 'mysql pool' 53 | balance 'leastconn' 54 | source node['ipaddress'] 55 | bind '0.0.0.0:3306' 56 | servers mysql_members 57 | config [ 58 | 'option mysql-check' 59 | ] 60 | end 61 | 62 | haproxy_defaults 'TCP' do 63 | mode 'tcp' 64 | balance 'leastconn' 65 | source node['ipaddress'] 66 | config [ 67 | 'option clitcpka', 68 | 'option srvtcpka', 69 | 'timeout connect 5s', 70 | 'timeout client 300s', 71 | 'timeout server 300s' 72 | ] 73 | end 74 | 75 | if Chef::Config[:solo] 76 | app_members = { 'name' => 'app', 'address' => '127.0.0.1', 'port' => 80 } 77 | else 78 | app_members = search(:node, "role:app").map do |n| 79 | { 80 | 'name' => n.name, 81 | 'address' => n.ipaddress, 82 | 'port' => 80, 83 | 'config' => 'check inter 5000 rise 2 fall 5' 84 | } 85 | end 86 | end 87 | 88 | # Temporarily disable the validation so we can create a bogus resource. 89 | # Reusing the actionable proxy test lets us confirm disabling compile-time 90 | # validation works, without also screwing up the rendered configuration. 91 | haproxy_backend 'should_not_exist' do 92 | verify false 93 | config [ 94 | 'bind 127.0.0.1:8080' # bogus config 95 | ] 96 | not_if { true } 97 | end 98 | 99 | haproxy_backend 'app' do 100 | mode 'http' 101 | acls [ 102 | { 103 | 'name' => 'inside', 104 | 'criterion' => 'src 10.0.0.0/8' 105 | } 106 | ] 107 | description 'app pool' 108 | balance 'roundrobin' 109 | source node['ipaddress'] 110 | servers app_members 111 | config [ 112 | 'option httpchk GET /health_check HTTP/1.1\r\nHost:\ localhost' 113 | ] 114 | end 115 | 116 | haproxy_frontend 'www' do 117 | mode 'http' 118 | acls [ 119 | { 120 | 'name' => 'inside', 121 | 'criterion' => 'src 10.0.0.0/8' 122 | } 123 | ] 124 | description 'http frontend' 125 | bind '*:80' 126 | default_backend 'app' 127 | use_backends [ 128 | { 129 | 'backend' => 'app', 130 | 'condition' => 'if inside' 131 | } 132 | ] 133 | config node['my-lb']['fe_config'] 134 | end 135 | 136 | haproxy_defaults 'HTTP' do 137 | mode 'http' 138 | default_backend 'app' 139 | balance 'roundrobin' 140 | source node['ipaddress'] 141 | config [ 142 | 'maxconn 2000', 143 | 'timeout connect 5s', 144 | 'timeout client 50s', 145 | 'timeout server 50s' 146 | ] 147 | end 148 | 149 | include_recipe 'haproxy-ng' 150 | -------------------------------------------------------------------------------- /test/integration/consul/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'my-consul-lb' do 4 | describe 'sets up consul' do 5 | describe service('consul') do 6 | it { should be_enabled } 7 | it { should be_running } 8 | end 9 | 10 | describe file('/etc/consul.d/default.json') do 11 | its(:content) { should match /server/ } 12 | end 13 | end 14 | 15 | describe 'sets up consul-template' do 16 | describe service('consul-template') do 17 | it { should be_enabled } 18 | it { should be_running } 19 | end 20 | 21 | describe file('/etc/consul-template.d/haproxy') do 22 | its(:content) { should match %r{source = "/etc/haproxy/consul-template.cfg"} } 23 | its(:content) { should match %r{destination = "/etc/haproxy/haproxy.cfg"} } 24 | its(:content) { should match %r{command = "systemctl restart haproxy.service"} } 25 | end 26 | end 27 | 28 | describe 'consul-template renders the template' do 29 | describe service('haproxy') do 30 | it { should be_enabled } 31 | it { should be_running } 32 | end 33 | 34 | describe file('/etc/haproxy/haproxy.cfg') do 35 | its(:content) { should match /peer consul-centos-70.*:1024/ } 36 | its(:content) { should match /server consul-centos-70.*:3306/ } 37 | its(:content) { should match /server consul-centos-70.*:8080/ } 38 | end 39 | 40 | describe command('haproxy -c -f /etc/haproxy/haproxy.cfg') do 41 | its(:exit_status) { should eq 0 } 42 | its(:stdout) { should match /valid/ } 43 | its(:stdout) { should_not match /warn/i } 44 | end 45 | 46 | describe port(80) do 47 | it { should be_listening } 48 | end 49 | 50 | describe port(1024) do 51 | it { should be_listening } 52 | end 53 | 54 | describe port(3306) do 55 | it { should be_listening } 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /test/integration/consul/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/integration/nodes/app01.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app01", 3 | "chef_environment": "_default", 4 | "run_list": ["role[app]"], 5 | "automatic": { 6 | "ipaddress": "12.34.56.78", 7 | "hostname": "app01" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/nodes/app02.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app02", 3 | "chef_environment": "_default", 4 | "run_list": ["role[app]"], 5 | "automatic": { 6 | "ipaddress": "22.34.56.78", 7 | "hostname": "app02" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/nodes/lb01.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lb01", 3 | "chef_environment": "_default", 4 | "run_list": ["role[lb]"], 5 | "automatic": { 6 | "ipaddress": "12.4.56.78", 7 | "hostname": "lb01" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/nodes/lb02.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lb02", 3 | "chef_environment": "_default", 4 | "run_list": ["role[lb]"], 5 | "automatic": { 6 | "ipaddress": "12.34.56.8", 7 | "hostname": "lb02" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/nodes/mysql01.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql01", 3 | "chef_environment": "_default", 4 | "run_list": ["role[mysql]"], 5 | "automatic": { 6 | "ipaddress": "12.34.56.89", 7 | "hostname": "mysql01" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/nodes/mysql02.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql02", 3 | "chef_environment": "_default", 4 | "run_list": ["role[mysql]"], 5 | "automatic": { 6 | "ipaddress": "12.34.56.90", 7 | "hostname": "mysql02" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/package/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'haproxy-ng::default' do 4 | describe 'installs haproxy' do 5 | describe package('haproxy') do 6 | it { should be_installed } 7 | end 8 | end 9 | 10 | describe 'configures haproxy instance' do 11 | describe file('/etc/haproxy/haproxy.cfg') do 12 | [ 13 | 'global', 14 | 'daemon', 15 | 'user haproxy', 16 | 'group haproxy', 17 | 'pidfile /var/run/haproxy.pid', 18 | 'maxconn 50000', 19 | ].each do |directive| 20 | its(:content) { should match %r{#{directive}} } 21 | end 22 | end 23 | 24 | describe command('haproxy -c -f /etc/haproxy/haproxy.cfg') do 25 | its(:stdout) { should_not match /warning/i } 26 | its(:stdout) { should match /valid/ } 27 | end 28 | end 29 | 30 | describe 'manages haproxy service' do 31 | describe service('haproxy') do 32 | it { should be_enabled } 33 | it { should be_running } 34 | end 35 | end 36 | 37 | describe 'skips proxies when appropriate' do 38 | describe file('/etc/haproxy/haproxy.cfg') do 39 | its(:content) { should_not match /should_not_exist/ } 40 | end 41 | end 42 | 43 | describe 'configures userlists correctly' do 44 | %w( 45 | /etc/haproxy/haproxy.cfg 46 | /tmp/kitchen/cache/haproxy.userlist.L1.cfg 47 | ).each do |f| 48 | describe file(f) do 49 | [ 50 | 'group G1 users tiger,scott', 51 | 'group G2 users xdb,scott', 52 | 'user tiger insecure-password password123', 53 | 'user scott insecure-password pa55word123', 54 | 'user xdb insecure-password hello' 55 | ].each do |directive| 56 | its(:content) { should match %r{#{directive}} } 57 | end 58 | end 59 | end 60 | end 61 | 62 | describe 'configures individual proxies correctly' do 63 | %w( 64 | /etc/haproxy/haproxy.cfg 65 | /tmp/kitchen/cache/haproxy.defaults.TCP.cfg 66 | ).each do |f| 67 | describe file(f) do 68 | [ 69 | 'balance leastconn', 70 | 'mode tcp', 71 | 'option clitcpka', 72 | 'option srvtcpka', 73 | 'timeout connect 5s', 74 | 'timeout client 300s', 75 | 'timeout server 300s', 76 | 'source', 77 | ].each do |directive| 78 | its(:content) { should match %r{#{directive}} } 79 | end 80 | end 81 | end 82 | 83 | %w( 84 | /etc/haproxy/haproxy.cfg 85 | /tmp/kitchen/cache/haproxy.listen.mysql.cfg 86 | ).each do |f| 87 | describe file(f) do 88 | [ 89 | 'bind 0.0.0.0:3306', 90 | 'balance leastconn', 91 | 'mode tcp', 92 | 'option mysql-check', 93 | 'description mysql pool', 94 | 'acl inside src 10.0.0.0/8', 95 | 'source', 96 | 'server mysql01 12.34.56.89:3306 maxconn 500 check port 3306 inter 2s backup', 97 | 'server mysql02 12.34.56.90:3306 maxconn 500 check port 3306 inter 2s backup', 98 | ].each do |directive| 99 | its(:content) { should match %r{#{directive}} } 100 | end 101 | end 102 | end 103 | 104 | %w( 105 | /etc/haproxy/haproxy.cfg 106 | /tmp/kitchen/cache/haproxy.defaults.HTTP.cfg 107 | ).each do |f| 108 | describe file(f) do 109 | [ 110 | 'defaults HTTP', 111 | 'mode http', 112 | 'balance roundrobin', 113 | 'maxconn 2000', 114 | 'timeout connect 5s', 115 | 'timeout client 50s', 116 | 'timeout server 50s', 117 | 'default_backend app', 118 | 'source', 119 | ].each do |directive| 120 | its(:content) { should match %r{#{directive}} } 121 | end 122 | end 123 | end 124 | 125 | %w( 126 | /etc/haproxy/haproxy.cfg 127 | /tmp/kitchen/cache/haproxy.frontend.www.cfg 128 | ).each do |f| 129 | describe file(f) do 130 | [ 131 | 'frontend www', 132 | 'bind \*:80', 133 | 'mode http', 134 | 'option clitcpka', 135 | 'description http frontend', 136 | 'acl inside src 10.0.0.0/8', 137 | 'default_backend app', 138 | 'use_backend app if inside', 139 | ].each do |directive| 140 | its(:content) { should match %r{#{directive}} } 141 | end 142 | end 143 | end 144 | 145 | %w( 146 | /etc/haproxy/haproxy.cfg 147 | /tmp/kitchen/cache/haproxy.backend.app.cfg 148 | ).each do |f| 149 | describe file(f) do 150 | [ 151 | 'backend app', 152 | 'balance roundrobin', 153 | 'mode http', 154 | 'option httpchk', 155 | 'description app pool', 156 | 'acl inside src 10.0.0.0/8', 157 | 'source', 158 | 'server app01 12.34.56.78:80 check inter 5000 rise 2 fall 5', 159 | 'server app02 22.34.56.78:80 check inter 5000 rise 2 fall 5', 160 | ].each do |directive| 161 | its(:content) { should match Regexp.new(directive) } 162 | end 163 | end 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /test/integration/package/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/integration/ppa/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'haproxy-ng::default' do 4 | describe 'configures the apt ppa' do 5 | describe file('/etc/apt/sources.list.d/haproxy.list') do 6 | its(:content) { should match /vbernat\/haproxy-1.5/ } 7 | end 8 | end 9 | 10 | describe 'installs haproxy' do 11 | describe package('haproxy') do 12 | it { should be_installed } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/integration/ppa/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | -------------------------------------------------------------------------------- /test/integration/source/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'haproxy-ng::default' do 4 | describe 'installs haproxy' do 5 | describe command('/usr/local/sbin/haproxy -vv') do 6 | its(:stdout) { should match /supports TLS extensions : yes/ } 7 | its(:stdout) { should match /supports SNI : yes/ } 8 | end 9 | end 10 | 11 | describe 'configures haproxy instance' do 12 | describe file('/etc/haproxy/haproxy.cfg') do 13 | [ 14 | 'global', 15 | 'daemon', 16 | 'user haproxy', 17 | 'group haproxy', 18 | 'pidfile /var/run/haproxy.pid', 19 | 'maxconn 50000', 20 | ].each do |directive| 21 | its(:content) { should match %r{#{directive}} } 22 | end 23 | end 24 | 25 | describe command('/usr/local/sbin/haproxy -c -f /etc/haproxy/haproxy.cfg') do 26 | its(:stdout) { should_not match /warning/i } 27 | its(:stdout) { should match /valid/ } 28 | end 29 | end 30 | 31 | describe 'manages haproxy service' do 32 | describe service('haproxy') do 33 | it { should be_running } 34 | end 35 | end 36 | 37 | describe 'skips proxies when appropriate' do 38 | describe file('/etc/haproxy/haproxy.cfg') do 39 | its(:content) { should_not match /should_not_exist/ } 40 | end 41 | end 42 | 43 | describe 'configures individual proxies correctly' do 44 | %w( 45 | /etc/haproxy/haproxy.cfg 46 | /tmp/kitchen/cache/haproxy.defaults.TCP.cfg 47 | ).each do |f| 48 | describe file(f) do 49 | [ 50 | 'balance leastconn', 51 | 'mode tcp', 52 | 'option clitcpka', 53 | 'option srvtcpka', 54 | 'timeout connect 5s', 55 | 'timeout client 300s', 56 | 'timeout server 300s', 57 | 'source', 58 | ].each do |directive| 59 | its(:content) { should match %r{#{directive}} } 60 | end 61 | end 62 | end 63 | 64 | %w( 65 | /etc/haproxy/haproxy.cfg 66 | /tmp/kitchen/cache/haproxy.listen.mysql.cfg 67 | ).each do |f| 68 | describe file(f) do 69 | [ 70 | 'bind 0.0.0.0:3306', 71 | 'balance leastconn', 72 | 'mode tcp', 73 | 'option mysql-check', 74 | 'description mysql pool', 75 | 'acl inside src 10.0.0.0/8', 76 | 'source', 77 | 'server mysql01 12.34.56.89:3306 maxconn 500 check port 3306 inter 2s backup', 78 | 'server mysql02 12.34.56.90:3306 maxconn 500 check port 3306 inter 2s backup', 79 | ].each do |directive| 80 | its(:content) { should match %r{#{directive}} } 81 | end 82 | end 83 | end 84 | 85 | %w( 86 | /etc/haproxy/haproxy.cfg 87 | /tmp/kitchen/cache/haproxy.defaults.HTTP.cfg 88 | ).each do |f| 89 | describe file(f) do 90 | [ 91 | 'defaults HTTP', 92 | 'mode http', 93 | 'balance roundrobin', 94 | 'maxconn 2000', 95 | 'timeout connect 5s', 96 | 'timeout client 50s', 97 | 'timeout server 50s', 98 | 'default_backend app', 99 | 'source', 100 | ].each do |directive| 101 | its(:content) { should match %r{#{directive}} } 102 | end 103 | end 104 | end 105 | 106 | %w( 107 | /etc/haproxy/haproxy.cfg 108 | /tmp/kitchen/cache/haproxy.frontend.www.cfg 109 | ).each do |f| 110 | describe file(f) do 111 | [ 112 | 'frontend www', 113 | 'bind \*:80', 114 | 'mode http', 115 | 'option clitcpka', 116 | 'description http frontend', 117 | 'acl inside src 10.0.0.0/8', 118 | 'default_backend app', 119 | 'use_backend app if inside', 120 | ].each do |directive| 121 | its(:content) { should match %r{#{directive}} } 122 | end 123 | end 124 | end 125 | 126 | %w( 127 | /etc/haproxy/haproxy.cfg 128 | /tmp/kitchen/cache/haproxy.backend.app.cfg 129 | ).each do |f| 130 | describe file(f) do 131 | [ 132 | 'backend app', 133 | 'balance roundrobin', 134 | 'mode http', 135 | 'option httpchk', 136 | 'description app pool', 137 | 'acl inside src 10.0.0.0/8', 138 | 'source', 139 | 'server app01 12.34.56.78:80 check inter 5000 rise 2 fall 5', 140 | 'server app02 22.34.56.78:80 check inter 5000 rise 2 fall 5', 141 | ].each do |directive| 142 | its(:content) { should match Regexp.new(directive) } 143 | end 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /test/integration/source/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | --------------------------------------------------------------------------------