3scale_toolbox 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1faca82c3c88cb4312fa76704e70d656a400f53c3aecb03c507e8a1aeb374284
4
- data.tar.gz: 32c28726d94b60b8238720b1ffe055b06b4ecb1aab3945fd069b899d19f12595
3
+ metadata.gz: bc76523728a2d5fa59805175676eac19b02a689db6c2b6045d821f8b6d5b8fc3
4
+ data.tar.gz: 33908dec9c0714102d0899643e40bcc933b90a412b440325ef8476b4427d8596
5
5
  SHA512:
6
- metadata.gz: 720b320e9e20edecdb93256c2a804ffaff13593f180960e2e10027d5c915c94094a7c2bbf2aca9c9a3b78fc7e9c993bcfc3151d4ac61eab8bf0113a63ed0d5d5
7
- data.tar.gz: c27a4f1aea1076ad163437cdf0b0082763a6f49fa10bcd2045fe63047ea08dc3ba0258833f8fcd8015a8de4604909c9ba7cf9b477b55511999a4c3544dcab5a2
6
+ metadata.gz: ff2630a709bb3af2a8e93cc650359f8d3461444a41438aa261415922a02b9ea9f425dbd027f6d8d46652462544d4e388e9b15ef5066f772bd1ae99457794ead5
7
+ data.tar.gz: 22c140218478ee375e3762e5cb5ffe674e9d1502e7b702d7303341c98d84ad4680ef3887f6608f9ed42cf7bb584a195a099e33d6dfc4196c11201be21b2ca920
data/README.md CHANGED
@@ -2,9 +2,18 @@
2
2
 
3
3
  3scale toolbox is a set of tools to help you manage your 3scale product. Using the [3scale API Ruby Client](https://github.com/3scale/3scale-api-ruby).
4
4
 
5
- ## Installation
6
-
5
+ ## Table of contents
6
+ * [Installation](#installation)
7
+ * [Usage](#usage)
8
+ * [Copy a service](#copy-a-service)
9
+ * [Update a service](#update-a-service)
10
+ * [Import from CSV](#import-from-csv)
11
+ * [Development](#development)
12
+ * [Plugins](#plugins)
13
+ * [Troubleshooting](#troubleshooting)
14
+ * [Contributing](#contributing)
7
15
 
16
+ ## Installation
8
17
  Install the CLI:
9
18
 
10
19
  $ gem install 3scale_toolbox
@@ -12,20 +21,150 @@ Install the CLI:
12
21
  ## Usage
13
22
 
14
23
  ```shell
15
- 3scale help
24
+ $ 3scale help
25
+ NAME
26
+ 3scale - 3scale CLI Toolbox
27
+
28
+ USAGE
29
+ 3scale <command> [options]
30
+
31
+ DESCRIPTION
32
+ 3scale CLI tools to manage your API from the terminal.
33
+
34
+ COMMANDS
35
+ copy 3scale copy command
36
+ help show help
37
+ import 3scale import command
38
+ update 3scale update command
39
+
40
+ OPTIONS
41
+ -k --insecure Proceed and operate even for server connections
42
+ otherwise considered insecure
43
+ -v --version Prints the version of this command
16
44
  ```
17
45
 
18
46
  ### Copy a service
47
+ Will create a new services, copy existing proxy settings, metrics, methods, application plans and mapping rules.
48
+
49
+ Help message:
19
50
 
20
- Will create a new service, copy existing methods, metrics, application plans and their usage limits.
51
+ ```shell
52
+ $ 3scale copy service --help
53
+ NAME
54
+ service - Copy service
55
+
56
+ USAGE
57
+ 3scale copy service [opts] -s <src> -d <dst>
58
+ <service_id>
59
+
60
+ DESCRIPTION
61
+ Will create a new services, copy existing proxy settings, metrics,
62
+ methods, application plans and mapping rules.
63
+
64
+ OPTIONS
65
+ -d --destination=<value> 3scale target instance. Format:
66
+ "http[s]://<provider_key>@3scale_url"
67
+ -s --source=<value> 3scale source instance. Format:
68
+ "http[s]://<provider_key>@3scale_url"
69
+ -t --target_system_name=<value> Target system name
70
+
71
+ OPTIONS FOR COPY
72
+ -h --help show help for this command
73
+ -k --insecure Proceed and operate even for server
74
+ connections otherwise considered
75
+ insecure
76
+ -v --version Prints the version of this command
77
+ ```
21
78
 
22
79
  ```shell
23
80
  3scale copy service NUMBER --source=https://provider_key@foo-admin.3scale.net --destination=https://provider_key@foo2-admin.3scale.net
24
81
  ```
25
82
 
83
+ ### Update a service
84
+
85
+ Will update existing service, update proxy settings, metrics, methods, application plans and mapping rules.
86
+
87
+ Help message:
88
+
89
+ ```shell
90
+ NAME
91
+ service - Update service
92
+
93
+ USAGE
94
+ 3scale update service [opts] -s <src> -d <dst>
95
+ <src_service_id> <dst_service_id>
96
+
97
+ DESCRIPTION
98
+ Will update existing service, update proxy settings, metrics, methods,
99
+ application plans and mapping rules.
100
+
101
+ OPTIONS
102
+ -d --destination=<value> 3scale target instance. Format:
103
+ "http[s]://<provider_key>@3scale_url"
104
+ -f --force Overwrites the mapping rules by deleting
105
+ all rules from target service first
106
+ -r --rules-only Updates only the mapping rules
107
+ -s --source=<value> 3scale source instance. Format:
108
+ "http[s]://<provider_key>@3scale_url"
109
+
110
+ OPTIONS FOR UPDATE
111
+ -h --help show help for this command
112
+ -k --insecure Proceed and operate even for server
113
+ connections otherwise considered insecure
114
+ -v --version Prints the version of this command
115
+ ```
116
+
117
+ Example:
118
+
119
+ ```shell
120
+ $ 3scale update service -s https://9874598743@source.example.com -d https://2342342342342@destination.example.com 3 2
121
+ ```
122
+
26
123
  ### Import from CSV
27
124
 
28
- Will create a new services, metrics, methods and mapping rules.
125
+ Will create new services, metrics, methods, and mapping rules having as source comma separated values (CSV) formatted file.
126
+
127
+ CSV header
128
+
129
+ ```csv
130
+ service_name,endpoint_name,endpoint_http_method,endpoint_path,auth_mode,endpoint_system_name,type
131
+ ```
132
+
133
+ File example
134
+
135
+ ```csv
136
+ service_name,endpoint_name,endpoint_http_method,endpoint_path,auth_mode,endpoint_system_name,type
137
+ Movies ,Movies (Biography),GET,/movies/biography/,api_key,movies_biography,metric
138
+ Movies ,Movies (Drama),GET,/movies/drama/,api_key,movies_drama,method
139
+ ```
140
+
141
+ Help message:
142
+
143
+ ```shell
144
+ $ 3scale import csv -h
145
+ NAME
146
+ csv - Import csv file
147
+
148
+ USAGE
149
+ 3scale import csv [opts] -d <dst> -f <file>
150
+
151
+ DESCRIPTION
152
+ Create new services, metrics, methods and mapping rules from CSV
153
+ formatted file
154
+
155
+ OPTIONS
156
+ -d --destination=<value> 3scale target instance. Format:
157
+ "http[s]://<provider_key>@3scale_url"
158
+ -f --file=<value> CSV formatted file
159
+
160
+ OPTIONS FOR IMPORT
161
+ -h --help show help for this command
162
+ -k --insecure Proceed and operate even for server
163
+ connections otherwise considered insecure
164
+ -v --version Prints the version of this command
165
+ ```
166
+
167
+ Example:
29
168
 
30
169
  ```shell
31
170
  3scale import csv --destination=https://provider_key@user-admin.3scale.net --file=examples/import_example.csv
@@ -37,6 +176,17 @@ After checking out the repo, run `bin/setup` to install dependencies. You can al
37
176
 
38
177
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
39
178
 
179
+ ## Plugins
180
+
181
+ As of 3scale Toolbox 0.5.0, 3scale Toolbox will load plugins installed in gems or $LOAD_PATH. Plugins are discovered via Gem::find_files then loaded.
182
+ Install, uninstall and update plugins using tools like [RubyGems](https://guides.rubygems.org/rubygems-basics/) and/or [Bundler](https://bundler.io/).
183
+
184
+ [Make your own plugin](docs/plugins.md)
185
+
186
+ ## Troubleshooting
187
+
188
+ * [SSL errors](docs/ssl_errors.md): If you run into SSL issues with the toolbox, you can take actions to resolve them.
189
+
40
190
  ## Contributing
41
191
 
42
192
  Bug reports and pull requests are welcome on GitHub at https://github.com/3scale/3scale_toolbox.
data/exe/3scale CHANGED
@@ -1,14 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require '3scale_toolbox'
3
4
  require '3scale_toolbox/cli'
4
5
 
5
- options, argv = ThreeScaleToolbox::CLI.parse
6
+ args = ARGV.clone
6
7
 
7
- unless options.command
8
- puts 'Available subcommands: '
9
- puts ThreeScaleToolbox::CLI.subcommands
10
- puts
11
- ThreeScaleToolbox::CLI.print_help!
12
- end
13
-
14
- exec options.command.full_path, *ARGV
8
+ ThreeScaleToolbox::CLI.run args
@@ -0,0 +1,28 @@
1
+
2
+ module ThreeScaleToolbox
3
+ module Command
4
+ def subcommands
5
+ @subcommands ||= []
6
+ end
7
+
8
+ def add_subcommand(command)
9
+ subcommands << command
10
+ end
11
+
12
+ ##
13
+ # Override to command
14
+ #
15
+ def command
16
+ raise Exception, 'base command has no command definition'
17
+ end
18
+
19
+ ##
20
+ # Iterate recursively over command tree
21
+ #
22
+ def build_command
23
+ subcommands.each_with_object(command) do |subcommand, root_command|
24
+ root_command.add_command(subcommand.build_command)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,78 +1,22 @@
1
- require 'optparse'
1
+ require '3scale_toolbox'
2
+ require '3scale_toolbox/commands'
2
3
 
3
- module ThreeScaleToolbox
4
- module CLI
5
- Options = Struct.new(:command)
6
-
7
- class Parser
8
- def self.parse(options)
9
- args = Options.new(nil)
10
-
11
- opt_parser = OptionParser.new do |opts|
12
- opts.banner = "Usage: 3scale <command> [options]"
13
-
14
-
15
- opts.on("-h", "--help", "Prints this help") do
16
- puts opts
17
- exit
18
- end
19
- end
20
-
21
- begin
22
- opt_parser.order!(options)
23
- rescue OptionParser::InvalidOption => e
24
- p e
25
- end
26
-
27
- return args
28
- end
29
- end
30
-
31
- def self.parse(argv = ARGV)
32
- options = Parser.parse(argv)
33
- options.command = argv.shift
34
- options.command = subcommands.find { |subcommand| subcommand.name == options.command }
35
-
36
- [ options, argv ]
37
- end
38
-
39
- def self.print_help!
40
- Parser.parse %w[--help]
41
- end
42
-
43
- def self.plugins
44
- Gem.loaded_specs.select{ |name, _| name.start_with?('3scale') }.values
45
- end
46
-
47
- def self.current_command
48
- File.expand_path($0, Dir.pwd)
49
- end
50
-
51
- def self.subcommands
52
- plugins
53
- .flat_map { |spec| spec.executables.flat_map{ |bin| Subcommand.new(bin, spec) } }
54
- .reject { |subcommand| subcommand.full_path == current_command || subcommand.name.nil? }
55
- end
56
-
57
- class Subcommand
58
- attr_reader :executable, :spec
59
-
60
- def initialize(executable, spec)
61
- @executable = executable
62
- @spec = spec
63
- end
4
+ module ThreeScaleToolbox::CLI
5
+ def self.root_command
6
+ ThreeScaleToolbox::Commands::ThreeScaleCommand
7
+ end
64
8
 
65
- def to_s
66
- name
67
- end
9
+ def self.add_command(command)
10
+ root_command.add_subcommand(command)
11
+ end
68
12
 
69
- def name
70
- executable.split('-', 2)[1]
71
- end
13
+ def self.load_builtin_commands
14
+ ThreeScaleToolbox::Commands::BUILTIN_COMMANDS.each(&method(:add_command))
15
+ end
72
16
 
73
- def full_path
74
- spec.bin_file(executable)
75
- end
76
- end
17
+ def self.run(args)
18
+ load_builtin_commands
19
+ ThreeScaleToolbox.load_plugins
20
+ root_command.build_command.run args
77
21
  end
78
22
  end
@@ -0,0 +1,28 @@
1
+ require 'cri'
2
+ require '3scale_toolbox/version'
3
+ require '3scale_toolbox/base_command'
4
+
5
+ module ThreeScaleToolbox
6
+ module Commands
7
+ module ThreeScaleCommand
8
+ extend ThreeScaleToolbox::Command
9
+ def self.command
10
+ Cri::Command.define do
11
+ name '3scale'
12
+ usage '3scale <command> [options]'
13
+ summary '3scale CLI Toolbox'
14
+ description '3scale CLI tools to manage your API from the terminal.'
15
+ flag :v, :version, 'Prints the version of this command' do |_, _|
16
+ puts ThreeScaleToolbox::VERSION
17
+ exit
18
+ end
19
+ flag :k, :insecure, 'Proceed and operate even for server connections otherwise considered insecure'
20
+ flag :h, :help, 'show help for this command' do |_, cmd|
21
+ puts cmd.help
22
+ exit 0
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,218 @@
1
+ require 'cri'
2
+ require '3scale_toolbox/base_command'
3
+
4
+ module ThreeScaleToolbox
5
+ module Commands
6
+ module CopyCommand
7
+ module CopyServiceSubcommand
8
+ extend ThreeScaleToolbox::Command
9
+ def self.command
10
+ Cri::Command.define do
11
+ name 'service'
12
+ usage 'service [opts] -s <src> -d <dst> <service_id>'
13
+ summary 'Copy service'
14
+ description 'Will create a new services, copy existing proxy settings, metrics, methods, application plans and mapping rules.'
15
+
16
+ required :s, :source, '3scale source instance. Format: "http[s]://<provider_key>@3scale_url"'
17
+ required :d, :destination, '3scale target instance. Format: "http[s]://<provider_key>@3scale_url"'
18
+ required :t, 'target_system_name', 'Target system name'
19
+
20
+ run do |opts, args, _|
21
+ CopyServiceSubcommand.run opts, args
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.run(opts, args)
27
+ source = fetch_required_option(opts, :source)
28
+ destination = fetch_required_option(opts, :destination)
29
+ system_name = fetch_required_option(opts, :target_system_name)
30
+ insecure = opts[:insecure] || false
31
+ exit_with_message 'error: missing service_id argument' if args.empty?
32
+ service_id = args[0]
33
+ copy_service(service_id, source, destination, system_name, insecure)
34
+ end
35
+
36
+ def self.exit_with_message(message)
37
+ puts message
38
+ exit 1
39
+ end
40
+
41
+ def self.fetch_required_option(options, key)
42
+ options.fetch(key) { exit_with_message "error: Missing argument #{key}" }
43
+ end
44
+
45
+ def self.compare_hashes(first, second, keys)
46
+ keys.map{ |key| first.fetch(key) } == keys.map{ |key| second.fetch(key) }
47
+ end
48
+
49
+ def self.provider_key_from_url(url)
50
+ url[/\w*@/][0..-2]
51
+ end
52
+
53
+ def self.endpoint_from_url(url)
54
+ url.sub /\w*@/, ''
55
+ end
56
+
57
+
58
+ # Returns new hash object with not nil valid params
59
+ def self.filter_params(valid_params, source)
60
+ valid_params.each_with_object({}) do |key, target|
61
+ target[key] = source[key] unless source[key].nil?
62
+ end
63
+ end
64
+
65
+ def self.copy_service_params(original, system_name)
66
+ service_params = filter_params(Commands.service_valid_params, original)
67
+ service_params.tap do |hash|
68
+ hash['system_name'] = system_name if system_name
69
+ end
70
+ end
71
+
72
+ def self.copy_service(service_id, source, destination, system_name, insecure)
73
+ require '3scale/api'
74
+
75
+ source_client = ThreeScale::API.new(
76
+ endpoint: endpoint_from_url(source),
77
+ provider_key: provider_key_from_url(source),
78
+ verify_ssl: !insecure
79
+ )
80
+ client = ThreeScale::API.new(
81
+ endpoint: endpoint_from_url(destination),
82
+ provider_key: provider_key_from_url(destination),
83
+ verify_ssl: !insecure
84
+ )
85
+
86
+ service = source_client.show_service(service_id)
87
+ copy = client.create_service(copy_service_params(service, system_name))
88
+
89
+ raise "Service has not been saved. Errors: #{copy['errors']}" unless copy['errors'].nil?
90
+
91
+ service_copy_id = copy.fetch('id')
92
+
93
+ puts "new service id #{service_copy_id}"
94
+
95
+ proxy = source_client.show_proxy(service_id)
96
+ client.update_proxy(service_copy_id, proxy)
97
+ puts "updated proxy of #{service_copy_id} to match the original"
98
+
99
+ metrics = source_client.list_metrics(service_id)
100
+ metrics_copies = client.list_metrics(service_copy_id)
101
+
102
+ hits = metrics.find{ |metric| metric['system_name'] == 'hits' } or raise 'missing hits metric'
103
+ hits_copy = metrics_copies.find{ |metric| metric['system_name'] == 'hits' } or raise 'missing hits metric'
104
+
105
+ methods = source_client.list_methods(service_id, hits['id'])
106
+ methods_copies = client.list_methods(service_copy_id, hits_copy['id'])
107
+
108
+ puts "original service hits metric #{hits['id']} has #{methods.size} methods"
109
+ puts "copied service hits metric #{hits_copy['id']} has #{methods_copies.size} methods"
110
+
111
+ missing_methods = methods.reject { |method| methods_copies.find{|copy| compare_hashes(method, copy, ['system_name']) } }
112
+
113
+ puts "creating #{missing_methods.size} missing methods on copied service"
114
+
115
+ missing_methods.each do |method|
116
+ copy = { friendly_name: method['friendly_name'], system_name: method['system_name'] }
117
+ client.create_method(service_copy_id, hits_copy['id'], copy)
118
+ end
119
+
120
+ metrics_copies = client.list_metrics(service_copy_id)
121
+
122
+ puts "original service has #{metrics.size} metrics"
123
+ puts "copied service has #{metrics_copies.size} metrics"
124
+
125
+ missing_metrics = metrics.reject { |metric| metrics_copies.find{|copy| compare_hashes(metric, copy, ['system_name']) } }
126
+
127
+ missing_metrics.map do |metric|
128
+ metric.delete('links')
129
+ client.create_metric(service_copy_id, metric)
130
+ end
131
+
132
+ puts "created #{missing_metrics.size} metrics on the copied service"
133
+
134
+ plans = source_client.list_service_application_plans(service_id)
135
+ plan_copies = client.list_service_application_plans(service_copy_id)
136
+
137
+ puts "original service has #{plans.size} application plans "
138
+ puts "copied service has #{plan_copies.size} application plans"
139
+
140
+ missing_application_plans = plans.reject { |plan| plan_copies.find{|copy| plan.fetch('system_name') == copy.fetch('system_name') } }
141
+
142
+ puts "copied service missing #{missing_application_plans.size} application plans"
143
+
144
+ missing_application_plans.each do |plan|
145
+ plan.delete('links')
146
+ plan.delete('default') # TODO: handle default plan
147
+
148
+ if plan.delete('custom') # TODO: what to do with custom plans?
149
+ puts "skipping custom plan #{plan}"
150
+ else
151
+ client.create_application_plan(service_copy_id, plan)
152
+ end
153
+ end
154
+
155
+ application_plan_mapping = client.list_service_application_plans(service_copy_id).map do |plan_copy|
156
+ plan = plans.find{|plan| plan.fetch('system_name') == plan_copy.fetch('system_name') }
157
+
158
+ [plan['id'], plan_copy['id']]
159
+ end
160
+
161
+ metrics_mapping = client.list_metrics(service_copy_id).map do |copy|
162
+ metric = metrics.find{|metric| metric.fetch('system_name') == copy.fetch('system_name') }
163
+ metric ||= {}
164
+
165
+ [metric['id'], copy['id']]
166
+ end.to_h
167
+
168
+ puts "destroying all mapping rules of the copy which have been created by default"
169
+ client.list_mapping_rules(service_copy_id).each do |mapping_rule|
170
+ client.delete_mapping_rule(service_copy_id, mapping_rule['id'])
171
+ end
172
+
173
+ mapping_rules = source_client.list_mapping_rules(service_id)
174
+ mapping_rules_copy = client.list_mapping_rules(service_copy_id)
175
+
176
+ puts "the original service has #{mapping_rules.size} mapping rules"
177
+ puts "the copy has #{mapping_rules_copy.size} mapping rules"
178
+
179
+ unique_mapping_rules_copy = mapping_rules_copy.dup
180
+
181
+ missing_mapping_rules = mapping_rules.reject do |mapping_rule|
182
+ matching_metric = unique_mapping_rules_copy.find do |copy|
183
+ compare_hashes(mapping_rule, copy, %w(pattern http_method delta)) &&
184
+ metrics_mapping.fetch(mapping_rule.fetch('metric_id')) == copy.fetch('metric_id')
185
+ end
186
+
187
+ unique_mapping_rules_copy.delete(matching_metric)
188
+ end
189
+
190
+ puts "missing #{missing_mapping_rules.size} mapping rules"
191
+
192
+ missing_mapping_rules.each do |mapping_rule|
193
+ mapping_rule.delete('links')
194
+ mapping_rule['metric_id'] = metrics_mapping.fetch(mapping_rule.delete('metric_id'))
195
+ client.create_mapping_rule(service_copy_id, mapping_rule)
196
+ end
197
+ puts "created #{missing_mapping_rules.size} mapping rules"
198
+
199
+ puts "extra #{unique_mapping_rules_copy.size} mapping rules"
200
+ puts unique_mapping_rules_copy.each{|rule| rule.delete('links') }
201
+
202
+ application_plan_mapping.each do |original_id, copy_id|
203
+ limits = source_client.list_application_plan_limits(original_id)
204
+ limits_copy = client.list_application_plan_limits(copy_id)
205
+
206
+ missing_limits = limits.reject { |limit| limits_copy.find{|limit_copy| limit.fetch('period') == limit_copy.fetch('period') } }
207
+
208
+ missing_limits.each do |limit|
209
+ limit.delete('links')
210
+ client.create_application_plan_limit(copy_id, metrics_mapping.fetch(limit.fetch('metric_id')), limit)
211
+ end
212
+ puts "copied application plan #{copy_id} is missing #{missing_limits.size} from the original plan #{original_id}"
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end