goapi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/README.md +67 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/examples/agent_stats.rb +31 -0
- data/examples/job_stats.rb +26 -0
- data/examples/junit_reports.rb +88 -0
- data/goapi.gemspec +24 -0
- data/lib/goapi.rb +99 -0
- data/lib/goapi/http.rb +57 -0
- data/lib/goapi/version.rb +3 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: da6488566478de3c1f4bd2a82aaadb9a7d1c4aba
|
4
|
+
data.tar.gz: f50eed750d0ad1058e84fdb4e0a66128a66d81c6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e1e41bcbb6531fdcf2870c45615e8cddecec35b0c86aef79848ff4ccff0e2748ea75ace5ece5e34885aee681f3f260add2e63483ba61c63af474b7be05bde37
|
7
|
+
data.tar.gz: 897c445726b3234d8877510a7ff61bef37067b7238eccf86557363426630f15ac11be112820883b7de2e5a978e8b4898d2ff19fdd3ee87062de53a33298bc9d8
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Go API
|
2
|
+
|
3
|
+
[Go](http://www.go.cd) API Ruby client.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'goapi'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install goapi
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Initialize with Go server url and Basic Auth credentials
|
24
|
+
|
25
|
+
require 'goapi'
|
26
|
+
|
27
|
+
go = GoAPI.new('server-url', basic_auth: [username, password])
|
28
|
+
|
29
|
+
### Fetch pipeline stages history
|
30
|
+
|
31
|
+
require 'goapi'
|
32
|
+
go = GoAPI.new('server-url', basic_auth: [username, password])
|
33
|
+
go.stages(pipeline_name, stage_name)
|
34
|
+
|
35
|
+
### Fetch artifacts
|
36
|
+
|
37
|
+
require 'goapi'
|
38
|
+
go = GoAPI.new('server-url', basic_auth: [username, password])
|
39
|
+
stages = go.stages(pipeline_name, stage_name)
|
40
|
+
artifacts = go.artifacts(stages[0], job_name)
|
41
|
+
artifacts.map do |artifact|
|
42
|
+
if artifact.type == 'file'
|
43
|
+
go.artifact(artifact)
|
44
|
+
else # when type == 'folder'
|
45
|
+
....
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
More complex examples please checkout [examples](examples) directory in codebase
|
50
|
+
|
51
|
+
## API design
|
52
|
+
|
53
|
+
1. flat: one level API, you can find all APIs definition in class GoAPI.
|
54
|
+
2. data: all APIs return data object only, they are:
|
55
|
+
1. Primitive type in Ruby
|
56
|
+
2. OpenStruct object, with primitive type values (rule #1 flat)
|
57
|
+
|
58
|
+
## Development
|
59
|
+
|
60
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
61
|
+
|
62
|
+
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).
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ThoughtWorksStudios/goapi.
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "goapi"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
server_url = <go server url>
|
3
|
+
username = <username>
|
4
|
+
password = <your password>
|
5
|
+
pipeline_name = <pipeline name>
|
6
|
+
stage_name = <stage name>
|
7
|
+
|
8
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
9
|
+
$:.unshift(lib_path)
|
10
|
+
|
11
|
+
require 'goapi'
|
12
|
+
|
13
|
+
go = GoAPI.new(server_url, basic_auth: [username, password])
|
14
|
+
|
15
|
+
go.stages(pipeline_name, stage_name).select do |s|
|
16
|
+
s.result == 'Passed'
|
17
|
+
end.map do |stage|
|
18
|
+
puts "fetch jobs' properties for #{go.stage_id(stage)}"
|
19
|
+
stage.jobs.map do |job|
|
20
|
+
go.job_properties(stage, job.name)
|
21
|
+
end
|
22
|
+
end.flatten.map do |job|
|
23
|
+
[job.cruise_agent, job.cruise_job_duration.to_i]
|
24
|
+
end.group_by do |tmp|
|
25
|
+
tmp[0]
|
26
|
+
end.map do |agent, jobs|
|
27
|
+
times = jobs.map{|t| t[1]}
|
28
|
+
[agent, times.reduce(:+)/times.size, times.sort[times.size/2], times.size]
|
29
|
+
end.sort_by{|r| r[1]}.reverse.each do |agent, avg, m, size|
|
30
|
+
puts "#{agent} ran #{size} jobs, avg time: #{avg.to_i} secs; median time: #{m.to_i} secs"
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
server_url = <go server url>
|
3
|
+
username = <username>
|
4
|
+
password = <your password>
|
5
|
+
pipeline_name = <pipeline name>
|
6
|
+
stage_name = <stage name>
|
7
|
+
|
8
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
9
|
+
$:.unshift(lib_path)
|
10
|
+
|
11
|
+
require 'goapi'
|
12
|
+
|
13
|
+
go = GoAPI.new(server_url, basic_auth: [username, password])
|
14
|
+
go.stages(pipeline_name, stage_name).select do |s|
|
15
|
+
s.result == 'Passed'
|
16
|
+
end.map do |stage|
|
17
|
+
stage.jobs.map do |job|
|
18
|
+
properties = go.job_properties(stage, job.name)
|
19
|
+
OpenStruct.new(properties.to_h.merge(name: job.name))
|
20
|
+
end
|
21
|
+
end.flatten.group_by(&:name).map do |name, jobs|
|
22
|
+
times = jobs.map(&:cruise_job_duration).map(&:to_i)
|
23
|
+
[name, times.reduce(:+)/times.size, times.sort[times.size/2], times.size]
|
24
|
+
end.sort_by{|r| r[1]}.reverse.each do |name, avg, m, size|
|
25
|
+
puts "#{name} avg time: #{avg.to_i} secs; median time: #{m.to_i} secs"
|
26
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
|
2
|
+
# We have selenium test suite that ran 11 hours 45 minutes in sequence on a 2 CPUs 3.5 GB memory VM.
|
3
|
+
# We configure 35 jobs on Go to run these tests. Tests are partitioned by [TLB] with a time based splitter.
|
4
|
+
# [TLB] should partition tests equally on build.
|
5
|
+
# But these jobs randomly ran from 18 minutes to 48 minutes. It seems really badly partitioned.
|
6
|
+
#
|
7
|
+
# This is one of scripts I wrote for myself to investigate the problem described above.
|
8
|
+
# We use ci_reporter to generate junit reports on build.
|
9
|
+
# This script fetches all junit reports on jobs of latest green stage.
|
10
|
+
# And try partition tests based on test run times in 2 ways:
|
11
|
+
# 1. random
|
12
|
+
# 2. test run time
|
13
|
+
#
|
14
|
+
# [TLB]: https://github.com/test-load-balancer/tlb
|
15
|
+
|
16
|
+
begin
|
17
|
+
require 'descriptive-statistics'
|
18
|
+
rescue LoadError
|
19
|
+
puts "Need descriptive-statistics to run this example"
|
20
|
+
puts "You can install it by: gem install descriptive-statistics"
|
21
|
+
exit(1)
|
22
|
+
end
|
23
|
+
|
24
|
+
server_url = <go server url>
|
25
|
+
username = <username>
|
26
|
+
password = <your password>
|
27
|
+
pipeline_name = <pipeline name>
|
28
|
+
stage_name = <stage name>
|
29
|
+
junit_report_file_name_regex = /TEST-[^.]+.xml/
|
30
|
+
|
31
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
32
|
+
$:.unshift(lib_path)
|
33
|
+
|
34
|
+
require 'goapi'
|
35
|
+
require 'rexml/document'
|
36
|
+
|
37
|
+
class JunitReport < Struct.new(:name, :tests)
|
38
|
+
def self.parse(xml)
|
39
|
+
root = REXML::Document.new(xml).root
|
40
|
+
suite = root.attributes
|
41
|
+
tests = []
|
42
|
+
root.each_element('//testcase') do |e|
|
43
|
+
tests << OpenStruct.new(name: "#{suite['name']}##{e.attributes['name']}", time: e.attributes['time'].to_f)
|
44
|
+
end
|
45
|
+
new(suite['name'], tests)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
go = GoAPI.new(server_url, basic_auth: [username, password])
|
50
|
+
|
51
|
+
stage = go.stages(pipeline_name, stage_name).find do |s|
|
52
|
+
s.result == 'Passed'
|
53
|
+
end
|
54
|
+
|
55
|
+
require 'pstore'
|
56
|
+
store = PStore.new('pstore')
|
57
|
+
reports = store.transaction do
|
58
|
+
store['reports'] ||= stage.jobs.map do |job|
|
59
|
+
files = go.artifacts(stage, job.name).map{|a| a.type == 'folder' ? a.files : a}.flatten.select{|f| f.name =~ junit_report_file_name_regex}
|
60
|
+
puts "Job #{job.name}, #{files.size} reports"
|
61
|
+
files.map { |f| JunitReport.parse(go.artifact(f)) }
|
62
|
+
end.compact.flatten
|
63
|
+
end
|
64
|
+
|
65
|
+
print_stats = lambda do |partitions|
|
66
|
+
stats = DescriptiveStatistics::Stats.new(partitions.map {|p|p.map(&:time).reduce(:+).to_i})
|
67
|
+
puts "partitions: #{partitions.size}"
|
68
|
+
puts "mean: #{stats.mean}"
|
69
|
+
puts "median: #{stats.median}"
|
70
|
+
puts "max: #{stats.max}"
|
71
|
+
puts "min: #{stats.min}"
|
72
|
+
puts "standard deviation: #{stats.standard_deviation}"
|
73
|
+
end
|
74
|
+
|
75
|
+
tests = reports.map(&:tests).flatten
|
76
|
+
puts tests.size
|
77
|
+
puts tests.map(&:time).reduce(:+)/3600
|
78
|
+
puts "Random partition result:"
|
79
|
+
print_stats[tests.shuffle.each_slice(tests.size/35 + 1)]
|
80
|
+
puts ''
|
81
|
+
puts "Partitioned by test runtime"
|
82
|
+
bins = Array.new(35) { [] }
|
83
|
+
tests.sort_by(&:time).reverse.each do |test|
|
84
|
+
bin = bins.min_by{|bin| bin.empty? ? 0 : bin.map(&:time).reduce(:+)}
|
85
|
+
bin << test
|
86
|
+
end
|
87
|
+
print_stats[bins]
|
88
|
+
|
data/goapi.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'goapi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "goapi"
|
8
|
+
spec.version = Goapi::VERSION
|
9
|
+
spec.authors = ["Xiao Li"]
|
10
|
+
spec.email = ["swing1979@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Go API ruby client.}
|
13
|
+
spec.homepage = "https://github.com/ThoughtWorksStudios/goapi"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "json", "~> 1.8"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
end
|
data/lib/goapi.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require "goapi/version"
|
2
|
+
require "goapi/http"
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
require 'time'
|
6
|
+
require 'uri'
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
class GoAPI
|
10
|
+
|
11
|
+
class Stages
|
12
|
+
include Enumerable
|
13
|
+
def initialize(data)
|
14
|
+
@data = data
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
@data.stages.each(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
@data.stages
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def logger
|
28
|
+
@logger ||= Logger.new(STDOUT)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# server: Go server url, for example: https://go01.domain.com
|
33
|
+
# credentials:
|
34
|
+
# only support basic auth right now, example: {basic_auth: [username, password]}
|
35
|
+
# default value is nil
|
36
|
+
def initialize(server, credentials=nil)
|
37
|
+
@server, @http = server, Http.new(credentials)
|
38
|
+
end
|
39
|
+
|
40
|
+
def stages(pipeline_name, stage_name)
|
41
|
+
Stages.new(fetch(:api, :stages, pipeline_name, stage_name, :history))
|
42
|
+
end
|
43
|
+
|
44
|
+
# stage:
|
45
|
+
# object has attribute methods: pipeline_name, pipeline_counter, name, counter
|
46
|
+
# You can get this object by calling stages(pipeline_name, stage_name)
|
47
|
+
# job_name: job name
|
48
|
+
def job_properties(stage, job_name)
|
49
|
+
text = @http.get(url(:properties, stage.pipeline_name, stage.pipeline_counter, stage.name, stage.counter, job_name))[1]
|
50
|
+
names, values = text.split("\n")
|
51
|
+
values = values.split(',')
|
52
|
+
OpenStruct.new(Hash[names.split(',').each_with_index.map do |name, i|
|
53
|
+
[name, values[i]]
|
54
|
+
end])
|
55
|
+
end
|
56
|
+
|
57
|
+
# stage:
|
58
|
+
# object has attribute methods: pipeline_name, pipeline_counter, name, counter
|
59
|
+
# You can get this object by calling stages(pipeline_name, stage_name)
|
60
|
+
# job_name: job name
|
61
|
+
def artifacts(stage, job_name)
|
62
|
+
fetch(:files, stage.pipeline_name, stage.pipeline_counter, stage.name, stage.counter, "#{job_name}.json")
|
63
|
+
end
|
64
|
+
|
65
|
+
# artifact: object from artifacts(stage, job_name), it should have 2 attributes "type" and "url", and "type" should be "file"
|
66
|
+
def artifact(artifact)
|
67
|
+
raise "Only accept file type artifact" if artifact.type != 'file'
|
68
|
+
u = URI(artifact.url)
|
69
|
+
@http.get([@server, u.path].join('/'))[1]
|
70
|
+
end
|
71
|
+
|
72
|
+
# build stage uniq identifier for stage object
|
73
|
+
def stage_id(stage)
|
74
|
+
"#{stage.pipeline_name}/#{stage.pipeline_counter}/#{stage.name}/#{stage.counter}"
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def fetch(*resources)
|
79
|
+
ostruct(JSON.parse(@http.get(url(*resources))[1]))
|
80
|
+
end
|
81
|
+
|
82
|
+
def url(*resources)
|
83
|
+
[@server, :go, *resources].join('/')
|
84
|
+
end
|
85
|
+
|
86
|
+
def ostruct(o)
|
87
|
+
case o
|
88
|
+
when Array
|
89
|
+
o.map(&method(:ostruct))
|
90
|
+
when Hash
|
91
|
+
r = o.map do |k, v|
|
92
|
+
[k, ostruct(v)]
|
93
|
+
end
|
94
|
+
OpenStruct.new(Hash[r])
|
95
|
+
else
|
96
|
+
o
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/goapi/http.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
class GoAPI
|
5
|
+
class Http
|
6
|
+
class Error < StandardError
|
7
|
+
def initialize(request_class, url, response)
|
8
|
+
super("error[#{request_class.name}][#{url}][#{response.code}]: #{response.body}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(credentials)
|
13
|
+
@credentials = credentials || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(url)
|
17
|
+
process(Net::HTTP::Get, url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(url, params)
|
21
|
+
process(Net::HTTP::Post, url, params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def process(request_class, url, form_data={}, headers=nil)
|
25
|
+
uri = URI(url)
|
26
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
27
|
+
|
28
|
+
if uri.scheme == 'https'
|
29
|
+
http.use_ssl = true
|
30
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
31
|
+
end
|
32
|
+
|
33
|
+
request = request_class.new(uri.request_uri, form_data)
|
34
|
+
|
35
|
+
if headers
|
36
|
+
headers.each do |key, value|
|
37
|
+
request[key] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if @credentials[:basic_auth]
|
42
|
+
request.basic_auth *@credentials[:basic_auth]
|
43
|
+
end
|
44
|
+
|
45
|
+
response = http.request(request)
|
46
|
+
raise Error.new(request_class, url, response) if response.code.to_i >= 300
|
47
|
+
|
48
|
+
to_canonical_response(response)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_canonical_response(response)
|
52
|
+
headers = {}
|
53
|
+
response.each_header {|key, value| headers[key] = value }
|
54
|
+
[response.code.to_i, response.body, headers]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Xiao Li
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- swing1979@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- bin/console
|
67
|
+
- bin/setup
|
68
|
+
- examples/agent_stats.rb
|
69
|
+
- examples/job_stats.rb
|
70
|
+
- examples/junit_reports.rb
|
71
|
+
- goapi.gemspec
|
72
|
+
- lib/goapi.rb
|
73
|
+
- lib/goapi/http.rb
|
74
|
+
- lib/goapi/version.rb
|
75
|
+
homepage: https://github.com/ThoughtWorksStudios/goapi
|
76
|
+
licenses: []
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.4.5
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Go API ruby client.
|
98
|
+
test_files: []
|