ops_manager_cli 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de760b1ee988f72565a9792c838e90b0ad0c4727
4
- data.tar.gz: ed687f1f53abfcd0e81ab380ba319b61b0eb4442
3
+ metadata.gz: 275c833c1e7b72ba2ab90fa53b3ef0025556907f
4
+ data.tar.gz: 435c7bde99641128d0f9fb57f108918f4a92cd16
5
5
  SHA512:
6
- metadata.gz: bd81cf062bed911b6f06f04ee005c328cc90cb535ef987a33d895caea05b3007684cab923fe8907aa32c8ca174cc430991ea8dc3841da869a67673918dbe38c6
7
- data.tar.gz: 0ef5e2082449ab75e5c3b12f033948c7782866a37ff5a3e24671b312c0a3d4fb431ec1e62d77a6f0276a8a9c5a52ea9ad55453c050aa7f34ffc98314bba595e9
6
+ metadata.gz: 41210680cd70a74580a55afd53368ac4e985b1e7c9b6cf6e08bd1a900711fd3c19252fa26d82042d860fbbb1836ea79708746e2473edb37de63a4dfca225a836
7
+ data.tar.gz: 507f8d1f8929e727e9fd6cf75955d3226150bd11251ea4baeb1099b22dd630d7e4df944780ebfe72866615130779d591f355fcc740fd1842a194aca3193aa450
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
3
+ - 2.2.0
4
4
  before_install: gem install bundler -v 1.10.6
data/Dockerfile CHANGED
@@ -1,28 +1,25 @@
1
1
  FROM ruby:2.3.0
2
2
 
3
3
  ENV GEM_NAME ops_manager_cli
4
- ENV GEM_VERSION 0.3.2
4
+ ENV GEM_VERSION 0.4.0
5
5
  ENV OVFTOOL_VERSION 4.1.0-2459827
6
- ENV OVFTOOL_INSTALLER vmware-ovftool-${OVFTOOL_VERSION}-lin.x86_64.bundle
7
- ARG DOWNLOAD_URL=https://storage.googleapis.com/mortarchive/pub/ovftool/${OVFTOOL_INSTALLER}
8
- ENV DOWNLOAD_URL ${DOWNLOAD_URL}
6
+ ENV OVFTOOL_INSTALLER VMware-ovftool-${OVFTOOL_VERSION}-lin.x86_64.bundle
7
+ ARG DOWNLOAD_URL
9
8
 
10
9
  # ================== Installs OVF tools ==============
11
10
  RUN echo $DOWNLOAD_URL
12
- RUN wget -q ${DOWNLOAD_URL} \
13
- && wget -q ${DOWNLOAD_URL}.sha256 \
14
- && sha256sum -c ${OVFTOOL_INSTALLER}.sha256 \
11
+ RUN wget -v ${DOWNLOAD_URL} \
15
12
  && sh ${OVFTOOL_INSTALLER} -p /usr/local --eulas-agreed --required \
16
13
  && rm -f ${OVFTOOL_INSTALLER}*
17
14
 
18
15
  # ================== Installs Spruce ==============
19
- RUN wget --no-check-certificate -q https://github.com/geofffranks/spruce/releases/download/v1.0.1/spruce_1.0.1_linux_amd64.tar.gz \
16
+ RUN wget -v --no-check-certificate https://github.com/geofffranks/spruce/releases/download/v1.0.1/spruce_1.0.1_linux_amd64.tar.gz \
20
17
  && tar -xvf spruce_1.0.1_linux_amd64.tar.gz \
21
18
  && chmod +x /spruce_1.0.1_linux_amd64/spruce \
22
19
  && ln -s /spruce_1.0.1_linux_amd64/spruce /usr/bin/.
23
20
 
24
21
  # ================== Installs JQ ==============
25
- RUN wget -q -O /usr/local/bin/jq --no-check-certificate https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
22
+ RUN wget -v -O /usr/local/bin/jq --no-check-certificate https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
26
23
  RUN chmod +x /usr/local/bin/jq
27
24
 
28
25
  # ================== Installs ops_manager_cli gem ==============
data/Gemfile CHANGED
@@ -2,4 +2,3 @@ source 'http://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in ops_manager.gemspec
4
4
  gemspec
5
-
data/README.md CHANGED
@@ -1,16 +1,17 @@
1
- # OpsManagerCli
1
+ # OpsManagerCli [![Build Status](https://travis-ci.org/compozed/ops_manager_cli.png?branch=master)](https://travis-ci.org/compozed/ops_manager_cli) [![Code Climate](https://codeclimate.com/github/compozed/ops_manager_cli.png)](https://codeclimate.com/github/compozed/ops_manager_cli)
2
+
2
3
 
3
4
  Command line tool to interact with Pivotal Operations Manager through its API, because GUIs are evil.
4
5
 
5
6
  Questions? Pop in our [slack channel](https://cloudfoundry.slack.com/messages/ops_manager_cli/)!
6
7
 
7
- *Please note that the APIs of Ops Manager is experimental at this point. Changes to the APIs with new Ops Manager releases may break functionality. The project also does not offer any support for the usage of this in any environments.*
8
+ *Please note that the APIs of Ops Manager is experimental at this point. Changes to the APIs with new Ops Manager releases may break functionality.*
8
9
 
9
10
  ## Features
10
11
 
11
12
  ### Core features
12
13
 
13
- - Support for deploying on vSphere infrastructure only
14
+ - Support for deploying on vSphere infrastructure
14
15
  - Deploy/Upgrade Ops Manager appliance
15
16
  - Deploy/Upgrade product tiles
16
17
  - Generate config settings templates for product tiles deployments
@@ -25,11 +26,17 @@ Questions? Pop in our [slack channel](https://cloudfoundry.slack.com/messages/op
25
26
 
26
27
  ### Limitations:
27
28
 
28
- - No support for enabling and running errands (future work)
29
+ - No support for enabling and running errands for ops_manager **< 1.7.x**
30
+
29
31
 
30
32
  ### Test with Ops Manager Version
31
33
 
32
- - up to 1.7.11.0
34
+ - up to 1.8.3.0
35
+
36
+ ## Upgrading OpsManager appliance from **1.7.x** to **1.8.x**
37
+
38
+ On ops_manager_cli **>=0.4.0** support for upgrading to **1.8.x** was removed.
39
+ Use ops_manager_cli **0.3.1** to upgrade from **1.7.x** -> **1.8.x**.
33
40
 
34
41
  ## Dependencies
35
42
 
@@ -1,8 +1,20 @@
1
1
  class OpsManager
2
2
  module Api
3
3
  class Base
4
+ attr_reader :silent
5
+
4
6
  include OpsManager::Logging
5
7
 
8
+ def initialize(opts = {})
9
+ @silent = opts[:silent]
10
+ end
11
+
12
+ %w{ get post put delete multipart_post}.each do |m|
13
+ define_method "authenticated_#{m}" do |endpoint, opts = {}|
14
+ send(m, endpoint, add_authentication(opts))
15
+ end
16
+ end
17
+
6
18
  def get(endpoint, opts = {})
7
19
  uri = uri_for(endpoint)
8
20
  http = http_for(uri)
@@ -136,6 +148,20 @@ class OpsManager
136
148
  http.read_timeout = 1200
137
149
  end
138
150
  end
151
+
152
+ def add_authentication(opts={})
153
+ opts[:headers] ||= {}
154
+ opts[:headers]['Authorization'] ||= authorization_header
155
+ opts
156
+ end
157
+
158
+ def say_green(str)
159
+ puts str.green unless silent
160
+ end
161
+
162
+ def print_green(str)
163
+ print str.green unless silent
164
+ end
139
165
  end
140
166
  end
141
167
  end
@@ -6,31 +6,18 @@ require "uaa"
6
6
  class OpsManager
7
7
  module Api
8
8
  class Opsman < OpsManager::Api::Base
9
- attr_reader :silent
10
-
11
- def initialize(opts = {})
12
- @silent = opts[:silent]
13
- end
14
-
15
- def say_green(str)
16
- puts str.green unless silent
17
- end
18
-
19
- def print_green(str)
20
- print str.green unless silent
21
- end
22
-
23
9
  def create_user
24
10
  body= "setup[decryption_passphrase]=#{password}&setup[decryption_passphrase_confirmation]=#{password}&setup[eula_accepted]=true&setup[identity_provider]=internal&setup[admin_user_name]=#{username}&setup[admin_password]=#{password}&setup[admin_password_confirmation]=#{password}"
25
11
  post("/api/v0/setup" , body: body)
26
12
  end
27
13
 
28
14
  def upload_installation_settings(filepath = 'installation_settings.json')
29
- say_green( '====> Uploading installation settings...')
15
+ print_green '====> Uploading installation settings ...'
30
16
  yaml = UploadIO.new(filepath, 'text/yaml')
31
17
  opts = { "installation[file]" => yaml}
32
18
  res = authenticated_multipart_post("/api/installation_settings", opts)
33
19
  raise OpsManager::InstallationSettingsError.new(res.body) unless res.code == '200'
20
+ say_green 'done'
34
21
  res
35
22
  end
36
23
 
@@ -39,39 +26,48 @@ class OpsManager
39
26
  end
40
27
 
41
28
  def get_installation_settings(opts = {})
42
- say_green( '====> Downloading installation settings...')
43
- authenticated_get("/api/installation_settings", opts)
29
+ print_green '====> Downloading installation settings ...'
30
+ res = authenticated_get("/api/installation_settings", opts)
31
+ say_green 'done'
32
+ res
44
33
  end
45
34
 
46
35
  def upload_installation_assets
47
- say_green( '====> Uploading installation assets...')
36
+ print_green( '====> Uploading installation assets ...')
48
37
  zip = UploadIO.new("#{Dir.pwd}/installation_assets.zip", 'application/x-zip-compressed')
49
38
  opts = {:passphrase => @password, "installation[file]" => zip }
50
- multipart_post( "/api/v0/installation_asset_collection", opts)
39
+ res = multipart_post( "/api/v0/installation_asset_collection", opts)
40
+ say_green 'done'
41
+ res
51
42
  end
52
43
 
53
44
  def get_installation_assets
54
45
  opts = { write_to: "installation_assets.zip" }
55
- say_green( '====> Download installation assets...')
56
- authenticated_get("/api/v0/installation_asset_collection", opts)
46
+ say_green '====> Download installation assets ...'
47
+ res = authenticated_get("/api/v0/installation_asset_collection", opts)
48
+ say_green 'done'
49
+ res
57
50
  end
58
51
 
59
52
  def delete_products(opts = {})
60
- say_green( '====> Deleating unused products...')
61
- opts = add_authentication(opts)
62
- delete('/api/v0/products', opts)
53
+ print_green '====> Deleating unused products ...'
54
+ res = authenticated_delete('/api/v0/products', opts)
55
+ say_green 'done'
56
+ res
63
57
  end
64
58
 
65
59
  def trigger_installation(opts = {})
66
- print_green('====> Applying changes...')
67
- authenticated_post('/api/v0/installations', opts)
60
+ print_green('====> Applying changes')
61
+ res = authenticated_post('/api/v0/installations', opts)
62
+ raise OpsManager::InstallationError.new(res.body) if res.code =~ /422/
63
+ res
68
64
  end
69
65
 
70
66
  def add_staged_products(name, version)
71
- print_green( "====> Adding available product to the installation...")
67
+ print_green( "====> Adding available product to the installation ...")
72
68
  body = "name=#{name}&product_version=#{version}"
73
69
  res = authenticated_post('/api/v0/staged/products', body: body)
74
- raise OpsManager::ProductDeploymentError.new(res.body) if res.code == '404'
70
+ raise OpsManager::ProductDeploymentError.new(res.body) if res.code =~ /404|500/
75
71
  say_green('done')
76
72
  res
77
73
  end
@@ -91,21 +87,24 @@ class OpsManager
91
87
  end
92
88
 
93
89
  def get_installations(opts = {})
94
- authenticated_get('/api/v0/installations')
90
+ print_green '====> Getting installations ...'
91
+ res = authenticated_get('/api/v0/installations')
92
+ say_green 'done'
93
+ res
95
94
  end
96
95
 
97
96
  def upgrade_product_installation(guid, product_version)
98
- say_green( "====> Bumping product installation #{guid} product_version to #{product_version}...")
97
+ print_green "====> Bumping product installation #{guid} product_version to #{product_version} ..."
99
98
  opts = { to_version: product_version }
100
- opts = add_authentication(opts)
101
- res = put("/api/v0/staged/products/#{guid}", opts)
99
+ res = authenticated_put("/api/v0/staged/products/#{guid}", opts)
102
100
  raise OpsManager::UpgradeError.new(res.body) unless res.code == '200'
101
+ say_green 'done'
103
102
  res
104
103
  end
105
104
 
106
105
  def upload_product(filepath)
107
106
  file = "#{filepath}"
108
- cmd = "curl -k \"https://#{target}/api/v0/available_products\" -F 'product[file]=@#{file}' -X POST -H 'Authorization: Bearer #{access_token}'"
107
+ cmd = "curl -s -k \"https://#{target}/api/v0/available_products\" -F 'product[file]=@#{file}' -X POST -H 'Authorization: Bearer #{access_token}'"
109
108
  logger.info "running cmd: #{cmd}"
110
109
  body = `#{cmd}`
111
110
  logger.info "Upload product response: #{body}"
@@ -116,24 +115,29 @@ class OpsManager
116
115
  authenticated_get("/api/v0/available_products")
117
116
  end
118
117
 
119
- def get_current_version
120
- products = JSON.parse(get_available_products.body)
121
- directors = products.select{ |i| i.fetch('name') =~/p-bosh|microbosh/ }
122
- versions = directors.inject([]){ |r, i| r << OpsManager::Semver.new(i.fetch('product_version')) }
123
- versions.sort.last.to_s.gsub(/.0$/,'')
124
-
118
+ def get_diagnostic_report
119
+ authenticated_get("/api/v0/diagnostic_report")
125
120
  rescue Errno::ETIMEDOUT , Errno::EHOSTUNREACH, Net::HTTPFatalError, Net::OpenTimeout
126
121
  nil
127
122
  end
128
123
 
129
-
130
124
  def import_stemcell(filepath)
131
125
  return unless filepath
132
- say_green('====> Uploading stemcell...')
133
126
  tar = UploadIO.new(filepath, 'multipart/form-data')
127
+ print_green "====> Uploading stemcell: #{tar} ..."
134
128
  opts = { "stemcell[file]" => tar }
135
- res = authenticated_multipart_post("/api/v0/stemcells", opts)
129
+ res = nil
130
+
131
+ 3.times do
132
+ res = authenticated_multipart_post("/api/v0/stemcells", opts)
133
+ case res.code
134
+ when '200' ; break
135
+ when '503' ; sleep(60)
136
+ end
137
+ end
138
+
136
139
  raise OpsManager::StemcellUploadError.new(res.body) unless res.code == '200'
140
+ say_green 'done'
137
141
  res
138
142
  end
139
143
 
@@ -150,24 +154,13 @@ class OpsManager
150
154
  end
151
155
 
152
156
  def get_token
153
- token_issuer.owner_password_grant('admin', password, 'opsman.admin').tap do |token|
157
+ token_issuer.owner_password_grant(username, password, 'opsman.admin').tap do |token|
154
158
  logger.info "UAA Token: #{token.inspect}"
155
159
  end
156
160
  rescue CF::UAA::TargetError
157
161
  nil
158
162
  end
159
163
 
160
- def authenticated_get(endpoint, opts = {})
161
- get(endpoint, add_authentication(opts))
162
- end
163
-
164
- def authenticated_post(endpoint, opts = {})
165
- post(endpoint, add_authentication(opts))
166
- end
167
-
168
- def authenticated_multipart_post(endpoint, opts = {})
169
- multipart_post(endpoint, add_authentication(opts))
170
- end
171
164
 
172
165
  private
173
166
  def token_issuer
@@ -179,11 +172,8 @@ class OpsManager
179
172
  @access_token ||= get_token.info['access_token']
180
173
  end
181
174
 
182
-
183
- def add_authentication(opts={})
184
- opts[:headers] ||= {}
185
- opts[:headers]['Authorization'] ||= "Bearer #{access_token}"
186
- opts
175
+ def authorization_header
176
+ "Bearer #{access_token}"
187
177
  end
188
178
  end
189
179
  end
@@ -2,64 +2,44 @@ class OpsManager
2
2
  module Api
3
3
  class Pivnet < OpsManager::Api::Base
4
4
 
5
- def initialize
6
- get_authentication
5
+ def get_product_releases(product_slug, opts = {})
6
+ authenticated_get("/api/v2/products/#{product_slug}/releases", opts)
7
7
  end
8
8
 
9
- def download_stemcell(stemcell_version, stemcell_path, filename_regex)
10
- puts "====> Downloading stemcell #{ stemcell_path }...".green
9
+ def accept_product_release_eula(product_slug, release_id)
10
+ print_green("====> Accepting #{product_slug} release #{release_id} eula ...")
11
+ res = authenticated_post("/api/v2/products/#{product_slug}/releases/#{release_id}/eula_acceptance")
12
+ say_green "done"
13
+ res
14
+ end
11
15
 
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')
16
+ def get_product_release_files(product_slug, release_id)
17
+ authenticated_get("/api/v2/products/#{product_slug}/releases/#{release_id}/product_files")
18
+ end
15
19
 
16
- accept_release_eula(release_id)
17
- download_product(release_id, product_file_id, stemcell_path)
20
+ def download_product_release_file(product_slug, release_id, file_id, opts = {})
21
+ print_green "====> Downloading stemcell: #{opts[:write_to]} ..."
22
+ res = authenticated_post("/api/v2/products/#{product_slug}/releases/#{release_id}/product_files/#{file_id}/download", opts)
23
+ say_green "done"
24
+ res
18
25
  end
19
26
 
20
27
  def get_authentication
21
- puts "====> Authentication to Pivnet".green
22
- opts = { headers: { 'Authorization' => "Token #{pivnet_token}" } }
23
- res = get("/api/v2/authentication", opts)
28
+ say_green "====> Authentication to Pivnet"
29
+ res = authenticated_get("/api/v2/authentication")
24
30
  raise OpsManager::PivnetAuthenticationError.new(res.body) unless res.code == '200'
25
31
  res
26
32
  end
27
33
 
28
34
  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
35
  def target
60
36
  @target ||= "network.pivotal.io"
61
37
  end
62
38
 
39
+ def authorization_header
40
+ "Token #{pivnet_token}"
41
+ end
42
+
63
43
  def pivnet_token
64
44
  @pivnet_token ||= OpsManager.get_conf(:pivnet_token)
65
45
  end
@@ -1,14 +1,15 @@
1
1
  require "ops_manager/api/opsman"
2
2
  require "ops_manager/api/pivnet"
3
- require "ops_manager/installation_settings"
4
3
  require 'ops_manager/configs/opsman_deployment'
4
+ require 'fileutils'
5
5
 
6
6
  class OpsManager::ApplianceDeployment
7
7
  extend Forwardable
8
- def_delegators :pivnet_api, :download_stemcell
8
+ def_delegators :pivnet_api, :get_product_releases, :accept_product_release_eula,
9
+ :get_product_release_files, :download_product_release_file
9
10
  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=
11
+ :get_installation_settings, :get_diagnostic_report, :upload_installation_assets,
12
+ :import_stemcell, :target, :password, :username, :ops_manager_version=
12
13
 
13
14
  attr_reader :config_file
14
15
 
@@ -26,15 +27,17 @@ class OpsManager::ApplianceDeployment
26
27
 
27
28
  case
28
29
  when current_version.empty?
29
- puts "No OpsManager deployed at #{target}. Deploying ...".green
30
+ puts "No OpsManager deployed at #{config.ip}. Deploying ...".green
30
31
  deploy
31
32
  create_first_user
32
33
  when current_version < desired_version then
33
- puts "OpsManager at #{target} version is #{current_version}. Upgrading to #{desired_version}.../".green
34
+ puts "OpsManager at #{config.ip} version is #{current_version}. Upgrading to #{desired_version} .../".green
34
35
  upgrade
35
36
  when current_version == desired_version then
36
- puts "OpsManager at #{target} version is already #{config.desired_version}. Skiping ...".green
37
+ puts "OpsManager at #{config.ip} version is already #{config.desired_version}. Skiping ...".green
37
38
  end
39
+
40
+ puts '====> Finish!'.green
38
41
  end
39
42
 
40
43
  def deploy
@@ -48,38 +51,107 @@ class OpsManager::ApplianceDeployment
48
51
  end
49
52
 
50
53
  def create_first_user
51
- puts '====> Creating initial user...'.green
54
+ puts '====> Creating initial user'.green
52
55
  until( create_user.code.to_i == 200) do
53
- print '.'.green ; sleep 1
56
+ print ' .'.green ; sleep 1
54
57
  end
55
58
  end
56
59
 
57
60
  def upgrade
58
61
  get_installation_assets
59
- get_installation_settings(write_to: 'installation_settings.json')
62
+ download_current_stemcells
60
63
  stop_current_vm(current_vm_name)
61
64
  deploy
62
65
  upload_installation_assets
63
- provision_missing_stemcells
66
+ provision_stemcells
64
67
  OpsManager::InstallationRunner.trigger!.wait_for_result
68
+ end
65
69
 
66
- puts "====> Finish!".green
70
+ def list_current_stemcells
71
+ JSON.parse(installation_settings).fetch('products').inject([]) do |a, p|
72
+ a << p['stemcell'].fetch('version')
73
+ end
67
74
  end
68
75
 
76
+ # Finds available stemcell's pivotal network release.
77
+ # If it can not find the exact version it will try to find the newest minor version available.
78
+ # #
79
+ # @param version [String] the version number, eg: '2362.17'
80
+ # @return release_id [Integer] the pivotal netowkr release id of the found stemcell.
81
+ def find_stemcell_release(version)
82
+ version = OpsManager::Semver.new(version)
83
+ releases = stemcell_releases.collect do |r|
84
+ {
85
+ release_id: r['id'],
86
+ version: OpsManager::Semver.new(r['version']),
87
+ }
88
+ end
89
+ releases.keep_if{ |r| r[:version].major == version.major }
90
+ exact_version = releases.select {|r| r[:version] == version }
91
+ return exact_version.first[:release_id] unless exact_version.empty?
92
+ releases_sorted_by_version = releases.sort_by{ |r| r[:version].minor }.reverse
93
+ return releases_sorted_by_version.first[:release_id] unless releases_sorted_by_version.empty?
94
+ end
95
+
96
+ # Finds stemcell's pivotal network release file.
97
+ # #
98
+ # @param release_id [String] the version number, eg: '2362.17'
99
+ # @param filename [Regex] the version number, eg: /vsphere/
100
+ # @return id and name [Array] the pivotal network file ID and Filename for the matching stemcell.
101
+ def find_stemcell_file(release_id, filename)
102
+ files = JSON.parse(get_product_release_files('stemcells', release_id).body).fetch('product_files')
103
+ file = files.select{ |r| r.fetch('aws_object_key') =~ filename }.first
104
+ return file['id'], file['aws_object_key'].split('/')[-1]
105
+ end
106
+
107
+ # Lists all the available stemcells in the current installation_settings.
108
+ # Downloads those stemcells.
109
+ def download_current_stemcells
110
+ puts "Downloading existing stemcells ...".green
111
+ FileUtils.mkdir_p current_stemcell_dir
112
+ list_current_stemcells.each do |stemcell_version|
113
+ release_id = find_stemcell_release(stemcell_version)
114
+ accept_product_release_eula('stemcells', release_id )
115
+ file_id, file_name = find_stemcell_file(release_id, /vsphere/)
116
+ download_product_release_file('stemcells', release_id, file_id, write_to: "#{current_stemcell_dir}/#{file_name}")
117
+ end
118
+ end
69
119
 
70
120
  def new_vm_name
71
121
  @new_vm_name ||= "#{config.name}-#{config.desired_version}"
72
122
  end
73
123
 
74
- private
75
124
  def current_version
76
- @current_version ||= OpsManager::Semver.new(get_current_version)
125
+ @current_version ||= OpsManager::Semver.new(version_from_diagnostic_report)
77
126
  end
78
127
 
79
128
  def desired_version
80
129
  @desired_version ||= OpsManager::Semver.new(config.desired_version)
81
130
  end
82
131
 
132
+ def provision_stemcells
133
+ Dir.glob("#{current_stemcell_dir}/*").each do |stemcell_filepath|
134
+ import_stemcell(stemcell_filepath)
135
+ end
136
+ end
137
+
138
+ private
139
+ def diagnostic_report
140
+ @diagnostic_report ||= get_diagnostic_report
141
+ end
142
+
143
+ def version_from_diagnostic_report
144
+ return unless diagnostic_report
145
+ version = parsed_diagnostic_report
146
+ .fetch("versions")
147
+ .fetch("release_version")
148
+ version.gsub(/.0$/,'')
149
+ end
150
+
151
+ def parsed_diagnostic_report
152
+ JSON.parse(diagnostic_report.body)
153
+ end
154
+
83
155
  def current_vm_name
84
156
  @current_vm_name ||= "#{config.name}-#{current_version}"
85
157
  end
@@ -88,14 +160,6 @@ class OpsManager::ApplianceDeployment
88
160
  @desired_vm_name ||= "#{config.name}-#{config.desired_version}"
89
161
  end
90
162
 
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
163
  def pivnet_api
100
164
  @pivnet_api ||= OpsManager::Api::Pivnet.new
101
165
  end
@@ -109,15 +173,23 @@ class OpsManager::ApplianceDeployment
109
173
  @config ||= OpsManager::Configs::OpsmanDeployment.new(parsed_yml)
110
174
  end
111
175
 
176
+ def desired_version?(version)
177
+ !!(desired_version.to_s =~/#{version}/)
178
+ end
179
+
112
180
  def installation_settings
113
- @installation_settings ||= OpsManager::InstallationSettings.new(parsed_installation_settings)
181
+ @installation_settings ||= get_installation_settings.body
114
182
  end
115
183
 
116
- def parsed_installation_settings
117
- JSON.parse(File.read('installation_settings.json'))
184
+ def get_stemcell_releases
185
+ get_product_releases('stemcells')
118
186
  end
119
187
 
120
- def desired_version?(version)
121
- !!(desired_version.to_s =~/#{version}/)
188
+ def stemcell_releases
189
+ @stemcell_releases ||= JSON.parse(get_stemcell_releases.body).fetch('releases')
190
+ end
191
+
192
+ def current_stemcell_dir
193
+ "/tmp/current_stemcells"
122
194
  end
123
195
  end
@@ -75,19 +75,7 @@ class OpsManager
75
75
  class DeleteUnusedProducts < Clamp::Command
76
76
 
77
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)}`
78
+ OpsManager::Api::Opsman.new.delete_products
91
79
  end
92
80
  end
93
81
 
@@ -106,7 +94,7 @@ class OpsManager
106
94
  if @installation_id == "last"
107
95
  puts OpsManager::Installation.all.last.logs
108
96
  else
109
- puts OpsManager::Installation.new(@installation_id).logs
97
+ puts OpsManager::Installation.new(@installation_id.to_i).logs
110
98
  end
111
99
  end
112
100
  end
@@ -119,7 +107,7 @@ class OpsManager
119
107
 
120
108
  class Curl < Clamp::Command
121
109
  option ['-X', '--http-method'], "HTTP_METHOD", "HTTP Method (GET,POST)", default: 'GET'
122
- parameter "ENDPOINT", "OpsManager api endpoint. eg: /v0/installation_settings", required: true
110
+ parameter "ENDPOINT", "OpsManager api endpoint. eg: /api/v0/installation_settings", required: true
123
111
 
124
112
  def execute
125
113
  puts case http_method.strip
@@ -138,6 +126,12 @@ class OpsManager
138
126
  end
139
127
  end
140
128
 
129
+ class Version < Clamp::Command
130
+ def execute
131
+ puts OpsManager::VERSION
132
+ end
133
+ end
134
+
141
135
  # Core commands
142
136
  subcommand "status", "Test credentials and shows status", Status
143
137
  subcommand "target", "Target an OpsManager appliance", Target
@@ -148,12 +142,12 @@ class OpsManager
148
142
  subcommand "get-product-template", "Generates Product tile installation template", GetProductTemplate
149
143
 
150
144
  # Other commands
145
+ subcommand "version", "Shows OpsManagerCLI version", Version
151
146
  subcommand "curl", "Authenticated curl requests(POST/GET)", Curl
147
+ subcommand "delete-unused-products", "Deletes unused product tiles", DeleteUnusedProducts
152
148
  subcommand "get-installation-settings", "Gets installation settings", GetInstallationSettings
153
149
  subcommand "get-installation-logs", "Gets installation logs", GetInstallationLogs
154
- subcommand "get-uaa-token", "Gets uaa token from OpsManager", GetUaaToken
155
150
  subcommand "import-stemcell", "Uploads stemcell to OpsManager", ImportStemcell
156
- subcommand "delete-unused-products", "Deletes unused product tiles", DeleteUnusedProducts
157
151
 
158
152
  end
159
153
  end
@@ -1,27 +1,29 @@
1
1
  require 'rbvmomi'
2
2
  require "uri"
3
+ require 'shellwords'
3
4
  require "ops_manager/logging"
4
5
 
5
6
  class OpsManager
6
7
  module Deployments
7
8
  module Vsphere
8
9
  include OpsManager::Logging
9
- include OpsManager::Logging
10
10
 
11
11
  def deploy_vm(name, ip)
12
- puts '====> Starts ova deployment'.green
12
+ print '====> Deploying ova ...'.green
13
13
  vcenter_target= "vi://#{vcenter_username}:#{vcenter_password}@#{config.opts['vcenter']['host']}/#{config.opts['vcenter']['datacenter']}/host/#{config.opts['vcenter']['cluster']}"
14
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}`
15
+ logger.info "Running: #{cmd}"
16
+ logger.info `#{cmd}`
17
+ puts 'done'.green
17
18
  end
18
19
 
19
20
  def stop_current_vm(name)
20
- puts "====> Stopping vm #{name}...".green
21
+ print "====> Stopping vm #{name}...".green
21
22
  dc = vim.serviceInstance.find_datacenter(config.opts['vcenter']['datacenter'])
22
23
  logger.info "finding vm: #{name}"
23
24
  vm = dc.find_vm(name) or fail "VM not found"
24
25
  vm.PowerOffVM_Task.wait_for_completion
26
+ puts 'done'.green
25
27
  end
26
28
 
27
29
  private
@@ -30,11 +32,11 @@ class OpsManager
30
32
  end
31
33
 
32
34
  def vcenter_username
33
- URI.encode(config.opts['vcenter']['username'])
35
+ Shellwords.escape(URI.encode(config.opts['vcenter']['username']))
34
36
  end
35
37
 
36
38
  def vcenter_password
37
- URI.encode(config.opts['vcenter']['password'])
39
+ Shellwords.escape(URI.encode(config.opts['vcenter']['password']))
38
40
  end
39
41
  end
40
42
  end
@@ -7,53 +7,56 @@ class OpsManager
7
7
  delete_uaa_ssl
8
8
  delete_guid
9
9
  delete_ip_assignments
10
+ add_merging_strategy_for_networks
11
+
10
12
  installation_settings.to_h
11
13
  end
12
14
 
13
15
  def generate_yml
14
16
  generate.to_yaml
15
- .gsub('"(( merge on guid ))"', '(( merge on guid ))')
17
+ .gsub('"(( merge on name ))"', '(( merge on name ))')
16
18
  .gsub('"(( merge on identifier ))"', '(( merge on identifier ))')
17
19
  end
18
20
 
19
21
  private
20
22
  def installation_settings
21
23
  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
+ res = OpsManager::Api::Opsman.new(silent: true).get_installation_settings
25
+ @installation_settings = JSON.parse(res.body)
24
26
  end
25
27
 
26
28
  def merge_director_template_products
27
- installation_settings.merge!('products' => director_product_template.fetch('products'))
29
+ installation_settings.merge!('products' => product_template.fetch('products'))
30
+ end
31
+
32
+ def add_merging_strategy_for_networks
33
+ installation_settings['infrastructure']['networks'].tap do |networks|
34
+ networks.unshift("(( merge on name ))") if networks
35
+ end
28
36
  end
29
37
 
30
38
  def delete_schema_version
31
- @installation_settings.delete('installation_schema_version')
39
+ installation_settings.delete('installation_schema_version')
32
40
  end
33
41
 
34
42
  def delete_ip_assignments
35
- @installation_settings.delete('ip_assignments')
43
+ installation_settings.delete('ip_assignments')
36
44
  end
37
45
 
38
46
  def delete_guid
39
- @installation_settings.delete('guid')
47
+ installation_settings.delete('guid')
40
48
  end
41
49
 
42
50
  def delete_director_ssl
43
- director_product_template["products"][1].delete("director_ssl")
51
+ product_template["products"][1].delete("director_ssl")
44
52
  end
45
53
 
46
54
  def delete_uaa_ssl
47
- director_product_template["products"][1].delete("uaa_ssl")
55
+ product_template["products"][1].delete("uaa_ssl")
48
56
  end
49
57
 
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
58
+ def product_template
59
+ @product_template ||= OpsManager::ProductTemplateGenerator.new('p-bosh').generate
57
60
  end
58
61
  end
59
62
  end
@@ -32,7 +32,7 @@ class OpsManager
32
32
  end
33
33
 
34
34
  def opsman_api
35
- @opsman_api ||= OpsManager::Api::Opsman.new
35
+ @opsman_api ||= OpsManager::Api::Opsman.new(silent: true)
36
36
  end
37
37
  end
38
38
  end
@@ -1,12 +1,12 @@
1
1
  class OpsManager
2
2
  class InstallationRunner
3
3
  extend Forwardable
4
- def_delegators :opsman_api, :trigger_installation, :get_installation, :get_current_version,
4
+ def_delegators :opsman_api, :trigger_installation, :get_installation,
5
5
  :get_staged_products, :get_staged_products_errands
6
6
  attr_reader :id
7
7
 
8
8
  def trigger!
9
- res = trigger_installation( body: body.join('&') )
9
+ res = trigger_installation( body: body )
10
10
  @id = JSON.parse(res.body).fetch('install').fetch('id').to_i
11
11
  self
12
12
  end
@@ -17,7 +17,7 @@ class OpsManager
17
17
 
18
18
  def wait_for_result
19
19
  while JSON.parse(get_installation(id).body).fetch('status') == 'running'
20
- print '.'.green
20
+ print ' .'.green
21
21
  sleep 10
22
22
  end
23
23
  puts ''
@@ -27,8 +27,7 @@ class OpsManager
27
27
  def body
28
28
  @body ||= [ 'ignore_warnings=true' ]
29
29
  @body << errands_body
30
-
31
- @body
30
+ @body.join('&')
32
31
  end
33
32
 
34
33
  def opsman_api
@@ -36,18 +35,16 @@ class OpsManager
36
35
  end
37
36
 
38
37
  def errands_body
39
- staged_products_guids.collect do |product_guid|
40
- post_deploy_errands_body_for(product_guid)
41
- end
38
+ staged_products_guids.collect { |product_guid| post_deploy_errands_body_for(product_guid) }
42
39
  end
43
40
 
44
41
  def post_deploy_errands_body_for(product_guid)
45
42
  post_deploy_errands = post_deploy_errands_for(product_guid)
46
43
 
47
- unless post_deploy_errands.empty?
48
- post_deploy_errands.collect{ |e| "enabled_errands[#{product_guid}][post_deploy_errands][]=#{e}" }
49
- else
44
+ if post_deploy_errands.empty?
50
45
  "enabled_errands[#{product_guid}]{}"
46
+ else
47
+ post_deploy_errands.collect{ |e| "enabled_errands[#{product_guid}][post_deploy_errands][]=#{e}" }
51
48
  end
52
49
  end
53
50
 
@@ -65,7 +62,8 @@ class OpsManager
65
62
 
66
63
  def errands_for(product_guid)
67
64
  res = get_staged_products_errands(product_guid)
68
- if res.code == 200
65
+
66
+ if res.code == '200'
69
67
  JSON.parse(res.body)['errands']
70
68
  else
71
69
  []
@@ -44,9 +44,11 @@ class OpsManager
44
44
  print "====> Uploading product...".green
45
45
  if ProductDeployment.exists?(config.name, config.desired_version)
46
46
  puts "product already exists".green
47
- else
47
+ elsif config.filepath
48
48
  upload_product(config.filepath)
49
49
  puts "done".green
50
+ else
51
+ puts "no filepath provided, skipping product upload.".green
50
52
  end
51
53
  end
52
54
 
@@ -36,7 +36,7 @@ class OpsManager
36
36
  end
37
37
 
38
38
  def opsman_api
39
- @opsman_api = OpsManager::Api::Opsman.new
39
+ @opsman_api = OpsManager::Api::Opsman.new(silent: true)
40
40
  end
41
41
  end
42
42
  end
@@ -1,7 +1,7 @@
1
1
  class OpsManager
2
2
  class ProductTemplateGenerator
3
- OPS_MANAGER_PASSWORD_LENGTH = 20
4
- OPS_MANAGER_SECRET_LENGTH = 20
3
+ OPS_MANAGER_PASSWORD_LENGTH = 32
4
+ OPS_MANAGER_SECRET_LENGTH = 32
5
5
  OPS_MANAGER_SALT_LENGTH = 16
6
6
 
7
7
  attr_reader :product_name
@@ -25,6 +25,7 @@ class OpsManager
25
25
  delete_private_key_pem
26
26
  delete_product_version_from_properties
27
27
  add_merging_strategy_for_jobs
28
+ add_merging_strategy_for_job_properties
28
29
 
29
30
  { 'products' => [ "(( merge on identifier ))" , selected_product ] }
30
31
  end
@@ -109,6 +110,12 @@ class OpsManager
109
110
  selected_product['jobs'].unshift("(( merge on identifier ))")
110
111
  end
111
112
 
113
+ def add_merging_strategy_for_job_properties
114
+ selected_product['jobs'].each do |j|
115
+ j['properties'].unshift("(( merge on identifier ))") if j['properties']
116
+ end
117
+ end
118
+
112
119
  def delete_partitions
113
120
  delete_from_jobs('partitions')
114
121
  end
@@ -136,12 +143,9 @@ class OpsManager
136
143
  end
137
144
 
138
145
  def installation_settings
139
- parsed_installation_settings = JSON.parse(installation_settings_response.body)
140
- OpsManager::InstallationSettings.new(parsed_installation_settings)
141
- end
142
-
143
- def installation_settings_response
144
- OpsManager::Api::Opsman.new(silent: true).get_installation_settings
146
+ return @installation_settings if @installation_settings
147
+ res = OpsManager::Api::Opsman.new(silent: true).get_installation_settings
148
+ @installation_settings = JSON.parse(res.body)
145
149
  end
146
150
  end
147
151
  end
@@ -5,6 +5,14 @@ class OpsManager
5
5
  super(s.split('.').map { |e| e.to_i })
6
6
  end
7
7
 
8
+ def major
9
+ self[0]
10
+ end
11
+
12
+ def minor
13
+ self[1]
14
+ end
15
+
8
16
  def < x
9
17
  (self <=> x) < 0
10
18
  end
@@ -1,3 +1,3 @@
1
1
  class OpsManager
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
data/ops_manager.gemspec CHANGED
@@ -20,7 +20,6 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "bundler", "~> 1.10"
21
21
  spec.add_development_dependency "rake"
22
22
  spec.add_development_dependency "rspec"
23
- spec.add_development_dependency "vcr"
24
23
  spec.add_development_dependency "webmock"
25
24
  spec.add_development_dependency "byebug"
26
25
  spec.add_dependency "rbvmomi"
@@ -29,4 +28,5 @@ Gem::Specification.new do |spec|
29
28
  spec.add_dependency "net-ping"
30
29
  spec.add_dependency "cf-uaa-lib"
31
30
  spec.add_dependency "session_config"
31
+ spec.add_dependency "rubysl-shellwords"
32
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ops_manager_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - CompoZed
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-08 00:00:00.000000000 Z
11
+ date: 2016-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: vcr
56
+ name: webmock
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: webmock
70
+ name: byebug
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,13 +81,13 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: byebug
84
+ name: rbvmomi
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
- type: :development
90
+ type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rbvmomi
98
+ name: multipart-post
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: multipart-post
112
+ name: clamp
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -123,7 +123,7 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: clamp
126
+ name: net-ping
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: net-ping
140
+ name: cf-uaa-lib
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
@@ -151,7 +151,7 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: cf-uaa-lib
154
+ name: session_config
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - ">="
@@ -165,7 +165,7 @@ dependencies:
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
167
  - !ruby/object:Gem::Dependency
168
- name: session_config
168
+ name: rubysl-shellwords
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
@@ -196,7 +196,6 @@ files:
196
196
  - LICENSE.txt
197
197
  - README.md
198
198
  - Rakefile
199
- - bin/clean_tapes
200
199
  - bin/console
201
200
  - bin/setup
202
201
  - exe/ops_manager
@@ -214,7 +213,6 @@ files:
214
213
  - lib/ops_manager/errors.rb
215
214
  - lib/ops_manager/installation.rb
216
215
  - lib/ops_manager/installation_runner.rb
217
- - lib/ops_manager/installation_settings.rb
218
216
  - lib/ops_manager/logging.rb
219
217
  - lib/ops_manager/product_deployment.rb
220
218
  - lib/ops_manager/product_installation.rb
data/bin/clean_tapes DELETED
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- echo 'Replacing sensitive data in the source file with dummy data'
4
-
5
- USAGE="Usage ./clean_tapes SENSITIVE_INFO DUMMY_INFO"
6
-
7
- if [[ $# -ne 2 ]]; then
8
- echo "Illegal number of parameters"
9
- echo $USAGE
10
- exit
11
- fi
12
-
13
- pattern=$1
14
- replacement=$2
15
-
16
- pushd spec/fixtures/vcr_cassettes/
17
- ack -l $pattern | xargs perl -pi -E "s/$pattern/$replacement/g"
18
- popd
19
-
@@ -1,16 +0,0 @@
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