├── .gitignore ├── .hound.yml ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── kong.gemspec ├── lib ├── kong.rb └── kong │ ├── acl.rb │ ├── api.rb │ ├── base.rb │ ├── basic_auth.rb │ ├── belongs_to_api.rb │ ├── belongs_to_consumer.rb │ ├── client.rb │ ├── consumer.rb │ ├── error.rb │ ├── hmac_auth.rb │ ├── jwt.rb │ ├── key_auth.rb │ ├── oauth2_token.rb │ ├── oauth_app.rb │ ├── plugin.rb │ ├── server.rb │ ├── target.rb │ ├── upstream.rb │ ├── util.rb │ └── version.rb ├── spec ├── kong │ ├── acl_spec.rb │ ├── api_spec.rb │ ├── base_spec.rb │ ├── basic_auth_spec.rb │ ├── client_spec.rb │ ├── consumer_spec.rb │ ├── error_spec.rb │ ├── hmac_auth_spec.rb │ ├── key_auth_spec.rb │ ├── oauth2_token_spec.rb │ ├── oauth_app_spec.rb │ ├── plugin_spec.rb │ ├── server_spec.rb │ ├── target_spec.rb │ ├── upstream_spec.rb │ └── util_spec.rb └── spec_helper.rb └── tasks └── rspec.rake /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | *.gem 15 | mkmf.log 16 | .idea 17 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisabledByDefault: true 3 | 4 | Layout/IndentationConsistency: 5 | Enabled: true 6 | AutoCorrect: true 7 | 8 | Layout/CommentIndentation: 9 | Enabled: true 10 | AutoCorrect: true 11 | 12 | Style/NestedModifier: 13 | Enabled: true 14 | 15 | Layout/IndentFirstArrayElement: 16 | Enabled: true 17 | 18 | Layout/AlignArray: 19 | Enabled: true 20 | 21 | Layout/AlignHash: 22 | Enabled: true 23 | 24 | #Lint/EmptyInterpolation: 25 | # Enabled: true 26 | # 27 | #Lint/Eval: 28 | # Enabled: true 29 | # Exclude: 30 | # - test/**/* 31 | # 32 | #Lint/Debugger: 33 | # Enabled: true 34 | # 35 | #Lint/EmptyEnsure: 36 | # Enabled: true 37 | # 38 | #Lint/DuplicatedKey: 39 | # Enabled: true 40 | # 41 | #Lint/UnreachableCode: 42 | # Enabled: true 43 | # 44 | #Lint/BlockAlignment: 45 | # Enabled: true 46 | # 47 | #Lint/DefEndAlignment: 48 | # Enabled: true 49 | # 50 | #Lint/DuplicateMethods: 51 | # Enabled: true 52 | # 53 | #Lint/AmbiguousOperator: 54 | # Enabled: true 55 | # 56 | #Lint/ConditionPosition: 57 | # Enabled: true 58 | 59 | # TODO JMB 2015-12-28: should we include this? 60 | #Lint/UselessAssignment: 61 | # Enabled: true 62 | 63 | #Lint/RequireParentheses: 64 | # Enabled: true 65 | # 66 | #Lint/LiteralInCondition: 67 | # Enabled: true 68 | # 69 | #Lint/UnusedBlockArgument: 70 | # Enabled: true 71 | # 72 | #Lint/UnusedMethodArgument: 73 | # Enabled: true 74 | # 75 | #Lint/UselessAccessModifier: 76 | # Enabled: true 77 | 78 | # TODO JMB 2015-12-28: needs latest rubocop 79 | #Lint/IneffectiveAccessModifier: 80 | # Enabled: true 81 | 82 | #Lint/StringConversionInInterpolation: 83 | # Enabled: true 84 | # 85 | #Lint/ShadowingOuterLocalVariable: 86 | # Enabled: true 87 | # 88 | #Rails/Delegate: 89 | # Enabled: true 90 | # 91 | #Rails/FindBy: 92 | # Enabled: true 93 | # 94 | #Rails/FindEach: 95 | # Enabled: true 96 | # 97 | #Rails/Output: 98 | # Enabled: true 99 | # 100 | #Rails/PluralizationGrammar: 101 | # Enabled: true 102 | # 103 | #Rails/Validation: 104 | # Enabled: true 105 | # 106 | #Performance/CaseWhenSplat: 107 | # Enabled: true 108 | # 109 | #Performance/Count: 110 | # Enabled: true 111 | # 112 | #Performance/Detect: 113 | # Enabled: true 114 | # 115 | #Performance/FixedSize: 116 | # Enabled: true 117 | # 118 | #Performance/FlatMap: 119 | # Enabled: true 120 | # 121 | #Performance/ReverseEach: 122 | # Enabled: true 123 | # 124 | #Performance/Sample: 125 | # Enabled: true 126 | # 127 | #Performance/Size: 128 | # Enabled: true 129 | # 130 | #Performance/StringReplacement: 131 | # Enabled: true 132 | 133 | # TODO JMB 2015-12-28: metrics 134 | 135 | ## Coding Style 136 | 137 | Metrics/MethodLength: 138 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#method-length 139 | Enabled: true 140 | CountComments: false # count full line comments? 141 | Max: 40 142 | Severity: refactor 143 | 144 | Layout/Tab: 145 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#tabs 146 | Enabled: true 147 | 148 | Metrics/ParameterLists: 149 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#parameter-count 150 | Enabled: true 151 | Severity: refactor 152 | Max: 5 153 | CountKeywordArgs: true 154 | 155 | Layout/IndentationWidth: 156 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#tabs 157 | Enabled: true 158 | 159 | Metrics/LineLength: 160 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#max-line-length 161 | Max: 120 162 | Enabled: false 163 | 164 | Layout/TrailingWhitespace: 165 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-trailing-whitespace 166 | Enabled: true 167 | 168 | Layout/TrailingBlankLines: 169 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#end-file-newline 170 | EnforcedStyle: final_newline 171 | 172 | Layout/SpaceAroundOperators: 173 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 174 | Enabled: true 175 | AllowForAlignment: true 176 | 177 | Layout/SpaceAroundEqualsInParameterDefault: 178 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 179 | EnforcedStyle: space 180 | 181 | Layout/SpaceAfterComma: 182 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 183 | Enabled: true 184 | 185 | Layout/SpaceAfterColon: 186 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 187 | Enabled: true 188 | 189 | Layout/SpaceAfterSemicolon: 190 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 191 | Enabled: true 192 | 193 | Layout/SpaceBeforeBlockBraces: 194 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 195 | Enabled: true 196 | EnforcedStyle: space 197 | 198 | Layout/SpaceInsideBlockBraces: 199 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 200 | Enabled: true 201 | EnforcedStyle: space 202 | # Valid values are: space, no_space 203 | EnforcedStyleForEmptyBraces: no_space 204 | # Space between { and |. Overrides EnforcedStyle if there is a conflict. 205 | SpaceBeforeBlockParameters: true 206 | 207 | Layout/SpaceInsideHashLiteralBraces: 208 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 209 | Enabled: true 210 | EnforcedStyle: space 211 | EnforcedStyleForEmptyBraces: no_space 212 | 213 | Layout/SpaceInsideStringInterpolation: 214 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#spaces-around-operators 215 | Enabled: true 216 | EnforcedStyle: no_space 217 | 218 | Layout/SpaceInsideParens: 219 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-spaces-around-delimiters 220 | Enabled: true 221 | 222 | Layout/SpaceInsideArrayLiteralBrackets: 223 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-spaces-around-delimiters 224 | Enabled: true 225 | 226 | Layout/SpaceInsideReferenceBrackets: 227 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-spaces-around-delimiters 228 | Enabled: true 229 | 230 | Layout/CaseIndentation: 231 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#case-indentation 232 | Enabled: true 233 | 234 | Layout/ElseAlignment: 235 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#case-indentation 236 | Enabled: true 237 | 238 | Layout/RescueEnsureAlignment: 239 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#case-indentation 240 | Enabled: true 241 | 242 | Layout/EndAlignment: 243 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#case-indentation 244 | Enabled: true 245 | AutoCorrect: true 246 | 247 | Layout/SpaceAfterNot: 248 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-space-after-not 249 | Enabled: true 250 | 251 | Layout/EmptyLineBetweenDefs: 252 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#lines-between-defs 253 | Enabled: true 254 | AllowAdjacentOneLineDefs: false 255 | 256 | ## Syntax 257 | 258 | Style/MethodDefParentheses: 259 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#method-parentheses 260 | Enabled: true 261 | 262 | Style/DefWithParentheses: 263 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#method-parentheses 264 | Enabled: true 265 | 266 | Style/For: 267 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#for-keyword 268 | Enabled: true 269 | 270 | Style/MultilineIfThen: 271 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#multiline-if-then 272 | Enabled: true 273 | 274 | Style/NestedTernaryOperator: 275 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#nested-ternary 276 | Enabled: true 277 | 278 | Style/AndOr: 279 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-and-or-keyword 280 | Enabled: true 281 | 282 | Style/MultilineTernaryOperator: 283 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#multiline-ternary 284 | Enabled: true 285 | 286 | Style/UnlessElse: 287 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#unless-else 288 | Enabled: true 289 | 290 | Style/ParenthesesAroundCondition: 291 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#parens-around-condition 292 | Enabled: true 293 | 294 | Style/MultilineBlockChain: 295 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#block-syntax 296 | Enabled: false 297 | 298 | Style/RedundantReturn: 299 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#avoid-return 300 | Enabled: true 301 | 302 | Style/PerlBackrefs: 303 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-perlisms 304 | Enabled: true 305 | 306 | Style/SpecialGlobalVars: 307 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-perlisms 308 | Enabled: true 309 | 310 | Layout/SpaceAfterMethodName: 311 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#no-space-after-method-name 312 | Enabled: true 313 | 314 | Naming/VariableName: 315 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#variable-names 316 | Enabled: true 317 | 318 | Naming/ConstantName: 319 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#constant-names 320 | Enabled: true 321 | 322 | ## Classes 323 | 324 | Style/ClassVars: 325 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#class-vars 326 | Enabled: true 327 | 328 | Layout/AccessModifierIndentation: 329 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#access-modifier-indent 330 | Enabled: true 331 | 332 | ## Exceptions 333 | 334 | Lint/RescueException: 335 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#rescue-specific-exceptions 336 | Enabled: true 337 | 338 | ## Collections 339 | 340 | Style/WordArray: 341 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#percent-w 342 | Enabled: true 343 | MinSize: 3 344 | 345 | # Align with the style guide. 346 | Style/CollectionMethods: 347 | # Mapping from undesired method to desired_method 348 | # e.g. to use `detect` over `find`: 349 | # 350 | # CollectionMethods: 351 | # PreferredMethods: 352 | # find: detect 353 | Enabled: true 354 | StyleGuide: https://github.com/backupify/backupify_styleguides/blob/master/RubyStyleguide.md#banned-aliases 355 | PreferredMethods: 356 | collect: 'map' 357 | collect!: 'map!' 358 | inject: 'reduce' 359 | detect: 'find' 360 | find_all: 'select' 361 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.4 4 | - 2.5 5 | - 2.6 6 | cache: bundler 7 | before_install: 8 | - # we want bundler 1.7 (for now) https://docs.travis-ci.com/user/languages/ruby/ 9 | - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true 10 | - gem install bundler -v '< 2' 11 | script: bundle install && bundle exec rspec spec/ 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.4 (2018-09-12) 2 | - Re-built gem package 3 | 4 | # 0.3.3 (2018-09-07) 5 | - Switch to application json content type ([#28](https://github.com/kontena/kong-client-ruby/pull/28)) 6 | - Flatten nested plugin config items ([#27](https://github.com/kontena/kong-client-ruby/pull/27)) 7 | 8 | # 0.3.2 (2018-02-05) 9 | - Remove Excon version dependency ([#25](https://github.com/kontena/kong-client-ruby/pull/25)) 10 | 11 | # 0.3.1 (2017-10-02) 12 | - Use consumer id to retrieve auth keys and tokens ([#19](https://github.com/kontena/kong-client-ruby/pull/19)) 13 | - Add methods to kong api base attributes ([#21](https://github.com/kontena/kong-client-ruby/pull/21)) 14 | # 0.3.0 (2017-07-31) 15 | - Add support for Upstream and Target resources ([#15](https://github.com/kontena/kong-client-ruby/pull/15)) 16 | 17 | # 0.2.0 (2017-04-20) 18 | - Add Server information support ([#14](https://github.com/kontena/kong-client-ruby/pull/14)) 19 | - Add optional attributes to Api 20 | ([#13](https://github.com/kontena/kong-client-ruby/pull/13)) 21 | - Fix Plugin#create and #update to save config properly 22 | ([#12](https://github.com/kontena/kong-client-ruby/pull/12)) 23 | 24 | # 0.1.2 (2017-01-12) 25 | - Add JWT support ([#2](https://github.com/kontena/kong-client-ruby/pull/2)) 26 | - Allow to properly set a custom api_url ([#4](https://github.com/kontena/kong-client-ruby/pull/4)) 27 | - Allow to redefine api_url 28 | ([#6](https://github.com/kontena/kong-client-ruby/pull/6)) 29 | - Add ACL support 30 | ([#9](https://github.com/kontena/kong-client-ruby/pull/9)) 31 | 32 | # 0.1.1 (2016-10-10) 33 | - Fix Kong::Base.respond_to? to return true for find_by_* methods 34 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in kong.gemspec 4 | gemspec 5 | group :development, :test do 6 | gem "rspec" 7 | gem "rubocop", "~> 0.76.0" 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2015 Kontena, Inc. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kong Client for Ruby 2 | 3 | [Kong](http://getkong.org) API client for Ruby 4 | 5 | [![Build Status](https://travis-ci.org/kontena/kong-client-ruby.svg?branch=master)](https://travis-ci.org/kontena/kong-client-ruby) 6 | [![Gem Version](https://badge.fury.io/rb/kong.svg)](https://badge.fury.io/rb/kong) 7 | 8 | ## Installation 9 | Add this line to your application's Gemfile: 10 | 11 | gem 'kong' 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install kong 20 | 21 | ## Usage 22 | 23 | By default Kong client tries to connect `http://localhost:8001` or address provided by environment variable: `KONG_URI='http://your-kong-url:8001'`. 24 | 25 | You can set it also in your code: 26 | ```ruby 27 | require 'kong' 28 | Kong::Client.api_url = 'http://your-kong-url:8001' 29 | ``` 30 | 31 | ### Design 32 | 33 | Kong client follows a design of resoures as Plain Old Ruby objects(tm). For examples to create a new Consumer resource you can do it like this: 34 | 35 | ```ruby 36 | consumer = Kong::Consumer.create({ username: 'testuser', custom_id: 'custom_id' }) 37 | ``` 38 | 39 | OR 40 | 41 | ```ruby 42 | consumer = Kong::Consumer.new({ username: 'testuser'}) 43 | consumer.custom_id = '12345' 44 | consumer.save 45 | ``` 46 | 47 | To find existing consumer: 48 | 49 | ```ruby 50 | consumer = Kong::Consumer.find_by_username('testuser') 51 | consumer = Kong::Consumer.find_by_custom_id('custom_id') 52 | ``` 53 | 54 | ### All Resources and Actions 55 | 56 | To see the complete Kong Admin API documentation, please visit: https://getkong.org/docs/0.11.x/admin-api/ 57 | 58 | #### Consumer 59 | 60 | ```ruby 61 | Kong::Consumer.list(filters) 62 | Kong::Consumer.all() 63 | Kong::Consumer.find(id) 64 | Kong::Consumer.find_by_*(value) 65 | Kong::Consumer.create(attributes) 66 | 67 | consumer = Kong::Consumer.new({ username: 'test-user' }) 68 | consumer.get # reloads resource 69 | consumer.create 70 | consumer.update 71 | consumer.save # requests create_or_update action 72 | consumer.delete 73 | 74 | consumer.plugins 75 | consumer.oauth_apps 76 | consumer.key_auths 77 | consumer.basic_auths 78 | consumer.oauth2_tokens 79 | ``` 80 | 81 | #### API 82 | 83 | ```ruby 84 | Kong::Api.list(filters) 85 | Kong::Api.all() 86 | Kong::Api.find(id) 87 | Kong::Api.find_by_*(value) 88 | Kong::Api.create(attributes) 89 | 90 | api = Kong::Api.new({ 91 | name: 'Mockbin', 92 | hosts: ['example.com'], 93 | uris: ['/someservice'], 94 | methods: ['GET'], 95 | strip_uri: false, 96 | preserve_host: false, 97 | upstream_url: 'https://mockbin.com' 98 | }) 99 | api.get # reloads resource 100 | api.create 101 | api.update 102 | api.save # requests create_or_update action 103 | api.delete 104 | 105 | api.plugins 106 | ``` 107 | 108 | #### Plugin 109 | 110 | ```ruby 111 | Kong::Plugin.list(filters) 112 | Kong::Plugin.all() 113 | Kong::Plugin.find(id) 114 | Kong::Plugin.find_by_*(value) 115 | Kong::Plugin.create(attributes) 116 | 117 | plugin = Kong::Plugin.new({ 118 | api_id: '5fd1z584-1adb-40a5-c042-63b19db49x21', 119 | consumer_id: 'a3dX2dh2-1adb-40a5-c042-63b19dbx83hF4', 120 | name: 'rate-limiting', 121 | config: { 122 | minute: 20, 123 | hour: 500 124 | } 125 | }) 126 | 127 | plugin.get # reloads resource 128 | plugin.create 129 | plugin.update 130 | plugin.save # requests create_or_update action 131 | plugin.delete 132 | ``` 133 | 134 | #### Upstream and Targets (for load-balanced APIs) 135 | 136 | ```ruby 137 | Kong::Upstream.list(filters) 138 | Kong::Upstream.all() 139 | Kong::Upstream.find(id) 140 | Kong::Upstream.find_by_*(value) 141 | Kong::Upstream.create(attributes) 142 | 143 | upstream = Kong::Upstream.new({ name: 'myservice' }) 144 | 145 | upstream.get # reloads resource 146 | upstream.create 147 | upstream.update 148 | upstream.save # requests create_or_update action 149 | upstream.delete 150 | 151 | upstream.targets # lists active targets 152 | 153 | # Add targets 154 | Kong::Target.new({ upstream_id: upstream.id, target: 'appserver1:80' }).save 155 | Kong::Target.new({ upstream_id: upstream.id, target: 'appserver2:80' }).save 156 | 157 | # Add the API 158 | Kong::Api.new({ 159 | ... 160 | upstream_url: 'http://myservice' 161 | }).save 162 | ``` 163 | 164 | #### OAuthApp 165 | 166 | ```ruby 167 | Kong::OAuthApp.list(filters) 168 | Kong::OAuthApp.all() 169 | Kong::OAuthApp.find(consumer_id) 170 | Kong::OAuthApp.find_by_*(value) 171 | Kong::OAuthApp.create(attributes) 172 | 173 | app = Kong::OAuthApp.new({ 174 | consumer_id: 'a3dX2dh2-1adb-40a5-c042-63b19dbx83hF4', 175 | redirect_uri: 'http://some-domain/endpoint/' 176 | }) 177 | 178 | app.create 179 | app.get # reloads resource 180 | app.update 181 | app.save # requests create_or_update action 182 | app.delete 183 | ``` 184 | 185 | #### KeyAuth 186 | 187 | ```ruby 188 | Kong::KeyAuth.create(attributes) 189 | 190 | auth = Kong::KeyAuth.new({ 191 | consumer_id: 'a3dX2dh2-1adb-40a5-c042-63b19dbx83hF4', 192 | }) 193 | 194 | auth.create 195 | auth.get # reloads resource 196 | auth.update 197 | auth.save # requests create_or_update action 198 | auth.delete 199 | ``` 200 | 201 | #### BasicAuth 202 | 203 | ```ruby 204 | Kong::BasicAuth.create(attributes) 205 | 206 | auth = Kong::BasicAuth.new({ 207 | consumer_id: 'a3dX2dh2-1adb-40a5-c042-63b19dbx83hF4', 208 | username: 'user123', 209 | password: 'secret' 210 | }) 211 | 212 | auth.create 213 | auth.get # reloads resource 214 | auth.update 215 | auth.save # requests create_or_update action 216 | auth.delete 217 | ``` 218 | 219 | #### OAuth2Token 220 | 221 | ```ruby 222 | token = Kong::OAuth2Token.find_by_access_token('SOME-TOKEN') 223 | 224 | token = Kong::OAuth2Token.new({ 225 | credential_id: 'KONG-APPLICATION-ID', 226 | token_type: 'bearer', 227 | access_token: 'SOME-TOKEN', 228 | refresh_token: 'SOME-TOKEN', 229 | expires_in: 3600 230 | }) 231 | 232 | token.create 233 | token.update 234 | token.save # requests create_or_update action 235 | token.delete 236 | 237 | token.oauth_app 238 | ``` 239 | 240 | #### JWT 241 | 242 | ```ruby 243 | 244 | jwt = Kong::JWT.new({ 245 | consumer_id: 'a3dX2dh2-1adb-40a5-c042-63b19dbx83hF4', 246 | key: 'a36c3049b36249a3c9f8891cb127243c', 247 | secret: 'e71829c351aa4242c2719cbfbe671c09' 248 | }) 249 | 250 | jwt.create 251 | jwt.update 252 | jwt.save # requests create_or_update action 253 | jwt.delete 254 | 255 | consumer = Kong::Consumer.find_by_username('testuser') 256 | consumer.jwts 257 | ``` 258 | 259 | #### ACL 260 | 261 | ```ruby 262 | 263 | acl = Kong::Acl.new({ 264 | consumer_id: 'a3dX2dh2-1adb-40a5-c042-63b19dbx83hF4', 265 | group: 'group1' 266 | }) 267 | 268 | acl.create 269 | acl.update 270 | acl.save # requests create_or_update action 271 | acl.delete 272 | 273 | consumer = Kong::Consumer.find_by_username('testuser') 274 | consumer.acls 275 | ``` 276 | 277 | #### Server Information 278 | 279 | ```ruby 280 | Kong::Server.info 281 | Kong::Server.version 282 | Kong::Server.status 283 | Kong::Server.cluster 284 | Kong::Server.remove_node(node_name) 285 | ``` 286 | 287 | ## Contributing 288 | 289 | 1. Fork it ( https://github.com/kontena/kong-client-ruby/fork ) 290 | 2. Create your feature branch (`git checkout -b my-new-feature`) 291 | 3. Commit your changes (`git commit -am 'Add some feature'`) 292 | 4. Push to the branch (`git push origin my-new-feature`) 293 | 5. Create a new Pull Request 294 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | Dir.glob('tasks/*.rake').each { |r| import r } 4 | 5 | task :default => :spec 6 | -------------------------------------------------------------------------------- /kong.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'kong/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "kong" 8 | spec.version = Kong::VERSION 9 | spec.authors = ["Lauri Nevala"] 10 | spec.email = ["lauri@kontena.io"] 11 | spec.summary = %q{A Ruby client for the Kong API } 12 | spec.description = %q{A Ruby client for the Kong API} 13 | spec.homepage = "https://github.com/kontena/kong-client-ruby" 14 | spec.license = "Apache-2.0" 15 | spec.files = `git ls-files -z`.split("\x0") 16 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 17 | spec.require_paths = ["lib"] 18 | 19 | spec.required_ruby_version = ">= 2.0.0" 20 | 21 | spec.add_development_dependency "bundler", "~> 1.17" 22 | spec.add_development_dependency "rake", "~> 12.0" 23 | spec.add_runtime_dependency "excon" 24 | end 25 | -------------------------------------------------------------------------------- /lib/kong.rb: -------------------------------------------------------------------------------- 1 | require 'kong/version' 2 | require_relative 'kong/base' 3 | require_relative 'kong/api' 4 | require_relative 'kong/belongs_to_api' 5 | require_relative 'kong/client' 6 | require_relative 'kong/consumer' 7 | require_relative 'kong/belongs_to_consumer' 8 | require_relative 'kong/plugin' 9 | require_relative 'kong/error' 10 | require_relative 'kong/oauth_app' 11 | require_relative 'kong/oauth2_token' 12 | require_relative 'kong/basic_auth' 13 | require_relative 'kong/hmac_auth' 14 | require_relative 'kong/key_auth' 15 | require_relative 'kong/jwt' 16 | require_relative 'kong/acl' 17 | require_relative 'kong/target' 18 | require_relative 'kong/upstream' 19 | require_relative 'kong/server' 20 | require_relative 'kong/util' 21 | -------------------------------------------------------------------------------- /lib/kong/acl.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Acl 3 | include Base 4 | include BelongsToConsumer 5 | ATTRIBUTE_NAMES = %w(id group consumer_id).freeze 6 | API_END_POINT = "/acls/".freeze 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/kong/api.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Api 3 | include Base 4 | 5 | ATTRIBUTE_NAMES = %w( 6 | id name request_host request_path strip_request_path 7 | hosts uris strip_uri preserve_host upstream_url retries 8 | upstream_connect_timeout upstream_send_timeout upstream_read_timeout 9 | https_only http_if_terminated methods 10 | ).freeze 11 | API_END_POINT = '/apis/'.freeze 12 | 13 | ## 14 | # @return [Array] 15 | def plugins 16 | Plugin.list({ api_id: self.id }) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/kong/base.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | module Base 3 | module ClassMethods 4 | 5 | # List resources 6 | # @return [Array] 7 | def list(params = {}) 8 | result = [] 9 | json_data = Client.instance.get(self::API_END_POINT, params) 10 | if json_data['data'] 11 | json_data['data'].each do |instance| 12 | result << self.new(instance) 13 | end 14 | end 15 | result 16 | end 17 | 18 | alias_method :all, :list 19 | 20 | # Create resource 21 | # @param [Hash] attributes 22 | def create(attributes = {}) 23 | self.new(attributes).create 24 | end 25 | 26 | # Find resource 27 | # @param [String] id 28 | def find(id) 29 | self.new.get(id) 30 | end 31 | 32 | def method_missing(method, *arguments, &block) 33 | if method.to_s.start_with?('find_by_') 34 | attribute = method.to_s.sub('find_by_', '') 35 | if self.attribute_names.include?(attribute) 36 | self.list({ attribute => arguments[0] })[0] 37 | else 38 | super 39 | end 40 | else 41 | super 42 | end 43 | end 44 | 45 | def respond_to?(method, include_private = false) 46 | if method.to_s.start_with?('find_by_') 47 | attribute = method.to_s.sub('find_by_', '') 48 | if self.attribute_names.include?(attribute) 49 | return true 50 | else 51 | super 52 | end 53 | else 54 | super 55 | end 56 | end 57 | end 58 | 59 | attr_accessor :attributes, :api_end_point 60 | 61 | def self.included(base) 62 | base.extend(ClassMethods) 63 | 64 | base.send(:define_singleton_method, :attribute_names) do 65 | base::ATTRIBUTE_NAMES 66 | end 67 | 68 | base.send(:define_method, :init_api_end_point) do 69 | @api_end_point = base::API_END_POINT 70 | end 71 | end 72 | 73 | ## 74 | # @param [Hash] attributes 75 | def initialize(attributes = {}) 76 | init_api_end_point 77 | init_attributes(attributes) 78 | end 79 | 80 | # Get Kong API client 81 | # @return [Kong::Client] 82 | def client 83 | Client.instance 84 | end 85 | 86 | # Get resource 87 | # @param [String] key 88 | def get(key = nil) 89 | key = self.id if key.nil? 90 | path = @api_end_point + key 91 | response = client.get(path) rescue nil 92 | return nil if response.nil? 93 | init_attributes(response) 94 | self 95 | end 96 | 97 | # Delete resource 98 | def delete 99 | client.delete("#{@api_end_point}#{self.id}") 100 | end 101 | 102 | def new? 103 | self.id.nil? 104 | end 105 | 106 | # Save resource to Kong 107 | def save 108 | create_or_update 109 | end 110 | 111 | # Create resource 112 | def create 113 | headers = { 'Content-Type' => 'application/json' } 114 | response = client.post(@api_end_point, attributes, nil, headers) 115 | init_attributes(response) 116 | self 117 | end 118 | 119 | # Create or update resource 120 | # Data is sent to Kong in JSON format and HTTP PUT request is used 121 | def create_or_update 122 | headers = { 'Content-Type' => 'application/json' } 123 | response = client.put(@api_end_point, attributes, nil, headers) 124 | init_attributes(response) 125 | self 126 | end 127 | 128 | # Update resource 129 | def update 130 | headers = { 'Content-Type' => 'application/json' } 131 | response = client.patch("#{@api_end_point}#{self.id}", attributes, nil, headers) 132 | init_attributes(response) 133 | self 134 | end 135 | 136 | def method_missing(method, *arguments, &block) 137 | if self.class.attribute_names.include?(method.to_s) 138 | @attributes[method.to_s] 139 | elsif method.to_s.end_with?('=') && self.class.attribute_names.include?(attribute = method.to_s.split('=').first) 140 | @attributes[attribute] = arguments[0] 141 | else 142 | super 143 | end 144 | end 145 | 146 | def respond_to?(method, include_private = false) 147 | if self.class.attribute_names.include?(method.to_s.split('=')[0]) 148 | true 149 | else 150 | super 151 | end 152 | end 153 | 154 | private 155 | 156 | def init_attributes(attributes) 157 | @attributes = {} 158 | attributes.each do |key, value| 159 | @attributes[key.to_s] = value 160 | end 161 | use_consumer_end_point if respond_to?(:use_consumer_end_point) 162 | use_api_end_point if respond_to?(:use_api_end_point) 163 | use_upstream_end_point if respond_to?(:use_upstream_end_point) 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /lib/kong/basic_auth.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class BasicAuth 3 | include Base 4 | include BelongsToConsumer 5 | ATTRIBUTE_NAMES = %w(id username password consumer_id).freeze 6 | API_END_POINT = "/basic-auth/".freeze 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/kong/belongs_to_api.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | module BelongsToApi 3 | attr_accessor :api 4 | 5 | # Convert API end point relative to Kong API resource 6 | def use_api_end_point 7 | self.api_end_point = "/apis/#{self.api_id}#{self.class::API_END_POINT}" if self.api_id 8 | end 9 | 10 | # Get Api resource 11 | # @return [Kong::Api] 12 | def api 13 | @api ||= Api.find(self.api_id) 14 | end 15 | 16 | # Set Api resource 17 | # @param [Kong::Api] api 18 | def api=(api) 19 | @api = api 20 | self.api_id = api.id 21 | end 22 | 23 | # Set Api id 24 | # @param [String] id 25 | def api_id=(id) 26 | super(id) 27 | use_api_end_point 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/kong/belongs_to_consumer.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | module BelongsToConsumer 3 | attr_accessor :consumer 4 | 5 | # Convert API end point relative to Kong Consumer resource 6 | def use_consumer_end_point 7 | self.api_end_point = "/consumers/#{self.consumer_id}#{self.class::API_END_POINT}" if self.consumer_id 8 | end 9 | 10 | # Get Consumer resource 11 | # @return [Kong::Consumer] 12 | def consumer 13 | @consumer ||= Consumer.find(self.consumer_id) 14 | end 15 | 16 | # Set Consumer resource 17 | # @param [Kong::Consumer] consumer 18 | def consumer=(consumer) 19 | @consumer = consumer 20 | self.consumer_id = consumer.id 21 | end 22 | 23 | # Set Consumer id 24 | # @param [String] id 25 | def consumer_id=(id) 26 | super(id) 27 | use_consumer_end_point 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/kong/client.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'json' 3 | require 'excon' 4 | require_relative './error' 5 | 6 | module Kong 7 | class Client 8 | class << self 9 | attr_accessor :http_client 10 | end 11 | 12 | include Singleton 13 | 14 | attr_accessor :default_headers 15 | 16 | # Initialize api client 17 | # 18 | def initialize 19 | Excon.defaults[:ssl_verify_peer] = false if ignore_ssl_errors? 20 | @api_url = api_url 21 | self.class.http_client = Excon.new(@api_url, omit_default_port: true) 22 | @default_headers = { 'Accept' => 'application/json' } 23 | end 24 | 25 | def self.api_url 26 | @api_url || ENV['KONG_URI'] || 'http://localhost:8001' 27 | end 28 | 29 | def self.api_url=(url) 30 | @api_url = url 31 | @http_client = Excon.new(self.api_url, omit_default_port: true) 32 | end 33 | 34 | def http_client 35 | self.class.http_client 36 | end 37 | 38 | # Kong Admin API URL 39 | # 40 | # @return [String] 41 | def api_url 42 | self.class.api_url 43 | end 44 | 45 | def api_url=(url) 46 | @api_url = url 47 | end 48 | 49 | # Get request 50 | # 51 | # @param [String] path 52 | # @param [Hash,NilClass] params 53 | # @param [Hash] headers 54 | # @return [Hash] 55 | def get(path, params = nil, headers = {}) 56 | response = http_client.get( 57 | path: path, 58 | query: encode_params(params), 59 | headers: request_headers(headers) 60 | ) 61 | if response.status == 200 62 | parse_response(response) 63 | else 64 | handle_error_response(response) 65 | end 66 | end 67 | 68 | # Post request 69 | # 70 | # @param [String] path 71 | # @param [Object] obj 72 | # @param [Hash] params 73 | # @param [Hash] headers 74 | # @return [Hash] 75 | def post(path, obj, params = {}, headers = {}) 76 | request_headers = request_headers(headers) 77 | request_options = { 78 | path: path, 79 | headers: request_headers, 80 | body: encode_body(obj, request_headers['Content-Type']), 81 | query: encode_params(params) 82 | } 83 | response = http_client.post(request_options) 84 | if [200, 201].include?(response.status) 85 | parse_response(response) 86 | else 87 | handle_error_response(response) 88 | end 89 | end 90 | 91 | # Patch request 92 | # 93 | # @param [String] path 94 | # @param [Object] obj 95 | # @param [Hash] params 96 | # @param [Hash] headers 97 | # @return [Hash] 98 | def patch(path, obj, params = {}, headers = {}) 99 | request_headers = request_headers(headers) 100 | request_options = { 101 | path: path, 102 | headers: request_headers, 103 | body: encode_body(obj, request_headers['Content-Type']), 104 | query: encode_params(params) 105 | } 106 | 107 | response = http_client.patch(request_options) 108 | if [200, 201].include?(response.status) 109 | parse_response(response) 110 | else 111 | handle_error_response(response) 112 | end 113 | end 114 | 115 | # Put request 116 | # 117 | # @param [String] path 118 | # @param [Object] obj 119 | # @param [Hash] params 120 | # @param [Hash] headers 121 | # @return [Hash] 122 | def put(path, obj, params = {}, headers = {}) 123 | request_headers = request_headers(headers) 124 | request_options = { 125 | path: path, 126 | headers: request_headers, 127 | body: encode_body(obj, request_headers['Content-Type']), 128 | query: encode_params(params) 129 | } 130 | 131 | response = http_client.put(request_options) 132 | if [200, 201].include?(response.status) 133 | parse_response(response) 134 | else 135 | handle_error_response(response) 136 | end 137 | end 138 | 139 | # Delete request 140 | # 141 | # @param [String] path 142 | # @param [Hash,String] body 143 | # @param [Hash] params 144 | # @param [Hash] headers 145 | # @return [Hash] 146 | def delete(path, body = nil, params = {}, headers = {}) 147 | request_headers = request_headers(headers) 148 | request_options = { 149 | path: path, 150 | headers: request_headers, 151 | body: encode_body(body, request_headers['Content-Type']), 152 | query: encode_params(params) 153 | } 154 | response = http_client.delete(request_options) 155 | unless response.status == 204 156 | handle_error_response(response) 157 | end 158 | end 159 | 160 | private 161 | 162 | ## 163 | # Get request headers 164 | # 165 | # @param [Hash] headers 166 | # @return [Hash] 167 | def request_headers(headers = {}) 168 | @default_headers.merge(headers) 169 | end 170 | 171 | ## 172 | # Encode body based on content type 173 | # 174 | # @param [Object] body 175 | # @param [String] content_type 176 | def encode_body(body, content_type) 177 | if content_type == 'application/json' 178 | dump_json(body) 179 | else 180 | body 181 | end 182 | end 183 | 184 | def encode_params(params) 185 | return nil if params.nil? 186 | URI.encode_www_form(params) 187 | end 188 | 189 | ## 190 | # Parse response 191 | # 192 | # @param [HTTP::Message] 193 | # @return [Object] 194 | def parse_response(response) 195 | if response.headers['Content-Type'].include?('application/json') 196 | parse_json(response.body) 197 | else 198 | response.body 199 | end 200 | end 201 | 202 | ## 203 | # Parse json 204 | # 205 | # @param [String] json 206 | # @return [Hash,Object,NilClass] 207 | def parse_json(json) 208 | JSON.parse(json) rescue nil 209 | end 210 | 211 | ## 212 | # Dump json 213 | # 214 | # @param [Object] obj 215 | # @return [String] 216 | def dump_json(obj) 217 | JSON.dump(obj) 218 | end 219 | 220 | def ignore_ssl_errors? 221 | ENV['SSL_IGNORE_ERRORS'] == 'true' 222 | end 223 | 224 | def handle_error_response(response) 225 | message = response.body 226 | if response.status == 404 && message == '' 227 | message = 'Not found' 228 | end 229 | raise Error.new(response.status, message) 230 | end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /lib/kong/consumer.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Consumer 3 | include Base 4 | 5 | ATTRIBUTE_NAMES = %w(id custom_id username created_at).freeze 6 | API_END_POINT = '/consumers/'.freeze 7 | 8 | def delete 9 | self.oauth2_tokens.each do |token| 10 | token.delete 11 | end 12 | super 13 | end 14 | 15 | # List plugins 16 | # 17 | # @return [Array] 18 | def plugins 19 | Plugin.list({ consumer_id: self.id }) 20 | end 21 | 22 | # List OAuth applications 23 | # 24 | # @return [Array] 25 | def oauth_apps 26 | apps = [] 27 | response = client.get("#{@api_end_point}#{self.id}/oauth2") rescue nil 28 | if response 29 | response['data'].each do |attributes| 30 | apps << Kong::OAuthApp.new(attributes) 31 | end 32 | end 33 | apps 34 | end 35 | 36 | # List KeyAuth credentials 37 | # 38 | # @return [Array] 67 | def oauth2_tokens 68 | if self.custom_id 69 | OAuth2Token.list({ authenticated_userid: self.custom_id }) 70 | else 71 | [] 72 | end 73 | end 74 | 75 | # List Acls 76 | # 77 | # @return [Array] 78 | def acls 79 | acls = [] 80 | response = client.get("#{@api_end_point}#{self.id}/acls") rescue nil 81 | if response 82 | response['data'].each do |attributes| 83 | acls << Kong::Acl.new(attributes) 84 | end 85 | end 86 | acls 87 | end 88 | 89 | # List JWTs 90 | # 91 | # @return [Array] 92 | def jwts 93 | apps = [] 94 | response = client.get("#{@api_end_point}#{self.id}/jwt") rescue nil 95 | if response 96 | response['data'].each do |attributes| 97 | apps << Kong::JWT.new(attributes) 98 | end 99 | end 100 | apps 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/kong/error.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Error < StandardError 3 | attr_reader :status 4 | 5 | def initialize(status, message) 6 | @status = status 7 | super(message) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/kong/hmac_auth.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class HmacAuth 3 | include Base 4 | include BelongsToConsumer 5 | 6 | ATTRIBUTE_NAMES = %w(id username consumer_id).freeze 7 | API_END_POINT = '/hmac-auth/' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/kong/jwt.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class JWT 3 | include Base 4 | include BelongsToConsumer 5 | ATTRIBUTE_NAMES = %w(id key secret consumer_id).freeze 6 | API_END_POINT = '/jwt/'.freeze 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/kong/key_auth.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class KeyAuth 3 | include Base 4 | include BelongsToConsumer 5 | ATTRIBUTE_NAMES = %w(id key consumer_id).freeze 6 | API_END_POINT = "/key-auth/".freeze 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/kong/oauth2_token.rb: -------------------------------------------------------------------------------- 1 | require_relative './base' 2 | module Kong 3 | class OAuth2Token 4 | include Base 5 | ATTRIBUTE_NAMES = %w(id credential_id expires_in created_at token_type access_token refresh_token scope authenticated_userid).freeze 6 | API_END_POINT = "/oauth2_tokens/".freeze 7 | 8 | # Get OAuthApp resource 9 | # @return [Kong::OAuthApp] 10 | def oauth_app 11 | Kong::OAuthApp.find_by_id(self.credential_id) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/kong/oauth_app.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class OAuthApp 3 | include Base 4 | include BelongsToConsumer 5 | ATTRIBUTE_NAMES = %w(id name client_id client_secret redirect_uri consumer_id).freeze 6 | API_END_POINT = "/oauth2/".freeze 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/kong/plugin.rb: -------------------------------------------------------------------------------- 1 | require_relative './util' 2 | 3 | module Kong 4 | class Plugin 5 | include Base 6 | include BelongsToApi 7 | 8 | ATTRIBUTE_NAMES = %w(id api_id name config enabled consumer_id).freeze 9 | API_END_POINT = '/plugins/'.freeze 10 | 11 | # Create resource 12 | def create 13 | flatten_config 14 | super 15 | end 16 | 17 | # update resource 18 | def update 19 | flatten_config 20 | super 21 | end 22 | 23 | private 24 | 25 | def flatten_config 26 | if attributes['config'] 27 | attributes.merge!(Util.flatten(attributes.delete('config'), 'config')) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/kong/server.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Server 3 | def self.version 4 | self.info['version'] rescue nil 5 | end 6 | 7 | def self.info 8 | Client.instance.get('/') 9 | end 10 | 11 | def self.status 12 | Client.instance.get('/status') 13 | end 14 | 15 | def self.cluster 16 | Client.instance.get('/cluster') 17 | end 18 | 19 | def self.remove_node(name) 20 | Client.instance.delete("/cluster/nodes/#{name}") 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/kong/target.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Target 3 | include Base 4 | 5 | ATTRIBUTE_NAMES = %w(id upstream_id target weight).freeze 6 | API_END_POINT = '/targets/'.freeze 7 | 8 | def self.find(id) 9 | raise NotImplementedError, 'Kong does not support direct access to targets, you must go via an upstream' 10 | end 11 | 12 | def self.list(params = {}) 13 | raise NotImplementedError, 'Kong does not support direct access to targets, you must go via an upstream' 14 | end 15 | 16 | def initialize(attributes = {}) 17 | super(attributes) 18 | raise ArgumentError, 'You must specify an upstream_id' unless self.upstream_id 19 | end 20 | 21 | def active? 22 | self.weight > 0 23 | end 24 | 25 | def save 26 | create 27 | end 28 | 29 | def create_or_update 30 | raise NotImplementedError, 'Kong does not support updating targets, you must delete and re-create' 31 | end 32 | 33 | def update 34 | raise NotImplementedError, 'Kong does not support updating targets, you must delete and re-create' 35 | end 36 | 37 | def use_upstream_end_point 38 | self.api_end_point = "/upstreams/#{self.upstream_id}#{self.class::API_END_POINT}" if self.upstream_id 39 | end 40 | 41 | # Get Upstream resource 42 | # @return [Kong::Upstream] 43 | def upstream 44 | @upstream ||= Upstream.find(self.upstream_id) 45 | end 46 | 47 | # Set Upstream resource 48 | # @param [Kong::Upstream] upstream 49 | def upstream=(upstream) 50 | @upstream = upstream 51 | self.upstream_id = upstream.id 52 | end 53 | 54 | # Set Upstream id 55 | # @param [String] id 56 | def upstream_id=(id) 57 | super(id) 58 | use_upstream_end_point 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/kong/upstream.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | class Upstream 3 | include Base 4 | 5 | ATTRIBUTE_NAMES = %w(id name slots orderlist).freeze 6 | API_END_POINT = '/upstreams/'.freeze 7 | 8 | ## 9 | # @return [Array] 10 | def targets 11 | targets = [] 12 | json_data = Client.instance.get("#{API_END_POINT}#{self.id}/targets") 13 | 14 | if json_data['data'] 15 | json_data['data'].each do |target_data| 16 | target = Target.new(target_data) 17 | targets << target if target.active? 18 | end 19 | end 20 | 21 | targets 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/kong/util.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | module Util 3 | def self.flatten(cursor, parent_key = nil, memo = {}) 4 | memo.tap do 5 | case cursor 6 | when Hash 7 | cursor.keys.each do |key| 8 | flatten(cursor[key], [parent_key, key].compact.join('.'), memo) 9 | end 10 | else 11 | memo["#{parent_key}"] = cursor 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/kong/version.rb: -------------------------------------------------------------------------------- 1 | module Kong 2 | VERSION = '0.3.4'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/kong/acl_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Acl do 4 | let(:valid_attribute_names) do 5 | %w(id group consumer_id) 6 | end 7 | 8 | describe '::ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe '::API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/acls/') 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/kong/api_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Api do 4 | let(:valid_attribute_names) do 5 | %w( 6 | id name request_host request_path strip_request_path 7 | hosts uris strip_uri preserve_host upstream_url retries 8 | upstream_connect_timeout upstream_send_timeout upstream_read_timeout 9 | https_only http_if_terminated methods 10 | ) 11 | end 12 | 13 | describe 'ATTRIBUTE_NAMES' do 14 | it 'contains valid names' do 15 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 16 | end 17 | end 18 | 19 | describe 'API_END_POINT' do 20 | it 'contains valid end point' do 21 | expect(subject.class::API_END_POINT).to eq('/apis/') 22 | end 23 | end 24 | 25 | describe '.plugins' do 26 | it 'requests plugins attached to Api' do 27 | subject.id = '12345' 28 | expect(Kong::Plugin).to receive(:list).with({ api_id: subject.id }) 29 | subject.plugins 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/kong/base_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Base do 4 | 5 | let(:resource_class) do 6 | Class.new do 7 | include Kong::Base 8 | 9 | end 10 | end 11 | 12 | let(:subject) do 13 | Klass.new 14 | end 15 | 16 | before do 17 | stub_const 'Klass', resource_class 18 | stub_const 'Klass::API_END_POINT', '/resources/' 19 | stub_const 'Klass::ATTRIBUTE_NAMES', %w(id name) 20 | end 21 | 22 | describe '.list' do 23 | it 'requests GET /:resource_end_point/' do 24 | expect(Kong::Client.instance).to receive(:get).with('/resources/', {}).and_return({ data: [] }) 25 | Klass.list 26 | end 27 | 28 | it 'returns array of resource instances' do 29 | allow(Kong::Client.instance).to receive(:get).with('/resources/', {}) 30 | .and_return({ 'data' => [{ 'id' => '12345', 'name' => 'resource' }] }) 31 | result = Klass.list 32 | expect(result[0].is_a?(Klass)).to be_truthy 33 | end 34 | end 35 | 36 | describe '.all' do 37 | it 'is alias of .list' do 38 | expect(Klass.method(:list)).to eq(Klass.method(:all)) 39 | end 40 | end 41 | 42 | describe '.find' do 43 | it 'creates GET /:resource_end_point/:id request' do 44 | expect(Kong::Client.instance).to receive(:get).with('/resources/12345').and_return({ data: [] }) 45 | Klass.find('12345') 46 | end 47 | end 48 | 49 | describe '.respond_to' do 50 | context 'when attribute exits' do 51 | it 'will respond to find_by_* methods' do 52 | expect(Klass.respond_to?(:find_by_name)).to be_truthy 53 | end 54 | end 55 | 56 | context 'when attribute does not exit' do 57 | it 'will not respond to find_by_* methods' do 58 | expect(Klass.respond_to?(:find_by_invalid)).to be_falsey 59 | end 60 | end 61 | end 62 | 63 | describe '#get' do 64 | it 'creates GET /:resource_end_point/:id request' do 65 | expect(Kong::Client.instance).to receive(:get).with('/resources/12345').and_return({ data: [] }) 66 | subject.get('12345') 67 | end 68 | 69 | it 'returns resource instance' do 70 | allow(Kong::Client.instance).to receive(:get).with('/resources/12345') 71 | .and_return({ 'data' => [{ 'id' => '12345', 'name' => 'resource' }] }) 72 | result = subject.get('12345') 73 | expect(result.is_a?(Klass)).to be_truthy 74 | end 75 | 76 | it 'returns nil if resource is not found' do 77 | allow(Kong::Client.instance).to receive(:get).with('/resources/123456') 78 | .and_return(nil) 79 | result = subject.get('123456') 80 | expect(result).to be_nil 81 | end 82 | end 83 | 84 | describe '#new?' do 85 | it 'returns true if resource id is missing' do 86 | expect(subject.new?).to be_truthy 87 | end 88 | 89 | it 'returns false if resource has id' do 90 | subject.id = '12345' 91 | expect(subject.new?).to be_falsey 92 | end 93 | end 94 | 95 | describe '#create' do 96 | it 'creates POST /:resource_end_point/ request with resource attributes' do 97 | headers = { 'Content-Type' => 'application/json' } 98 | attributes = { 'name' => 'test object' } 99 | expect(Kong::Client.instance).to receive(:post).with('/resources/', attributes, nil, headers) 100 | .and_return(attributes) 101 | subject.name = 'test object' 102 | subject.create 103 | end 104 | 105 | it 'returns resource instance' do 106 | headers = { 'Content-Type' => 'application/json' } 107 | attributes = { 'name' => 'test object' } 108 | allow(Kong::Client.instance).to receive(:post).with('/resources/', attributes, nil, headers) 109 | .and_return(attributes.merge({ 'id' => '12345' })) 110 | subject.name = 'test object' 111 | expect(subject.create).to eq(subject) 112 | expect(subject.id).to eq('12345') 113 | end 114 | end 115 | 116 | describe '#create_or_update' do 117 | it 'creates PUT /:resource_end_point/ request with resource attributes as json payload' do 118 | headers = { 'Content-Type' => 'application/json' } 119 | attributes = { 'name' => 'test object' } 120 | expect(Kong::Client.instance).to receive(:put).with('/resources/', attributes, nil, headers) 121 | .and_return(attributes.merge({ 'id' => '12345' })) 122 | subject.name = 'test object' 123 | subject.create_or_update 124 | end 125 | 126 | it 'returns resource instance' do 127 | headers = { 'Content-Type' => 'application/json' } 128 | attributes = { 'name' => 'test object' } 129 | expect(Kong::Client.instance).to receive(:put).with('/resources/', attributes, nil, headers) 130 | .and_return(attributes.merge({ 'id' => '12345' })) 131 | subject.name = 'test object' 132 | expect(subject.create_or_update).to eq(subject) 133 | end 134 | 135 | describe '#update' do 136 | it 'creates PATCH /:resource_end_point/:resource_id request with resource attributes' do 137 | headers = { 'Content-Type' => 'application/json' } 138 | subject.id = '12345' 139 | subject.name = 'test object' 140 | expect(Kong::Client.instance).to receive(:patch).with('/resources/12345', subject.attributes, nil, headers) 141 | .and_return(subject.attributes) 142 | subject.update 143 | end 144 | 145 | it 'returns resource instance' do 146 | headers = { 'Content-Type' => 'application/json' } 147 | subject.id = '12345' 148 | subject.name = 'test object' 149 | allow(Kong::Client.instance).to receive(:patch).with('/resources/12345', subject.attributes, nil, headers) 150 | .and_return(subject.attributes) 151 | expect(subject.update).to eq(subject) 152 | end 153 | end 154 | 155 | it 'will respond to attribute' do 156 | expect(subject.respond_to?(:name)).to be_truthy 157 | end 158 | 159 | end 160 | 161 | end 162 | -------------------------------------------------------------------------------- /spec/kong/basic_auth_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::BasicAuth do 4 | let(:valid_attribute_names) do 5 | %w(id username password consumer_id) 6 | end 7 | 8 | describe 'ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe 'API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/basic-auth/') 17 | end 18 | end 19 | 20 | describe '#init_attributes' do 21 | it 'uses correct api end point if api_id is present' do 22 | subject = described_class.new({ consumer_id: ':consumer_id' }) 23 | expect(subject.api_end_point).to eq('/consumers/:consumer_id/basic-auth/') 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/kong/client_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Client do 4 | 5 | let(:subject) do 6 | described_class.instance 7 | end 8 | 9 | let(:http_client) do 10 | spy 11 | end 12 | 13 | describe '#initialize' do 14 | it 'initializes Excon with Kong URI' do 15 | described_class.api_url = nil 16 | expect(Excon).to receive(:new).with('http://localhost:8001', { omit_default_port: true }) 17 | described_class.send(:new) 18 | end 19 | 20 | it 'initializes default headers' do 21 | expect(described_class.instance.default_headers).to eq({ 'Accept' => 'application/json' }) 22 | described_class.send(:new) 23 | end 24 | end 25 | 26 | describe '#api_url' do 27 | 28 | def client_params 29 | uri = URI.parse described_class.api_url 30 | params = { 31 | host: uri.host, 32 | hostname: uri.hostname, 33 | path: uri.path, 34 | port: uri.port, 35 | query: uri.query, 36 | scheme: uri.scheme 37 | } 38 | described_class.instance.http_client.params.merge(params) 39 | end 40 | 41 | it 'returns localhost as default' do 42 | described_class.api_url = nil 43 | expect(subject.api_url).to eq('http://localhost:8001') 44 | end 45 | 46 | it 'returns value from environment variable' do 47 | allow(ENV).to receive(:[]).with('NO_PROXY') 48 | allow(ENV).to receive(:[]).with('no_proxy') 49 | allow(ENV).to receive(:[]).with('SSL_IGNORE_ERRORS') 50 | allow(ENV).to receive(:[]).with('KONG_URI').and_return('http://kong-api:8001') 51 | described_class.api_url = nil 52 | subject = described_class.send(:new) 53 | expect(subject.api_url).to eq('http://kong-api:8001') 54 | end 55 | 56 | it 'returns custom api_url if set' do 57 | url = 'http://foo.bar:1337' 58 | described_class.api_url = url 59 | expect(described_class.send(:new).api_url).to eq(url) 60 | end 61 | 62 | it 'can edit api_url' do 63 | described_class.api_url = nil 64 | expect(described_class.instance.http_client.params).to eq(client_params) 65 | described_class.api_url = 'http://foo.bar:1337' 66 | expect(described_class.instance.http_client.params).to eq(client_params) 67 | end 68 | end 69 | 70 | describe '#get' do 71 | before(:each) do 72 | allow(subject).to receive(:http_client).and_return(http_client) 73 | end 74 | it 'creates HTTP GET request with given params' do 75 | http_client_params = { 76 | path: 'path', 77 | query: { key: 'value' }, 78 | headers: {} 79 | } 80 | response = spy 81 | allow(response).to receive(:status).and_return(200) 82 | expect(http_client).to receive(:get).and_return(response) 83 | subject.get('path', { key: 'value' }) 84 | end 85 | 86 | it 'raises Kong::Error if request returns error' do 87 | http_client_params = { 88 | path: 'path', 89 | query: { key: 'value' }, 90 | headers: {} 91 | } 92 | response = spy 93 | allow(response).to receive(:status).and_return(403) 94 | expect(http_client).to receive(:get).and_return(response) 95 | expect { 96 | subject.get('path', { key: 'value' }) 97 | }.to raise_error(Kong::Error) 98 | end 99 | 100 | it 'parses response JSON' do 101 | http_client_params = { 102 | path: 'path', 103 | query: { key: 'value' }, 104 | headers: {} 105 | } 106 | response = spy 107 | allow(response).to receive(:status).and_return(200) 108 | allow(response).to receive(:body).and_return({ id: '12345' }.to_json) 109 | allow(response).to receive(:headers).and_return({ 'Content-Type' => 'application/json' }) 110 | allow(http_client).to receive(:get).and_return(response) 111 | expect(subject.get('path', { key: 'value' })).to eq({ 'id' => '12345' }) 112 | end 113 | 114 | it 'returns response text' do 115 | http_client_params = { 116 | path: 'path', 117 | query: { key: 'value' }, 118 | headers: {} 119 | } 120 | response = spy 121 | allow(response).to receive(:status).and_return(200) 122 | allow(response).to receive(:body).and_return('') 123 | allow(response).to receive(:headers).and_return({ 'Content-Type' => 'text/html' }) 124 | allow(http_client).to receive(:get).and_return(response) 125 | expect(subject.get('path', { key: 'value' })).to eq('') 126 | end 127 | end 128 | 129 | describe '#post' do 130 | before(:each) do 131 | allow(subject).to receive(:http_client).and_return(http_client) 132 | end 133 | it 'creates HTTP POST request with given params' do 134 | http_client_params = { 135 | path: 'path', 136 | query: { key: 'value' }, 137 | headers: {} 138 | } 139 | response = spy 140 | allow(response).to receive(:status).and_return(200) 141 | expect(http_client).to receive(:post).and_return(response) 142 | subject.post('path', nil, { key: 'value' }) 143 | end 144 | 145 | 146 | it 'raises Kong::Error if request returns error' do 147 | http_client_params = { 148 | path: 'path', 149 | query: { key: 'value' }, 150 | headers: {} 151 | } 152 | response = spy 153 | allow(response).to receive(:status).and_return(403) 154 | expect(http_client).to receive(:post).and_return(response) 155 | expect { 156 | subject.post('path', nil, { key: 'value' }) 157 | }.to raise_error(Kong::Error) 158 | end 159 | 160 | it 'parses response JSON' do 161 | http_client_params = { 162 | path: 'path', 163 | query: { key: 'value' }, 164 | headers: {} 165 | } 166 | response = spy 167 | allow(response).to receive(:status).and_return(200) 168 | allow(response).to receive(:body).and_return({ id: '12345' }.to_json) 169 | allow(response).to receive(:headers).and_return({ 'Content-Type' => 'application/json' }) 170 | allow(http_client).to receive(:post).and_return(response) 171 | expect(subject.post('path', nil, { key: 'value' })).to eq({ 'id' => '12345' }) 172 | end 173 | end 174 | describe '#patch' do 175 | before(:each) do 176 | allow(subject).to receive(:http_client).and_return(http_client) 177 | end 178 | it 'creates HTTP PATCH request with given params' do 179 | http_client_params = { 180 | path: 'path', 181 | query: { key: 'value' }, 182 | headers: {} 183 | } 184 | response = spy 185 | allow(response).to receive(:status).and_return(200) 186 | expect(http_client).to receive(:patch).and_return(response) 187 | subject.patch('path', nil, { key: 'value' }) 188 | end 189 | 190 | 191 | it 'raises Kong::Error if request returns error' do 192 | http_client_params = { 193 | path: 'path', 194 | query: { key: 'value' }, 195 | headers: {} 196 | } 197 | response = spy 198 | allow(response).to receive(:status).and_return(403) 199 | expect(http_client).to receive(:patch).and_return(response) 200 | expect { 201 | subject.patch('path', nil, { key: 'value' }) 202 | }.to raise_error(Kong::Error) 203 | end 204 | 205 | it 'parses response JSON' do 206 | http_client_params = { 207 | path: 'path', 208 | query: { key: 'value' }, 209 | headers: {} 210 | } 211 | response = spy 212 | allow(response).to receive(:status).and_return(200) 213 | allow(response).to receive(:body).and_return({ id: '12345' }.to_json) 214 | allow(response).to receive(:headers).and_return({ 'Content-Type' => 'application/json' }) 215 | allow(http_client).to receive(:patch).and_return(response) 216 | expect(subject.patch('path', nil, { key: 'value' })).to eq({ 'id' => '12345' }) 217 | end 218 | end 219 | 220 | describe '#put' do 221 | before(:each) do 222 | allow(subject).to receive(:http_client).and_return(http_client) 223 | end 224 | it 'creates HTTP PUT request with given params' do 225 | http_client_params = { 226 | path: 'path', 227 | query: { key: 'value' }, 228 | headers: {} 229 | } 230 | response = spy 231 | allow(response).to receive(:status).and_return(200) 232 | expect(http_client).to receive(:put).and_return(response) 233 | subject.put('path', nil, { key: 'value' }) 234 | end 235 | 236 | 237 | it 'raises Kong::Error if request returns error' do 238 | http_client_params = { 239 | path: 'path', 240 | query: { key: 'value' }, 241 | headers: {} 242 | } 243 | response = spy 244 | allow(response).to receive(:status).and_return(403) 245 | expect(http_client).to receive(:put).and_return(response) 246 | expect { 247 | subject.put('path', nil, { key: 'value' }) 248 | }.to raise_error(Kong::Error) 249 | end 250 | 251 | it 'parses response JSON' do 252 | http_client_params = { 253 | path: 'path', 254 | query: { key: 'value' }, 255 | headers: {} 256 | } 257 | response = spy 258 | allow(response).to receive(:status).and_return(200) 259 | allow(response).to receive(:body).and_return({ id: '12345' }.to_json) 260 | allow(response).to receive(:headers).and_return({ 'Content-Type' => 'application/json' }) 261 | allow(http_client).to receive(:put).and_return(response) 262 | expect(subject.put('path', nil, { key: 'value' })).to eq({ 'id' => '12345' }) 263 | end 264 | end 265 | 266 | describe '#delete' do 267 | before(:each) do 268 | allow(subject).to receive(:http_client).and_return(http_client) 269 | end 270 | it 'creates HTTP DELETE request with given params' do 271 | http_client_params = { 272 | path: 'path', 273 | query: {}, 274 | headers: {} 275 | } 276 | response = spy 277 | allow(response).to receive(:status).and_return(204) 278 | expect(http_client).to receive(:delete).and_return(response) 279 | subject.delete('path', nil, {}) 280 | end 281 | 282 | 283 | it 'raises Kong::Error if request returns other than 204' do 284 | http_client_params = { 285 | path: 'path', 286 | query: {}, 287 | headers: {} 288 | } 289 | response = spy 290 | allow(response).to receive(:status).and_return(403) 291 | expect(http_client).to receive(:delete).and_return(response) 292 | expect { 293 | subject.delete('path', nil, {}) 294 | }.to raise_error(Kong::Error) 295 | end 296 | end 297 | end 298 | -------------------------------------------------------------------------------- /spec/kong/consumer_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Consumer do 4 | let(:valid_attribute_names) do 5 | %w(id custom_id username created_at) 6 | end 7 | 8 | describe '::ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe '::API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/consumers/') 17 | end 18 | end 19 | 20 | describe '#oauth_apps' do 21 | it 'requests consumer\'s oauth_apps' do 22 | subject.id = ':id' 23 | expect(Kong::Client.instance).to receive(:get).with("/consumers/:id/oauth2") 24 | .and_return({ 'data' => [{ 'id' => '123456', 'name' => 'my app' }] }) 25 | subject.oauth_apps 26 | end 27 | 28 | it 'returns list of OAuthApp' do 29 | subject.id = ':id' 30 | allow(Kong::Client.instance).to receive(:get).with("/consumers/:id/oauth2") 31 | .and_return({ 'data' => [{ 'id' => '123456', 'name' => 'my app' }] }) 32 | result = subject.oauth_apps 33 | expect(result.first.is_a?(Kong::OAuthApp)).to be_truthy 34 | end 35 | end 36 | 37 | describe '#oauth2_tokens' do 38 | context 'when custom_id is set' do 39 | it 'requests oauth2_tokens assigned to consumer' do 40 | subject.custom_id = 'custom_id' 41 | expect(Kong::OAuth2Token).to receive(:list).with({ authenticated_userid: subject.custom_id }) 42 | subject.oauth2_tokens 43 | end 44 | end 45 | context 'when custom_id is not set' do 46 | it 'requests oauth2_tokens assigned to consumer' do 47 | expect(Kong::OAuth2Token).not_to receive(:list) 48 | subject.oauth2_tokens 49 | end 50 | it 'returns empty array' do 51 | expect(subject.oauth2_tokens).to eq([]) 52 | end 53 | end 54 | end 55 | 56 | describe '#acls' do 57 | it 'requests consumer\'s acls' do 58 | subject.id = ':id' 59 | expect(Kong::Client.instance).to receive(:get).with("/consumers/:id/acls") 60 | .and_return({ 'data' => [{ 'id' => '123456', 'group' => 'group1' }] }) 61 | subject.acls 62 | end 63 | 64 | it 'returns list of Acls' do 65 | subject.id = ':id' 66 | allow(Kong::Client.instance).to receive(:get).with("/consumers/:id/acls") 67 | .and_return({ 'data' => [{ 'id' => '123456', 'group' => 'group1' }] }) 68 | result = subject.acls 69 | expect(result.first.is_a?(Kong::Acl)).to be_truthy 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/kong/error_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Error do 4 | let(:instance) { described_class.new(status, message) } 5 | let(:status) { 123 } 6 | let(:message) { 'Error Message' } 7 | 8 | describe '.status' do 9 | subject { instance.status } 10 | 11 | it 'exposes error status' do 12 | is_expected.to eq status 13 | end 14 | end 15 | 16 | describe '.message' do 17 | subject { instance.message } 18 | 19 | it 'exposes error message' do 20 | is_expected.to eq message 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/kong/hmac_auth_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::HmacAuth do 4 | let(:valid_attribute_names) do 5 | %w(id username consumer_id) 6 | end 7 | 8 | describe 'ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe 'API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/hmac-auth/') 17 | end 18 | end 19 | 20 | describe '#init_attributes' do 21 | it 'uses correct api end point if api_id is present' do 22 | subject = described_class.new({ consumer_id: ':consumer_id' }) 23 | expect(subject.api_end_point).to eq('/consumers/:consumer_id/hmac-auth/') 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/kong/key_auth_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::KeyAuth do 4 | let(:valid_attribute_names) do 5 | %w(id key consumer_id) 6 | end 7 | 8 | describe 'ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe 'API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/key-auth/') 17 | end 18 | end 19 | 20 | describe '#init_attributes' do 21 | it 'uses correct api end point if api_id is present' do 22 | subject = described_class.new({ consumer_id: ':consumer_id' }) 23 | expect(subject.api_end_point).to eq('/consumers/:consumer_id/key-auth/') 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/kong/oauth2_token_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::OAuth2Token do 4 | let(:valid_attribute_names) do 5 | %w(id credential_id expires_in created_at token_type access_token refresh_token scope authenticated_userid) 6 | end 7 | 8 | describe 'ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe 'API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/oauth2_tokens/') 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/kong/oauth_app_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::OAuthApp do 4 | let(:valid_attribute_names) do 5 | %w(id name client_id client_secret redirect_uri consumer_id) 6 | end 7 | 8 | describe '::ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe '::API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/oauth2/') 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/kong/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Plugin do 4 | let(:valid_attribute_names) do 5 | %w(id api_id name config enabled consumer_id) 6 | end 7 | 8 | describe 'ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe 'API_END_POINT' do 15 | it 'contains valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/plugins/') 17 | end 18 | end 19 | 20 | describe '#init_attributes' do 21 | it 'uses correct api end point if api_id is present' do 22 | subject = described_class.new({ api_id: ':api_id' }) 23 | expect(subject.api_end_point).to eq('/apis/:api_id/plugins/') 24 | end 25 | end 26 | 27 | describe '#create' do 28 | it 'transforms config keys to config.key format' do 29 | headers = { 'Content-Type' => 'application/json' } 30 | attributes = { 'api_id' => ':api_id', 'config.anonymous' => '12345' } 31 | expect(Kong::Client.instance).to receive(:post).with('/apis/:api_id/plugins/', attributes, nil, headers).and_return(attributes) 32 | subject = described_class.new({ api_id: ':api_id', config: { 'anonymous' => '12345' } }) 33 | subject.create 34 | end 35 | 36 | it 'transforms nested config keys to config.key format' do 37 | headers = { 'Content-Type' => 'application/json' } 38 | attributes = { 'api_id' => ':api_id', 'config.anonymous' => '12345', 'config.first.second' => '1' } 39 | expect(Kong::Client.instance).to receive(:post).with('/apis/:api_id/plugins/', attributes, nil, headers).and_return(attributes) 40 | subject = described_class.new({ api_id: ':api_id', config: { 'anonymous' => '12345', 'first' => { 'second' => '1' } } }) 41 | subject.create 42 | end 43 | end 44 | 45 | describe '#update' do 46 | it 'transforms config keys to config.key format' do 47 | headers = { 'Content-Type' => 'application/json' } 48 | attributes = { 'api_id' => ':api_id', 'config.anonymous' => '12345' } 49 | expect(Kong::Client.instance).to receive(:patch).with('/apis/:api_id/plugins/', attributes, nil, headers).and_return(attributes) 50 | subject = described_class.new({ api_id: ':api_id', config: { 'anonymous' => '12345' } }) 51 | subject.update 52 | end 53 | 54 | it 'transforms nested config keys to config.key format' do 55 | headers = { 'Content-Type' => 'application/json' } 56 | attributes = { 'api_id' => ':api_id', 'config.anonymous' => '12345', 'config.first.second' => '1' } 57 | expect(Kong::Client.instance).to receive(:patch).with('/apis/:api_id/plugins/', attributes, nil, headers).and_return(attributes) 58 | subject = described_class.new({ api_id: ':api_id', config: { 'anonymous' => '12345', 'first' => { 'second' => '1' } } }) 59 | subject.update 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/kong/server_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Server do 4 | describe '.info' do 5 | it 'makes GET / request' do 6 | expect(Kong::Client.instance).to receive(:get).with('/') 7 | described_class.info 8 | end 9 | end 10 | 11 | describe '.version' do 12 | it 'returns version information' do 13 | allow(Kong::Client.instance).to receive(:get).with('/') 14 | .and_return({ 'version' => '0.10.0' }) 15 | expect(described_class.version).to eq('0.10.0') 16 | end 17 | end 18 | 19 | describe '.status' do 20 | it 'makes GET /status request' do 21 | expect(Kong::Client.instance).to receive(:get).with('/status') 22 | described_class.status 23 | end 24 | end 25 | 26 | describe '.cluster' do 27 | it 'makes GET /cluster request' do 28 | expect(Kong::Client.instance).to receive(:get).with('/cluster') 29 | described_class.cluster 30 | end 31 | end 32 | 33 | describe '.remove_node' do 34 | it 'makes DELETE /cluster/nodes/:name request' do 35 | expect(Kong::Client.instance).to receive(:delete).with('/cluster/nodes/:name') 36 | described_class.remove_node(':name') 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/kong/target_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Target do 4 | let(:upstream_id) { '1234' } 5 | 6 | let(:valid_attribute_names) do 7 | %w(id upstream_id target weight) 8 | end 9 | 10 | subject { described_class.new(upstream_id: upstream_id) } 11 | 12 | context 'without an upstream_id' do 13 | it 'raises an ArgumentError' do 14 | expect { described_class.new } 15 | .to raise_error(ArgumentError) 16 | end 17 | end 18 | 19 | describe 'ATTRIBUTE_NAMES' do 20 | it 'contains valid names' do 21 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 22 | end 23 | end 24 | 25 | describe 'API_END_POINT' do 26 | it 'contains valid end point' do 27 | expect(subject.class::API_END_POINT).to eq('/targets/') 28 | end 29 | end 30 | 31 | describe '#init_attributes' do 32 | it 'uses the correct api end point if the upstream_id is present' do 33 | expect(subject.api_end_point).to eq("/upstreams/#{upstream_id}/targets/") 34 | end 35 | end 36 | 37 | describe '.upstream' do 38 | it 'requests the attached Upstream' do 39 | expect(Kong::Upstream).to receive(:find).with(upstream_id) 40 | subject.upstream 41 | end 42 | end 43 | 44 | describe '.active?' do 45 | it 'returns true if the weight is > 0' do 46 | target1 = described_class.new(upstream_id: upstream_id, target: 'google.com', weight: 100) 47 | target2 = described_class.new(upstream_id: upstream_id, target: 'google.com', weight: 0) 48 | 49 | expect(target1).to be_active 50 | expect(target2).to_not be_active 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/kong/upstream_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Upstream do 4 | let(:valid_attribute_names) do 5 | %w(id name slots orderlist) 6 | end 7 | 8 | describe 'ATTRIBUTE_NAMES' do 9 | it 'contains valid names' do 10 | expect(subject.class::ATTRIBUTE_NAMES).to eq(valid_attribute_names) 11 | end 12 | end 13 | 14 | describe 'API_END_POINT' do 15 | it 'contains a valid end point' do 16 | expect(subject.class::API_END_POINT).to eq('/upstreams/') 17 | end 18 | end 19 | 20 | describe '.targets' do 21 | it 'requests targets attached to the Upstream' do 22 | subject.id = '12345' 23 | 24 | expect(Kong::Client.instance) 25 | .to receive(:get).with("/upstreams/12345/targets") 26 | .and_return({ 27 | 'data' => [{ 'upstream_id' => 12345, 'target' => 'google.com:80', 'weight' => 100 }], 28 | 'total' => 1 29 | }) 30 | 31 | targets = subject.targets 32 | 33 | expect(targets.size).to eq(1) 34 | expect(targets.first).to be_a(Kong::Target) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/kong/util_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe Kong::Util do 4 | describe '.flatten' do 5 | subject { described_class.method(:flatten) } 6 | 7 | it 'works' do 8 | expect(subject[{ 9 | a: { 10 | b: 1, 11 | c: { 12 | d: 3 13 | } 14 | }, 15 | b: 2 16 | }]).to include({ 17 | "a.b" => 1, 18 | "a.c.d" => 3, 19 | "b" => 2, 20 | }) 21 | end 22 | 23 | it 'accepts a scope' do 24 | expect(subject[{ a: "1" }, 'config']).to eq({ 25 | "config.a" => "1" 26 | }) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # Require this file using `require "spec_helper"` to ensure that it is only 4 | # loaded once. 5 | # 6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 7 | 8 | require 'kong' 9 | 10 | RSpec.configure do |config| 11 | config.run_all_when_everything_filtered = true 12 | config.filter_run :focus 13 | 14 | # Run specs in random order to surface order dependencies. If you find an 15 | # order dependency and want to debug it, you can fix the order by providing 16 | # the seed, which is printed after each run. 17 | # --seed 1234 18 | config.order = 'random' 19 | end 20 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'rspec/core/rake_task' 3 | RSpec::Core::RakeTask.new(:spec) 4 | rescue LoadError 5 | end 6 | --------------------------------------------------------------------------------