ops_manager_cli 0.3.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,68 @@
1
+ class OpsManager
2
+ module Api
3
+ class Pivnet < OpsManager::Api::Base
4
+
5
+ def initialize
6
+ get_authentication
7
+ end
8
+
9
+ def download_stemcell(stemcell_version, stemcell_path, filename_regex)
10
+ puts "====> Downloading stemcell #{ stemcell_path }...".green
11
+
12
+ release_id = get_release_for(stemcell_version).fetch('id')
13
+ product_file = get_product_file_for(release_id, filename_regex)
14
+ product_file_id = product_file.fetch('id')
15
+
16
+ accept_release_eula(release_id)
17
+ download_product(release_id, product_file_id, stemcell_path)
18
+ end
19
+
20
+ def get_authentication
21
+ puts "====> Authentication to Pivnet".green
22
+ opts = { headers: { 'Authorization' => "Token #{pivnet_token}" } }
23
+ res = get("/api/v2/authentication", opts)
24
+ raise OpsManager::PivnetAuthenticationError.new(res.body) unless res.code == '200'
25
+ res
26
+ end
27
+
28
+ private
29
+
30
+ def accept_release_eula(release_id)
31
+ puts "====> Accepting stemcell eula ...".green
32
+ opts = { headers: { 'Authorization' => "Token #{pivnet_token}" } }
33
+ post("/api/v2/products/stemcells/releases/#{release_id}/eula_acceptance", opts)
34
+ end
35
+
36
+ def download_product(release_id, product_file_id, stemcell_path)
37
+ opts = { write_to: stemcell_path, headers: { 'Authorization' => "Token #{pivnet_token}" } }
38
+ post("/api/v2/products/stemcells/releases/#{release_id}/product_files/#{product_file_id}/download", opts)
39
+ end
40
+
41
+ def get_stemcell_releases(opts = {})
42
+ get("/api/v2/products/stemcells/releases", opts)
43
+ end
44
+
45
+ def get_product_files(release_id, opts = {})
46
+ get("/api/v2/products/stemcells/releases/#{release_id}/product_files",opts)
47
+ end
48
+
49
+ def get_release_for(stemcell_version)
50
+ releases = JSON.parse(get_stemcell_releases.body).fetch('releases')
51
+ releases.select{ |r| r.fetch('version') == stemcell_version }.first
52
+ end
53
+
54
+ def get_product_file_for(release_id, filename_regex)
55
+ products = JSON.parse(get_product_files(release_id).body).fetch('product_files')
56
+ products.select{ |r| r.fetch('aws_object_key') =~ filename_regex }.first
57
+ end
58
+
59
+ def target
60
+ @target ||= "network.pivotal.io"
61
+ end
62
+
63
+ def pivnet_token
64
+ @pivnet_token ||= OpsManager.get_conf(:pivnet_token)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,123 @@
1
+ require "ops_manager/api/opsman"
2
+ require "ops_manager/api/pivnet"
3
+ require "ops_manager/installation_settings"
4
+ require 'ops_manager/configs/opsman_deployment'
5
+
6
+ class OpsManager::ApplianceDeployment
7
+ extend Forwardable
8
+ def_delegators :pivnet_api, :download_stemcell
9
+ def_delegators :opsman_api, :create_user, :trigger_installation, :get_installation_assets,
10
+ :get_installation_settings, :upload_installation_assets, :import_stemcell, :target,
11
+ :password, :username, :get_current_version ,:ops_manager_version=
12
+
13
+ attr_reader :config_file
14
+
15
+ def initialize(config_file)
16
+ @config_file = config_file
17
+ end
18
+
19
+ def run
20
+ OpsManager.set_conf(:target, config.ip)
21
+ OpsManager.set_conf(:username, config.username)
22
+ OpsManager.set_conf(:password, config.password)
23
+ OpsManager.set_conf(:pivnet_token, config.pivnet_token)
24
+
25
+ self.extend(OpsManager::Deployments::Vsphere)
26
+
27
+ case
28
+ when current_version.empty?
29
+ puts "No OpsManager deployed at #{target}. Deploying ...".green
30
+ deploy
31
+ create_first_user
32
+ when current_version < desired_version then
33
+ puts "OpsManager at #{target} version is #{current_version}. Upgrading to #{desired_version}.../".green
34
+ upgrade
35
+ when current_version == desired_version then
36
+ puts "OpsManager at #{target} version is already #{config.desired_version}. Skiping ...".green
37
+ end
38
+ end
39
+
40
+ def deploy
41
+ deploy_vm(desired_vm_name , config.ip)
42
+ end
43
+
44
+ %w{ stop_current_vm deploy_vm }.each do |m|
45
+ define_method(m) do
46
+ raise NotImplementedError
47
+ end
48
+ end
49
+
50
+ def create_first_user
51
+ puts '====> Creating initial user...'.green
52
+ until( create_user.code.to_i == 200) do
53
+ print '.'.green ; sleep 1
54
+ end
55
+ end
56
+
57
+ def upgrade
58
+ get_installation_assets
59
+ get_installation_settings(write_to: 'installation_settings.json')
60
+ stop_current_vm(current_vm_name)
61
+ deploy
62
+ upload_installation_assets
63
+ provision_missing_stemcells
64
+ OpsManager::InstallationRunner.trigger!.wait_for_result
65
+
66
+ puts "====> Finish!".green
67
+ end
68
+
69
+
70
+ def new_vm_name
71
+ @new_vm_name ||= "#{config.name}-#{config.desired_version}"
72
+ end
73
+
74
+ private
75
+ def current_version
76
+ @current_version ||= OpsManager::Semver.new(get_current_version)
77
+ end
78
+
79
+ def desired_version
80
+ @desired_version ||= OpsManager::Semver.new(config.desired_version)
81
+ end
82
+
83
+ def current_vm_name
84
+ @current_vm_name ||= "#{config.name}-#{current_version}"
85
+ end
86
+
87
+ def desired_vm_name
88
+ @desired_vm_name ||= "#{config.name}-#{config.desired_version}"
89
+ end
90
+
91
+ def provision_missing_stemcells
92
+ puts '====> Reprovisioning missing stemcells...'.green
93
+ installation_settings.stemcells.each do |s|
94
+ download_stemcell(s.fetch(:version), s.fetch(:file), /vsphere/)
95
+ import_stemcell(s.fetch(:file))
96
+ end
97
+ end
98
+
99
+ def pivnet_api
100
+ @pivnet_api ||= OpsManager::Api::Pivnet.new
101
+ end
102
+
103
+ def opsman_api
104
+ @opsman_api ||= OpsManager::Api::Opsman.new
105
+ end
106
+
107
+ def config
108
+ parsed_yml = ::YAML.load_file(@config_file)
109
+ @config ||= OpsManager::Configs::OpsmanDeployment.new(parsed_yml)
110
+ end
111
+
112
+ def installation_settings
113
+ @installation_settings ||= OpsManager::InstallationSettings.new(parsed_installation_settings)
114
+ end
115
+
116
+ def parsed_installation_settings
117
+ JSON.parse(File.read('installation_settings.json'))
118
+ end
119
+
120
+ def desired_version?(version)
121
+ !!(desired_version.to_s =~/#{version}/)
122
+ end
123
+ end
@@ -0,0 +1,159 @@
1
+ require "clamp"
2
+ require "ops_manager/product_deployment"
3
+ require "ops_manager/appliance_deployment"
4
+ require "ops_manager/product_template_generator"
5
+ require "ops_manager/director_template_generator"
6
+ require "ops_manager/installation"
7
+
8
+ class OpsManager
9
+ class Cli < Clamp::Command
10
+
11
+ class Target < Clamp::Command
12
+ parameter "OPS_MANAGER_IP", "OpsManager url", required: true
13
+
14
+ def execute
15
+ OpsManager.set_target(@ops_manager_ip)
16
+ end
17
+ end
18
+
19
+ class Status < Clamp::Command
20
+ def execute
21
+ puts OpsManager.show_status
22
+ end
23
+ end
24
+
25
+ class Login < Clamp::Command
26
+ parameter "USERNAME", "OpsManager username", required: true
27
+ parameter "PASSWORD", "OpsManager password", required: true
28
+
29
+ def execute
30
+ OpsManager.login(@username, @password)
31
+ end
32
+ end
33
+
34
+ class DeployAppliance < Clamp::Command
35
+ parameter "OPS_MANAGER_CONFIG", "OpsManager appliance config file", required: true
36
+
37
+ def execute
38
+ OpsManager::ApplianceDeployment.new(ops_manager_config).run
39
+ end
40
+ end
41
+
42
+ class DeployProduct < Clamp::Command
43
+ parameter "PRODUCT_CONFIG", "OpsManager product config file", required: true
44
+ option "--force", :flag, "force deployment"
45
+
46
+ def execute
47
+ OpsManager::ProductDeployment.new(product_config, force?).run
48
+ end
49
+ end
50
+
51
+ class GetInstallationSettings < Clamp::Command
52
+ def execute
53
+ puts parsed_response.to_yaml
54
+ end
55
+
56
+ private
57
+ def parsed_response
58
+ response = opsman.get_installation_settings
59
+ JSON.parse(response.body)
60
+ end
61
+
62
+ def opsman
63
+ OpsManager::Api::Opsman.new(silent: true)
64
+ end
65
+ end
66
+
67
+ class ImportStemcell < Clamp::Command
68
+ parameter "STEMCELL_FILEPATH", "Stemcell file path", required: true
69
+
70
+ def execute
71
+ OpsManager::Api::Opsman.new.import_stemcell(@stemcell_filepath)
72
+ end
73
+ end
74
+
75
+ class DeleteUnusedProducts < Clamp::Command
76
+
77
+ def execute
78
+ OpsManager.new.delete_products
79
+ end
80
+ end
81
+
82
+ class GetUaaToken < Clamp::Command
83
+ def execute
84
+ puts OpsManager::Api::Opsman.new.get_token.info.fetch('access_token')
85
+ end
86
+ end
87
+
88
+ class SSH < Clamp::Command
89
+ def execute
90
+ `ssh ubuntu@#{OpsManager.get_conf(:target)}`
91
+ end
92
+ end
93
+
94
+ class GetProductTemplate < Clamp::Command
95
+ parameter "PRODUCT_NAME", "Product tile name. Example: p-cf", required: true
96
+
97
+ def execute
98
+ puts OpsManager::ProductTemplateGenerator.new(@product_name).generate_yml
99
+ end
100
+ end
101
+
102
+ class GetInstallationLogs < Clamp::Command
103
+ parameter "INSTALLATION_ID", "Installation ID. Use 'last' to retrive the latest", required: true
104
+
105
+ def execute
106
+ if @installation_id == "last"
107
+ puts OpsManager::Installation.all.last.logs
108
+ else
109
+ puts OpsManager::Installation.new(@installation_id).logs
110
+ end
111
+ end
112
+ end
113
+
114
+ class GetDirectorTemplate < Clamp::Command
115
+ def execute
116
+ puts OpsManager::DirectorTemplateGenerator.new.generate_yml
117
+ end
118
+ end
119
+
120
+ class Curl < Clamp::Command
121
+ option ['-X', '--http-method'], "HTTP_METHOD", "HTTP Method (GET,POST)", default: 'GET'
122
+ parameter "ENDPOINT", "OpsManager api endpoint. eg: /v0/installation_settings", required: true
123
+
124
+ def execute
125
+ puts case http_method.strip
126
+ when 'GET'
127
+ opsman.authenticated_get(@endpoint).body
128
+ when 'POST'
129
+ opsman.authenticated_post(@endpoint).body
130
+ else
131
+ "Unsupported method: #{http_method.strip}"
132
+ end
133
+ end
134
+
135
+ private
136
+ def opsman
137
+ OpsManager::Api::Opsman.new(silent: true)
138
+ end
139
+ end
140
+
141
+ # Core commands
142
+ subcommand "status", "Test credentials and shows status", Status
143
+ subcommand "target", "Target an OpsManager appliance", Target
144
+ subcommand "login", "Login to OpsManager" , Login
145
+ subcommand "deploy-appliance", "Deploys/Upgrades OpsManager", DeployAppliance
146
+ subcommand "deploy-product", "Deploys/Upgrades product tiles", DeployProduct
147
+ subcommand "get-director-template", "Generates Director installation template", GetDirectorTemplate
148
+ subcommand "get-product-template", "Generates Product tile installation template", GetProductTemplate
149
+
150
+ # Other commands
151
+ subcommand "curl", "Authenticated curl requests(POST/GET)", Curl
152
+ subcommand "get-installation-settings", "Gets installation settings", GetInstallationSettings
153
+ subcommand "get-installation-logs", "Gets installation logs", GetInstallationLogs
154
+ subcommand "get-uaa-token", "Gets uaa token from OpsManager", GetUaaToken
155
+ subcommand "import-stemcell", "Uploads stemcell to OpsManager", ImportStemcell
156
+ subcommand "delete-unused-products", "Deletes unused product tiles", DeleteUnusedProducts
157
+
158
+ end
159
+ end
@@ -0,0 +1,27 @@
1
+ require 'ostruct'
2
+
3
+ class OpsManager
4
+ class Configs
5
+ class Base < OpenStruct
6
+ def initialize(config)
7
+ @config = config
8
+ super(config)
9
+ end
10
+
11
+ def validate_presence_of!(*present_attrs)
12
+ present_attrs.map!(&:to_s).each do |attr|
13
+ raise "missing #{attr} on config" unless @config.has_key?(attr)
14
+ end
15
+ end
16
+
17
+ def filepath
18
+ find_full_path(@config['filepath'])
19
+ end
20
+
21
+ def find_full_path(filepath)
22
+ return unless filepath
23
+ `find #{filepath}`.split("\n").first
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'ops_manager/configs/base'
2
+
3
+ class OpsManager
4
+ class Configs
5
+ class OpsmanDeployment < Base
6
+ def initialize(config)
7
+ super(config)
8
+ validate_presence_of!(:name, :desired_version, :provider, :username, :password, :pivnet_token, :ip, :opts)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require 'ops_manager/configs/base'
2
+
3
+ class OpsManager
4
+ class Configs
5
+ class ProductDeployment < Base
6
+ def initialize(config)
7
+ super(config)
8
+ validate_presence_of!(:name, :desired_version)
9
+ end
10
+
11
+ def stemcell
12
+ find_full_path(@config['stemcell'])
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ require 'rbvmomi'
2
+ require "uri"
3
+ require "ops_manager/logging"
4
+
5
+ class OpsManager
6
+ module Deployments
7
+ module Vsphere
8
+ include OpsManager::Logging
9
+ include OpsManager::Logging
10
+
11
+ def deploy_vm(name, ip)
12
+ puts '====> Starts ova deployment'.green
13
+ vcenter_target= "vi://#{vcenter_username}:#{vcenter_password}@#{config.opts['vcenter']['host']}/#{config.opts['vcenter']['datacenter']}/host/#{config.opts['vcenter']['cluster']}"
14
+ cmd = "echo yes | ovftool --acceptAllEulas --noSSLVerify --powerOn --X:waitForIp --net:\"Network 1=#{config.opts['portgroup']}\" --name=#{name} -ds=#{config.opts['datastore']} --prop:ip0=#{ip} --prop:netmask0=#{config.opts['netmask']} --prop:gateway=#{config.opts['gateway']} --prop:DNS=#{config.opts['dns']} --prop:ntp_servers=#{config.opts['ntp_servers'].join(',')} --prop:admin_password=#{config.password} #{config.opts['ova_path']} #{vcenter_target}"
15
+ logger.info cmd
16
+ puts `#{cmd}`
17
+ end
18
+
19
+ def stop_current_vm(name)
20
+ puts "====> Stopping vm #{name}...".green
21
+ dc = vim.serviceInstance.find_datacenter(config.opts['vcenter']['datacenter'])
22
+ logger.info "finding vm: #{name}"
23
+ vm = dc.find_vm(name) or fail "VM not found"
24
+ vm.PowerOffVM_Task.wait_for_completion
25
+ end
26
+
27
+ private
28
+ def vim
29
+ RbVmomi::VIM.connect host: config.opts['vcenter']['host'], user: URI.unescape(config.opts['vcenter']['username']), password: URI.unescape(config.opts['vcenter']['password']), insecure: true
30
+ end
31
+
32
+ def vcenter_username
33
+ URI.encode(config.opts['vcenter']['username'])
34
+ end
35
+
36
+ def vcenter_password
37
+ URI.encode(config.opts['vcenter']['password'])
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
@@ -0,0 +1,59 @@
1
+ class OpsManager
2
+ class DirectorTemplateGenerator
3
+ def generate
4
+ merge_director_template_products
5
+ delete_schema_version
6
+ delete_director_ssl
7
+ delete_uaa_ssl
8
+ delete_guid
9
+ delete_ip_assignments
10
+ installation_settings.to_h
11
+ end
12
+
13
+ def generate_yml
14
+ generate.to_yaml
15
+ .gsub('"(( merge on guid ))"', '(( merge on guid ))')
16
+ .gsub('"(( merge on identifier ))"', '(( merge on identifier ))')
17
+ end
18
+
19
+ private
20
+ def installation_settings
21
+ return @installation_settings if @installation_settings
22
+ parsed_installation_settings = JSON.parse(installation_settings_response.body)
23
+ @installation_settings = OpsManager::InstallationSettings.new(parsed_installation_settings)
24
+ end
25
+
26
+ def merge_director_template_products
27
+ installation_settings.merge!('products' => director_product_template.fetch('products'))
28
+ end
29
+
30
+ def delete_schema_version
31
+ @installation_settings.delete('installation_schema_version')
32
+ end
33
+
34
+ def delete_ip_assignments
35
+ @installation_settings.delete('ip_assignments')
36
+ end
37
+
38
+ def delete_guid
39
+ @installation_settings.delete('guid')
40
+ end
41
+
42
+ def delete_director_ssl
43
+ director_product_template["products"][1].delete("director_ssl")
44
+ end
45
+
46
+ def delete_uaa_ssl
47
+ director_product_template["products"][1].delete("uaa_ssl")
48
+ end
49
+
50
+ def director_product_template
51
+ @director_product_template ||= OpsManager::ProductTemplateGenerator.new('p-bosh').generate
52
+ end
53
+
54
+
55
+ def installation_settings_response
56
+ OpsManager::Api::Opsman.new(silent: true).get_installation_settings
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,9 @@
1
+ class OpsManager
2
+ class UpgradeError < RuntimeError ; end
3
+ class InstallationError < RuntimeError ; end
4
+ class ProductDeploymentError < RuntimeError ; end
5
+ class InstallationSettingsError < RuntimeError ; end
6
+ class PivnetAuthenticationError < RuntimeError ; end
7
+ class StemcellUploadError < RuntimeError ; end
8
+ class ProductUploadError < RuntimeError ; end
9
+ end
@@ -0,0 +1,38 @@
1
+ class OpsManager
2
+ class Installation
3
+ attr_reader :id
4
+
5
+ def initialize(id)
6
+ @id = id
7
+ end
8
+
9
+ def logs
10
+ parsed_logs.fetch('logs')
11
+ end
12
+
13
+ class << self
14
+ def all
15
+ parsed_installations.fetch('installations').collect{ |i| new(i.fetch('id')) }.reverse
16
+ end
17
+
18
+ private
19
+
20
+ def parsed_installations
21
+ JSON.parse(opsman_api.get_installations.body)
22
+ end
23
+
24
+ def opsman_api
25
+ OpsManager::Api::Opsman.new
26
+ end
27
+ end
28
+
29
+ private
30
+ def parsed_logs
31
+ JSON.parse(opsman_api.get_installation_logs(id).body)
32
+ end
33
+
34
+ def opsman_api
35
+ @opsman_api ||= OpsManager::Api::Opsman.new
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,75 @@
1
+ class OpsManager
2
+ class InstallationRunner
3
+ extend Forwardable
4
+ def_delegators :opsman_api, :trigger_installation, :get_installation, :get_current_version,
5
+ :get_staged_products, :get_staged_products_errands
6
+ attr_reader :id
7
+
8
+ def trigger!
9
+ res = trigger_installation( body: body.join('&') )
10
+ @id = JSON.parse(res.body).fetch('install').fetch('id').to_i
11
+ self
12
+ end
13
+
14
+ def self.trigger!
15
+ new.trigger!
16
+ end
17
+
18
+ def wait_for_result
19
+ while JSON.parse(get_installation(id).body).fetch('status') == 'running'
20
+ print '.'.green
21
+ sleep 10
22
+ end
23
+ puts ''
24
+ end
25
+
26
+ private
27
+ def body
28
+ @body ||= [ 'ignore_warnings=true' ]
29
+ @body << errands_body
30
+
31
+ @body
32
+ end
33
+
34
+ def opsman_api
35
+ @opsman_api ||= OpsManager::Api::Opsman.new
36
+ end
37
+
38
+ def errands_body
39
+ staged_products_guids.collect do |product_guid|
40
+ post_deploy_errands_body_for(product_guid)
41
+ end
42
+ end
43
+
44
+ def post_deploy_errands_body_for(product_guid)
45
+ post_deploy_errands = post_deploy_errands_for(product_guid)
46
+
47
+ unless post_deploy_errands.empty?
48
+ post_deploy_errands.collect{ |e| "enabled_errands[#{product_guid}][post_deploy_errands][]=#{e}" }
49
+ else
50
+ "enabled_errands[#{product_guid}]{}"
51
+ end
52
+ end
53
+
54
+ def staged_products_guids
55
+ staged_products.collect {|product| product.fetch('guid') }
56
+ end
57
+
58
+ def staged_products
59
+ JSON.parse(get_staged_products.body)
60
+ end
61
+
62
+ def post_deploy_errands_for(product_guid)
63
+ errands_for(product_guid).keep_if{ |errand| errand['post_deploy'] }.map{ |o| o['name']}
64
+ end
65
+
66
+ def errands_for(product_guid)
67
+ res = get_staged_products_errands(product_guid)
68
+ if res.code == 200
69
+ JSON.parse(res.body)['errands']
70
+ else
71
+ []
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,16 @@
1
+ class OpsManager
2
+ class InstallationSettings < Hash
3
+ def initialize(parsed_installation_settings)
4
+ super.merge!(parsed_installation_settings)
5
+ end
6
+
7
+ def stemcells
8
+ self.fetch('products').inject([]) do |a, p|
9
+ a << {
10
+ version: p['stemcell'].fetch('version'),
11
+ file: p['stemcell'].fetch('file'),
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'logger'
2
+
3
+ class OpsManager
4
+ module Logging
5
+ def logger
6
+ Logging.logger
7
+ end
8
+
9
+ def self.logger
10
+ @logger ||= Logger.new( ENV['DEBUG'].nil? ? 'ops_manager.log' : STDOUT)
11
+ end
12
+
13
+ def self.logger=(logger)
14
+ @logger = logger
15
+ end
16
+ end
17
+ end