diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9aea2dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +.kitchen +*.sw? +*~ +.env.sh diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b661574 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# 0.1.1 / 2015-06-04 +* Destroy working +* Password updates +* Documentation updates +* Cleanup and refactoring + +# 0.1.0 / 2015-05-xx + +* Initial release! Woo! + +[@hh]: https://github.com/hh +[@taylor]: https://github.com/taylor +[@vulk]: https://github.com/vulk diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..5d91b6f --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +# Encoding: UTF-8 + +source 'https://rubygems.org' + +# Specify your gem's dependencies in kitchen-rackspace.gemspec +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d08c02f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,16 @@ +Authors:: Chris McClimans () +Authors:: Taylor Carpenter () + +Copyright (c) 2015 Vulk + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 4cb68de..54bfdfb 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,236 @@ -# kitchen-vcair +Kitchen::Vcair +================== -This repo is for tracking work on the Test Kitchen driver for vCloud Air. +A vCloud Air Servers driver for Test Kitchen! -A driver for vCloud Air exists and can be found here: -https://github.com/vulk/kitchen-vcair +Originally based on the [Rackspace driver](https://github.com/test-kitchen/kitchen-rackspace) (from [Jonathan Hartman's](https://github.com/RoboticCheese)) -This work is being refactored and will be posted here. You may follow -the work in the "refactor" branch of this repository. +Installation +------------ + +Add this line to your application's Gemfile: + + gem 'kitchen-vcair' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install kitchen-vcair + +Usage +----- + +Provide, at a minimum, the required driver options in your `.kitchen.yml` file: + + driver: + name: vcair + vcair_username: [Your vCloud Air username] + vcair_password: [Your vCloud Air password] + vcair_api_host: [Your vCloud Air API Host] + vcair_vm_password: [Initial system password used for bootstrap] + vcair_org: [Your vCloud Air Organization ID] + require_chef_omnibus: [e.g. 'true' or a version number if you need Chef] + platforms: + - name: [A PLATFORM NAME, e.g. 'centos-6'] + +By default, the driver will spawn a 1GB server on the base image for your +specified platform. Additional, optional overrides can be provided: + + image_id: [SERVER IMAGE ID] + vcair_net: [ROUTED_NETWORK_WITH_ACCESS_TO_CHEF_SERVER] + flavor_id: [SERVER FLAVOR ID] + server_name: [A FRIENDLY SERVER NAME] + public_key_path: [PATH TO YOUR PUBLIC SSH KEY] + wait_for: [NUM OF SECONDS TO WAIT BEFORE TIMING OUT, DEFAULT 600] + no_ssh_tcp_check: [DEFAULTS TO false, SKIPS TCP CHECK WHEN true] + no_ssh_tcp_check_sleep: [NUM OF SECONDS TO SLEEP IF no_ssh_tcp_check IS SET] + +If targeting windows, be sure to add ```transport``` and ```verifier`` options: + + transport: + name: winrm + connection_retries: 15 + connection_retry_sleep: 15 + max_wait_until_ready: 600 + username: 'administrator' + password: 'Password1' + verifier: + name: pester + +You also have the option of providing some configs via environment variables: + + export VCAIR_API_HOST='API_HOST.vchs.vmware.com' + export VCAIR_VM_PASSWORD='SOME_INITIAL_PASSWORD' + export VCAIR_ORG='MNNNNNNNNN-NNNN' + export VCAIR_USERNAME='YOUR_USERNAME' + export VCAIR_PASSWORD='YOUR_PASSWORD' + +Execution: + + KITCHEN_YAML=.kitchen.vcair.yml kitchen test + +Known Issues / Work Arounds +--------------------------- + +##### ssh authentication happens via password only and public_key auth isn't available + +You must populate :vcair_vm_password in your kitchen.yml + +##### vCloud Air VMs default to an isolated network + +You must populate :vcair_net _OR_ create a non-isolated network (it will use the first available) + +##### SSH access to nodes requires default firewall policy open port 22 + +You may find it easier to use a provisioning node within the same network you nodes will be provisioned on + +##### Windows images do not turn on winrm by default + +##### Windows images force login via rdp console requiring a password change + +Both of these can be worked around by including a ```:customization_script``` that sets the password manually, removes the expiry, opens the firewall for and enables winrm. + +```yaml +platforms: + - name: win2012-chef12 + driver_config: + image_id: W2K12-STD-64BIT + size: 2gb + customization_script: 'install-winrm-vcair.bat' +``` + +```bat +@echo off + +@rem First Boot... +if “%1%” == “precustomization” ( + +echo Do precustomization tasks +@rem during this boot the hostname is set, which requires a reboot + +@rem we also enable winrm over http, plaintext, long timeout, more memory etc + +cmd.exe /c winrm quickconfig -q +cmd.exe /c winrm quickconfig -transport:http +cmd.exe /c winrm set winrm/config @{MaxTimeoutms="1800000"} +cmd.exe /c winrm set winrm/config/winrs @{MaxMemoryPerShellMB="300"} +cmd.exe /c winrm set winrm/config/service @{AllowUnencrypted="true"} +cmd.exe /c winrm set winrm/config/service/auth @{Basic="true"} +cmd.exe /c winrm set winrm/config/client/auth @{Basic="true"} +cmd.exe /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} + +@rem Make sure winrm is off for this boot, but enabled on next +@rem as we don't want a tcp connection available until we are +@rem past postcustomization + +cmd.exe /c net stop winrm +cmd.exe /c sc config winrm start= auto + +@rem make sure the default on password age is unlimited +@rem this ensures we don't have a password change forced on us +cmd.exe /c net accounts /maxpwage:unlimited + +@rem write out a timestamp for this first boot / customization completes +echo %DATE% %TIME% > C:\vm-is-customized + +) else if “%1%” == “postcustomization” ( + +@rem Second Boot / start winrm, just incase, and fix firewall + +cmd.exe /c net start winrm +cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes +cmd.exe /c netsh firewall add portopening TCP 5985 "Port 5985 for WinRM" + +@rem Password Setting and Autologin currently seem broken +@rem when done via the API, so we MUST set it in the postcustomization phase +cmd.exe /c net user administrator Password1 + +@rem in some environments we found the need to specify a DNS address +@rem cmd.exe /c netsh interface ipv4 add dnsserver "Ethernet" address=8.8.8.8 +@rem cmd.exe /c netsh interface ipv4 add dnsserver "Ethernet0" address=8.8.8.8 + +@rem this is our 'ready' boot, password and winrm should be up +echo %DATE% %TIME% > C:\vm-is-ready + +) +``` + +Feature Requests +---------------- + +##### Non CentOS64-64BIT image support + +CentoOS64-64BIT is the only image that allowed setting the password +CentOS and Ubuntu failed to set the password correctly + +##### NAT support + +Only routed networks supported for now + +Walkthru of kitchen-vcair for linux guests +------------------------------------------ + +* [github.com/vulk/kitchen-vcair](https://www.youtube.com/watch?v=5srDko69XJ0&t=03) +* [vchs.vmware.com](https://www.youtube.com/watch?v=5srDko69XJ0&t=15) +* [Walkthrough steps for cloning, building gem](https://www.youtube.com/watch?v=5srDko69XJ0&t=30) +* [git clone git@github.com:/vulk/kitchen-vcair.git](https://www.youtube.com/watch?v=5srDko69XJ0&t=68) +* [cd kitchen-vcair](https://www.youtube.com/watch?v=5srDko69XJ0&t=94) +* [gem build kitchen-vcair.gemspec](https://www.youtube.com/watch?v=5srDko69XJ0&t=100) +* [gem install ./kitchen-vcair-0.1.0.gem](https://www.youtube.com/watch?v=5srDko69XJ0&t=120) +* [quick look through code ](https://www.youtube.com/watch?v=5srDko69XJ0&t=126) +* [git clone git@github.com:chef-cookbooks/httpd.git ](https://www.youtube.com/watch?v=5srDko69XJ0&t=173) +* [walkthrough of .kitchen.vcair.yml](https://www.youtube.com/watch?v=5srDko69XJ0&t=199) +* [walkthrough of environment variables](https://www.youtube.com/watch?v=5srDko69XJ0&t=247) +* [kitchen test](https://www.youtube.com/watch?v=5srDko69XJ0&t=282) +* [vchs.vmware.com virtualmachine list, showing creation of helloworldtest VM](https://www.youtube.com/watch?v=5srDko69XJ0&t=296) +* [knife vcair server list showing creation of helloworld test VM](https://www.youtube.com/watch?v=5srDko69XJ0&t=326) +* [instance provisionied, waiting for ssh](https://www.youtube.com/watch?v=5srDko69XJ0&t=355) +* [ssh available, installing chef-client](https://www.youtube.com/watch?v=5srDko69XJ0&t=400) +* [chef-client starting](https://www.youtube.com/watch?v=5srDko69XJ0&t=499) +* [chef-client finished, apache install completed](https://www.youtube.com/watch?v=5srDko69XJ0&t=515) +* [Kitchen Setup and Verify](https://www.youtube.com/watch?v=5srDko69XJ0&t=516) +* [Kitichen Destroy](https://www.youtube.com/watch?v=5srDko69XJ0&t=517) +* [Kitchen is finished](https://www.youtube.com/watch?v=5srDko69XJ0&t=525) +* [vchs.vmware.com and knife vcair shows vm destroyed](https://www.youtube.com/watch?v=5srDko69XJ0&t=530) + + +Walkthru of kitchen-vcair for windows guests +------------------------------------------ + +* [vmwair-vcair.env.example](https://www.youtube.com/watch?v=k8OZII4UGZs&t=09) +* [.kitchen.vcair.yml](https://www.youtube.com/watch?v=k8OZII4UGZs&t=20) +* [.yml / platforms:customization_script note](https://www.youtube.com/watch?v=k8OZII4UGZs&t=30) +* [customization_script install-winrm-vcair.bat](https://www.youtube.com/watch?v=k8OZII4UGZs&t=37) +* [git clone opscode-cookbooks/iis](https://www.youtube.com/watch?v=k8OZII4UGZs&t=54) +* [start coping files into iis cookbooks](https://www.youtube.com/watch?v=k8OZII4UGZs&t=60) +* [Add kitchen-vcair and kitchen-pester to the Gemfile](https://www.youtube.com/watch?v=k8OZII4UGZs&t=98) +* [bundle install kitchen vcair and pester](https://www.youtube.com/watch?v=k8OZII4UGZs&t=120) +* [KITCHEN_YAML=.kitchen.vcair.yml bundle exec kitchen verify](https://www.youtube.com/watch?v=k8OZII4UGZs&t=150) +* [Server is allocated.](https://www.youtube.com/watch?v=k8OZII4UGZs&t=270) +* ['pre'/'post' customization script ](https://www.youtube.com/watch?v=k8OZII4UGZs&t=300) +* ['pre' customization reboot ](https://www.youtube.com/watch?v=k8OZII4UGZs&t=412) +* ['post' customization boot ](https://www.youtube.com/watch?v=k8OZII4UGZs&t=440) +* [winrm is online](https://www.youtube.com/watch?v=k8OZII4UGZs&t=555) +* [installing chef omnibus](https://www.youtube.com/watch?v=k8OZII4UGZs&t=560) +* [chef-client starts](https://www.youtube.com/watch?v=k8OZII4UGZs&t=600) +* [iis:default recipe runs](https://www.youtube.com/watch?v=k8OZII4UGZs&t=630) +* [verification via kitche-pester](https://www.youtube.com/watch?v=k8OZII4UGZs&t=647) +* [kitchen verify complete!](https://www.youtube.com/watch?v=k8OZII4UGZs&t=660) +* [iis default web page via links](https://www.youtube.com/watch?v=k8OZII4UGZs&t=695) +* [kitchen verify again](https://www.youtube.com/watch?v=k8OZII4UGZs&t=710) +* [kitchen destroy](https://www.youtube.com/watch?v=k8OZII4UGZs&t=735) + +Contributing +------------ + +1. Fork it +2. `bundle install` +3. Create your feature branch (`git checkout -b my-new-feature`) +4. `bundle exec rake` must pass +5. Commit your changes (`git commit -am 'Add some feature'`) +6. Push to the branch (`git push origin my-new-feature`) +7. Create new Pull Request diff --git a/examples/httpd-cookbook/.kitchen.vcair.yml b/examples/httpd-cookbook/.kitchen.vcair.yml new file mode 100644 index 0000000..a93f1c3 --- /dev/null +++ b/examples/httpd-cookbook/.kitchen.vcair.yml @@ -0,0 +1,26 @@ +--- +driver_config: + vcair_username: <%= ENV['VCAIR_USERNAMEX'] %> + vcair_password: <%= ENV['VCAIR_PASSWORDX'] %> + vcair_api_host: <%= ENV['VCAIR_API_HOSTX'] %> + vcair_org: <%= ENV['VCAIR_ORGX'] %> + +provisioner: + name: chef_zero + require_chef_omnibus: latest + +platforms: +- name: centos-6.4 + driver_plugin: vcair + driver_config: + size: 2gb + image: centos-6-4-x64 + vcair_ssh_password: <%= ENV['VCAIR_SSH_PASSWORDX'] %> + +suites: + # + # hello_world_test + # + - name: hello_world_test + run_list: + - recipe[hello_world_test] diff --git a/kitchen-vcair.gemspec b/kitchen-vcair.gemspec new file mode 100644 index 0000000..fde858c --- /dev/null +++ b/kitchen-vcair.gemspec @@ -0,0 +1,37 @@ +# Encoding: UTF-8 + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'kitchen/driver/vcair_version' + +Gem::Specification.new do |spec| + spec.name = 'kitchen-vcair' + spec.version = Kitchen::Driver::VCAIR_VERSION + spec.authors = ['Taylor Carpenter', 'Chris McClimans'] + spec.email = %w(wolfpack+c+t@vulk.co) + spec.description = 'A Test Kitchen vCloud Air driver' + spec.summary = 'A Test Kitchen vCloud Air driver built on Fog' + spec.homepage = 'https://github.com/vulk/kitchen-vcair' + spec.license = 'Apache' + + spec.files = `git ls-files -z`.split("\x0") + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = %w(lib) + + spec.required_ruby_version = '>= 1.9.3' + + spec.add_dependency 'test-kitchen', '~> 1.1' + spec.add_dependency 'pester' + spec.add_dependency 'fog', '~> 1.18' + + spec.add_development_dependency 'bundler', '~> 1.0' + spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'rubocop', '~> 0.29' + spec.add_development_dependency 'cane', '~> 2.6' + spec.add_development_dependency 'countloc', '~> 0.4' + spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'simplecov', '~> 0.9' + spec.add_development_dependency 'simplecov-console', '~> 0.2' + spec.add_development_dependency 'coveralls', '~> 0.8' +end diff --git a/lib/kitchen/driver/vcair.rb b/lib/kitchen/driver/vcair.rb new file mode 100644 index 0000000..395010c --- /dev/null +++ b/lib/kitchen/driver/vcair.rb @@ -0,0 +1,434 @@ +# Encoding: UTF-8 +# +# Authors:: Chris McClimans () +# Authors:: Taylor Carpenter () +# +# Copyright (C) 2015, Vulk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'benchmark' +require 'fog' +require 'kitchen' +require 'etc' +require 'socket' +require 'pp' + +module Kitchen + module Driver + # vCloud Air driver for Kitchen. + # + class Vcair < Kitchen::Driver::Base + default_config :version, 'v2' + default_config :flavor_id, 'performance1-1' + default_config :username, 'root' + default_config :port, '22' + default_config :wait_for, 600 +# default_config :no_ssh_tcp_check, false + default_config :no_ssh_tcp_check, true + default_config :no_ssh_tcp_check_sleep, 120 + default_config :servicenet, false + default_config(:image_id) { |driver| driver.default_image } + default_config(:server_name) { |driver| driver.default_name } + default_config :networks, nil + default_config :vcair_show_progress, false + + default_config :vcair_username do + ENV['VCAIR_USERNAME'] + end + + default_config :vcair_password do + ENV['VCAIR_PASSWORD'] + end + + default_config :vcair_api_host do + ENV['VCAIR_API_HOST'] + end + + default_config :vcair_org do + ENV['VCAIR_ORG'] + end + + # default_config :vcloud_director_username do + # config[:vcair_username] || ENV['VCAIR_USERNAME'] + # end + # + # default_config :vcloud_director_password do + # config[:vcair_password] || ENV['VCAIR_PASSWORD'] + # end + + # default_config :vcloud_director_api_host do + # config[:vcair_api_host] || ENV['VCAIR_API_HOST'] + # end + + # default_config :vcloud_director_org do + # config[:vcair_org] || ENV['VCAIR_ORG'] + # end + + default_config :vcair_vm_password do + ENV['VCAIR_VM_PASSWORD'] + end + + required_config :vcair_username + required_config :vcair_password + required_config :vcair_api_host + required_config :vcair_org + required_config :image_id + required_config :vcair_vm_password + #required_config :public_key_path + + def initialize(config) + super + Fog.timeout = config[:wait_for].to_i + + # NOTE: this is for compatibility with fog + config[:vcloud_director_org] = config[:vcair_org] || ENV['VCAIR_ORG'] + config[:vcloud_director_api_host] = config[:vcair_api_host] || ENV['VCAIR_API_HOST'] + config[:vcloud_director_username] = config[:vcair_username] || ENV['VCAIR_USERNAME'] + config[:vcloud_director_password] = config[:vcair_password] || ENV['VCAIR_PASSWORD'] + end + + def create(state) + server = create_server + state[:server_id] = server.id + info("vCloud Air instance <#{state[:server_id]}> created.") + server.wait_for { ready? } + puts '(server ready)' + state[:hostname] = hostname(server) + state[:password] = config[:vcair_vm_password] + # would be better to do a tcp_check here + sleep(500) + # tcp_check(state) + rescue Fog::Errors::Error, Excon::Errors::Error => ex + raise ActionFailed, ex.message + end + + + def destroy_machine(action_handler, machine_spec, machine_options) + server = server_for(machine_spec) + if server && server.status != 'archive' # TODO: does Vcair do archive? + action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do + #NOTE: currently doing 1 vm for 1 vapp + vapp = vdc.vapps.get_by_name(machine_spec.name) + if vapp + vapp.power_off + vapp.undeploy + vapp.destroy + else + Chef::Log.warn "No VApp named '#{server_name}' was found." + end + end + end + machine_spec.location = nil + strategy = convergence_strategy_for(machine_spec, machine_options) + strategy.cleanup_convergence(action_handler, machine_spec) + end + + def destroy(state) + return if state[:server_id].nil? + begin + vapp = vdc.vapps.get(state[:server_id]) + rescue Fog::Compute::VcloudDirector::Forbidden => e + vapp = nil + rescue Exception => e + info("VApp <#{state[:server_id]}> not found!") + end + if vapp + vapp.power_off + vapp.undeploy + vapp.destroy + info("VApp <#{state[:server_id]}> destroyed.") + else + warn("VApp <#{state[:server_id]}> not found!") + end + state.delete(:server_id) + state.delete(:hostname) + end + + def default_image + 'CentOS64-64BIT' + end + + # Generate what should be a unique server name up to 63 total chars + # Base name: 3 + # Username: 3 + # Hostname: 3 + # Random string: 3 + # Separators: 3 + # ================ + # Total: 15 (3x5) + # FIXME: Windows only supports 15 character hostnames + def default_name + [ + instance.name.gsub(/\W/, '')[0..2], + (Etc.getlogin || 'nologin').gsub(/\W/, '')[0..2], + Socket.gethostname.gsub(/\W/, '')[0..2], + Array.new(3) { rand(36).to_s(36) }.join + ].join('-') + end + + private + + def compute + server_def = { provider: 'vclouddirector' } # fog driver for vcair + opts = [:vcair_username, :vcair_password, :vcair_api_host] + opts.each do |opt| + # map vcair to vcloud_director fog naming + case opt + when :vcair_username + username = [config[opt], config[:vcair_org]].join('@') + server_def[:vcloud_director_username] = username + when :vcair_password + server_def[:vcloud_director_password] = config[opt] + when :vcair_api_host + server_def[:vcloud_director_host] = config[opt] + when :vcair_api_version + server_def[:vcloud_director_api_version] = config[opt] + when :vcair_show_progress + server_def[:vcloud_director_show_progress] = config[opt] + else + server_def[opt] = config[opt] + end + end + begin + Fog::Compute.new(server_def) + rescue Excon::Errors::Unauthorized => e + error_message = "Connection failure, please check your username and password." + Chef::Log.error(error_message) + raise "#{e.message}. #{error_message}" + rescue Excon::Errors::SocketError => e + error_message = "Connection failure, please check your authentication URL." + Chef::Log.error(error_message) + raise "#{e.message}. #{error_message}" + end + end + + def create_server + server_def = { name: config[:server_name], networks: networks } + [:image_id, + :flavor_id, + :public_key_path, + :customization_script, + :vcair_vm_password + ].each do |opt| + server_def[opt] = config[opt] + end + + + server_def[:image_name] = config[:image_id] || config[:image_name] + # Prevent destructive operations on bootstrap_options + clean_bootstrap_options = Marshal.load(Marshal.dump(server_def)) + bootstrap_options = clean_bootstrap_options + bootstrap_options[:name] = default_name.gsub(/\W/,"-").slice(0..14) + + begin + instantiate(clean_bootstrap_options) + vapp = vdc.vapps.get_by_name(bootstrap_options[:name]) + vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]} + + update_customization(clean_bootstrap_options, vm) + if clean_bootstrap_options[:cpus] + vm.cpu = bootstrap_options[:cpus] + end + if clean_bootstrap_options[:memory] + vm.memory = bootstrap_options[:memory] + end + update_network(clean_bootstrap_options, vapp, vm) + + rescue Excon::Errors::BadRequest => e + response = Chef::JSONCompat.from_json(e.response.body) + if response['badRequest']['code'] == 400 + message = "Bad request (400): #{response['badRequest']['message']}" + Chef::Log.error(message) + else + message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}" + Chef::Log.error(message) + end + raise message + rescue Fog::Errors::Error => e + raise e.message + end + + vm.power_on + yield vm if block_given? + vm + end + + def images + @images ||= begin + json_file = File.expand_path('../../../../data/images.json', __FILE__) + JSON.load(IO.read(json_file)) + end + end + + # Blocks until a TCP socket is available where a remote SSH server + # should be listening. + # + # @param hostname [String] remote SSH server host + # @param username [String] SSH username (default: `nil`) + # @param options [Hash] configuration hash (default: `{}`) + # @api private + # def wait_for_sshd(hostname, username = nil, options = {}) + # pseudo_state = { :hostname => hostname } + # pseudo_state[:username] = username if username + # pseudo_state.merge!(options) + + # instance.transport.connection(backcompat_merged_state(pseudo_state)). + # wait_until_ready + # end + + + # def tcp_check(state) + # # allow driver config to bypass SSH tcp check -- because + # # it doesn't respect ssh_config values that might be required + # # FIXME: wait_for_sshd doesn't exist + # # we need this for winrm + # wait_for_sshd(state[:hostname]) unless config[:no_ssh_tcp_check] + # sleep(config[:no_ssh_tcp_check_sleep]) if config[:no_ssh_tcp_check] + # puts '(ssh ready)' + # end + + def hostname(server) + # we don't trust dns yet + server.ip_address + end + + def networks + base_nets = %w( + 00000000-0000-0000-0000-000000000000 + 11111111-1111-1111-1111-111111111111 + ) + config[:networks] ? base_nets + config[:networks] : nil + end + + + def org + @org ||= compute.organizations.get_by_name(config[:vcair_org]) + end + + def vdc + if config[:vcair_vdc] + @vdc ||= org.vdcs.get_by_name(config[:vcair_vdc]) + else + @vdc ||= org.vdcs.first + end + end + + + def net + if config[:vcair_net] + @net ||= org.networks.get_by_name(config[:vcair_net]) + else + # Grab first non-isolated (bridged, natRouted) network + @net ||= org.networks.find { |n| n if !n.fence_mode.match("isolated") } + end + end + + def template(bootstrap_options) + # TODO: find by catalog item ID and/or NAME + # TODO: add option to search just public and/or private catalogs + tmpl=org.catalogs.map do |cat| + #cat.catalog_items.get_by_name(config(:image_id)) + cat.catalog_items.get_by_name(bootstrap_options[:image_name]) + end.compact.first + tmpl + end + + def instantiate(bootstrap_options) + begin + #node_name = config_value(:chef_node_name) + #node_name = bootstrap_options[:name] + node_name = bootstrap_options[:name] + template(bootstrap_options).instantiate( + node_name, + vdc_id: vdc.id, + network_id: net.id, + description: "id:#{node_name}") + #rescue CloudExceptions::ServerCreateError => e + rescue => e + raise e + end + end + + def update_customization(bootstrap_options, server) + ## Initialization before first power on. + custom=server.customization + + if bootstrap_options[:customization_script] + custom.script = open(bootstrap_options[:customization_script]).read + end + + if bootstrap_options[:vcair_vm_password] + custom.admin_password = bootstrap_options[:vcair_vm_password] + custom.admin_password_auto = false + custom.reset_password_required = false + else + # Password will be autogenerated + custom.admin_password_auto=true + # API will force password resets when auto is enabled + # Which is frustrating, because it means + # we can't login over winrm to change it + custom.reset_password_required = true + end + + # DNS and Windows want AlphaNumeric and dashes for hostnames + # Windows can only handle 15 character hostnames + # TODO: only change name for Windows! + custom.computer_name = bootstrap_options[:name].gsub(/\W/,"-").slice(0..14) + # FIXME, names ending in - don't work either + custom.computer_name = custom.computer_name.gsub(/-$/,"").slice(0..14) + custom.enabled = true + custom.save + end + + ## Vcair + ## TODO: make work with floating_ip + ## NOTE: current vcair networking changes require VM to be powered off + def update_network(bootstrap_options, vapp, vm) + ## TODO: allow user to specify network to connect to (see above net used) + # Define network connection for vm based on existing routed network + + # Vcair inlining vapp() and vm() + #vapp = vdc.vapps.get_by_name(bootstrap_options[:name]) + #vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]} + nc = vapp.network_config.find { |n| n if n[:networkName].match(net.name) } + networks_config = [nc] + section = {PrimaryNetworkConnectionIndex: 0} + section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i| + connection = { + network: network[:networkName], + needsCustomization: true, + NetworkConnectionIndex: i, + IsConnected: true + } + ip_address = network[:ip_address] + ## TODO: support config options for allocation mode + #allocation_mode = network[:allocation_mode] + #allocation_mode = 'manual' if ip_address + #allocation_mode = 'dhcp' unless %w{dhcp manual pool}.include?(allocation_mode) + #allocation_mode = 'POOL' + #connection[:Dns1] = dns1 if dns1 + allocation_mode = 'pool' + connection[:IpAddressAllocationMode] = allocation_mode.upcase + connection[:IpAddress] = ip_address if ip_address + connection + end + + ## attach the network to the vm + nc_task = compute.put_network_connection_system_section_vapp( + vm.id,section).body + compute.process_task(nc_task) + end + end + end +end diff --git a/lib/kitchen/driver/vcair_version.rb b/lib/kitchen/driver/vcair_version.rb new file mode 100644 index 0000000..434388a --- /dev/null +++ b/lib/kitchen/driver/vcair_version.rb @@ -0,0 +1,27 @@ +# Encoding: UTF-8 +# +# Authors:: Chris McClimans () +# Authors:: Taylor Carpenter () +# +# Copyright (C) 2015, Vulk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Kitchen + # Version string for vCloud Air Kitchen driver + # + # @author Chris McClimans and Taylor Carpenter + module Driver + VCAIR_VERSION = '0.1.1' + end +end diff --git a/using-vcair-test-kitchen-driver.mkd b/using-vcair-test-kitchen-driver.mkd new file mode 100644 index 0000000..c4d5bf8 --- /dev/null +++ b/using-vcair-test-kitchen-driver.mkd @@ -0,0 +1,92 @@ +# Using kitchen-vcair with the httpd (Apache) cookbook + +These steps assume you have chef installed on your workstation +and access to a vClour Air environment. + +## Grab the kitchen-vcair gem + +Install the gem from source: + +``` +git clone git@github.com:vulk/kitchen-vcair.git +``` + +or whatever your favorite method is. + +## Build and install the gem if you grabbed from source/git + +Build it: +``` +cd kitchen-vcair +gem build kitchen-vcair.gemspec +``` + +Install it... eg. + +``` +gem install ./kitchen-vcair-0.1.0.gem +``` + +## Grab the the Apache (httpd) cookbook + +Install using your favorite method. Eg. with git: + +``` +git clone git@github.com:chef-cookbooks/httpd.git +``` + +## Create a test kitchen configuration file + +Go into the cookbook and create a `.kitchen.vcair.yml`. +Example: + +``` +--- +driver_config: + vcair_username: <%= ENV['VCAIR_USERNAMEX'] %> + vcair_password: <%= ENV['VCAIR_PASSWORDX'] %> + vcair_api_host: <%= ENV['VCAIR_API_HOSTX'] %> + vcair_org: <%= ENV['VCAIR_ORGX'] %> + +provisioner: + name: chef_zero + require_chef_omnibus: latest + +platforms: +- name: centos-6.4 + driver_plugin: vcair + driver_config: + size: 2gb + image: centos-6-4-x64 + vcair_ssh_password: <%= ENV['VCAIR_SSH_PASSWORDX'] %> + +suites: + # + # hello_world_test + # + - name: hello_world_test + run_list: + - recipe[hello_world_test] +``` + +## Create and load an environment configuration file + +Add vCloud Air configuration to environment. eg.: + +``` +export VCAIR_API_HOST='pNvNN-vcd.vchs.vmware.com' +export VCAIR_SSH_PASSWORD='RANDOM_PASSWORD_HERE' + +export VCAIR_ORG='MNNNNNNNNN-NNN' + +export VCAIR_USERNAME='your@example.com' +export VCAIR_PASSWORD='YOUR_VCAIR_PASSWORD' +``` + +## Run test-kitchen using the configuration created previously + +Go into the httpd cookbook directory and run + +``` +KITCHEN_YAML=".kitchen.vcair.yml" kitchen test +```