appfirst 0.0.1

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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in appfirst.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "awesome_print"
8
+ gem "pry-nav"
9
+ gem "rspec", "~> 2.14"
10
+ gem "guard-rspec"
11
+ end
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: "bundle exec rspec" do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Josh Lane & Thom Mahoney
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Appfirst
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'appfirst'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install appfirst
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/<my-github-username>/appfirst/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'appfirst/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "appfirst"
8
+ spec.version = Appfirst::VERSION
9
+ spec.authors = ["Josh Lane & Thom Mahoney"]
10
+ spec.email = ["jlane@engineyard.com"]
11
+ spec.summary = %q{A ruby client for the AppFirst API}
12
+ spec.description = ""
13
+ spec.homepage = "http://github.com/engineyard/appfirst"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "faraday", "~> 0.8.9"
22
+ spec.add_dependency "faraday_middleware"
23
+ spec.add_dependency "cistern", "~> 0.2.3"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.5"
26
+ spec.add_development_dependency "rake"
27
+ end
@@ -0,0 +1,14 @@
1
+ require "appfirst/version"
2
+
3
+ require 'logger'
4
+ require 'faraday'
5
+ require 'faraday_middleware'
6
+ require 'cistern'
7
+
8
+ module Appfirst
9
+ require 'appfirst/collection'
10
+ require 'appfirst/logger'
11
+ require 'appfirst/response'
12
+
13
+ require 'appfirst/client'
14
+ end
@@ -0,0 +1,132 @@
1
+ class Appfirst::Client < Cistern::Service
2
+ model_path "appfirst/models"
3
+ request_path "appfirst/requests"
4
+
5
+ #collection :servers
6
+ collection :server_tags
7
+
8
+ model :server
9
+ model :server_tag
10
+
11
+ request :create_server_tag
12
+ #request :get_server
13
+ #request :get_server_tags
14
+ #request :get_servers
15
+ #request :get_servers_tags
16
+ #request :get_tag_servers
17
+ #request :set_servers_tags
18
+
19
+ recognizes :url, :logger, :adapter, :connection_options, :user, :password, :path
20
+
21
+ module Shared
22
+ attr_reader :url, :user, :path
23
+
24
+ def setup(options)
25
+ @url = options[:url] || ENV["APPFIRST_URL"] || "https://engineyard.appfirst.com"
26
+ @path = options[:path] || "/api/v3"
27
+
28
+ # For auth
29
+ @user = options[:user]
30
+ @password = options[:password]
31
+
32
+ @logger = options[:logger] || Logger.new(nil)
33
+ end
34
+ end
35
+
36
+ class Real
37
+ include Shared
38
+
39
+ def initialize(options={})
40
+ setup(options)
41
+ adapter = options[:adapter] || Faraday.default_adapter
42
+ connection_options = options[:connection_options] || {ssl: {verify: false}}
43
+
44
+ @connection = Faraday.new({url: @url}.merge(connection_options)) do |builder|
45
+ builder.basic_auth @user, @password
46
+ builder.request :url_encoded
47
+ builder.response :json, content_type: /\bjson$/
48
+ builder.adapter adapter
49
+ builder.request :retry, max: 10, interval: 0.05, interval_randomness: 0.5, backoff_factor: 2
50
+
51
+ builder.use Appfirst::Logger, @logger
52
+
53
+ builder.adapter *adapter
54
+ end
55
+ end
56
+
57
+ def request(method, request_path, params = nil)
58
+ response = @connection.send(method) do |req|
59
+ req.url(File.join(@url, path, request_path, "/"))
60
+ req.params.merge!(params)
61
+ end
62
+
63
+ Appfirst::Response.new(response.status, response.headers, response.body).raise!
64
+ end
65
+ end # Real
66
+
67
+ class Mock
68
+ include Shared
69
+
70
+ def self.data
71
+ @data ||= Hash.new do |h,k|
72
+ {
73
+ :servers => {},
74
+ :server_tags => {},
75
+ }
76
+ end
77
+ end
78
+
79
+ def data
80
+ self.class.data[self.url]
81
+ end
82
+
83
+ def self.reset!
84
+ @data = nil
85
+ @serial_id = 0
86
+ end
87
+
88
+ def initialize(options={})
89
+ setup(options)
90
+ end
91
+
92
+ def response(options={})
93
+ status = options[:status] || 200
94
+ body = options[:body]
95
+ headers = {
96
+ "Content-Type" => "application/json; charset=utf-8"
97
+ }.merge(options[:headers] || {})
98
+
99
+ Appfirst::Response.new(status, headers, body).raise!
100
+ end
101
+
102
+ def url_for(path)
103
+ File.join(@url.to_s, path.to_s)
104
+ end
105
+
106
+ def stringify_keys(hash)
107
+ hash.is_a?(Hash) ? hash.inject({}){|r,(k,v)| r.merge(k.to_s => stringify_keys(v))} : hash
108
+ end
109
+
110
+ def resource_uri(fragment)
111
+ File.join(path, fragment)
112
+ end
113
+
114
+ def require_parameter(params, param, options={})
115
+ if value = params[param]
116
+ value
117
+ else
118
+ errors = [{"message" => (options[:message] || "Missing parameter: #{param}"), "code" => (options[:code] || -1)}]
119
+
120
+ response(
121
+ :body => {"errors" => errors},
122
+ :status => 400,
123
+ )
124
+ end
125
+ end
126
+
127
+ def serial_id
128
+ @@serial_id ||= 0
129
+ @@serial_id += 1
130
+ end
131
+ end # Mock
132
+ end # Appfirst::Client
@@ -0,0 +1,58 @@
1
+ module Appfirst::Collection
2
+ def self.included(klass)
3
+ klass.attribute :next_link
4
+ klass.attribute :prev_link
5
+ klass.attribute :last_link
6
+ klass.attribute :total_count
7
+ klass.attribute :url
8
+ klass.send(:extend, Appfirst::Collection::Attributes)
9
+ end
10
+
11
+ module Attributes
12
+ attr_accessor :model_root, :model_request, :collection_root, :collection_request
13
+ end
14
+
15
+ def create!(*args)
16
+ model = self.new(*args)
17
+ model.save!
18
+ end
19
+
20
+ def model_root
21
+ self.class.model_root
22
+ end
23
+
24
+ def model_request
25
+ self.class.model_request
26
+ end
27
+
28
+ def get(id)
29
+ if data = connection.send(self.model_request, "id" => id).body[self.model_root]
30
+ new(data)
31
+ else
32
+ nil
33
+ end
34
+ rescue Appfirst::Response::NotFound
35
+ nil
36
+ end
37
+
38
+ def collection_root
39
+ self.class.instance_variable_get(:@collection_root)
40
+ end
41
+
42
+ def collection_request
43
+ self.class.instance_variable_get(:@collection_request)
44
+ end
45
+
46
+ def ==(comparison_object)
47
+ comparison_object.equal?(self) ||
48
+ (comparison_object.is_a?(self.class) &&
49
+ comparison_object.map(&:identity) == self.map(&:identity))
50
+ end
51
+
52
+ def all(params={})
53
+ params["url"] ||= self.url
54
+
55
+ response = self.connection.send(self.collection_request, params)
56
+ load_page(response)
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ class Appfirst::Logger < Faraday::Response::Middleware
2
+ extend Forwardable
3
+
4
+ def initialize(app, logger = nil)
5
+ super(app)
6
+ @logger = logger || ::Logger.new(nil)
7
+ end
8
+
9
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
10
+
11
+ def call(env)
12
+ info "REQUEST: #{env[:method].upcase} #{env[:url].to_s}"
13
+ info('request') { dump_headers env[:request_headers] }
14
+ info('request.body') { env[:body] } if (env[:request_headers]["Accept"] || "").match("application/json")
15
+ info('')
16
+ super
17
+ end
18
+
19
+ def on_complete(env)
20
+ info "RESPONSE: #{env[:status]}"
21
+ info('response') { dump_headers env[:response_headers] }
22
+ info('response.body') { env[:body] } if (env[:response_headers]["Content-Type"] || "").match("application/json")
23
+ info('')
24
+ end
25
+
26
+ private
27
+
28
+ def dump_headers(headers)
29
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ class Appfirst::Client::Server < Cistern::Model
2
+ identity :id, type: :integer # (read-only) Unique server ID.
3
+
4
+ attribute :architecture, type: :string # (read-only) The architecture of the server.
5
+ attribute :capacity_cpu_freq, type: :integer # (read-only) The frequency of the CPU in MHz.
6
+ attribute :capacity_cpu_num, type: :integer # (read-only) The number of CPU cores on the system.
7
+ attribute :capacity_disks # (read-only) Mapping of the names of the disks (mount points) to their capacity in MB.
8
+ attribute :capacity_mem, type: :integer # (read-only) The total available memory on the server in bytes.
9
+ attribute :created, type: :integer # (read-only) The time the server was created in UTC seconds.
10
+ attribute :current_version, type: :string # (read-only) Version of AppFirst collector that’s running on the server.
11
+ attribute :description, type: :string # The user-defined description of the server.
12
+ attribute :distribution, type: :string # (read-only) The OS distribution e.g. Ubuntu, RedHat.
13
+ attribute :hostname, type: :string # (read-only) The hostname for this server.
14
+ attribute :kernel_version, type: :string # (read-only) The specific kernel version of the server’s OS.
15
+ attribute :location_ip, type: :string # (read-only) IP address that collector is uploading from.
16
+ attribute :nickname, type: :string # The user-defined nickname of the server.
17
+ attribute :os, type: :string # (read-only) The OS for this server, either Windows or Linux.
18
+ attribute :resource_uri, type: :string # (read-only) The URI to get more information about this item.
19
+ attribute :running, type: :boolean # (read-only) Boolean indicating whether the server is currently uploading data to AppFirst.
20
+ end
@@ -0,0 +1,19 @@
1
+ class Appfirst::Client::ServerTag < Cistern::Model
2
+ identity :id, type: :integer # (read-only) Unique server tag ID.
3
+
4
+ attribute :name, type: :string # The name of the server tag.
5
+ attribute :resource_uri, type: :string # (read-only) The URI to get more information about this item.
6
+ attribute :server_ids, type: :array, aliases: ['servers'] # IDs of servers that belong to this server tag.
7
+
8
+ def servers
9
+ self.server_ids.map { |server_id| self.connection.servers.get(server_id) }
10
+ end
11
+
12
+ def save
13
+ if new_record?
14
+ self.collection.new(self.connection.create_server_tag(Cistern::Hash.slice(self.attributes, :name, :servers)).body)
15
+ else
16
+ raise NotImplementedError # update tag
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ class Appfirst::Client::ServerTags < Cistern::Collection
2
+ include Appfirst::Collection
3
+
4
+ model Appfirst::Client::ServerTag
5
+
6
+ self.model_root = "server_tags"
7
+ self.model_request = :get_server_tag
8
+ self.collection_root = "server_tags"
9
+ self.collection_request = :get_server_tags
10
+ end
@@ -0,0 +1,33 @@
1
+ class Appfirst::Client
2
+ class Real
3
+ def create_server_tag(params={})
4
+ request(:post, "/server_tags", params)
5
+ end
6
+ end # Real
7
+
8
+ class Mock
9
+ def create_server_tag(params={})
10
+ identity = self.serial_id
11
+
12
+ name = require_parameter(params, :name, code: 5, message: "Invalid server tag name, name must be provided") # {"errors"=>[{"message"=>"Invalid server tag name, name must be provided", "code"=>5}]}
13
+ servers = params[:servers] || []
14
+
15
+ response_hash = {
16
+ :id => identity,
17
+ :name => name,
18
+ :servers => servers,
19
+ :resource_uri => resource_uri("/server_tags/#{identity}")
20
+ }
21
+
22
+ self.data[:server_tags][identity] = response_hash
23
+
24
+ response(
25
+ :body => response_hash,
26
+ :status => 200,
27
+ :headers => {
28
+ "Content-Type" => "application/json; charset=utf8"
29
+ }
30
+ )
31
+ end
32
+ end # Mock
33
+ end # Appfirst::Client
@@ -0,0 +1,41 @@
1
+ class Appfirst::Response
2
+ class Error < StandardError
3
+ attr_reader :response
4
+
5
+ def initialize(response)
6
+ @response = response
7
+ super({status: response.status, headers: response.headers, body: response.body}.inspect)
8
+ end
9
+ end
10
+
11
+ BadRequest = Class.new(Error)
12
+ NotFound = Class.new(Error)
13
+ Unprocessable = Class.new(Error)
14
+ Conflict = Class.new(Error)
15
+ Unexpected = Class.new(Error)
16
+
17
+ EXCEPTION_MAPPING = {
18
+ 400 => BadRequest,
19
+ 404 => NotFound,
20
+ 409 => Conflict,
21
+ 422 => Unprocessable,
22
+ 500 => Unexpected,
23
+ }
24
+
25
+ attr_reader :headers, :status, :body
26
+
27
+ def initialize(status, headers, body)
28
+ @status, @headers, @body = status, headers, body
29
+ end
30
+
31
+ def successful?
32
+ self.status < 300 && self.status > 199 || self.status == 304
33
+ end
34
+
35
+ def raise!
36
+ if !successful?
37
+ raise (EXCEPTION_MAPPING[self.status] || Error).new(self)
38
+ else self
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Appfirst
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe "server_tags" do
4
+ let!(:client) { create_client }
5
+
6
+ it "should create a server tag" do
7
+ name = "josh"
8
+
9
+ server_tag = client.server_tags.create(name: name)
10
+ server_tag.name.should == name
11
+ server_tag.resource_uri.should_not be_nil
12
+ server_tag.server_ids.should be_empty
13
+
14
+ server_tag.servers.should be_empty
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ ENV['MOCK_APPFIRST'] ||= 'true'
2
+ ENV['APPFIRST_URL'] ||= "http://appfirst.localdev.engineyard.com:3000"
3
+
4
+ Bundler.require(:test)
5
+
6
+ require File.expand_path("../../lib/appfirst", __FILE__)
7
+ Dir[File.expand_path("../{shared,support}/*.rb", __FILE__)].each{|f| require(f)}
8
+
9
+ Cistern.formatter = Cistern::Formatter::AwesomePrint
10
+
11
+ if ENV['MOCK_APPFIRST'] == 'true'
12
+ Cistern.timeout = 0
13
+ Appfirst::Client.mock!
14
+ else
15
+ Cistern.timeout = 10
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.order = :random
20
+
21
+ config.before(:each) do
22
+ Appfirst::Client.mocking? && Appfirst::Client.reset!
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module ClientHelper
2
+ def create_client(attributes={})
3
+ user = ENV["APPFIRST_USER"] || attributes[:user] || "engineyard"
4
+ password = ENV["APPFIRST_PASSWORD"] || attributes[:password] || "dontworryaboutit"
5
+
6
+ merged_attributes = {user: user, password: password}
7
+ merged_attributes.merge!(logger: Logger.new(STDOUT)) if ENV['VERBOSE']
8
+
9
+ Appfirst::Client.new(merged_attributes)
10
+ end
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ config.include(ClientHelper)
15
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: appfirst
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josh Lane & Thom Mahoney
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-01-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.8.9
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.8.9
30
+ - !ruby/object:Gem::Dependency
31
+ name: faraday_middleware
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: cistern
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.2.3
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.2.3
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.5'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '1.5'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ''
95
+ email:
96
+ - jlane@engineyard.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - Gemfile
103
+ - Guardfile
104
+ - LICENSE.txt
105
+ - README.md
106
+ - Rakefile
107
+ - appfirst.gemspec
108
+ - lib/appfirst.rb
109
+ - lib/appfirst/client.rb
110
+ - lib/appfirst/collection.rb
111
+ - lib/appfirst/logger.rb
112
+ - lib/appfirst/models/server.rb
113
+ - lib/appfirst/models/server_tag.rb
114
+ - lib/appfirst/models/server_tags.rb
115
+ - lib/appfirst/requests/create_server_tag.rb
116
+ - lib/appfirst/response.rb
117
+ - lib/appfirst/version.rb
118
+ - spec/server_tags_spec.rb
119
+ - spec/spec_helper.rb
120
+ - spec/support/client_helper.rb
121
+ homepage: http://github.com/engineyard/appfirst
122
+ licenses:
123
+ - MIT
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 1.8.23
143
+ signing_key:
144
+ specification_version: 3
145
+ summary: A ruby client for the AppFirst API
146
+ test_files:
147
+ - spec/server_tags_spec.rb
148
+ - spec/spec_helper.rb
149
+ - spec/support/client_helper.rb
150
+ has_rdoc: