ssp 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Secret Sauce Partners, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ The SSP gem provides various command line tools that we use internally. It might be useful to others as well, but we're not trying to make them general.
data/bin/ssp ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4
+ require 'ssp/app'
5
+
6
+ SSP::App.start
@@ -0,0 +1,106 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+
4
+ module SSP
5
+ class App < Thor
6
+ namespace :app
7
+ map "-T" => :list
8
+
9
+ # Override Thor#help so it can give information about any class and any method.
10
+ #
11
+ def help(meth=nil)
12
+ if meth && !self.respond_to?(meth)
13
+ initialize_commands
14
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
15
+ if klass
16
+ klass.start(["-h", task].compact, :shell => self.shell)
17
+ else
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ # If a task is not found on SSP::App, method missing is invoked and
26
+ # SSP::App is then responsable for finding the task in all classes.
27
+ #
28
+ def method_missing(meth, *args)
29
+ meth = meth.to_s
30
+ initialize_commands
31
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
32
+ args.unshift(task) if task
33
+ klass.start(args, :shell => self.shell)
34
+ end
35
+
36
+ desc "version", "Show SSP version"
37
+ def version
38
+ require 'ssp/version'
39
+ say "SSP #{SSP::VERSION}"
40
+ end
41
+
42
+ desc "list [SEARCH]", "List the available ssp tasks (--substring means .*SEARCH)"
43
+ method_options :substring => :boolean, :group => :string, :all => :boolean
44
+ def list(search="")
45
+ initialize_commands
46
+
47
+ search = ".*#{search}" if options["substring"]
48
+ search = /^#{search}.*/i
49
+ group = options[:group] || "standard"
50
+
51
+ klasses = Thor::Base.subclasses.select do |k|
52
+ (options[:all] || k.group == group) && k.namespace =~ search
53
+ end
54
+
55
+ display_klasses(klasses)
56
+ end
57
+
58
+
59
+ private
60
+ def initialize_commands
61
+ unless @_commands_initialized
62
+ Dir[File.join(File.dirname(__FILE__), "application", "*.rb")].each do |command|
63
+ require(command)
64
+ end
65
+ @_commands_initialized = true
66
+ end
67
+ end
68
+
69
+ # Display information about the given klasses.
70
+ #
71
+ def display_klasses(klasses=Thor::Base.subclasses)
72
+ klasses -= [Thor, SSP::App, Thor::Group]
73
+
74
+ raise Error, "No Thor tasks available" if klasses.empty?
75
+
76
+ # Remove subclasses
77
+ klasses.dup.each do |klass|
78
+ klasses -= Thor::Util.thor_classes_in(klass)
79
+ end
80
+
81
+ list = Hash.new { |h,k| h[k] = [] }
82
+ groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
83
+
84
+ # Get classes which inherit from Thor
85
+ (klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
86
+
87
+ # Get classes which inherit from Thor::Base
88
+ groups.map! { |k| k.printable_tasks(false).first }
89
+ list["root"] = groups
90
+
91
+ # Order namespaces with default coming first
92
+ list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
93
+ list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
94
+ end
95
+
96
+ def display_tasks(namespace, list) #:nodoc:
97
+ list.sort!{ |a,b| a[0] <=> b[0] }
98
+
99
+ say shell.set_color(namespace, :blue, true)
100
+ say "-" * namespace.size
101
+
102
+ print_table(list, :truncate => true)
103
+ say
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,67 @@
1
+ require 'json'
2
+ require 'pathname'
3
+
4
+ class SSP::App::Bags < Thor
5
+ namespace :bags
6
+
7
+ class_option :knife_config, :aliases => "-c",
8
+ :desc => "Location of the knife configuration file",
9
+ :default => File.join(ENV['HOME'], '.chef', 'knife.rb')
10
+
11
+ class_option :path, :aliases => "-p",
12
+ :desc => "Root path for the data bag files",
13
+ :default => "./databags"
14
+
15
+
16
+ desc "download", "Downloads data bags from the server"
17
+ def download
18
+ bags = JSON.parse(knife_cmd("list"))
19
+ bags.each do |bag|
20
+ say "Downloading #{shell.set_color(bag, :bold)}: "
21
+ bag_path = data_bag_root.join(bag)
22
+ bag_path.mkpath
23
+ items = JSON.parse(knife_cmd("show #{bag}"))
24
+ items.each_with_index do |item, i|
25
+ say "#{", " unless i == 0}#{item}", nil, false
26
+ knife_cmd "show #{bag} #{item} > #{bag_path.join("#{item}.json")}"
27
+ end
28
+ puts
29
+ end
30
+ end
31
+
32
+ desc "upload [BAG [ITEM]]", "Uploads data bags to the server"
33
+ def upload(selected_bag = nil, selected_item = nil)
34
+ bags = if selected_bag
35
+ [data_bag_root.join(selected_bag)]
36
+ else
37
+ Pathname.glob(data_bag_root.join("*")).select {|p| p.directory?}
38
+ end
39
+ bags.each do |bag_path|
40
+ bag = bag_path.basename
41
+ say "Uploading #{shell.set_color(bag, :bold)}: "
42
+ knife_cmd "create #{bag} > /dev/null"
43
+ current_items = JSON.parse(knife_cmd("show #{bag}"))
44
+ items = if selected_bag and selected_item
45
+ [data_bag_root.join(selected_bag, "#{selected_item}.json")]
46
+ else
47
+ Pathname.glob(bag_path.join("*.json"))
48
+ end
49
+ items.each_with_index do |item, i|
50
+ key = item.basename.to_s.split(".").first
51
+ say "#{", " unless i == 0}#{key}", nil, false
52
+ command = current_items.include?(key) ? "edit" : "create"
53
+ knife_cmd "#{command} -e 'cat #{item} >' #{bag} #{key}"
54
+ end
55
+ puts
56
+ end
57
+ end
58
+
59
+ private
60
+ def knife_cmd(cmd)
61
+ %x{knife data bag -c #{options[:knife_config]} #{cmd}}
62
+ end
63
+
64
+ def data_bag_root
65
+ Pathname.new(options[:path])
66
+ end
67
+ end
@@ -0,0 +1,128 @@
1
+ require 'fog'
2
+ require 'chef/config'
3
+ require 'chef/knife/ssh'
4
+ require 'ssp/config'
5
+
6
+ class SSP::App::Node < Thor
7
+ namespace :node
8
+
9
+ class_option :api_key,
10
+ :desc => "Rackspace API key",
11
+ :default => SSP::Config[:rackspace][:api_key]
12
+
13
+ class_option :api_username,
14
+ :desc => "Rackspace API username",
15
+ :default => SSP::Config[:rackspace][:username]
16
+
17
+ class_option :chef_config, :aliases => "-c",
18
+ :desc => "Location of the chef configuration file",
19
+ :default => File.join(ENV['HOME'], '.chef', 'knife.rb')
20
+
21
+
22
+ desc "create FQDN [ROLES...]", "Creates a new chef client on Rackspace"
23
+ method_option :flavor, :aliases => "-f", :type => :numeric,
24
+ :desc => "Sets the flavor of the server (1: 256MB, 2: 512MB, 3: 1024MB, ...)",
25
+ :default => 2
26
+ method_option :image, :aliases => "-i", :type => :numeric,
27
+ :desc => "Sets the image to be used (49: Ubuntu 10.04, 4151016: chef-node)",
28
+ :default => 4151016
29
+ def create(fqdn, *roles)
30
+ server = connection.servers.new
31
+
32
+ # Convert roles array to proper run list
33
+ run_list = roles.map { |r| r =~ /recipe\[(.*)\]/ ? $1 : "role[#{r}]" }
34
+
35
+ # Script for creating an /etc/hosts file that will result in correct
36
+ # fqdn and will point to the temporary chef server if we're testing
37
+ setup_node_script = <<-EOH
38
+ #!/bin/bash
39
+
40
+ IP=$(ifconfig eth0 | sed -ne 's/.*inet addr:\\([0-9\\.]*\\).*/\\1/p')
41
+
42
+ echo "127.0.0.1 localhost localhost.localdomain" > /etc/hosts
43
+ echo "$IP #{fqdn} #{fqdn.split(".").first}" >> /etc/hosts
44
+ EOH
45
+ if chef_config[:chef_server_url] == "https://chef-server.rackspace"
46
+ chef_server_ip = %x{dscl . read Hosts/chef-server.rackspace 2>/dev/null}[/[\d\.]+/]
47
+ setup_node_script << %{echo "#{chef_server_ip} chef.secretsaucepartners.com" >> /etc/hosts\n}
48
+ chef_config[:chef_server_url] = "https://chef.secretsaucepartners.com"
49
+ end
50
+ setup_node_script << "exit 0\n"
51
+
52
+ # Always include the chef-client role in the run list
53
+ unless run_list.include? "role[chef-client]"
54
+ run_list.unshift "role[chef-client]"
55
+ end
56
+
57
+ server.flavor_id = options[:flavor]
58
+ server.image_id = options[:image]
59
+ server.name = fqdn
60
+ server.personality = [
61
+ {
62
+ 'path' => '/etc/setup-node',
63
+ 'contents' => setup_node_script
64
+ },
65
+ {
66
+ 'path' => "/etc/chef/validation.pem",
67
+ 'contents' => IO.read(chef_config[:validation_key])
68
+ },
69
+ {
70
+ 'path' => "/etc/chef/client.rb",
71
+ 'contents' => <<-EOH
72
+ log_level :info
73
+ log_location STDOUT
74
+ chef_server_url "#{chef_config[:chef_server_url]}"
75
+ validation_client_name "#{chef_config[:validation_client_name]}"
76
+ EOH
77
+ },
78
+ {
79
+ 'path' => "/etc/chef/first-boot.json",
80
+ 'contents' => { "run_list" => run_list }.to_json
81
+ },
82
+ ]
83
+
84
+ server.save
85
+
86
+ say_status "Name: ", server.name, :cyan
87
+ say_status "Flavor: ", server.flavor_id, :cyan
88
+ say_status "Image: ", server.image_id, :cyan
89
+ say_status "Public IP: ", server.addresses["public"], :cyan
90
+ say_status "Private IP: ", server.addresses["private"], :cyan
91
+ say_status "Password: ", server.password, :cyan
92
+
93
+ say "\nRequesting server", :magenta
94
+ saved_password = server.password
95
+
96
+ # wait for it to be ready to do stuff
97
+ server.wait_for { print "."; ready? }
98
+
99
+ say "\nServer ready, waiting 15 seconds to bootstrap."
100
+ sleep 15
101
+
102
+ say "\nBootstrapping #{shell.set_color(server.name, :bold)}..."
103
+
104
+ ssh = Chef::Knife::Ssh.new
105
+ ssh.name_args = [ server.addresses["public"][0], "/bin/bash /etc/setup-node && /usr/local/bin/chef-client -j /etc/chef/first-boot.json" ]
106
+ ssh.config[:ssh_user] = "root"
107
+ ssh.config[:manual] = true
108
+ ssh.config[:password] = saved_password
109
+ ssh.password = saved_password
110
+ ssh.run
111
+ end
112
+
113
+ protected
114
+ def connection
115
+ @connection ||= Fog::Rackspace::Servers.new(
116
+ :rackspace_api_key => options[:api_key],
117
+ :rackspace_username => options[:api_username]
118
+ )
119
+ end
120
+
121
+ def chef_config
122
+ unless defined?(@_chef_config_loaded)
123
+ Chef::Config.from_file(options[:chef_config])
124
+ @_chef_config_loaded = true
125
+ end
126
+ Chef::Config
127
+ end
128
+ end
@@ -0,0 +1,107 @@
1
+ require 'yaml'
2
+ require 'open-uri'
3
+
4
+ class SSP::App::Pair < Thor
5
+ namespace :pair
6
+
7
+ default_task :set_author
8
+ desc "set_author [PAIR]", "Sets the commit author for the repository to a pair or just yourself"
9
+ method_option :devsfile,
10
+ :desc => "Location of the SSP developers file",
11
+ :default => File.join(ENV['HOME'], '.sspdevs')
12
+ method_option :pair_email,
13
+ :desc => "Base email to use for constructing pair emails",
14
+ :default => "dev@secretsaucepartners.com"
15
+ def set_author(pair=nil)
16
+ chek_dot_git!
17
+
18
+ me = %x{git config --global user.name}.chomp
19
+ my_email = sspdevs[me]
20
+ devs = sspdevs.map {|name,_| name}.sort
21
+ devs.delete(me)
22
+
23
+ if pair.nil?
24
+ say "Pairing?", :bold
25
+ devs.each_with_index do |name, index|
26
+ say_status "#{index + 1}:", name, :cyan
27
+ end
28
+ print "Enter the index of your pair or leave blank if not pairing: "
29
+
30
+ while (choice = File.new("/dev/tty").readline.chomp) !~ /\A\d*\Z/
31
+ say "Bad input `#{choice}'"
32
+ end
33
+ elsif pair =~ /^(|x|me|not)$/i
34
+ choice = ""
35
+ else
36
+ pair = pair.downcase
37
+ pair = devs.detect do |dev|
38
+ initial = dev.split(" ").map {|x| x[0..0]}.join.downcase
39
+ initial == pair or dev.downcase.split(" ").include?(pair)
40
+ end
41
+ raise Thor::Error, "No such pair" unless pair
42
+ choice = devs.index(pair) + 1
43
+ end
44
+
45
+ commit_name, commit_email = if choice == ''
46
+ [me, my_email]
47
+ else
48
+ pair = devs[choice.to_i - 1]
49
+ ["#{me} & #{pair}", options[:pair_email].sub('@', initials(me, pair)+'@')]
50
+ end
51
+
52
+ say "Setting #{shell.set_color("commit author", :bold)} to #{shell.set_color(commit_name, :green)}"
53
+ %x{git config user.name '#{commit_name}'}
54
+ say "Setting #{shell.set_color("commit email", :bold)} to #{shell.set_color(commit_email, :green)}"
55
+ %x{git config user.email '#{commit_email}'}
56
+ end
57
+
58
+ desc "status", "Displays current commit author and email and asks for confirmation (used by the install hook)"
59
+ def status
60
+ chek_dot_git!
61
+
62
+ commit_name = %x{git config user.name}.chomp
63
+ commit_email = %x{git config user.email}.chomp
64
+ say "Current #{shell.set_color("commit author", :bold)} is #{shell.set_color(commit_name, :yellow, true)}"
65
+ say "Current #{shell.set_color("commit email", :bold)} is #{shell.set_color(commit_email, :yellow, true)}"
66
+
67
+ say "Is this ok? [Y/n] "
68
+ unless File.new("/dev/tty").readline.chomp =~ /^(y(es)?)?$/i
69
+ exit 1
70
+ end
71
+ end
72
+
73
+ desc "install_hook", "Installs a git pre-commit hook which checks commit author and email"
74
+ def install_hook
75
+ chek_dot_git!
76
+
77
+ hook_file = ".git/hooks/pre-commit"
78
+ raise Thor::Error, "a pre-commit hook file already exists" if File.exists?(hook_file)
79
+
80
+ File.open(hook_file, "w") { |f| f.write "#!/bin/sh\nssp pair:status\n" }
81
+ File.chmod(0755, hook_file)
82
+
83
+ say "pre-commit hook is installed in #{hook_file}"
84
+ end
85
+
86
+ private
87
+ def chek_dot_git!
88
+ unless File.exists?(".git")
89
+ raise Thor::Error, "This doesn't look like a git repository."
90
+ end
91
+ end
92
+
93
+ def sspdevs
94
+ @sspdevs ||= begin
95
+ unless File.exists?(options[:devsfile])
96
+ File.open(options[:devsfile], "w") do |f|
97
+ f.write(open("https://gist.github.com/bba2eeabf72da8d61254.txt").read)
98
+ end
99
+ end
100
+ YAML.load_file(options[:devsfile])
101
+ end
102
+ end
103
+
104
+ def initials(*names)
105
+ "+" + names.map {|n| n.split(" ").map {|x| x[0..0]}.join}.join("+").downcase
106
+ end
107
+ end
@@ -0,0 +1,7 @@
1
+ require 'yaml'
2
+
3
+ module SSP
4
+ CONFIG_FILE = File.expand_path("~/.ssp")
5
+ Config = Hash.new({})
6
+ Config.merge!(YAML.load_file(CONFIG_FILE)) if File.exists?(CONFIG_FILE)
7
+ end
@@ -0,0 +1,3 @@
1
+ module SSP
2
+ VERSION = "0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssp
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - !binary |
13
+ TMOhc3psw7MgQsOhY3Np
14
+
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-06-30 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: thor
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 17
31
+ segments:
32
+ - 0
33
+ - 13
34
+ version: "0.13"
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: chef
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 25
46
+ segments:
47
+ - 0
48
+ - 9
49
+ version: "0.9"
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: " The SSP gem provides various command line tools that\n Secret Sauce Partners, Inc. uses internally.\n"
53
+ email: dev@secretsaucepartners.com
54
+ executables:
55
+ - ssp
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - README.md
62
+ - LICENSE
63
+ - bin/ssp
64
+ - lib/ssp/app.rb
65
+ - lib/ssp/application/bags.rb
66
+ - lib/ssp/application/node.rb
67
+ - lib/ssp/application/pair.rb
68
+ - lib/ssp/config.rb
69
+ - lib/ssp/version.rb
70
+ has_rdoc: true
71
+ homepage: http://github.com/sspinc/ssp
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options: []
76
+
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.7
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Various command line tools for Secret Sauce Partners, Inc.
104
+ test_files: []
105
+