konzertmeister 2.0.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5679713dea43f8aa152592f629a42b5a7ae79d89
4
+ data.tar.gz: 6bd78152ad09ed1ae8d9daf7f42c124c8b61ed2e
5
+ SHA512:
6
+ metadata.gz: e5888c8b9b3277252faa67d3c259ff22640b4caf88437f250b26a5fdd606941eea6b032bb39f1bf64e4cc48220f8acbeabaa8d1d56bc2fdff232bb3a680809fd
7
+ data.tar.gz: 8d500622431f22d2625ab48e55add264d3841862803a3ea7412fd49b72d4b4a48a8de518f05a33e60f3a6dbbb1672348b940f61c42b49784a71ea04d3ea6166f
@@ -0,0 +1,34 @@
1
+ require 'rest-client'
2
+
3
+ class Konzertmeister
4
+ autoload :Backend, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'backend.rb'))
5
+ autoload :Host, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'host.rb'))
6
+ autoload :Customer, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'customer.rb'))
7
+ autoload :MetaVersion, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'meta_version.rb'))
8
+ autoload :InfrastructureVersion, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'infrastructure_version.rb'))
9
+ autoload :ProductVersion, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'product_version.rb'))
10
+ autoload :Session, File.expand_path(File.join(File.dirname(__FILE__), 'konzertmeister', 'session.rb'))
11
+
12
+ def self.session
13
+ return @@session
14
+ end
15
+
16
+ def self.session=(new_session)
17
+ @@session = new_session
18
+ return @@session
19
+ end
20
+
21
+ def self.new_session(
22
+ hostname: 'konzertmeister',
23
+ port: 443,
24
+ ssl_client_cert: nil,
25
+ ssl_client_key: nil
26
+ )
27
+ @@session = Konzertmeister::Session.new(
28
+ hostname: hostname,
29
+ port: port,
30
+ ssl_client_cert: ssl_client_cert,
31
+ ssl_client_key: ssl_client_key
32
+ )
33
+ end
34
+ end
@@ -0,0 +1,197 @@
1
+ class Konzertmeister
2
+ class Backend
3
+ KINDS = %w[
4
+ production
5
+ sandbox
6
+ development
7
+ internal
8
+ ]
9
+
10
+ attr_accessor :name, :kind
11
+ attr_reader :id, :short_name, :puppet_master, :monthly_cost, :created_at, :updated_at
12
+
13
+ def self.all
14
+ response = Konzertmeister.session.get('/backends')
15
+ if response
16
+ response.map do |data|
17
+ Konzertmeister::Backend.new(data)
18
+ end.sort_by do |b|
19
+ "#{b.customer.short_name} #{b.short_name}"
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.find_by(attr, value)
25
+ Backend.new(Konzertmeister.session.get("/backends/#{attr}/#{value}"))
26
+ end
27
+
28
+ def initialize(data = {})
29
+ @data = data
30
+ data.each do |k,value|
31
+ next if %w[hosts customer meta_version].include?(k)
32
+
33
+ if m = k.match(/^backend_(.*)$/)
34
+ key = m[1]
35
+ else
36
+ key = k
37
+ end
38
+
39
+ instance_variable_set("@#{key}", value)
40
+ end
41
+ end
42
+
43
+ def save
44
+ params = {
45
+ backend_platform: 'Rackspace',
46
+ backend_name: name,
47
+ backend_kind: kind,
48
+ customer: {customer_id: customer.id},
49
+ meta_version: {meta_version_id: meta_version.id},
50
+ coda_count: coda_count,
51
+ coda_flavor: coda_flavor,
52
+ postgres_flavor: postgres_flavor
53
+ }
54
+ puts "Creating with params: #{params}"
55
+
56
+ response = Konzertmeister.session.post("/backends", params)
57
+ if response
58
+ response_object = Konzertmeister::Backend.new(response)
59
+ @id = response_object.id
60
+ @short_name = response_object.short_name
61
+ @puppet_master = response_object.puppet_master
62
+ @monthly_cost = response_object.monthly_cost
63
+ @created_at = response_object.created_at
64
+ @updated_at = response_object.updated_at
65
+ response_object
66
+ end
67
+ end
68
+
69
+ def hosts
70
+ @hosts ||= if @data.key?('hosts')
71
+ @data.fetch('hosts').map do |host_hash|
72
+ Konzertmeister::Host.new(host_hash)
73
+ end.sort_by(&:hostname)
74
+ else
75
+ Backend.find_by('id', self.id).hosts
76
+ end
77
+ end
78
+
79
+ def coda_count
80
+ @coda_count ||= self.hosts.size
81
+ end
82
+
83
+ def coda_count=(size)
84
+ @coda_count = size
85
+ end
86
+
87
+ def coda_flavor
88
+ @coda_flavor ||= self.hosts.find{|h| h.kind == Konzertmeister::Host::CODA_KIND}.flavor
89
+ end
90
+
91
+ def coda_flavor=(input_flavor)
92
+ if Konzertmeister::Host::FLAVORS.include?(input_flavor)
93
+ @coda_flavor = input_flavor
94
+ else
95
+ raise "Invalid flavor: #{input_flavor}. Choose one of: #{Konzertmeister::Host::Flavors.join(', ')}"
96
+ end
97
+ end
98
+
99
+ def postgres_flavor
100
+ @postgres_flavor ||= self.hosts.find{|h| h.kind == Konzertmeister::Host::POSTGRES_KIND}.flavor
101
+ end
102
+
103
+ def postgres_flavor=(input_flavor)
104
+ if Konzertmeister::Host::FLAVORS.include?(input_flavor)
105
+ @postgres_flavor = input_flavor
106
+ else
107
+ raise "Invalid flavor: #{input_flavor}. Choose one of: #{Konzertmeister::Host::Flavors.join(', ')}"
108
+ end
109
+ end
110
+
111
+ def meta_version
112
+ @meta_version ||= if @data.key?('meta_version')
113
+ Konzertmeister::MetaVersion.new(@data.fetch('meta_version'))
114
+ else
115
+ Backend.find_by('id', self.id).meta_version
116
+ end
117
+ end
118
+
119
+ def meta_version=(input_mv)
120
+ @meta_version = input_mv
121
+ end
122
+
123
+ def customer
124
+ @customer ||= if @data.key?('customer')
125
+ Konzertmeister::Customer.new(@data.fetch('customer'))
126
+ else
127
+ Backend.find_by('id', self.id).customer
128
+ end
129
+ end
130
+
131
+ def customer=(input_customer)
132
+ @customer = input_customer
133
+ end
134
+
135
+ def proxy_host
136
+ @proxy_host ||= @data.fetch('proxy').fetch('proxy_host')
137
+ end
138
+
139
+ def proxy_port
140
+ @proxy_port ||= @data.fetch('proxy').fetch('proxy_port')
141
+ end
142
+
143
+ def full_name
144
+ [
145
+ short_name,
146
+ customer.short_name
147
+ ].join("-")
148
+ end
149
+
150
+ def fqdn(scope = nil)
151
+ first = scope.nil? ? full_name : "#{full_name}-admin"
152
+ [
153
+ first,
154
+ "backend",
155
+ "tempoiq",
156
+ "com"
157
+ ].join(".")
158
+ end
159
+
160
+ def production?
161
+ @kind == 'production'
162
+ end
163
+
164
+ def ssh_prefix(user, port=1535, *args)
165
+ proxy = @data.fetch("proxy")
166
+ proxy_host = proxy.fetch("proxy_host")
167
+ proxy_port = proxy.fetch("proxy_port")
168
+
169
+ %Q{ssh -p #{port} -o "ProxyCommand ssh -p #{proxy_port} #{user}@#{proxy_host} nc %h %p" #{args.join(' ')}}
170
+ end
171
+
172
+ def proxy_command(host, user, port=1535, *args)
173
+ prefix = ssh_prefix(user, port, *args)
174
+ %Q{#{prefix} #{user}@#{host.management_ip}}
175
+ end
176
+
177
+ def rsync_up_command(host, user, file_local, file_remote="", port=1535, *args)
178
+ prefix = ssh_prefix(user, port, *args)
179
+ %Q{rsync -avzP -e '#{prefix}' #{file_local} #{user}@#{host.management_ip}:#{file_remote}}
180
+ end
181
+
182
+ def rsync_down_command(host, user, file_remote, file_local, port=1535, *args)
183
+ prefix = ssh_prefix(user, port, *args)
184
+ %Q{rsync -avzP -e '#{prefix}' #{user}@#{host.management_ip}:#{file_remote} #{file_local}}
185
+ end
186
+
187
+ def to_s
188
+ cost = "%1.2f" % monthly_cost
189
+
190
+ ([
191
+ "$#{cost}/month #{full_name} (#{kind})",
192
+ "MetaVersion: #{meta_version}",
193
+ "Hosts:"
194
+ ] + hosts.sort_by(&:name).map {|h| " #{h}"}).join("\n")
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,65 @@
1
+ class Konzertmeister
2
+ class Customer
3
+ attr_accessor :name
4
+ attr_reader :id, :short_name, :created_at, :updated_at
5
+
6
+ def self.all
7
+ response = Konzertmeister.session.get('/customers')
8
+ if response
9
+ response.map do |data|
10
+ Konzertmeister::Customer.new(data)
11
+ end.sort_by(&:short_name)
12
+ end
13
+ end
14
+
15
+ def self.find_by(attr, value)
16
+ Customer.new(Konzertmeister.session.get("/customers/#{attr}/#{value}"))
17
+ end
18
+
19
+ def initialize(data = {})
20
+ @data = data
21
+ data.each do |k,value|
22
+ next if k == 'backends'
23
+
24
+ if m = k.match(/^customer_(.*)$/)
25
+ key = m[1]
26
+ else
27
+ key = k
28
+ end
29
+
30
+ instance_variable_set("@#{key}", value)
31
+ end
32
+ end
33
+
34
+ def save
35
+ params = {
36
+ customer_name: name
37
+ }
38
+ response = Konzertmeister.session.post("/customers", params)
39
+ if response
40
+ response_object = Konzertmeister::Customer.new(response)
41
+ @id = response_object.id
42
+ @short_name = response_object.short_name
43
+ end
44
+ end
45
+
46
+ def backends
47
+ @backends ||= if @data.key?('backends')
48
+ @data.fetch('backends').map do |be_hash|
49
+ Konzertmeister::Backend.new(be_hash)
50
+ end.sort_by(&:short_name)
51
+ else
52
+ Customer.find_by('id', self.id).backends
53
+ end
54
+ end
55
+
56
+ def to_s
57
+ monthly_cost = backends.inject(0) do |sum, backend|
58
+ sum + backend.monthly_cost
59
+ end
60
+
61
+ cost_string = "$%1.2f/month" % [monthly_cost]
62
+ "%-14s %s" % [cost_string, "#{name} (#{short_name})"]
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,66 @@
1
+ class Konzertmeister
2
+ class Host
3
+ FLAVORS = %w[
4
+ 2x_small
5
+ extra_small
6
+ small
7
+ medium
8
+ large
9
+ extra_large
10
+ 2x_large
11
+ ]
12
+ POSTGRES_KIND = 'postgres'
13
+ CODA_KIND = 'coda'
14
+ UTIL_KIND = 'util'
15
+
16
+ attr_reader :id, :hostname, :backend_ip, :management_ip, :kind, :token, :status, :monthly_cost, :created_at, :updated_at, :flavor
17
+
18
+ def self.all
19
+ response = Konzertmeister.session.get('/hosts')
20
+ if response
21
+ response.map do |data|
22
+ Konzertmeister::Host.new(data)
23
+ end.sort_by(&:hostname)
24
+ end
25
+ end
26
+
27
+ def self.find_by(attr, value)
28
+ Host.new(Konzertmeister.session.get("/hosts/#{attr}/#{value}"))
29
+ end
30
+
31
+ def initialize(data)
32
+ @data = data
33
+ data.each do |k,value|
34
+ next if %w[backend].include?(k)
35
+ if m = k.match(/^host_(.*)$/)
36
+ key = m[1]
37
+ else
38
+ key = k
39
+ end
40
+
41
+ instance_variable_set("@#{key}", value)
42
+ end
43
+ end
44
+
45
+ def backend
46
+ @backend ||= if @data.key?('backend')
47
+ Konzertmeister::Backend.new(@data.fetch('backend'))
48
+ else
49
+ Host.find_by('id', self.id).backend
50
+ end
51
+ end
52
+
53
+ def name
54
+ "#{kind}-#{token}"
55
+ end
56
+
57
+ def to_s
58
+ if status == "active"
59
+ cost_string = "$%1.2f/month" % [monthly_cost]
60
+ "%-14s %s" % [cost_string, name]
61
+ else
62
+ "%-14s %s" % ["(inactive)", name]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,65 @@
1
+ class Konzertmeister
2
+ class InfrastructureVersion
3
+ attr_reader :id, :commit, :tag, :branch, :created_at, :updated_at
4
+
5
+ def initialize(data = {})
6
+ @data = data
7
+ data.each do |k,value|
8
+ if m = k.match(/^infrastructure_version_id$/)
9
+ key = "id"
10
+ else
11
+ key = k
12
+ end
13
+
14
+ instance_variable_set("@#{key}", value)
15
+ end
16
+ end
17
+
18
+ def save
19
+ if commit
20
+ params = { 'commit' => commit }
21
+ elsif tag
22
+ params = { 'tag' => tag }
23
+ elsif branch
24
+ params = { 'branch' => branch }
25
+ end
26
+
27
+ response = Konzertmeister.session.post('/infrastructure_versions', params)
28
+ if response
29
+ response_object = Konzertmeister::InfrastructureVersion.new(response)
30
+ @id = response_object.id
31
+ response_object
32
+ end
33
+ end
34
+
35
+ def commit=(input_commit)
36
+ remove_instance_variable(:@tag) if instance_variable_defined?(:@tag)
37
+ remove_instance_variable(:@branch) if instance_variable_defined?(:@branch)
38
+ @commit = input_commit
39
+ end
40
+
41
+ def tag=(input_tag)
42
+ remove_instance_variable(:@branch) if instance_variable_defined?(:@branch)
43
+ remove_instance_variable(:@commit) if instance_variable_defined?(:@commit)
44
+ @tag = input_tag
45
+ end
46
+
47
+ def branch=(input_branch)
48
+ remove_instance_variable(:@tag) if instance_variable_defined?(:@tag)
49
+ remove_instance_variable(:@commit) if instance_variable_defined?(:@commit)
50
+ @branch = input_branch
51
+ end
52
+
53
+ def to_s
54
+ if @data.has_key?("commit")
55
+ inf_version_string = "commit:#{@data["commit"]}"
56
+ elsif @data.has_key?("tag")
57
+ inf_version_string = "tag:#{@data["tag"]}"
58
+ else
59
+ inf_version_string = "branch:#{@data["branch"]}"
60
+ end
61
+
62
+ "Inf<#{inf_version_string}>"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,109 @@
1
+ class Konzertmeister
2
+ class MetaVersion
3
+ attr_reader :id, :created_at, :updated_at
4
+
5
+ def self.all
6
+ response = Konzertmeister.session.get('/meta_versions')
7
+ if response
8
+ response.map do |data|
9
+ Konzertmeister::MetaVersion.new(data)
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.find_by(attr, value)
15
+ MetaVersion.new(Konzertmeister.session.get("/meta_versions/#{attr}/#{value}"))
16
+ end
17
+
18
+ def initialize(data = {})
19
+ @data = data
20
+ data.each do |k,value|
21
+ next if %w[
22
+ infrastructure_version
23
+ tapp_version
24
+ legato_version
25
+ forte_version
26
+ backends
27
+ ].include?(k)
28
+
29
+ if m = k.match(/^meta_version_(.*)$/)
30
+ key = m[1]
31
+ else
32
+ key = k
33
+ end
34
+
35
+ instance_variable_set("@#{key}", value)
36
+ end
37
+ end
38
+
39
+ def save
40
+ params = {
41
+ 'infrastructure_version' => {'infrastructure_version_id' => infrastructure_version.id},
42
+ 'tapp_version' => {'tapp_version_id' => tapp_version.id},
43
+ 'legato_version' => {'legato_version_id' => legato_version.id},
44
+ 'forte_version' => {'forte_version_id' => forte_version.id}
45
+ }
46
+ response = Konzertmeister.session.post('/meta_versions', params)
47
+ if response
48
+ response_object = Konzertmeister::MetaVersion.new(response)
49
+ @id = response_object.id
50
+ response_object
51
+ end
52
+ end
53
+
54
+ def infrastructure_version
55
+ @infrastructure_version ||= Konzertmeister::InfrastructureVersion.new(@data.fetch("infrastructure_version"))
56
+ end
57
+
58
+ def infrastructure_version=(input_iv)
59
+ @infrastructure_version = input_iv
60
+ end
61
+
62
+ def tapp_version
63
+ @tapp_version ||= Konzertmeister::ProductVersion.new("tapp", @data.fetch("tapp_version"))
64
+ end
65
+
66
+ def tapp_version=(input_tv)
67
+ @tapp_version = input_tv
68
+ end
69
+
70
+ def legato_version
71
+ @legato_version ||= Konzertmeister::ProductVersion.new("legato", @data.fetch("legato_version"))
72
+ end
73
+
74
+ def legato_version=(input_lv)
75
+ @legato_version = input_lv
76
+ end
77
+
78
+ def forte_version
79
+ @forte_version ||= Konzertmeister::ProductVersion.new("forte", @data.fetch("forte_version"))
80
+ end
81
+
82
+ def forte_version=(input_fv)
83
+ @forte_version = input_fv
84
+ end
85
+
86
+ def to_s(format = :short)
87
+ fields = [
88
+ id,
89
+ infrastructure_version,
90
+ tapp_version,
91
+ legato_version,
92
+ forte_version
93
+ ]
94
+ table_widths = "%-3s %-25s %-20s %-25s %-25s"
95
+
96
+ if @data.has_key?("backends")
97
+ fields << "Backends: #{@data.fetch("backends").size}"
98
+ table_widths += " %-11s"
99
+ end
100
+
101
+ case format
102
+ when :short
103
+ fields.join(" ")
104
+ when :table
105
+ table_widths % fields
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,33 @@
1
+ class Konzertmeister
2
+ class ProductVersion
3
+ attr_accessor :version
4
+ attr_reader :id, :product, :created_at, :updated_at
5
+
6
+ def initialize(product, data = {})
7
+ @product = product
8
+ @data = data
9
+ data.each do |k,value|
10
+ if m = k.match(/^(tapp|legato|forte)_version_id$/)
11
+ key = "id"
12
+ else
13
+ key = k
14
+ end
15
+
16
+ instance_variable_set("@#{key}", value)
17
+ end
18
+ end
19
+
20
+ def save
21
+ response = Konzertmeister.session.post("/#{product}_versions", {'version' => version})
22
+ if response
23
+ response_object = Konzertmeister::ProductVersion.new(product, response)
24
+ @id = response_object.id
25
+ response_object
26
+ end
27
+ end
28
+
29
+ def to_s
30
+ "#{@product.capitalize}<#{version}>"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ require 'rest-client'
2
+
3
+ class Konzertmeister
4
+ class Session
5
+
6
+ def initialize(
7
+ hostname: 'konzertmeister',
8
+ port: 443,
9
+ ssl_client_cert: nil,
10
+ ssl_client_key: nil
11
+ )
12
+ @ssl_client_cert = OpenSSL::X509::Certificate.new(File.read(ssl_client_cert)) unless ssl_client_cert.nil?
13
+ @ssl_client_key = OpenSSL::PKey::RSA.new(File.read(ssl_client_key)) unless ssl_client_key.nil?
14
+ @hostname = hostname
15
+ @port = port
16
+ end
17
+
18
+ def get(path, headers = {}, &block)
19
+ url = _url(path)
20
+ params = _request_params(
21
+ :method => :get,
22
+ :url => url,
23
+ :headers => headers,
24
+ :timeout => nil
25
+ )
26
+ begin
27
+ json = RestClient::Request.execute(params, &block)
28
+ JSON.parse(json)
29
+ rescue => e
30
+ puts "ERROR"
31
+ p e
32
+ false
33
+ end
34
+ end
35
+
36
+ def post(path, params, headers = {}, &block)
37
+ json = JSON.generate(params)
38
+ url = _url(path)
39
+ request_params = _request_params({
40
+ :method => :post,
41
+ :url => url,
42
+ :payload => json,
43
+ :headers => headers,
44
+ :timeout => nil
45
+ })
46
+ begin
47
+ response = RestClient::Request.execute(request_params, &block)
48
+ JSON.parse(response)
49
+ rescue RestClient::Exception => e
50
+ puts
51
+ puts "ERROR making request to #{path}:"
52
+ p e
53
+ false
54
+ end
55
+ end
56
+
57
+ def _request_params(extra = {})
58
+ base = {}
59
+ base[:ssl_client_cert] = @ssl_client_cert if @ssl_client_cert
60
+ base[:ssl_client_key] = @ssl_client_key if @ssl_client_key
61
+
62
+ base.merge(extra)
63
+ end
64
+
65
+ def _url(path = nil)
66
+ scheme = (@port == 443 ? "https" : "http")
67
+ [
68
+ "#{scheme}://#{@hostname}:#{@port}",
69
+ path
70
+ ].compact.join("")
71
+ end
72
+
73
+ end
74
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: konzertmeister
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - TempoIQ Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ description: This is the client code for an internal book-keeping and cloud orchestration
28
+ service named Konzertmeister.
29
+ email:
30
+ - service@tempoiq.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/konzertmeister.rb
36
+ - lib/konzertmeister/backend.rb
37
+ - lib/konzertmeister/customer.rb
38
+ - lib/konzertmeister/host.rb
39
+ - lib/konzertmeister/infrastructure_version.rb
40
+ - lib/konzertmeister/meta_version.rb
41
+ - lib/konzertmeister/product_version.rb
42
+ - lib/konzertmeister/session.rb
43
+ homepage: http://tempoiq.com
44
+ licenses: []
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.2.2
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Konzertmeister http client
66
+ test_files: []