sambot 0.1.69 → 0.1.83
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 +4 -4
- data/lib/sambot/cli.rb +16 -18
- data/lib/sambot/commands/cookbook.rb +56 -38
- data/lib/sambot/commands/packer.rb +21 -0
- data/lib/sambot/commands/session.rb +15 -5
- data/lib/sambot/commands/workstation.rb +8 -10
- data/lib/sambot/domain/bastion_host.rb +59 -0
- data/lib/sambot/domain/chef/kitchen.rb +39 -0
- data/lib/sambot/domain/{cookbooks → chef}/metadata.rb +6 -5
- data/lib/sambot/domain/common/{application_exception.rb → application_error.rb} +1 -1
- data/lib/sambot/domain/common/config.rb +13 -6
- data/lib/sambot/domain/common/file_checker.rb +3 -2
- data/lib/sambot/domain/common/runtime.rb +5 -5
- data/lib/sambot/domain/common/template_provider.rb +1 -1
- data/lib/sambot/domain/cookbook.rb +103 -0
- data/lib/sambot/domain/dns.rb +24 -0
- data/lib/sambot/domain/packer.rb +26 -0
- data/lib/sambot/domain/session.rb +25 -0
- data/lib/sambot/domain/{workstations/ssh_config_file.rb → ssh/config_file.rb} +7 -7
- data/lib/sambot/domain/{workstations/ssh_config_section.rb → ssh/config_section.rb} +2 -2
- data/lib/sambot/domain/{workstations/ssh_parser.rb → ssh/parser.rb} +8 -7
- data/lib/sambot/domain/ui.rb +19 -0
- data/lib/sambot/domain/vault.rb +32 -0
- data/lib/sambot/domain/workstation.rb +25 -0
- data/lib/sambot/templates/{.kitchen.gcp.windows.yml → .kitchen.gcp.yml.erb} +33 -5
- data/lib/sambot/templates/.kitchen.rackspace.yml.erb +49 -0
- data/lib/sambot/templates/{.kitchen.centos.yml → .kitchen.yml.erb} +6 -1
- data/lib/sambot/templates/metadata.rb.erb +9 -2
- data/lib/sambot/templates/packer.linux.json +22 -0
- data/lib/sambot/templates/packer.windows.json.erb +18 -0
- data/lib/sambot/templates/teamcity.sh.erb +7 -7
- data/lib/sambot/version.rb +1 -1
- data/sambot.gemspec +7 -1
- metadata +120 -36
- data/lib/sambot/commands/secret.rb +0 -32
- data/lib/sambot/commands/teamcity.rb +0 -15
- data/lib/sambot/domain/common/ui.rb +0 -21
- data/lib/sambot/domain/cookbooks/assistant_chef.rb +0 -103
- data/lib/sambot/domain/cookbooks/kitchen.rb +0 -30
- data/lib/sambot/domain/secrets/vault.rb +0 -28
- data/lib/sambot/domain/workstations/env.rb +0 -0
- data/lib/sambot/domain/workstations/hosts.rb +0 -0
- data/lib/sambot/domain/workstations/install.sh +0 -1
- data/lib/sambot/templates/.kitchen.gcp.centos.yml +0 -39
- data/lib/sambot/templates/.kitchen.rackspace.centos.yml +0 -27
- data/lib/sambot/templates/.kitchen.rackspace.windows.yml +0 -34
- data/lib/sambot/templates/.kitchen.windows.yml +0 -16
@@ -5,17 +5,17 @@ module Sambot
|
|
5
5
|
module Common
|
6
6
|
module Runtime
|
7
7
|
|
8
|
-
def is_obsolete
|
8
|
+
def self.is_obsolete
|
9
9
|
latest_version = Gems.new.versions('sambot')[0]["number"]
|
10
10
|
Gem::Version.new(Sambot::VERSION) < Gem::Version.new(latest_version)
|
11
11
|
end
|
12
12
|
|
13
|
-
def ensure_latest
|
13
|
+
def self.ensure_latest
|
14
14
|
latest_version = Gems.new.versions('sambot')[0]["number"]
|
15
|
-
debug("Current Sambot version is #{Sambot::VERSION}.")
|
16
|
-
debug("Latest Sambot version is #{latest_version}.")
|
15
|
+
UI.debug("Current Sambot version is #{Sambot::VERSION}.")
|
16
|
+
UI.debug("Latest Sambot version is #{latest_version}.")
|
17
17
|
if is_obsolete
|
18
|
-
|
18
|
+
UI.info('A newer version of the sambot gem exists. Please update the gem before continuing.', :red)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'git'
|
3
|
+
|
4
|
+
module Sambot
|
5
|
+
module Domain
|
6
|
+
class Cookbook
|
7
|
+
|
8
|
+
def self.build(essential_files, generated_files)
|
9
|
+
config = Common::Config.new.read
|
10
|
+
validate_cookbook_structure(config['platforms'], essential_files, generated_files)
|
11
|
+
setup_test_kitchen(config)
|
12
|
+
build_metadata(config)
|
13
|
+
copy_git_hooks()
|
14
|
+
UI.info('The cookbook has been successfully built.')
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.clean(generated_files)
|
18
|
+
UI.info('Removing all generated files from this cookbook.')
|
19
|
+
delete_file('metadata.rb')
|
20
|
+
delete_file('winrm_config')
|
21
|
+
delete_file('packer.json')
|
22
|
+
generated_files.each { |file| delete_file(file) }
|
23
|
+
Dir.glob('\.kitchen*\.yml').each do |file|
|
24
|
+
delete_file(file)
|
25
|
+
end
|
26
|
+
UI.info('The cookbook has been successfully cleaned.')
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.generate(name, platforms, type, description, essential_files, generated_files)
|
30
|
+
Git.init(name)
|
31
|
+
Dir.chdir(name) do
|
32
|
+
FileUtils.mkdir('test')
|
33
|
+
FileUtils.mkdir('spec')
|
34
|
+
FileUtils.mkdir('recipes')
|
35
|
+
FileUtils.touch('README.md')
|
36
|
+
write_config(name, description, platforms, type)
|
37
|
+
build(essential_files, generated_files)
|
38
|
+
end
|
39
|
+
UI.info('The cookbook has been successfully generated.')
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.write_config(name, description, platforms, type)
|
45
|
+
contents = {
|
46
|
+
'name' => name,
|
47
|
+
'version' => '0.0.1',
|
48
|
+
'platforms' => platforms,
|
49
|
+
'suites' => [{
|
50
|
+
'name' => 'default',
|
51
|
+
'run_list' => [
|
52
|
+
"recipe[#{name}]"
|
53
|
+
],
|
54
|
+
'verifier' => {
|
55
|
+
'inspec_tests' => ['./test']
|
56
|
+
}
|
57
|
+
}],
|
58
|
+
'description' => description,
|
59
|
+
}.to_yaml
|
60
|
+
File.write('.config.yml', contents)
|
61
|
+
Domain::UI.debug("./.config.yml has been added to the cookbook.")
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.copy_git_hooks
|
65
|
+
working_path = '.git/hooks/pre-push'
|
66
|
+
template_path = Common::TemplateProvider.get_path('pre-push')
|
67
|
+
File.delete(working_path) if File.exist?(working_path)
|
68
|
+
FileUtils.cp(template_path, working_path)
|
69
|
+
UI.debug("The pre-push Git hook has been added to the cookbook.")
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.delete_file(filename)
|
73
|
+
filename = filename.gsub(/\.erb/, '') if filename.end_with?(".erb")
|
74
|
+
return unless File.exist?(filename)
|
75
|
+
File.delete(filename)
|
76
|
+
UI.debug("./#{filename} has been removed.")
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.validate_cookbook_structure(platform, essential_files, generated_files)
|
80
|
+
essential_files.each { |path| Common::FileChecker.new.verify(path) }
|
81
|
+
if platform.include?('windows')
|
82
|
+
Common::FileChecker.new.update(['winrm_config'])
|
83
|
+
end
|
84
|
+
Common::FileChecker.new.update(generated_files)
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.setup_test_kitchen(config)
|
88
|
+
files = Chef::Kitchen.generate_yml(config['name'], config['platforms'], config['suites'])
|
89
|
+
files.each do |filename, contents|
|
90
|
+
File.write(filename.to_s, contents)
|
91
|
+
UI.debug("#{filename.to_s} has been added to the cookbook.")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.build_metadata(config)
|
96
|
+
result = Chef::Metadata.generate(config['name'], config['platforms'], config['version'], config['description'], config['dependencies'], config['gems'])
|
97
|
+
File.write('metadata.rb', result)
|
98
|
+
UI.debug("A new metadata.rb file has been generated.")
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'hosts'
|
2
|
+
|
3
|
+
module Sambot
|
4
|
+
module Domain
|
5
|
+
|
6
|
+
HOST_ENTRIES = {
|
7
|
+
'teamcity.brighter.io' => '127.0.0.1',
|
8
|
+
'chef.brighter.io' => '127.0.0.1',
|
9
|
+
'monitoring.brighter.io' => '127.0.0.1',
|
10
|
+
'jenkins.brighter.io' => '127.0.0.1',
|
11
|
+
'splunk.brighter.io' => '127.0.0.1',
|
12
|
+
'rundeck.brighter.io' => '127.0.0.1',
|
13
|
+
'grafana.brighter.io' => '127.0.0.1',
|
14
|
+
'prometheus.brighter.io' => '127.0.0.1'
|
15
|
+
}
|
16
|
+
|
17
|
+
class DNS
|
18
|
+
|
19
|
+
def self.update_hosts()
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Sambot
|
4
|
+
module Domain
|
5
|
+
class Packer
|
6
|
+
|
7
|
+
def self.prepare(config_file = nil)
|
8
|
+
config = Common::Config.new.read(config_file)
|
9
|
+
cookbook_name = config['name']
|
10
|
+
target_platform = ENV['TARGET_IMAGE_PLATFORM']
|
11
|
+
unless target_platform == 'windows' || target_platform == 'linux'
|
12
|
+
raise ApplicationError.new("The target image platform must be either 'windows' or 'linux'.")
|
13
|
+
end
|
14
|
+
generate_config(cookbook_name, target_platform)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.generate_config(cookbook_name, target_platform)
|
18
|
+
filename = TemplateProvider.get_path("packer.#{target_platform}.json")
|
19
|
+
File.read(filename).gsub(/@@cookbook_name@@/, cookbook_name)
|
20
|
+
File.write("packer.json", contents)
|
21
|
+
UI.debug("The configuration file for Packer has been added.")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sambot
|
2
|
+
module Domain
|
3
|
+
class Session
|
4
|
+
|
5
|
+
def self.start(username, password)
|
6
|
+
UI.debug("Opening a tunnel to the Rackspace DEV/QE environment...")
|
7
|
+
bastion = BastionHost.new.connect(username, password)
|
8
|
+
bastion.forwards.each do |forward|
|
9
|
+
UI.debug(" -- #{forward} will be forwarded to #{forward}")
|
10
|
+
end
|
11
|
+
UI.debug("Authenticating with Hashicorp Vault in Rackspace DEV/QE...")
|
12
|
+
token = Vault.authenticate(username, password)
|
13
|
+
UI.debug("Saving your Vault authentication token to ~/.vault-token...")
|
14
|
+
Vault.save_token(token)
|
15
|
+
UI.info("Your session has now started. Run `sambot session stop` to close it.")
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.stop
|
19
|
+
BastionHost.disconnect
|
20
|
+
UI.info("Your session has been closed.")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Sambot
|
2
2
|
module Domain
|
3
|
-
module
|
4
|
-
class
|
3
|
+
module Ssh
|
4
|
+
class ConfigFile
|
5
5
|
|
6
|
-
def initialize
|
6
|
+
def initialize(config_file = nil)
|
7
7
|
@make_backups = true
|
8
8
|
@header_lines = []
|
9
9
|
@sections = []
|
10
10
|
@sections_by_name = {}
|
11
|
-
read_config
|
11
|
+
read_config(config_file)
|
12
12
|
end
|
13
13
|
|
14
14
|
def sections
|
@@ -20,15 +20,15 @@ module Sambot
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def add_section(name)
|
23
|
-
section =
|
23
|
+
section = ConfigSection.new(name)
|
24
24
|
@sections << section
|
25
25
|
@sections_by_name[section.name] = section
|
26
26
|
section
|
27
27
|
end
|
28
28
|
|
29
|
-
def read_config
|
29
|
+
def read_config(config_file = nil)
|
30
30
|
current_section = nil
|
31
|
-
IO.readlines(File.expand_path("~/.ssh/config")).each_with_index do |line, i|
|
31
|
+
IO.readlines(config_file || File.expand_path("~/.ssh/config")).each_with_index do |line, i|
|
32
32
|
line.rstrip!
|
33
33
|
if line =~ /\bHost\s+(.+)/
|
34
34
|
current_section = add_section($1)
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
1
|
+
require_relative 'config_file'
|
2
|
+
require_relative 'config_section'
|
3
3
|
|
4
4
|
module Sambot
|
5
5
|
module Domain
|
6
|
-
module
|
7
|
-
class
|
6
|
+
module Ssh
|
7
|
+
class Parser
|
8
8
|
|
9
9
|
ENTRIES = {
|
10
10
|
'bastion': {
|
@@ -23,8 +23,8 @@ module Sambot
|
|
23
23
|
}
|
24
24
|
}
|
25
25
|
|
26
|
-
def update(username)
|
27
|
-
config =
|
26
|
+
def update(username, config_file = nil)
|
27
|
+
config = ConfigFile.new(config_file)
|
28
28
|
sections = config.sections_by_name
|
29
29
|
ENTRIES.each_pair do |key, val|
|
30
30
|
config.rm(key.to_s)
|
@@ -34,12 +34,13 @@ module Sambot
|
|
34
34
|
config.add_alias(key.to_s, new_alias.to_s)
|
35
35
|
end
|
36
36
|
elsif x == :username
|
37
|
-
config.set(key.to_s, '
|
37
|
+
config.set(key.to_s, 'User', username)
|
38
38
|
else
|
39
39
|
config.set(key.to_s, x.to_s, y)
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
|
+
config
|
43
44
|
end
|
44
45
|
|
45
46
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Sambot
|
2
|
+
module Domain
|
3
|
+
module UI
|
4
|
+
|
5
|
+
def self.debug(msg)
|
6
|
+
Thor.new.say("debug: #{msg}", :yellow)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.info(msg)
|
10
|
+
Thor.new.say(" info: #{msg}", :green)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.error(msg)
|
14
|
+
Thor.new.say("error: #{msg}", :red)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'vault'
|
2
|
+
|
3
|
+
module Sambot
|
4
|
+
module Domain
|
5
|
+
class Vault
|
6
|
+
|
7
|
+
ENVIRONMENT_VARIABLES = {
|
8
|
+
VAULT_ADDR: 'https://localhost:8200',
|
9
|
+
VAULT_SKIP_VERIFY: true
|
10
|
+
}
|
11
|
+
|
12
|
+
ENV_ROOT = '/etc/profile.d/'
|
13
|
+
|
14
|
+
def self.authenticate(username, password)
|
15
|
+
secret = ::Vault.auth.ldap(username, password)
|
16
|
+
secret.auth.client_token
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.setup_environment(root = ENV_ROOT)
|
20
|
+
ENVIRONMENT_VARIABLES.each_pair do |key, value|
|
21
|
+
path = File.join(root, "#{key}.sh")
|
22
|
+
File.write(path, "export #{key}=#{value}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.save_token(token)
|
27
|
+
File.write("~/.vault-token", token)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Sambot
|
2
|
+
module Domain
|
3
|
+
class Workstation
|
4
|
+
|
5
|
+
def self.configure(username)
|
6
|
+
UI.debug("Updating your SSH configuration.")
|
7
|
+
update_ssh_configuration(username)
|
8
|
+
UI.debug("Updating your environment variables.")
|
9
|
+
update_environment_variables()
|
10
|
+
UI.debug("Updating your hosts file.")
|
11
|
+
DNS.update_hosts()
|
12
|
+
UI.info("Your workstation is now ready for use. Please close this shell and open up a new one to start making use of your new environment.")
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.update_ssh_configuration(username)
|
16
|
+
config = Domain::Ssh::Parser.new.update('DEV\\' + username)
|
17
|
+
config.save
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.update_environment_variables
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -3,11 +3,43 @@ provisioner:
|
|
3
3
|
name: chef_zero
|
4
4
|
log_level: <%= ENV['TEST_KITCHEN_LOG_LEVEL'] || 'info' %>
|
5
5
|
deprecations_as_errors: true
|
6
|
+
require_chef_omnibus: 12
|
6
7
|
cookbooks_path:
|
7
8
|
- .
|
8
9
|
|
9
10
|
platforms:
|
11
|
+
<% if @platforms.include?('centos') %>
|
12
|
+
- name: centos
|
13
|
+
transport:
|
14
|
+
username: chefuser
|
15
|
+
ssh_key:
|
16
|
+
- <%= ENV['GCP_SSH_KEY'] || "" %>
|
17
|
+
driver:
|
18
|
+
name: sfmc_google
|
19
|
+
region: <%= ENV['GCP_REGION'] %>
|
20
|
+
project: <%= ENV['GCP_PROJECT'] %>
|
21
|
+
image_project: <%= ENV['GCP_CENTOS_IMAGE_PROJECT'] %>
|
22
|
+
image_family: <%= ENV['GCP_CENTOS_IMAGE_FAMILY'] %>
|
23
|
+
network: <%= ENV['GCP_NETWORK'] %>
|
24
|
+
subnet: <%= ENV['GCP_SUBNETWORK'] %>
|
25
|
+
use_private_ip: false
|
26
|
+
preemptible: true
|
27
|
+
service_account_name: <%= ENV['GCP_SERVICE_ACCOUNT_NAME'] %>
|
28
|
+
service_account_scopes:
|
29
|
+
- userinfo-email
|
30
|
+
- logging-write
|
31
|
+
- monitoring-write
|
32
|
+
tags:
|
33
|
+
- "test-kitchen"
|
34
|
+
- "consul-agent"
|
35
|
+
- "vault-client"
|
36
|
+
<% end %>
|
37
|
+
<% if @platforms.include?('windows') %>
|
10
38
|
- name: windows
|
39
|
+
transport:
|
40
|
+
name: winrm
|
41
|
+
username: chefuser
|
42
|
+
elevated: true
|
11
43
|
driver:
|
12
44
|
name: sfmc_google
|
13
45
|
region: <%= ENV['GCP_REGION'] %>
|
@@ -32,11 +64,7 @@ platforms:
|
|
32
64
|
- "test-kitchen"
|
33
65
|
- "consul-agent"
|
34
66
|
- "vault-client"
|
35
|
-
|
36
|
-
transport:
|
37
|
-
name: winrm
|
38
|
-
elevated: true
|
39
|
-
username: chefuser
|
67
|
+
<% end %>
|
40
68
|
|
41
69
|
verifier:
|
42
70
|
name: inspec
|