hula 0.7.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.
- checksums.yaml +7 -0
 - data/LEGAL +13 -0
 - data/lib/hula/bosh_director.rb +246 -0
 - data/lib/hula/bosh_manifest/job.rb +35 -0
 - data/lib/hula/bosh_manifest.rb +102 -0
 - data/lib/hula/cloud_foundry/service_broker.rb +36 -0
 - data/lib/hula/cloud_foundry.rb +308 -0
 - data/lib/hula/command_runner.rb +38 -0
 - data/lib/hula/helpers/socket_tools.rb +44 -0
 - data/lib/hula/helpers/timeout_tools.rb +27 -0
 - data/lib/hula/http_proxy_upstream_socks.rb +72 -0
 - data/lib/hula/service_broker/api.rb +123 -0
 - data/lib/hula/service_broker/catalog.rb +43 -0
 - data/lib/hula/service_broker/client.rb +69 -0
 - data/lib/hula/service_broker/errors.rb +24 -0
 - data/lib/hula/service_broker/http_json_client.rb +90 -0
 - data/lib/hula/service_broker/instance_binding.rb +30 -0
 - data/lib/hula/service_broker/plan.rb +32 -0
 - data/lib/hula/service_broker/service.rb +47 -0
 - data/lib/hula/service_broker/service_instance.rb +24 -0
 - data/lib/hula/socks4_proxy_ssh.rb +118 -0
 - data/lib/hula/version.rb +13 -0
 - data/lib/hula.rb +16 -0
 - metadata +235 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: c4c2d89b28b0e529f5236121502d5fc7da46a3db
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 380624ff929ed4d53a4383132ca4a49ac75fd893
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 6d9b0269dd701fd4a12b864315ae06af48438e02197359e01663466c2bb7312d47258f642d84ef36475356fe5de214c14d4a14b0d1c4a59cf8a417cabe247963
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 2fa96cadf394a54cc57e004a8e1d4ab21ce82f9702566482b70bb69dd15d39503538f713e94e8dc9c21871cf463f83aab7de32f8c355f494adbbd9f9d49447df
         
     | 
    
        data/LEGAL
    ADDED
    
    | 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2014-2015 Pivotal Software, Inc. All rights reserved.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Unauthorized use, copying or distribution of this source code via any
         
     | 
| 
      
 4 
     | 
    
         
            +
            medium is strictly prohibited without the express written consent of
         
     | 
| 
      
 5 
     | 
    
         
            +
            Pivotal Software, Inc.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 8 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 9 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
         
     | 
| 
      
 10 
     | 
    
         
            +
            IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
         
     | 
| 
      
 11 
     | 
    
         
            +
            CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         
     | 
| 
      
 12 
     | 
    
         
            +
            TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
         
     | 
| 
      
 13 
     | 
    
         
            +
            SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
         @@ -0,0 +1,246 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright (c) 2014-2015 Pivotal Software, Inc.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # All rights reserved.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
         
     | 
| 
      
 4 
     | 
    
         
            +
            # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
         
     | 
| 
      
 5 
     | 
    
         
            +
            # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 6 
     | 
    
         
            +
            # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         
     | 
| 
      
 7 
     | 
    
         
            +
            # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
         
     | 
| 
      
 8 
     | 
    
         
            +
            # USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            require 'tempfile'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require 'socket'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require 'uri'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            require 'hula/command_runner'
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            module Hula
         
     | 
| 
      
 19 
     | 
    
         
            +
              class BoshDirector
         
     | 
| 
      
 20 
     | 
    
         
            +
                class NoManifestSpecified < StandardError; end
         
     | 
| 
      
 21 
     | 
    
         
            +
                class DirectorPortNotOpen < StandardError; end
         
     | 
| 
      
 22 
     | 
    
         
            +
                class DirectorIsBroken < StandardError; end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def initialize(
         
     | 
| 
      
 25 
     | 
    
         
            +
                  target_url:,
         
     | 
| 
      
 26 
     | 
    
         
            +
                  username:,
         
     | 
| 
      
 27 
     | 
    
         
            +
                  password:,
         
     | 
| 
      
 28 
     | 
    
         
            +
                  manifest_path: nil,
         
     | 
| 
      
 29 
     | 
    
         
            +
                  command_runner: CommandRunner.new,
         
     | 
| 
      
 30 
     | 
    
         
            +
                  logger: default_logger
         
     | 
| 
      
 31 
     | 
    
         
            +
                )
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @target_url            = target_url
         
     | 
| 
      
 33 
     | 
    
         
            +
                  @username              = username
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @password              = password
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @default_manifest_path = manifest_path
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @command_runner        = command_runner
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @logger                = logger
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  target_and_login
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                # Should rely on `bosh status` and CPI, but currently Bosh Lite is
         
     | 
| 
      
 43 
     | 
    
         
            +
                # reporting 'vsphere' instead of 'warden'.
         
     | 
| 
      
 44 
     | 
    
         
            +
                def lite?
         
     | 
| 
      
 45 
     | 
    
         
            +
                  target_url.include? '192.168.50.4'
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def deploy(manifest_path = default_manifest_path)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  run_bosh("--deployment #{manifest_path} deploy")
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def delete_deployment(deployment_name, force: false)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  cmd = ["delete deployment #{deployment_name}"]
         
     | 
| 
      
 54 
     | 
    
         
            +
                  cmd << '-f' if force
         
     | 
| 
      
 55 
     | 
    
         
            +
                  run_bosh(cmd.join(' '))
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def run_errand(name, manifest_path: default_manifest_path)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  run_bosh("--deployment #{manifest_path} run errand #{name}")
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def recreate_all(jobs)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  jobs.each do |name|
         
     | 
| 
      
 64 
     | 
    
         
            +
                    recreate(name)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def recreate_instance(name, index)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  validate_job_instance_index(name, index)
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  run_bosh("recreate #{name} #{index}")
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def recreate(name)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  properties = job_properties(name)
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  instances = properties.fetch('instances')
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  instances.times do |instance_index|
         
     | 
| 
      
 80 
     | 
    
         
            +
                    run_bosh("recreate #{name} #{instance_index}")
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def stop(name, index)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  validate_job_instance_index(name, index)
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                  run_bosh("stop #{name} #{index} --force")
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                def start(name, index)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  validate_job_instance_index(name, index)
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                  run_bosh("start #{name} #{index} --force")
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def job_logfiles(job_name)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  tmpdir = Dir.tmpdir
         
     | 
| 
      
 98 
     | 
    
         
            +
                  run_bosh("logs #{job_name} 0 --job --dir #{tmpdir}")
         
     | 
| 
      
 99 
     | 
    
         
            +
                  tarball = Dir[File.join(tmpdir, job_name.to_s + '*.tgz')].last
         
     | 
| 
      
 100 
     | 
    
         
            +
                  output = command_runner.run("tar tf #{tarball}")
         
     | 
| 
      
 101 
     | 
    
         
            +
                  lines = output.split(/\n+/)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  filepaths = lines.map { |f| Pathname.new(f) }
         
     | 
| 
      
 103 
     | 
    
         
            +
                  logpaths = filepaths.select { |f| f.extname == '.log' }
         
     | 
| 
      
 104 
     | 
    
         
            +
                  logpaths.map(&:basename).map(&:to_s)
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                def has_logfiles?(job_name, logfile_names)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  logs = job_logfiles(job_name)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  logfile_names.each do |logfile_name|
         
     | 
| 
      
 110 
     | 
    
         
            +
                    return false unless logs.include?(logfile_name)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
                  true
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                def deployment_names
         
     | 
| 
      
 116 
     | 
    
         
            +
                  deployments = run_bosh('deployments')
         
     | 
| 
      
 117 
     | 
    
         
            +
                  # [\n\r]+ a new line,
         
     | 
| 
      
 118 
     | 
    
         
            +
                  # \s* maybe followed by whitespace,
         
     | 
| 
      
 119 
     | 
    
         
            +
                  # \| followed by a pipe,
         
     | 
| 
      
 120 
     | 
    
         
            +
                  # \s+ followed by whitespace,
         
     | 
| 
      
 121 
     | 
    
         
            +
                  # ([^\s]+) followed some characters (ie, not whitespace, or a pipe) — this is the match
         
     | 
| 
      
 122 
     | 
    
         
            +
                  first_column = deployments.scan(/[\n\r]+\s*\|\s+([^\s\|]+)/).flatten
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                  first_column.drop(1) # without header
         
     | 
| 
      
 125 
     | 
    
         
            +
                end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                # Parses output of `bosh vms` like below, getting an array of IPs for a job name
         
     | 
| 
      
 128 
     | 
    
         
            +
                # +------------------------------------+---------+---------------+--------------+
         
     | 
| 
      
 129 
     | 
    
         
            +
                # | Job/index                          | State   | Resource Pool | IPs          |
         
     | 
| 
      
 130 
     | 
    
         
            +
                # +------------------------------------+---------+---------------+--------------+
         
     | 
| 
      
 131 
     | 
    
         
            +
                # | api_z1/0                           | running | large_z1      | 10.244.0.138 |
         
     | 
| 
      
 132 
     | 
    
         
            +
                # ...
         
     | 
| 
      
 133 
     | 
    
         
            +
                def ips_for_job(job, deployment_name = nil)
         
     | 
| 
      
 134 
     | 
    
         
            +
                  output = run_bosh("vms #{deployment_name}")
         
     | 
| 
      
 135 
     | 
    
         
            +
                  deployments = output.split(/^Deployment/)
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  job_ip_map = {}
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  deployments.each do |deployment|
         
     | 
| 
      
 140 
     | 
    
         
            +
                    rows = deployment.split("\n")
         
     | 
| 
      
 141 
     | 
    
         
            +
                    row_cols = rows.map { |row| row.split('|') }
         
     | 
| 
      
 142 
     | 
    
         
            +
                    job_cols = row_cols.  select { |cols| cols.length == 5 } # match job boxes
         
     | 
| 
      
 143 
     | 
    
         
            +
                    job_ip_pairs = job_cols.map { |cols| [cols[1].strip, cols.last.strip] }
         
     | 
| 
      
 144 
     | 
    
         
            +
                    jobs_with_real_ips = job_ip_pairs.select { |pairs| pairs.last =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ }
         
     | 
| 
      
 145 
     | 
    
         
            +
                    # converts eg   cf-redis-broker/2  to cf-redis-broker
         
     | 
| 
      
 146 
     | 
    
         
            +
                    jobs_without_instance_numbers = jobs_with_real_ips.map { |pair| [pair.first.gsub(/\/.*/, ''), pair.last] }
         
     | 
| 
      
 147 
     | 
    
         
            +
                    jobs_without_instance_numbers.each do |job|
         
     | 
| 
      
 148 
     | 
    
         
            +
                      name, ip = job
         
     | 
| 
      
 149 
     | 
    
         
            +
                      job_ip_map[name] ||= []
         
     | 
| 
      
 150 
     | 
    
         
            +
                      job_ip_map[name] << ip
         
     | 
| 
      
 151 
     | 
    
         
            +
                    end
         
     | 
| 
      
 152 
     | 
    
         
            +
                  end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                  job_ip_map.fetch(job, [])
         
     | 
| 
      
 155 
     | 
    
         
            +
                end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                private
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                attr_reader :target_url, :username, :password, :command_runner, :logger
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                def job_properties(job_name)
         
     | 
| 
      
 162 
     | 
    
         
            +
                  manifest.fetch('jobs').find { |job| job.fetch('name') == job_name }.tap do |properties|
         
     | 
| 
      
 163 
     | 
    
         
            +
                    fail ArgumentError.new('Job not found in manifest') unless properties
         
     | 
| 
      
 164 
     | 
    
         
            +
                  end
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def validate_job_instance_index(job_name, index)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  properties = job_properties(job_name)
         
     | 
| 
      
 169 
     | 
    
         
            +
                  instances = properties.fetch('instances')
         
     | 
| 
      
 170 
     | 
    
         
            +
                  fail ArgumentError.new('Index out of range') unless (0...instances).include? index
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                def default_logger
         
     | 
| 
      
 174 
     | 
    
         
            +
                  @default_logger ||= begin
         
     | 
| 
      
 175 
     | 
    
         
            +
                    STDOUT.sync = true
         
     | 
| 
      
 176 
     | 
    
         
            +
                    require 'logger'
         
     | 
| 
      
 177 
     | 
    
         
            +
                    Logger.new(STDOUT)
         
     | 
| 
      
 178 
     | 
    
         
            +
                  end
         
     | 
| 
      
 179 
     | 
    
         
            +
                end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                def default_manifest_path?
         
     | 
| 
      
 182 
     | 
    
         
            +
                  !!@default_manifest_path
         
     | 
| 
      
 183 
     | 
    
         
            +
                end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                def default_manifest_path
         
     | 
| 
      
 186 
     | 
    
         
            +
                  fail NoManifestSpecified unless default_manifest_path?
         
     | 
| 
      
 187 
     | 
    
         
            +
                  @default_manifest_path
         
     | 
| 
      
 188 
     | 
    
         
            +
                end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                def manifest
         
     | 
| 
      
 191 
     | 
    
         
            +
                  YAML.load_file(default_manifest_path)
         
     | 
| 
      
 192 
     | 
    
         
            +
                end
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                def target_and_login
         
     | 
| 
      
 195 
     | 
    
         
            +
                  run_bosh("target #{target_url}")
         
     | 
| 
      
 196 
     | 
    
         
            +
                  run_bosh("deployment #{default_manifest_path}") if default_manifest_path?
         
     | 
| 
      
 197 
     | 
    
         
            +
                  run_bosh("login #{username} #{password}")
         
     | 
| 
      
 198 
     | 
    
         
            +
                end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                def run_bosh(cmd)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  command = "bosh -v -n --config '#{bosh_config_path}' #{cmd}"
         
     | 
| 
      
 202 
     | 
    
         
            +
                  logger.info(command)
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                  command_runner.run(command)
         
     | 
| 
      
 205 
     | 
    
         
            +
                rescue CommandFailedError => e
         
     | 
| 
      
 206 
     | 
    
         
            +
                  logger.error(e.message)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  health_check!
         
     | 
| 
      
 208 
     | 
    
         
            +
                  raise e
         
     | 
| 
      
 209 
     | 
    
         
            +
                end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                def bosh_config_path
         
     | 
| 
      
 212 
     | 
    
         
            +
                  # We should keep a reference to the tempfile, otherwise,
         
     | 
| 
      
 213 
     | 
    
         
            +
                  # when the object gets GC'd, the tempfile is deleted.
         
     | 
| 
      
 214 
     | 
    
         
            +
                  @bosh_config_tempfile ||= Tempfile.new('bosh_config')
         
     | 
| 
      
 215 
     | 
    
         
            +
                  @bosh_config_tempfile.path
         
     | 
| 
      
 216 
     | 
    
         
            +
                end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                def health_check!
         
     | 
| 
      
 219 
     | 
    
         
            +
                  check_port!
         
     | 
| 
      
 220 
     | 
    
         
            +
                  check_deployments!
         
     | 
| 
      
 221 
     | 
    
         
            +
                end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                def target_uri
         
     | 
| 
      
 224 
     | 
    
         
            +
                  @target_uri ||= URI.parse(target_url)
         
     | 
| 
      
 225 
     | 
    
         
            +
                end
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                def check_deployments!
         
     | 
| 
      
 228 
     | 
    
         
            +
                  http = Net::HTTP.new(target_uri.host, target_uri.port)
         
     | 
| 
      
 229 
     | 
    
         
            +
                  http.use_ssl = target_uri.scheme == 'https'
         
     | 
| 
      
 230 
     | 
    
         
            +
                  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                  response = http.get('/deployments')
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                  unless response.is_a? Net::HTTPSuccess
         
     | 
| 
      
 235 
     | 
    
         
            +
                    fail DirectorIsBroken, "Failed to GET /deployments from #{target_uri}. Returned:\n\n#{response.to_hash}\n\n#{response.body}"
         
     | 
| 
      
 236 
     | 
    
         
            +
                  end
         
     | 
| 
      
 237 
     | 
    
         
            +
                end
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
                def check_port!
         
     | 
| 
      
 240 
     | 
    
         
            +
                  socket = TCPSocket.new(target_uri.host, target_uri.port)
         
     | 
| 
      
 241 
     | 
    
         
            +
                  socket.close
         
     | 
| 
      
 242 
     | 
    
         
            +
                rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
         
     | 
| 
      
 243 
     | 
    
         
            +
                  raise DirectorPortNotOpen, "Cannot connect to #{target_uri.host}:#{target_uri.port}"
         
     | 
| 
      
 244 
     | 
    
         
            +
                end
         
     | 
| 
      
 245 
     | 
    
         
            +
              end
         
     | 
| 
      
 246 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright (c) 2014-2015 Pivotal Software, Inc.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # All rights reserved.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
         
     | 
| 
      
 4 
     | 
    
         
            +
            # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
         
     | 
| 
      
 5 
     | 
    
         
            +
            # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 6 
     | 
    
         
            +
            # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         
     | 
| 
      
 7 
     | 
    
         
            +
            # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
         
     | 
| 
      
 8 
     | 
    
         
            +
            # USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module Hula
         
     | 
| 
      
 12 
     | 
    
         
            +
              class BoshManifest
         
     | 
| 
      
 13 
     | 
    
         
            +
                class Job
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(job_hash)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @job_hash = job_hash
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def static_ips
         
     | 
| 
      
 19 
     | 
    
         
            +
                    first_network.fetch('static_ips')
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def properties
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @job_hash.fetch('properties')
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  private
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  attr_reader :job_hash
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def first_network
         
     | 
| 
      
 31 
     | 
    
         
            +
                    job_hash.fetch('networks').first
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,102 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright (c) 2014-2015 Pivotal Software, Inc.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # All rights reserved.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
         
     | 
| 
      
 4 
     | 
    
         
            +
            # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
         
     | 
| 
      
 5 
     | 
    
         
            +
            # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 6 
     | 
    
         
            +
            # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         
     | 
| 
      
 7 
     | 
    
         
            +
            # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
         
     | 
| 
      
 8 
     | 
    
         
            +
            # USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 12 
     | 
    
         
            +
            require 'hula/bosh_manifest/job'
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            module Hula
         
     | 
| 
      
 15 
     | 
    
         
            +
              class BoshManifest
         
     | 
| 
      
 16 
     | 
    
         
            +
                class NoManifestPathGiven < StandardError; end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                attr_reader :path
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def initialize(manifest_yaml, path: nil)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @manifest_hash = YAML.load(manifest_yaml)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @path = path
         
     | 
| 
      
 23 
     | 
    
         
            +
                  fail 'Invalid manifest' unless manifest_hash.is_a?(Hash)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def self.from_file(path)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  new(File.read(path), path: path)
         
     | 
| 
      
 28 
     | 
    
         
            +
                rescue Errno::ENOENT
         
     | 
| 
      
 29 
     | 
    
         
            +
                  raise "Could not open the manifest file: '#{path}'"
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def property(property_name)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  components = property_components(property_name)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  traverse_properties(components)
         
     | 
| 
      
 35 
     | 
    
         
            +
                rescue KeyError
         
     | 
| 
      
 36 
     | 
    
         
            +
                  raise "Could not find property '#{property_name}' in #{properties.inspect}"
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def set_property(property_name, value)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  components = property_components(property_name)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  traverse_properties_and_set(properties, components, value)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  save
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def deployment_name
         
     | 
| 
      
 46 
     | 
    
         
            +
                  manifest_hash.fetch('name')
         
     | 
| 
      
 47 
     | 
    
         
            +
                rescue KeyError
         
     | 
| 
      
 48 
     | 
    
         
            +
                  raise "Could not find deployment name in #{manifest_hash.inspect}"
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def job(job_name)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  jobs = manifest_hash.fetch('jobs')
         
     | 
| 
      
 53 
     | 
    
         
            +
                  job = jobs.detect { |j| j.fetch('name') == job_name }
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  fail "Could not find job name '#{job_name}' in job list: #{jobs.inspect}" if job.nil?
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  Job.new(job)
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                def resource_pools
         
     | 
| 
      
 61 
     | 
    
         
            +
                  manifest_hash.fetch('resource_pools')
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                private
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                attr_reader :manifest_hash
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def save
         
     | 
| 
      
 69 
     | 
    
         
            +
                  unless path
         
     | 
| 
      
 70 
     | 
    
         
            +
                    fail NoManifestPathGiven, 'Cannot save manifest without providing a path'
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  File.write(path, manifest_hash.to_yaml)
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def properties
         
     | 
| 
      
 76 
     | 
    
         
            +
                  manifest_hash.fetch('properties')
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def property_components(property_name)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  property_name.split('.')
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                def traverse_properties(components)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  components.inject(properties) do |current_node, component|
         
     | 
| 
      
 85 
     | 
    
         
            +
                    current_node.fetch(component)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def traverse_properties_and_set(properties, components, value)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  component = components.shift
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                  if components.any?
         
     | 
| 
      
 93 
     | 
    
         
            +
                    unless properties.key?(component)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      fail "Could not find property '#{component}' in #{properties.inspect}"
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
                    traverse_properties_and_set(properties[component], components, value)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  else
         
     | 
| 
      
 98 
     | 
    
         
            +
                    properties[component] = value
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Copyright (c) 2014-2015 Pivotal Software, Inc.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # All rights reserved.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
         
     | 
| 
      
 4 
     | 
    
         
            +
            # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
         
     | 
| 
      
 5 
     | 
    
         
            +
            # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 6 
     | 
    
         
            +
            # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         
     | 
| 
      
 7 
     | 
    
         
            +
            # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
         
     | 
| 
      
 8 
     | 
    
         
            +
            # USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            require 'uri'
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            module Hula
         
     | 
| 
      
 14 
     | 
    
         
            +
              class CloudFoundry
         
     | 
| 
      
 15 
     | 
    
         
            +
                class ServiceBroker
         
     | 
| 
      
 16 
     | 
    
         
            +
                  attr_reader :name
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def initialize(name:, url:)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @name = name
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @url = url
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def ==(other)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    is_a?(other.class) && @name == other.name && @url == other.url
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def uri
         
     | 
| 
      
 28 
     | 
    
         
            +
                    URI.parse(url)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  protected
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  attr_reader :url
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     |