jn_services 1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cee0b10f7814fc15d743fbec9be6c8b727fb7f34
4
+ data.tar.gz: 29f644614ad66c76051774e77f798873b3be4a3e
5
+ SHA512:
6
+ metadata.gz: 9ab1f4d25e5b846a14f929bfe289c32692446199db1a4a8cbaef12c17c7e5e2aab2575081d622991afaca6d57732d7b388c799ab8920169ccfa27b266c2622e2
7
+ data.tar.gz: 262b787d5ea448e9a187e0fcbf399f4ef173304439cdeb570b6bd489134b78b620058b1067269fa690c95ef6f6156a42d9e3bdf6beaf839d5ebfb6136bb9c73c
data/.coco.yml ADDED
@@ -0,0 +1,5 @@
1
+ :directories:
2
+ - lib
3
+ :excludes:
4
+ - spec
5
+ - .bundle
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ _yardoc
8
+ coverage
9
+ spec/reports
10
+ tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ Includes:
3
+ - Gemfile
4
+ - lib/**
5
+ Excludes:
6
+ - test/**
7
+ - vendor/**
8
+
9
+ Encoding:
10
+ Enabled: false
11
+
12
+ LineLength:
13
+ Max: 100
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ before_install:
2
+ - bash build_etcd
3
+ - bundle install --path .bundle
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ branches:
9
+ only:
10
+ - master
11
+ script: "ETCD_BIN=./etcd/bin/etcd bundle exec rake"
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ ### 1.2.0
2
+ * switch to rubocop
3
+ * update etcd gem to 0.0.6
4
+ * compatable with etcd 0.2.0 & 0.3.0
5
+
6
+ ### 1.1.1
7
+ * bugfix remove of options attr from endoint
8
+ * cleanup tailor issues
9
+
10
+ ### 1.1.0
11
+ * add all method to services that returns an array of all services
12
+ * add subscribed method to services that returns array of all services a node is mapped too
13
+
14
+ ### 1.0.6
15
+ * revert naming change
16
+
17
+ ### 1.0.5
18
+ * Fix bug where .load method was returnign a hash instead of self
19
+ * make compatable with the 0.0.4 etcd-ruby gem
20
+ * make service return objects for endpoint and members instead of hashes.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in etcd.gemspec
4
+ gemspec
5
+ gem 'etcd', '~> 0.0.6'
6
+
7
+ group :dev do
8
+ gem 'simplecov'
9
+ gem 'rubocop'
10
+ gem 'uuid'
11
+ # aparently macaddr has broken gem dep
12
+ gem 'systemu'
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jesse Nelson
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.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/spheromak/services.png?branch=master)](https://travis-ci.org/spheromak/services)
2
+
3
+ Services Library
4
+
5
+ Sets up libraries for defining endpoints and serevicess ontop of etcd
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rdoc/task"
4
+
5
+ task default: 'test:quick'
6
+
7
+
8
+ RDoc::Task.new do |rdoc|
9
+ rdoc.main = "README.rdoc"
10
+ rdoc.rdoc_files.include("lib /*.rb")
11
+ end
12
+
13
+ namespace :test do
14
+ RSpec::Core::RakeTask.new("spec") do |t|
15
+ t.rspec_opts = '--color --fail-fast'
16
+ end
17
+
18
+ begin
19
+ require 'rubocop/rake_task'
20
+ Rubocop::RakeTask.new do |task|
21
+ task.fail_on_error = true
22
+ end
23
+ rescue LoadError
24
+ warn 'Rubocop gem not installed, now the code will look like crap!'
25
+ end
26
+
27
+ desc 'Run all of the quick tests.'
28
+ task :quick do
29
+ Rake::Task['test:rubocop'].invoke
30
+ Rake::Task['test:spec'].invoke
31
+ end
32
+ end
data/build_etcd ADDED
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ wget -c https://go.googlecode.com/files/go1.2.linux-amd64.tar.gz
4
+ tar -zxf go1.2.linux-amd64.tar.gz
5
+ git clone https://github.com/coreos/etcd
6
+ export GOROOT=$PWD/go
7
+ export PATH=$GOROOT/bin:$PATH
8
+ cd etcd && ./build
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'services/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "jn_services"
9
+ spec.version = Services::VERSION
10
+ spec.authors = ["Jesse Nelson"]
11
+ spec.email = ["spheromak@gmail.com"]
12
+ spec.description = %q{Consitently model servives with etcd in and outside chef}
13
+ spec.summary = spec.description
14
+ spec.homepage = "https://github.com/spheromak/services"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "etcd"
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,148 @@
1
+ module Services
2
+ #
3
+ # Setup ETCD connection via chef or plain host
4
+ # Also stores that aconnection in Services.connection
5
+ # Most other classes require this to be setup
6
+ #
7
+ class Connection
8
+ require 'openssl'
9
+
10
+ attr_reader :node, :run_context, :client, :host, :port, :ssl_verify
11
+
12
+ if defined?(Chef) == 'constant' && Chef.class == Class
13
+ if Chef::Version.new(Chef::VERSION) <= Chef::Version.new('11.0.0')
14
+ include ::Chef::Mixin::Language
15
+ else
16
+ include ::Chef::DSL::DataQuery
17
+ end
18
+ end
19
+
20
+ #
21
+ # Initialize etcd client
22
+ #
23
+ # You should pass either a run_context or explicit host/port arguments
24
+ # the run_context will take prescedence
25
+ #
26
+ # @param [Hash] options
27
+ # @option args [Chef::RunContext] :run_context (nil)
28
+ # The chef run context to find things in
29
+ # @option args [String] :host (nil) The host address to connect too
30
+ # @option args [String] :port (4001) The etcd port to connect too
31
+ def initialize(args)
32
+ @run_context = args.fetch(:run_context, nil)
33
+ Services.run_context = args[:run_context]
34
+ @node = args[:run_context].node if run_context
35
+ @host = args[:host]
36
+ @port = args[:port] || 4001
37
+ @ssl_verify = args[:verify] || OpenSSL::SSL::VERIFY_NONE
38
+
39
+ validate
40
+ load_gem
41
+ Services.connection = get_connection(find_servers)
42
+ end
43
+
44
+ private
45
+
46
+ #
47
+ # Validate args passeed in on initialize
48
+ #
49
+ # We require run_context OR host to function
50
+ #
51
+ def validate
52
+ unless run_context || host
53
+ fail ArgumentError, 'Must provide a run_context OR host to initialize'
54
+ end
55
+ end
56
+
57
+ #
58
+ # Lazily Load the gem requirement so we can run inside chef
59
+ #
60
+ # If @run_context exists it will use that to install the gem via
61
+ # Chefs chef_gem resource
62
+ #
63
+ def load_gem
64
+ require 'etcd'
65
+ rescue LoadError
66
+ if run_context
67
+ Chef::Log.info 'etcd gem not found. attempting to install'
68
+ g = Chef::Resource::ChefGem.new 'etcd', run_context
69
+ g.version '0.0.6'
70
+ run_context.resource_collection.insert g
71
+ g.run_action :install
72
+ require 'etcd'
73
+ end
74
+ end
75
+
76
+ #
77
+ # Find other Etd Servers by looking at node attributes or via Chef Search
78
+ #
79
+ def find_servers
80
+ # need a run_context to find anything in
81
+ return nil unless run_context
82
+ # If there are already servers in attribs use those
83
+ return node[:etcd][:servers] if node.key?(:etcd) &&
84
+ node[:etcd].key?(:servers)
85
+
86
+ # if we have already searched in this run use those
87
+ return node.run_state[:etcd_servers] if node.run_state.key? :etcd_servers
88
+
89
+ # find nodes and build array of ip's
90
+ etcd_nodes = search(:node, search_query)
91
+ servers = etcd_nodes.map { |n| n[:ipaddress] }
92
+
93
+ # store that in the run_state
94
+ node.run_state[:etcd_servers] = servers
95
+ end
96
+
97
+ #
98
+ # Setup proper chef search term for other etcd boxen
99
+ #
100
+ # Will search for known recipe in run_list or specified term
101
+ #
102
+ def search_query
103
+ query = "(chef_environment:#{node.chef_environment} "
104
+ query << 'AND recipes:etcd) '
105
+ if node[:etcd][:recipe]
106
+ query << "OR (chef_environment:#{node.chef_environment} "
107
+ query << "AND #{node[:etcd][:search_term]})"
108
+ end
109
+ query
110
+ end
111
+
112
+ #
113
+ # connect to ip/port and store in @@client
114
+ # If given an arry of servers then try each until we
115
+ # connect
116
+ # TODO: refactor
117
+ # rubocop:disable MethodLength
118
+ def get_connection(servers = nil)
119
+ c = nil
120
+ if servers
121
+ servers.each do |s|
122
+ c = try_connect(s)
123
+ break if c
124
+ end
125
+ else
126
+ c = try_connect host
127
+ end
128
+ fail 'Unable to get a valid connection to Etcd' unless c
129
+ c
130
+ end
131
+
132
+ #
133
+ # Try to grab an etcd connection
134
+ #
135
+ # @param [String] server () The server to try to connect too
136
+ #
137
+ def try_connect(server)
138
+ c = ::Etcd.client(host: server, port: port)
139
+ begin
140
+ c.get '/_etcd/machines'
141
+ return c
142
+ rescue
143
+ puts "ETCD: failed to connect to #{c.host}:#{c.port}"
144
+ return nil
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,24 @@
1
+ # Services::Endpoint
2
+ # The VIP of the service. This class describes where an endpoint lives
3
+ #
4
+ module Services
5
+ require_relative 'entity'
6
+
7
+ # endpoint describes a VIP ip
8
+ class Endpoint < Services::Entity
9
+ attr_accessor :ip, :port, :proto
10
+ def initialize(name, args = {})
11
+ @ip = args[:ip] || ''
12
+ @proto = args[:proto] || 'http'
13
+ @port = args[:port] || 80
14
+ @path = "#{name}/endpoint"
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ def validate
21
+ fail 'endpont requires a service name' unless name
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ #
2
+ # Services::Entity:
3
+ # This is the generic 'entity' for anything you want to attach to a service.
4
+ # Inherit this for any other type a service may have.
5
+ module Services
6
+ require_relative '../services'
7
+
8
+ # entity base class.
9
+ # member,service,endpoint are all "services::entity"
10
+ class Entity
11
+ attr_reader :name, :path
12
+ def initialize(name, args = {})
13
+ @name = name
14
+ validate
15
+ end
16
+
17
+ def store
18
+ _store
19
+ end
20
+ alias_method :save, :store
21
+
22
+ def load
23
+ _load
24
+ end
25
+
26
+ def to_hash
27
+ vars = {}
28
+ instance_variables.each do |name|
29
+ key = name[1..-1].to_s
30
+ # don't store "path"
31
+ next if key == 'path'
32
+ vars[key] = instance_variable_get(name).to_s
33
+ end
34
+ vars
35
+ end
36
+
37
+ private
38
+
39
+ def validate
40
+ fail 'This class should be extended. Not used directly' unless path
41
+ end
42
+
43
+ def _store
44
+ to_hash.each do |k, v|
45
+ next if k == 'name'
46
+ # etcd doesn't like nil
47
+ v ||= ''
48
+ Services.set "#{KEY}/#{path}/#{k}", v
49
+ end
50
+ end
51
+
52
+ def _load
53
+ return unless valid_path
54
+ to_hash.each do |k, v|
55
+ next if k == 'name'
56
+ value = Services.get("#{KEY}/#{path}/#{k}").value
57
+ instance_variable_set "@#{k}", value
58
+ end
59
+ self
60
+ end
61
+
62
+ def valid_path
63
+ begin
64
+ Services.get("#{KEY}/#{path}")
65
+ rescue
66
+ return false
67
+ end
68
+ true
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'entity'
2
+
3
+ module Services
4
+ # This is a service member usually something that
5
+ # would be sitting behind some VIP
6
+ class Member < Services::Entity
7
+ attr_accessor :ip, :port, :proto, :service, :weight
8
+ def initialize(name, args = {})
9
+ @ip = args[:ip] || ''
10
+ @proto = args[:proto] || 'http'
11
+ @port = args[:port] || 80
12
+ @weight = args[:weight] || 20
13
+ @service = args[:service]
14
+ @path = "#{service}/members/#{name}"
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ def validate
21
+ unless @service
22
+ fail ArgumentError,
23
+ "#{self.class} requires service: argument"
24
+ end
25
+
26
+ unless name
27
+ fail ARgumentError,
28
+ "#{self.class} requires name argument"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ # Services::Service
2
+ # this is the almalgamate service class which allows you to load a service
3
+ # and it's endpoint/members
4
+ #
5
+ # TODO: Allow arbitrary entity loading.
6
+ #
7
+ module Services
8
+ require_relative 'connection'
9
+ require_relative 'endpoint'
10
+ require_relative 'member'
11
+
12
+ # service container
13
+ class Service
14
+ attr_reader :name
15
+ attr_reader :members
16
+ attr_reader :endpoint
17
+
18
+ def initialize(name)
19
+ @name = name
20
+ @members = []
21
+ @endpoint = Services::Endpoint.new name
22
+
23
+ create_if_missing
24
+ load_members
25
+ load_endpoint
26
+ end
27
+
28
+ private
29
+
30
+ def create_if_missing
31
+ Services.get "#{KEY}/#{name}"
32
+ rescue Net::HTTPServerException => e
33
+ Services.set "#{KEY}/#{name}/_created", Time.now if e.message.match 'Not Found'
34
+ end
35
+
36
+ def load_endpoint
37
+ endpoint.load
38
+ endpoint
39
+ end
40
+
41
+ # rubocop:disable MethodLength
42
+ def load_members
43
+ begin
44
+ etcd_members = Services.get "#{KEY}/#{name}/members"
45
+ rescue Net::HTTPServerException => e
46
+ etcd_members = nil if e.message.match 'Not Found'
47
+ end
48
+
49
+ unless etcd_members.nil? || etcd_members.empty?
50
+ etcd_members.node.nodes.each do |m|
51
+ m_name = File.basename m['key']
52
+ m1 = Services::Member.new(m_name, service: name)
53
+ m1.load
54
+ @members.push m1
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ # setup the version here
2
+ module Services
3
+ VERSION = '1.0.0'
4
+ end
data/lib/services.rb ADDED
@@ -0,0 +1,53 @@
1
+ #
2
+ # Services Module
3
+ #
4
+ # Uses etcd to manage state of Service Endpoint & Members
5
+ #
6
+ module Services
7
+ require_relative 'services/version'
8
+ require_relative 'services/connection'
9
+ require_relative 'services/entity'
10
+ require_relative 'services/service'
11
+ require_relative 'services/endpoint'
12
+ require_relative 'services/member'
13
+
14
+ # this will change or be slurped up from a config/node attrib
15
+ KEY = '/services'
16
+
17
+ #
18
+ # Share a connection between all classess using this module
19
+ #
20
+ class << self
21
+ attr_accessor :connection, :run_context
22
+
23
+ def get(*args)
24
+ Chef::Log.debug "connection.get args #{args}" unless run_context.nil?
25
+ connection.get(*args)
26
+ end
27
+
28
+ def set(*args)
29
+ Chef::Log.debug "connection.set args #{args}" unless run_context.nil?
30
+ connection.set(*args)
31
+ end
32
+
33
+ # return a list of all services
34
+ def all
35
+ services = []
36
+ get(KEY).each do |s|
37
+ name = File.basename s.key
38
+ puts "loading #{name}"
39
+ services << Services::Service.new(name)
40
+ end
41
+ services
42
+ end
43
+
44
+ # return all services a node is subscribed to
45
+ def subscribed(f = nil)
46
+ fail 'param and run_context can not both be nil' if f.nil? && run_context.nil?
47
+
48
+ fqdn = f.nil? ? rteraun_context.node.fqdn : f
49
+ services = all.map { |s| s.members.include?(fqdn) ? s : nil }
50
+ services.compact
51
+ end
52
+ end
53
+ end
data/spec/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from:
2
+ - ../.rubocop.yml
3
+
4
+ LineLength:
5
+ Max: 200
@@ -0,0 +1,47 @@
1
+ describe 'Services::Endpoint' do
2
+ before(:each) do
3
+ @ep_data = { 'name' => 'test', 'ip' => '127.0.0.1', 'proto' => 'http', 'port' => '80' }
4
+ Services::Connection.new host: 'localhost'
5
+ @ep = Services::Endpoint.new 'test', ip: '127.0.0.1', port: 80
6
+ end
7
+
8
+ it 'should raise without a service name' do
9
+ expect { Services::Endpoint.new }.to raise_error
10
+ end
11
+
12
+ describe '#to_hash' do
13
+ it 'should return each pair of insance vars' do
14
+ @ep.to_hash.should eql @ep_data
15
+ end
16
+ end
17
+
18
+ describe '#store' do
19
+ it 'should store' do
20
+ puts "storing #{@ep.inspect}"
21
+ @ep.store
22
+ end
23
+ end
24
+
25
+ describe '#load' do
26
+ before do
27
+ @ep = Services::Endpoint.new 'test'
28
+ end
29
+
30
+ it 'should handle no endpoint' do
31
+ e = Services::Endpoint.new 'test_endpoint_fail'
32
+ e.load
33
+ end
34
+
35
+ it 'should load' do
36
+ @ep.load
37
+ @ep.to_hash.should eql @ep_data
38
+ end
39
+
40
+ it 'should get ip' do
41
+ @ep.ip.should eql ''
42
+ @ep.load
43
+ @ep.ip.should eql '127.0.0.1'
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,37 @@
1
+ describe 'Services::Member' do
2
+ before(:each) do
3
+ @m_data = { 'ip' => '127.0.0.2', 'proto' => 'http', 'port' => '80', 'weight' => '20', 'service' => 'test', 'name' => 'test_member' }
4
+ Services::Connection.new host: 'localhost'
5
+ @m = Services::Member.new 'test_member', service: 'test', ip: '127.0.0.2', port: 80, weight: 20
6
+ @m2 = Services::Member.new 'test_member2', service: 'test', ip: '127.0.0.3', port: 80, weight: 20
7
+ end
8
+
9
+ it 'should raise without a service name' do
10
+ expect { Services::Member.new 'foo' }.to raise_error
11
+ end
12
+
13
+ describe '#to_hash' do
14
+ it 'should return each pair of insance vars' do
15
+ @m.to_hash.should == @m_data
16
+ end
17
+ end
18
+
19
+ describe '#store' do
20
+ it 'should store' do
21
+ @m.store
22
+ @m2.store
23
+ end
24
+ end
25
+
26
+ describe '#load' do
27
+ before do
28
+ @m = Services::Member.new 'test_member', service: 'test'
29
+ end
30
+
31
+ it 'should load' do
32
+ @m.load
33
+ @m.to_hash.should == @m_data
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Services::Service' do
3
+ let(:c) { Services::Connection.new host: 'localhost' }
4
+ let(:m1) { Services::Member.new 'test_member', service: 'test', ip: '127.0.0.2', port: 80, weight: 20 }
5
+ let(:m2) { Services::Member.new 'test_member2', service: 'test', ip: '127.0.0.3', port: 80, weight: 20 }
6
+
7
+ it 'should load the test service' do
8
+ Services::Connection.new host: 'localhost'
9
+ members = Services::Service.new('test').members
10
+ members.length.should eql 2
11
+ members[0].to_hash.should eql m1.to_hash
12
+ members[1].to_hash.should eql m2.to_hash
13
+ end
14
+
15
+ it 'should handle non-existant services' do
16
+ expect { Services::Service.new 'should_not_exist' }.to_not raise_error
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ require_relative 'spec_helper.rb'
2
+
3
+ describe 'Services' do
4
+ describe '::Connection' do
5
+ it 'should setup' do
6
+ Services::Connection.new host: 'localhost'
7
+ end
8
+
9
+ it 'should fail without something to connect too' do
10
+ expect { Services::Connection.new }.to raise_error
11
+ end
12
+
13
+ # chef functions in chef-spec specs ?
14
+ # it "should accept a chef run_context" do
15
+ # Services::Connection.new(
16
+ # run_context: Chef::RunContext.new(
17
+ # Chef::Node.new,
18
+ # Chef::CookbookCollection.new,
19
+ # Chef::EventDispatch::Dispatcher.new
20
+ # )
21
+ # )
22
+ # end
23
+ end
24
+
25
+ before(:each) do
26
+ Services::Connection.new host: 'localhost'
27
+ end
28
+
29
+ it 'can set' do
30
+ Services.set '/test/1', 1
31
+ end
32
+
33
+ it 'can get' do
34
+ Services.get '/_etcd/machines'
35
+ end
36
+
37
+ describe '::Entity' do
38
+ before(:each) do
39
+ Services::Connection.new host: 'localhost'
40
+ end
41
+
42
+ it 'should raise when directly instanced' do
43
+ expect { Services::Entity.new('foo') }.to raise_error(RuntimeError)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,104 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ require_relative '../lib/services.rb'
4
+
5
+ require 'uuid'
6
+ require 'etcd'
7
+ # thanks @ranjib for this handy helper
8
+ module Etcd
9
+ # helpers to start an etcd cluster
10
+ # rubocop:disable ClassVars
11
+ module SpecHelper
12
+ @@pids = []
13
+
14
+ def self.etcd_binary
15
+ if File.exists? './etcd/etcd'
16
+ './etcd/etcd'
17
+ elsif !!ENV['ETCD_BIN']
18
+ ENV['ETCD_BIN']
19
+ elsif File.exists? '/usr/local/bin/etcd'
20
+ '/usr/local/bin/etcd'
21
+ else
22
+ fail 'etcd binary not found., you need to set ETCD_BIN'
23
+ end
24
+ end
25
+
26
+ def self.start_etcd_servers
27
+ @@tmpdir = Dir.mktmpdir
28
+ pid = spawn_etcd_server(@@tmpdir + '/leader')
29
+ @@pids = Array(pid)
30
+ leader = '127.0.0.1:7001'
31
+ 4.times do |n|
32
+ client_port = 4002 + n
33
+ server_port = 7002 + n
34
+ pid = spawn_etcd_server(@@tmpdir + client_port.to_s, client_port, server_port, leader)
35
+ @@pids << pid
36
+ end
37
+ end
38
+
39
+ def self.stop_etcd_servers
40
+ @@pids.each do |pid|
41
+ Process.kill('TERM', pid)
42
+ end
43
+ FileUtils.remove_entry_secure(@@tmpdir, true)
44
+ end
45
+
46
+ def self.spawn_etcd_server(dir, client_port = 4001, server_port = 7001, leader = nil)
47
+ args = " -addr 127.0.0.1:#{client_port} -peer-addr 127.0.0.1:#{server_port} -data-dir #{dir} -name node_#{client_port}"
48
+ command = if leader.nil?
49
+ etcd_binary + args
50
+ else
51
+ etcd_binary + args + " -peers #{leader}"
52
+ end
53
+ pid = spawn(command, out: '/dev/null')
54
+ Process.detach(pid)
55
+ sleep 1
56
+ pid
57
+ end
58
+
59
+ def uuid
60
+ @uuid ||= UUID.new
61
+ end
62
+
63
+ def random_key(n = 1)
64
+ key = ''
65
+ n.times do
66
+ key << '/' + uuid.generate
67
+ end
68
+ key
69
+ end
70
+
71
+ def etcd_servers
72
+ (1..5).map { |n| "http://127.0.0.1:700#{n}" }
73
+ end
74
+
75
+ def other_client
76
+ Etcd.client
77
+ end
78
+
79
+ def read_only_client
80
+ Etcd.client(allow_redirect: false, port: 4004)
81
+ end
82
+ end
83
+ end
84
+
85
+ RSpec.configure do |config|
86
+
87
+ config.include Etcd::SpecHelper
88
+
89
+ config.before(:suite) do
90
+ Etcd::SpecHelper.start_etcd_servers
91
+ end
92
+
93
+ config.after(:suite) do
94
+ Etcd::SpecHelper.stop_etcd_servers
95
+ end
96
+ # Use color in STDOUT
97
+ config.color_enabled = true
98
+
99
+ # Use color not only in STDOUT but also in pagers and files
100
+ config.tty = true
101
+
102
+ # Use the specified formatter
103
+ config.formatter = :documentation # :progress, :html, :textmate
104
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jn_services
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jesse Nelson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: etcd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Consitently model servives with etcd in and outside chef
70
+ email:
71
+ - spheromak@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .coco.yml
77
+ - .gitignore
78
+ - .rubocop.yml
79
+ - .travis.yml
80
+ - CHANGELOG.md
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - build_etcd
86
+ - jn_services.gemspec
87
+ - lib/services.rb
88
+ - lib/services/connection.rb
89
+ - lib/services/endpoint.rb
90
+ - lib/services/entity.rb
91
+ - lib/services/member.rb
92
+ - lib/services/service.rb
93
+ - lib/services/version.rb
94
+ - spec/.rubocop.yml
95
+ - spec/endpoint_spec.rb
96
+ - spec/member_spec.rb
97
+ - spec/service_spec.rb
98
+ - spec/services_spec.rb
99
+ - spec/spec_helper.rb
100
+ homepage: https://github.com/spheromak/services
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.0.14
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Consitently model servives with etcd in and outside chef
124
+ test_files:
125
+ - spec/.rubocop.yml
126
+ - spec/endpoint_spec.rb
127
+ - spec/member_spec.rb
128
+ - spec/service_spec.rb
129
+ - spec/services_spec.rb
130
+ - spec/spec_helper.rb