pe-razor-client 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ ---
2
+
3
+ # This file helps Razor::CLI::Navigate with a few things by annotating the
4
+ # API. Ultimately, this should be something the server provides. But until
5
+ # we better understand what sort of annotation we need from the server,
6
+ # we'll keep this with the client.
7
+ #
8
+ # Possible argument types are
9
+ # - json: parse the argument value as a JSON document and use that
10
+ # - boolean: if the argument has no value associated with it, or the value
11
+ # is "true", convert to +true+; everything else gets converted
12
+ # to +false+
13
+ commands:
14
+ create-broker:
15
+ args:
16
+ configuration: json
17
+ create-tag:
18
+ args:
19
+ rule: json
20
+ add-policy-tag:
21
+ args:
22
+ rule: json
23
+ update-tag-rule:
24
+ args:
25
+ rule: json
26
+ reboot-node:
27
+ args:
28
+ hard: boolean
@@ -0,0 +1,86 @@
1
+ require 'uri'
2
+ require 'optparse'
3
+
4
+ module Razor::CLI
5
+
6
+ class Parse
7
+ DEFAULT_RAZOR_API = "http://localhost:8080/api"
8
+
9
+ def get_optparse
10
+ @optparse ||= OptionParser.new do |opts|
11
+ opts.banner = "Usage: razor [FLAGS] NAVIGATION\n"
12
+
13
+ opts.on "-d", "--dump", "Dumps API output to the screen" do
14
+ @dump = true
15
+ end
16
+
17
+ opts.on "-u", "--url URL",
18
+ "The full Razor API URL, can also be set\n" + " "*37 +
19
+ "with the RAZOR_API environment variable\n" + " "*37 +
20
+ "(default #{DEFAULT_RAZOR_API})" do |url|
21
+ parse_and_set_api_url(url, :opts)
22
+ end
23
+
24
+ opts.on "-h", "--help", "Show this screen" do
25
+ @option_help = true
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ def list_things(name, items)
32
+ "\n #{name}:\n" +
33
+ items.map {|x| x["name"]}.compact.sort.map do |name|
34
+ " #{name}"
35
+ end.join("\n")
36
+ end
37
+
38
+ def help
39
+ output = get_optparse.to_s
40
+ begin
41
+ output << list_things("Collections", navigate.collections)
42
+ output << "\n\n Navigate to entries of a collection using COLLECTION NAME, for example,\n 'nodes node15' for the details of a node or 'nodes node15 log' to see\n the log for node15\n"
43
+ output << list_things("Commands", navigate.commands)
44
+ output << "\n\n Pass arguments to commands either directly by name ('--name=NAME')\n or save the JSON body for the command in a file and pass it with\n '--json FILE'. Using --json is the only way to pass arguments in\n nested structures such as the configuration for a broker.\n"
45
+ rescue
46
+ output << "\nCould not connect to the server at #{@api_url}. More help is available after "
47
+ output << "pointing\nthe client to a Razor server"
48
+ end
49
+ output
50
+ end
51
+
52
+ def show_help?
53
+ !!@option_help
54
+ end
55
+
56
+ def dump_response?
57
+ !!@dump
58
+ end
59
+
60
+ attr_reader :api_url
61
+
62
+ def initialize(args)
63
+ parse_and_set_api_url(ENV["RAZOR_API"] || DEFAULT_RAZOR_API, :env)
64
+ @args = args.dup
65
+ rest = get_optparse.order(args)
66
+ if rest.any?
67
+ @navigation = rest
68
+ else
69
+ @option_help = true
70
+ end
71
+ end
72
+
73
+ def navigate
74
+ @navigate ||=Navigate.new(self, @navigation)
75
+ end
76
+
77
+ private
78
+ def parse_and_set_api_url(url, source)
79
+ begin
80
+ @api_url = URI.parse(url)
81
+ rescue URI::InvalidURIError => e
82
+ raise Razor::CLI::InvalidURIError.new(url, source)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pe-razor-client"
7
+ spec.version = "0.14.0"
8
+ spec.authors = ["Puppet Labs"]
9
+ spec.email = ["info@puppetlabs.com"]
10
+ spec.description = "The client for the Razor server"
11
+ spec.summary = "The client for everybody's favorite provisioning tool"
12
+ spec.homepage = "https://github.com/puppetlabs/razor-client"
13
+ spec.license = "ASL2"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.bindir = "bin"
17
+ spec.executables = ['razor']
18
+ spec.test_files = spec.files.grep(%r{^spec/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version
22
+
23
+ # mime-types is a dependency of rest-client. We need to explicitly depend
24
+ # on it and pin its version to make sure the gem works with Ruby 1.8.7
25
+ spec.add_dependency "mime-types", '< 2.0'
26
+ spec.add_dependency "multi_json"
27
+ spec.add_dependency "rest-client"
28
+ spec.add_dependency "terminal-table"
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.3"
31
+ spec.add_development_dependency "rake"
32
+ end
@@ -0,0 +1,52 @@
1
+ # Needed to make the client work on Ruby 1.8.7
2
+ unless Kernel.respond_to?(:require_relative)
3
+ module Kernel
4
+ def require_relative(path)
5
+ require File.join(File.dirname(caller[0]), path.to_str)
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative '../spec_helper'
11
+
12
+ describe Razor::CLI::Navigate do
13
+ context "with no path", :vcr do
14
+ subject(:nav) {Razor::CLI::Parse.new([]).navigate}
15
+ it do
16
+ nav.get_document.should_not be_nil
17
+ nav.get_document.should == nav.entrypoint
18
+ end
19
+ end
20
+
21
+ context "with a single item path", :vcr do
22
+ subject(:nav) {Razor::CLI::Parse.new(["tags"]).navigate}
23
+ it { nav.get_document.should == []}
24
+
25
+ it do
26
+ nav.get_document;
27
+ nav.last_url.to_s.should =~ %r{/api/collections/tags$}
28
+ end
29
+ end
30
+
31
+ context "with an invalid path", :vcr do
32
+ subject(:nav) {Razor::CLI::Parse.new(["going","nowhere"]).navigate}
33
+
34
+ it {expect{nav.get_document}.to raise_error Razor::CLI::NavigationError}
35
+ end
36
+
37
+ context "with authentication", :vcr do
38
+ AuthArg = %w[-u http://fred:dead@localhost:8080/api].freeze
39
+
40
+ it "should supply that to the API service" do
41
+ nav = Razor::CLI::Parse.new(AuthArg).navigate
42
+ nav.get_document.should be_an_instance_of Hash
43
+ URI.parse(nav.last_url.to_s).userinfo.should == "fred:dead"
44
+ end
45
+
46
+ it "should preserve that across navigation" do
47
+ nav = Razor::CLI::Parse.new(AuthArg + ['tags']).navigate
48
+ nav.get_document.should == []
49
+ URI.parse(nav.last_url.to_s).userinfo.should == "fred:dead"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,76 @@
1
+ # Needed to make the client work on Ruby 1.8.7
2
+ unless Kernel.respond_to?(:require_relative)
3
+ module Kernel
4
+ def require_relative(path)
5
+ require File.join(File.dirname(caller[0]), path.to_str)
6
+ end
7
+ end
8
+ end
9
+
10
+ require "rspec/expectations"
11
+ require_relative '../spec_helper'
12
+
13
+ describe Razor::CLI::Parse do
14
+
15
+ def parse(*args)
16
+ Razor::CLI::Parse.new(args)
17
+ end
18
+
19
+ describe "#new" do
20
+ context "with no arguments" do
21
+ it {parse.show_help?.should be true}
22
+ end
23
+
24
+ context "with a '-h'" do
25
+ it {parse("-h").show_help?.should be true}
26
+ end
27
+
28
+ context "with a '-d'" do
29
+ it {parse("-d").dump_response?.should be true}
30
+ end
31
+
32
+ context "with a '-u'" do
33
+ it "should use the given URL" do
34
+ url = 'http://razor.example.com:2150/path/to/api'
35
+ parse('-u',url).api_url.to_s.should == url
36
+ end
37
+
38
+ it "should terminate with an error if an invalid URL is provided" do
39
+ expect{parse('-u','not valid url')}.to raise_error(Razor::CLI::InvalidURIError)
40
+ end
41
+ end
42
+
43
+ context "with ENV RAZOR_API set" do
44
+ it "should use the given URL" do
45
+ url = 'http://razor.example.com:2150/env/path/to/api'
46
+ ENV["RAZOR_API"] = url
47
+ parse.api_url.to_s.should == url
48
+ end
49
+
50
+ it "should use -u before ENV" do
51
+ env_url = 'http://razor.example.com:2150/env/path/to/api'
52
+ url = 'http://razor.example.com:2150/path/to/api'
53
+ ENV["RAZOR_API"] = env_url
54
+ parse('-u',url).api_url.to_s.should == url
55
+ end
56
+
57
+ it "should terminate with an error if an invalid URL is provided" do
58
+ ENV["RAZOR_API"] = 'not valid url'
59
+ expect{parse}.to raise_error(Razor::CLI::InvalidURIError)
60
+ end
61
+ end
62
+
63
+ describe "#help", :vcr do
64
+ subject(:p) {parse}
65
+ it { should respond_to :help}
66
+
67
+ it { p.help.should be_a String}
68
+
69
+ it "should print a list of known endpoints" do
70
+ p.navigate.should_receive(:collections).and_return([])
71
+ p.navigate.should_receive(:commands).and_return([])
72
+ p.help
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,69 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://localhost:8080/api
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/json
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - Apache-Coyote/1.1
23
+ X-Content-Type-Options:
24
+ - nosniff
25
+ Content-Type:
26
+ - application/json;charset=utf-8
27
+ Content-Length:
28
+ - '1566'
29
+ Date:
30
+ - Wed, 28 Aug 2013 21:46:05 GMT
31
+ body:
32
+ encoding: US-ASCII
33
+ string: ! '{"commands":[{"name":"create-image","rel":"http://api.puppetlabs.com/razor/v1/commands/create-image","id":"http://localhost:8080/api/commands/create-image"},{"name":"delete-image","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-image","id":"http://localhost:8080/api/commands/delete-image"},{"name":"create-installer","rel":"http://api.puppetlabs.com/razor/v1/commands/create-installer","id":"http://localhost:8080/api/commands/create-installer"},{"name":"create-tag","rel":"http://api.puppetlabs.com/razor/v1/commands/create-tag","id":"http://localhost:8080/api/commands/create-tag"},{"name":"create-broker","rel":"http://api.puppetlabs.com/razor/v1/commands/create-broker","id":"http://localhost:8080/api/commands/create-broker"},{"name":"create-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/create-policy","id":"http://localhost:8080/api/commands/create-policy"}],"collections":[{"name":"brokers","rel":"http://api.puppetlabs.com/razor/v1/collections/brokers","id":"http://localhost:8080/api/collections/brokers"},{"name":"images","rel":"http://api.puppetlabs.com/razor/v1/collections/images","id":"http://localhost:8080/api/collections/images"},{"name":"tags","rel":"http://api.puppetlabs.com/razor/v1/collections/tags","id":"http://localhost:8080/api/collections/tags"},{"name":"policies","rel":"http://api.puppetlabs.com/razor/v1/collections/policies","id":"http://localhost:8080/api/collections/policies"},{"name":"nodes","rel":"http://api.puppetlabs.com/razor/v1/collections/nodes","id":"http://localhost:8080/api/collections/nodes"}]}'
34
+ http_version:
35
+ recorded_at: Wed, 28 Aug 2013 21:46:05 GMT
36
+ - request:
37
+ method: get
38
+ uri: http://localhost:8080/api/collections/tags
39
+ body:
40
+ encoding: US-ASCII
41
+ string: ''
42
+ headers:
43
+ Accept:
44
+ - application/json
45
+ Accept-Encoding:
46
+ - gzip, deflate
47
+ User-Agent:
48
+ - Ruby
49
+ response:
50
+ status:
51
+ code: 200
52
+ message: OK
53
+ headers:
54
+ Server:
55
+ - Apache-Coyote/1.1
56
+ X-Content-Type-Options:
57
+ - nosniff
58
+ Content-Type:
59
+ - application/json;charset=utf-8
60
+ Content-Length:
61
+ - '2'
62
+ Date:
63
+ - Wed, 28 Aug 2013 21:46:05 GMT
64
+ body:
65
+ encoding: US-ASCII
66
+ string: ! '[]'
67
+ http_version:
68
+ recorded_at: Wed, 28 Aug 2013 21:46:05 GMT
69
+ recorded_with: VCR 2.5.0
@@ -0,0 +1,36 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://localhost:8080/api
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/json
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - Apache-Coyote/1.1
23
+ X-Content-Type-Options:
24
+ - nosniff
25
+ Content-Type:
26
+ - application/json;charset=utf-8
27
+ Content-Length:
28
+ - '1566'
29
+ Date:
30
+ - Wed, 28 Aug 2013 21:46:05 GMT
31
+ body:
32
+ encoding: US-ASCII
33
+ string: ! '{"commands":[{"name":"create-image","rel":"http://api.puppetlabs.com/razor/v1/commands/create-image","id":"http://localhost:8080/api/commands/create-image"},{"name":"delete-image","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-image","id":"http://localhost:8080/api/commands/delete-image"},{"name":"create-installer","rel":"http://api.puppetlabs.com/razor/v1/commands/create-installer","id":"http://localhost:8080/api/commands/create-installer"},{"name":"create-tag","rel":"http://api.puppetlabs.com/razor/v1/commands/create-tag","id":"http://localhost:8080/api/commands/create-tag"},{"name":"create-broker","rel":"http://api.puppetlabs.com/razor/v1/commands/create-broker","id":"http://localhost:8080/api/commands/create-broker"},{"name":"create-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/create-policy","id":"http://localhost:8080/api/commands/create-policy"}],"collections":[{"name":"brokers","rel":"http://api.puppetlabs.com/razor/v1/collections/brokers","id":"http://localhost:8080/api/collections/brokers"},{"name":"images","rel":"http://api.puppetlabs.com/razor/v1/collections/images","id":"http://localhost:8080/api/collections/images"},{"name":"tags","rel":"http://api.puppetlabs.com/razor/v1/collections/tags","id":"http://localhost:8080/api/collections/tags"},{"name":"policies","rel":"http://api.puppetlabs.com/razor/v1/collections/policies","id":"http://localhost:8080/api/collections/policies"},{"name":"nodes","rel":"http://api.puppetlabs.com/razor/v1/collections/nodes","id":"http://localhost:8080/api/collections/nodes"}]}'
34
+ http_version:
35
+ recorded_at: Wed, 28 Aug 2013 21:46:05 GMT
36
+ recorded_with: VCR 2.5.0
@@ -0,0 +1,102 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://localhost:8080/api/collections/tags
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - application/json
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - Apache-Coyote/1.1
23
+ X-Content-Type-Options:
24
+ - nosniff
25
+ Content-Type:
26
+ - application/json;charset=utf-8
27
+ Content-Length:
28
+ - '73'
29
+ Date:
30
+ - Wed, 29 Jan 2014 21:06:11 GMT
31
+ body:
32
+ encoding: US-ASCII
33
+ string: ! '{"spec":"http://api.puppetlabs.com/razor/v1/collections/tags","items":[]}'
34
+ http_version:
35
+ recorded_at: Wed, 29 Jan 2014 21:06:11 GMT
36
+ - request:
37
+ method: get
38
+ uri: http://fred:dead@localhost:8080/api
39
+ body:
40
+ encoding: US-ASCII
41
+ string: ''
42
+ headers:
43
+ Accept:
44
+ - application/json
45
+ Accept-Encoding:
46
+ - gzip, deflate
47
+ User-Agent:
48
+ - Ruby
49
+ response:
50
+ status:
51
+ code: 200
52
+ message: OK
53
+ headers:
54
+ Server:
55
+ - Apache-Coyote/1.1
56
+ X-Content-Type-Options:
57
+ - nosniff
58
+ Content-Type:
59
+ - application/json;charset=utf-8
60
+ Content-Length:
61
+ - '4491'
62
+ Date:
63
+ - Wed, 29 Jan 2014 21:14:20 GMT
64
+ body:
65
+ encoding: US-ASCII
66
+ string: ! '{"commands":[{"name":"create-repo","rel":"http://api.puppetlabs.com/razor/v1/commands/create-repo","id":"http://localhost:8080/api/commands/create-repo"},{"name":"delete-repo","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-repo","id":"http://localhost:8080/api/commands/delete-repo"},{"name":"delete-node","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-node","id":"http://localhost:8080/api/commands/delete-node"},{"name":"delete-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-policy","id":"http://localhost:8080/api/commands/delete-policy"},{"name":"update-node-metadata","rel":"http://api.puppetlabs.com/razor/v1/commands/update-node-metadata","id":"http://localhost:8080/api/commands/update-node-metadata"},{"name":"remove-node-metadata","rel":"http://api.puppetlabs.com/razor/v1/commands/remove-node-metadata","id":"http://localhost:8080/api/commands/remove-node-metadata"},{"name":"modify-node-metadata","rel":"http://api.puppetlabs.com/razor/v1/commands/modify-node-metadata","id":"http://localhost:8080/api/commands/modify-node-metadata"},{"name":"reinstall-node","rel":"http://api.puppetlabs.com/razor/v1/commands/reinstall-node","id":"http://localhost:8080/api/commands/reinstall-node"},{"name":"set-node-ipmi-credentials","rel":"http://api.puppetlabs.com/razor/v1/commands/set-node-ipmi-credentials","id":"http://localhost:8080/api/commands/set-node-ipmi-credentials"},{"name":"reboot-node","rel":"http://api.puppetlabs.com/razor/v1/commands/reboot-node","id":"http://localhost:8080/api/commands/reboot-node"},{"name":"set-node-desired-power-state","rel":"http://api.puppetlabs.com/razor/v1/commands/set-node-desired-power-state","id":"http://localhost:8080/api/commands/set-node-desired-power-state"},{"name":"create-task","rel":"http://api.puppetlabs.com/razor/v1/commands/create-task","id":"http://localhost:8080/api/commands/create-task"},{"name":"create-tag","rel":"http://api.puppetlabs.com/razor/v1/commands/create-tag","id":"http://localhost:8080/api/commands/create-tag"},{"name":"delete-tag","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-tag","id":"http://localhost:8080/api/commands/delete-tag"},{"name":"update-tag-rule","rel":"http://api.puppetlabs.com/razor/v1/commands/update-tag-rule","id":"http://localhost:8080/api/commands/update-tag-rule"},{"name":"create-broker","rel":"http://api.puppetlabs.com/razor/v1/commands/create-broker","id":"http://localhost:8080/api/commands/create-broker"},{"name":"delete-broker","rel":"http://api.puppetlabs.com/razor/v1/commands/delete-broker","id":"http://localhost:8080/api/commands/delete-broker"},{"name":"create-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/create-policy","id":"http://localhost:8080/api/commands/create-policy"},{"name":"move-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/move-policy","id":"http://localhost:8080/api/commands/move-policy"},{"name":"enable-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/enable-policy","id":"http://localhost:8080/api/commands/enable-policy"},{"name":"disable-policy","rel":"http://api.puppetlabs.com/razor/v1/commands/disable-policy","id":"http://localhost:8080/api/commands/disable-policy"},{"name":"add-policy-tag","rel":"http://api.puppetlabs.com/razor/v1/commands/add-policy-tag","id":"http://localhost:8080/api/commands/add-policy-tag"},{"name":"remove-policy-tag","rel":"http://api.puppetlabs.com/razor/v1/commands/remove-policy-tag","id":"http://localhost:8080/api/commands/remove-policy-tag"},{"name":"modify-policy-max-count","rel":"http://api.puppetlabs.com/razor/v1/commands/modify-policy-max-count","id":"http://localhost:8080/api/commands/modify-policy-max-count"}],"collections":[{"name":"brokers","rel":"http://api.puppetlabs.com/razor/v1/collections/brokers","id":"http://localhost:8080/api/collections/brokers"},{"name":"repos","rel":"http://api.puppetlabs.com/razor/v1/collections/repos","id":"http://localhost:8080/api/collections/repos"},{"name":"tags","rel":"http://api.puppetlabs.com/razor/v1/collections/tags","id":"http://localhost:8080/api/collections/tags"},{"name":"policies","rel":"http://api.puppetlabs.com/razor/v1/collections/policies","id":"http://localhost:8080/api/collections/policies"},{"name":"nodes","rel":"http://api.puppetlabs.com/razor/v1/collections/nodes","id":"http://localhost:8080/api/collections/nodes"},{"name":"tasks","rel":"http://api.puppetlabs.com/razor/v1/collections/tasks","id":"http://localhost:8080/api/collections/tasks"}]}'
67
+ http_version:
68
+ recorded_at: Wed, 29 Jan 2014 21:14:20 GMT
69
+ - request:
70
+ method: get
71
+ uri: http://fred:dead@localhost:8080/api/collections/tags
72
+ body:
73
+ encoding: US-ASCII
74
+ string: ''
75
+ headers:
76
+ Accept:
77
+ - application/json
78
+ Accept-Encoding:
79
+ - gzip, deflate
80
+ User-Agent:
81
+ - Ruby
82
+ response:
83
+ status:
84
+ code: 200
85
+ message: OK
86
+ headers:
87
+ Server:
88
+ - Apache-Coyote/1.1
89
+ X-Content-Type-Options:
90
+ - nosniff
91
+ Content-Type:
92
+ - application/json;charset=utf-8
93
+ Content-Length:
94
+ - '73'
95
+ Date:
96
+ - Wed, 29 Jan 2014 21:14:20 GMT
97
+ body:
98
+ encoding: US-ASCII
99
+ string: ! '{"spec":"http://api.puppetlabs.com/razor/v1/collections/tags","items":[]}'
100
+ http_version:
101
+ recorded_at: Wed, 29 Jan 2014 21:14:21 GMT
102
+ recorded_with: VCR 2.5.0