desi 0.0.2

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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
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
18
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in desi.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Dominique Rose-Rosette
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,47 @@
1
+ # Desi
2
+
3
+ Desi (Developper ElasticSearch Installer) is very simple tool to quickly set up
4
+ an [Elastic Search](http://www.elasticsearch.org/) local install for
5
+ development purposes. It will download and install ElasticSearch (the latest
6
+ version by default) and let you start/stop/restart it.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'desi'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install desi
21
+
22
+ ## Usage
23
+
24
+ $ desi list # List locally installed ElasticSearch versions
25
+ $ desi releases # List all upstream Elastic Search releases (latest 5 by default)
26
+ $ desi install [VERSION] # Install a specific version (latest by default)
27
+ $ desi start # Start a local 1-node cluster (noop if active)
28
+ $ desi restart # (Re)start cluster (even if active)
29
+ $ desi stop # Stop cluster
30
+ $ desi status # Show running cluster info
31
+
32
+ ## TODO
33
+
34
+ * add tests, dammit!
35
+
36
+ * index management (list, create, delete ES indices)
37
+ * `desi upgrade` (Upgrade to latest version and migrate data)
38
+ * `desi switch VERSION` (Switch currently active ES version to VERSION)
39
+ * plugin management ? (list, install, remove ES plugins)
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/desi ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: se ft=ruby :
3
+
4
+ require 'desi'
5
+ require 'desi/runner'
6
+
7
+ Desi::Runner.start
@@ -0,0 +1,6 @@
1
+ # Desi provided configuration
2
+ # Look in elasticsearch.dist.yml for the original configuration
3
+
4
+ # Let's set the host to localhost, so that we don't connect to other nearby
5
+ # instances
6
+ network.host: 127.0.0.1
data/desi.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/desi/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "desi"
6
+ gem.authors = ["Dominique Rose-Rosette"]
7
+ gem.email = ["drose-rosette@af83.com"]
8
+ gem.summary = %q{A developer tool to quickly set up an Elastic Search local install.}
9
+ gem.description = %q{Desi (Developper ElasticSearch Installer) is very simple tool to quickly set up
10
+ an Elastic Search local install for development purposes.}
11
+ gem.homepage = "https://github.com/AF83/desi/"
12
+ gem.version = Desi::VERSION
13
+
14
+ gem.add_dependency "boson"
15
+ gem.add_dependency "cocaine"
16
+
17
+ gem.files = `git ls-files`.split($\)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require "pathname"
4
+ require "desi/local_install"
5
+ require "desi/http_client"
6
+ require "uri"
7
+
8
+ module Desi
9
+ class Downloader
10
+
11
+ def initialize(opts = {})
12
+ @destination_dir = Pathname(opts.fetch(:destination_dir, Desi::LocalInstall.new))
13
+ @host = URI(opts.fetch(:host, 'http://cloud.github.com/'))
14
+ @client = Desi::HttpClient.new(@host)
15
+ @verbose = opts[:verbose]
16
+ end
17
+
18
+ def download!(version, opts = {})
19
+ path = "/downloads/elasticsearch/elasticsearch/#{version.name}"
20
+ destination_name = @destination_dir.join File.basename(version.name)
21
+
22
+ raise "ERROR: File #{destination_name} already present!" if destination_name.exist?
23
+
24
+ puts " * fetching release #{version} from #{@host + path}" if @verbose
25
+
26
+ File.open(destination_name, 'w') {|f| f << @client.get(path).body }
27
+
28
+ destination_name
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require "net/https"
4
+ require "uri"
5
+ require "json"
6
+
7
+ module Desi
8
+
9
+ class HttpClient
10
+
11
+ attr_reader :uri
12
+
13
+ def initialize(uri)
14
+ @uri = URI(uri)
15
+
16
+ case @uri.scheme
17
+ when 'https'
18
+ @http = ::Net::HTTP.new(@uri.host, 443)
19
+ @http.use_ssl = true
20
+ @http.verify_mode = ::OpenSSL::SSL::VERIFY_PEER
21
+ when 'http'
22
+ @http = ::Net::HTTP.new(@uri.host, @uri.port)
23
+ else
24
+ raise ArgumentError, "Won't process scheme #{@uri.scheme}"
25
+ end
26
+ end
27
+
28
+ def get(uri, limit = 5)
29
+ raise "Too many HTTP redirects!" if limit <= 0
30
+
31
+ response = @http.request(Net::HTTP::Get.new(uri))
32
+
33
+ case response
34
+ when Net::HTTPSuccess
35
+ response
36
+ when Net::HTTPRedirection
37
+ fetch(response['location'], limit - 1)
38
+ else
39
+ raise response.error!
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ require "fileutils"
4
+ require "cocaine"
5
+
6
+ module Desi
7
+ class Installer
8
+
9
+ def initialize(archive, opts = {})
10
+ @verbose = opts[:verbose]
11
+ @archive = archive.to_s
12
+ @local_install = Desi::LocalInstall.new(opts[:destination_dir])
13
+ end
14
+
15
+ def install!
16
+ extract! unless extracted?
17
+ install_config_file
18
+ update_symlink!
19
+ end
20
+
21
+ def extracted?
22
+ !!@extracted
23
+ end
24
+
25
+ def install_config_file
26
+ unless original_config_backup.exist?
27
+ puts " * Installing custom config file" if @verbose
28
+ FileUtils.mv config_file, original_config_backup
29
+ FileUtils.cp our_config_file, config_file
30
+ end
31
+ end
32
+
33
+
34
+ def update_symlink!
35
+ unless @local_install.current_dir.symlink?
36
+ raise "Mmmm!! #{@local_install.current_dir} is not a symlink!"
37
+ end
38
+
39
+ puts " * Updating #{@local_install.current_dir} symlink" if @verbose
40
+ FileUtils.remove(@local_install.current_dir)
41
+ FileUtils.ln_sf(release_dir, @local_install.current_dir)
42
+ end
43
+
44
+ def config_file
45
+ release_dir.join('config', 'elasticsearch.yml')
46
+ end
47
+
48
+ def original_config_backup
49
+ release_dir.join('config', 'elasticsearch.yml.dist')
50
+ end
51
+
52
+ def our_config_file
53
+ File.expand_path('../../../config/elasticsearch.yml', __FILE__)
54
+ end
55
+
56
+ private
57
+
58
+ def extract!
59
+ line = Cocaine::CommandLine.new("tar", "--keep-newer-files -C :extract_dir -zxf :archive", extract_dir: @local_install.to_s, archive: @archive)
60
+ begin
61
+ line.run
62
+ rescue Cocaine::CommandNotFoundError => e
63
+ warn "The tar command must be available for this to work! #{e}"
64
+ exit 1
65
+ else
66
+ @extracted = true
67
+ end
68
+ end
69
+
70
+ def release_dir
71
+ @release_dir ||= Pathname(@local_install).join(File.basename(@archive, '.tar.gz'))
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ module Desi
4
+ class LocalInstall
5
+ DEFAULT_DIR = '~/elasticsearch'
6
+
7
+ def initialize(workdir = nil)
8
+ @workdir = Pathname(File.expand_path(workdir || DEFAULT_DIR))
9
+ end
10
+
11
+ def exists?
12
+ @workdir.exist?
13
+ end
14
+
15
+ def current_dir
16
+ @workdir.join('current')
17
+ end
18
+
19
+ def create!
20
+ FileUtils.mkdir_p @workdir
21
+ end
22
+
23
+ def versions
24
+ Dir[@workdir.join('*')].select {|subdir| File.directory?(subdir) && File.basename(subdir) =~ /^elasticsearch\-\d+\.\d+\.\d+/ }
25
+ end
26
+
27
+ def to_path
28
+ @workdir.to_s
29
+ end
30
+
31
+ def to_s
32
+ to_path
33
+ end
34
+
35
+ def pidfile
36
+ @workdir.join('elasticsearch.pid')
37
+ end
38
+
39
+ def launcher
40
+ current_dir.join('bin', 'elasticsearch')
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+
3
+ require "json"
4
+ require "cocaine"
5
+ require "ostruct"
6
+
7
+ module Desi
8
+ class ProcessManager
9
+
10
+ def initialize(opts = {})
11
+ @verbose = opts[:verbose]
12
+ @local_install = LocalInstall.new
13
+ @client = Desi::HttpClient.new('http://localhost:9200/')
14
+ end
15
+
16
+ def start
17
+ if cluster_ready?
18
+ puts "ES cluster is already running" if @verbose
19
+ else
20
+ start_cluster
21
+ puts " * Elastic Search #{running_version} started" if @verbose
22
+ end
23
+ end
24
+
25
+ def restart
26
+ puts " * (Re)starting cluster" if @verbose
27
+ stop if has_pid?
28
+ start_cluster
29
+ puts " * Elastic Search #{running_version} started" if @verbose
30
+ end
31
+
32
+ def stop
33
+ if pid
34
+ puts " * Will stop instance with pid #{pid}" if @verbose
35
+ stop_cluster
36
+ else
37
+ puts " * No pidfile detected!. Won't stop" if @verbose
38
+ end
39
+ end
40
+
41
+ def restart
42
+ stop
43
+ start
44
+ end
45
+
46
+ def status
47
+ if version = running_version
48
+ msg = "OK. Elastic Search cluster '#{cluster_health.cluster_name}' (v#{version}) is running on #{cluster_health.number_of_nodes} node(s) with status #{cluster_health.status}"
49
+ else
50
+ msg = "KO. No Elastic Search instance was found running on #{@client.uri}"
51
+ end
52
+ puts msg if @verbose
53
+ msg
54
+ end
55
+
56
+ def has_pid?
57
+ pid && !pid.empty?
58
+ end
59
+
60
+ def pid
61
+ @pid ||= File.read(pidfile) if pidfile.exist?
62
+ end
63
+
64
+ def running_version
65
+ begin
66
+ JSON.parse(@client.get('/').body)["version"]["number"]
67
+ rescue
68
+ nil
69
+ end
70
+ end
71
+
72
+ def wait_until_cluster_becomes_ready(max_wait = 10, step = 0.5)
73
+ wait_for(max_wait, step) { cluster_ready? }
74
+ end
75
+
76
+ def wait_until_cluster_is_down(max_wait = 5, step = 0.3)
77
+ wait_for(max_wait, step) { !cluster_ready? }
78
+ end
79
+
80
+ private
81
+
82
+ def start_cluster
83
+ line = Cocaine::CommandLine.new(@local_install.launcher.to_s, "-p :pidfile", pidfile: pidfile.to_s)
84
+ line.run
85
+
86
+ unless wait_until_cluster_becomes_ready
87
+ raise "Cluster still not ready after #{max_wait} seconds!"
88
+ end
89
+ end
90
+
91
+ def stop_cluster
92
+ kill!
93
+
94
+ unless wait_until_cluster_is_down
95
+ raise "Strange. Cluster seems still up after #{max_wait} seconds!"
96
+ end
97
+ end
98
+
99
+ def kill!
100
+ Process.kill("HUP", Integer(pid)) if has_pid?
101
+ end
102
+
103
+ def pidfile
104
+ @pidfile ||= Pathname(@local_install.pidfile)
105
+ end
106
+
107
+ def cluster_ready?
108
+ begin
109
+ JSON.parse(@client.get('/').body)["ok"]
110
+ rescue
111
+ false
112
+ end
113
+ end
114
+
115
+ def wait_for(max_wait = 10, step = 0.5, &condition)
116
+ delay = 0
117
+ until delay > max_wait || condition.call
118
+ sleep step
119
+ delay += step
120
+ end
121
+ delay < max_wait
122
+ end
123
+
124
+ def cluster_health
125
+ @cluster_health ||= OpenStruct.new(JSON.parse(@client.get('/_cluster/health').body))
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+
3
+ require 'boson/runner'
4
+
5
+ module Desi
6
+ class Runner < Boson::Runner
7
+
8
+ def self.verbosity_option
9
+ option :verbose, type: :boolean, desc: "Display information messages", default: STDOUT.tty?
10
+ option :quiet, type: :boolean, desc: "Do not output anything", default: !STDOUT.tty?
11
+ end
12
+
13
+ desc "List locally installed Elastic Search releases"
14
+ verbosity_option
15
+ def list(options = {})
16
+ puts "Local ES installs:" unless quiet?(options)
17
+ Desi::LocalInstall.new.versions.each do |v|
18
+ puts "* #{v}"
19
+ end
20
+ end
21
+
22
+ desc "List latest ElasticSearch releases (latest 5 by default)"
23
+ verbosity_option
24
+ option :limit, type: :numeric, desc: "Number of releases to show (0 for all)", default: 5
25
+ def releases(options = {})
26
+ limit = options[:limit]
27
+ releases = Desi::Upstream.new.releases.each_with_index.
28
+ take_while {|rel, i| i < limit || limit == 0 }.map(&:first)
29
+
30
+ if quiet?(options)
31
+ releases
32
+ else
33
+ puts "Here are #{limit == 0 ? 'all the' : "the #{limit} latest"} releases"
34
+ releases.each {|rel| puts " * #{rel.name} (#{rel.release_date})" }
35
+ end
36
+ end
37
+
38
+ desc "Install ES (to latest stable version by default)"
39
+ verbosity_option
40
+ def install(version_or_full_name = nil, options = {})
41
+ release = if version_or_full_name
42
+ Desi::Upstream.new.find_release(version_or_full_name)
43
+ else
44
+ puts " * No release specified, will fetch latest." unless quiet?(options)
45
+ Desi::Upstream.new.latest_release
46
+ end
47
+
48
+ puts " * fetching release #{release}" unless quiet?(options)
49
+ package = Desi::Downloader.new(verbose: !quiet?(options)).download!(release)
50
+
51
+ puts " * #{release} installed" if Desi::Installer.new(package).install! && !quiet?(options)
52
+ end
53
+
54
+ desc "Start Elastic Search (do nothing if already active)"
55
+ verbosity_option
56
+ def start(options = {})
57
+ Desi::ProcessManager.new(verbose: !quiet?(options)).start
58
+ end
59
+
60
+ desc "Start or restart Elastic Search (restart if already active)"
61
+ verbosity_option
62
+ def restart(options = {})
63
+ Desi::ProcessManager.new(verbose: !quiet?(options)).restart
64
+ end
65
+
66
+ desc "Stop Elastic Search"
67
+ verbosity_option
68
+ def stop(options = {})
69
+ Desi::ProcessManager.new(verbose: !quiet?(options)).stop
70
+ end
71
+
72
+ desc "Show current status"
73
+ verbosity_option
74
+ def status(options = {})
75
+ Desi::ProcessManager.new(verbose: !quiet?(options)).status
76
+ end
77
+
78
+ # desc "Upgrade to latest ElasticSearch version"
79
+ # def upgrade
80
+ # end
81
+
82
+ # desc "Switch currently active ES version to VERSION"
83
+ # option :version, type: :string
84
+ # def switch
85
+ # end
86
+
87
+ private
88
+
89
+ def quiet?(opts = {})
90
+ opts[:quiet] || !opts[:verbose]
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ require "desi/http_client"
4
+
5
+ module Desi
6
+ class Upstream
7
+
8
+ class Release < Struct.new(:name, :description, :release_date, :download_url)
9
+ def to_s
10
+ self.name
11
+ end
12
+ end
13
+
14
+ def initialize
15
+ @client = Desi::HttpClient.new('https://api.github.com/')
16
+ end
17
+
18
+ def releases
19
+ @releases ||= fetch_releases.
20
+ select {|v| v['content_type'] == 'application/gzip' }.
21
+ sort {|a,b| b["name"] <=> a['name'] }.
22
+ map {|v| Release.new(v['name'], v['description'], v['created_at'], v['html_url']) }
23
+ end
24
+
25
+ def latest_release
26
+ releases.first
27
+ end
28
+
29
+ def find_release(name)
30
+ releases.detect {|r| r.name == name || r.version == name }
31
+ end
32
+
33
+ private
34
+
35
+ def fetch_releases
36
+ JSON.parse @client.get('/repos/elasticsearch/elasticsearch/downloads').body
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Desi
2
+ VERSION = "0.0.2"
3
+ end
data/lib/desi.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "desi/version"
2
+
3
+ module Desi
4
+ autoload :Downloader, 'desi/downloader'
5
+ autoload :HttpClient, 'desi/http_client'
6
+ autoload :LocalInstall, 'desi/local_install'
7
+ autoload :Upstream, 'desi/upstream'
8
+ autoload :Installer, 'desi/installer'
9
+ autoload :ProcessManager, 'desi/process_manager'
10
+ end
11
+
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: desi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dominique Rose-Rosette
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: boson
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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'
30
+ - !ruby/object:Gem::Dependency
31
+ name: cocaine
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
+ description: ! 'Desi (Developper ElasticSearch Installer) is very simple tool to quickly
47
+ set up
48
+
49
+ an Elastic Search local install for development purposes.'
50
+ email:
51
+ - drose-rosette@af83.com
52
+ executables:
53
+ - desi
54
+ extensions: []
55
+ extra_rdoc_files: []
56
+ files:
57
+ - .gitignore
58
+ - Gemfile
59
+ - LICENSE
60
+ - README.md
61
+ - Rakefile
62
+ - bin/desi
63
+ - config/elasticsearch.yml
64
+ - desi.gemspec
65
+ - lib/desi.rb
66
+ - lib/desi/downloader.rb
67
+ - lib/desi/http_client.rb
68
+ - lib/desi/installer.rb
69
+ - lib/desi/local_install.rb
70
+ - lib/desi/process_manager.rb
71
+ - lib/desi/runner.rb
72
+ - lib/desi/upstream.rb
73
+ - lib/desi/version.rb
74
+ homepage: https://github.com/AF83/desi/
75
+ licenses: []
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 1.8.24
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: A developer tool to quickly set up an Elastic Search local install.
98
+ test_files: []
99
+ has_rdoc: