From 0685cf27bc00de762c2516008e7bbcf2db351f9f Mon Sep 17 00:00:00 2001 From: Ezra Pagel Date: Wed, 29 Jun 2011 14:54:15 -0500 Subject: [PATCH] initial commit --- LICENSE | 201 ++++++++++++++++++++++++ README.rdoc | 61 +++++++ Rakefile | 10 ++ knife-vsphere.gemspec | 18 +++ lib/chef/knife/VsphereBaseCommand.rb | 105 +++++++++++++ lib/chef/knife/vsphere_template_list.rb | 51 ++++++ lib/chef/knife/vsphere_vm_clone.rb | 71 +++++++++ lib/chef/knife/vsphere_vm_delete.rb | 54 +++++++ lib/chef/knife/vsphere_vm_list.rb | 45 ++++++ lib/knife-vsphere/version.rb | 4 + pkg/knife-vsphere-0.1.1.gem | Bin 0 -> 5632 bytes 11 files changed, 620 insertions(+) create mode 100644 LICENSE create mode 100644 README.rdoc create mode 100644 Rakefile create mode 100644 knife-vsphere.gemspec create mode 100644 lib/chef/knife/VsphereBaseCommand.rb create mode 100644 lib/chef/knife/vsphere_template_list.rb create mode 100644 lib/chef/knife/vsphere_vm_clone.rb create mode 100644 lib/chef/knife/vsphere_vm_delete.rb create mode 100644 lib/chef/knife/vsphere_vm_list.rb create mode 100644 lib/knife-vsphere/version.rb create mode 100644 pkg/knife-vsphere-0.1.1.gem diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..11069edd --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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.rdoc b/README.rdoc new file mode 100644 index 00000000..58c2c77b --- /dev/null +++ b/README.rdoc @@ -0,0 +1,61 @@ += Knife vSphere + += DESCRIPTION: + +This is an Opscode Knife plugin to interact with VMware's vSphere. This plugin currently lists, clones, and deletes Virtual Machines and Templates; it will soon be able to apply run lists to vSphere-manged virtual machines, + += INSTALLATION: + + gem install knife-vsphere + += CONFIGURATION: + +For initial development, the plugin targets all communication at a vCenter instance rather than at specific hosts. Only named user authentication is currently supported; you'll need to add the credentials to your knife.rb file: + + knife[:vsphere_host] = "vcenter-hostname" + knife[:vsphere_user] = "privileged username" + knife[:vsphere_pass] = "your password" + knife[:vsphere_dc] = "your-datacenter" + +Environment variables and command-line credentials are not currently supported, but will be forthcoming soon. + += SUBCOMMANDS: + +This plugin provides the following Knife subcommands. Specific command options can be found by invoking the subcommand with a --help flag + +== knife vsphere vm list + +Enumerates the Virtual Machines registered in the target datacenter. Only name is currently displayed. + +== knife vsphere template list + +Enumerates the VM Templates registered in the target datacenter. Only name is currently displayed. + +== knife vsphere vm clone --template --vmname + +Clones an existing VM template into a new VM instance. + +== knife vsphere vm delete --vmname + +Unregisters an existing VM. Currently, it does not perform any datastore cleanup to remove VM files. + += LICENSE: + +Author:: Ezra Pagel () +Copyright:: Copyright (c) 2011 Ezra Pagel + +VMware vSphere is a trademark of VMware, Inc. + +License:: Apache License, Version 2.0 + +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/Rakefile b/Rakefile new file mode 100644 index 00000000..847be3f5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +require 'rake/gempackagetask' + +spec = Gem::Specification.load(Dir['*.gemspec'].first) +gem = Rake::GemPackageTask.new(spec) +gem.define + +desc "Push gem to rubygems.org" +task :push => :gem do + sh "gem push #{gem.package_dir}/#{gem.gem_file}" +end diff --git a/knife-vsphere.gemspec b/knife-vsphere.gemspec new file mode 100644 index 00000000..0f324e70 --- /dev/null +++ b/knife-vsphere.gemspec @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "knife-vsphere" + s.version = "0.1.1" + s.summary = "VSphere Support for Knife" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + + s.author = "Ezra Pagel" + s.description = "VMware Vsphere Support for Chef's Knife Command" + s.email = "ezra@cpan.org" + s.files = Dir["lib/**/*"] + s.rubygems_version = "1.6.2" + + s.add_dependency("chef", ["~> 0.10.0"]) + s.add_dependency("rbvmomi", ["~> 1.2.3"]) +end diff --git a/lib/chef/knife/VsphereBaseCommand.rb b/lib/chef/knife/VsphereBaseCommand.rb new file mode 100644 index 00000000..5799f251 --- /dev/null +++ b/lib/chef/knife/VsphereBaseCommand.rb @@ -0,0 +1,105 @@ +# +# Author:: Ezra Pagel () +# License:: Apache License, Version 2.0 +# +# 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 'chef/knife' +require 'rbvmomi' + +class Chef + class Knife + class VsphereBaseCommand < Knife + + deps do + require 'chef/knife/bootstrap' + Chef::Knife::Bootstrap.load_deps + require 'fog' + require 'socket' + require 'net/ssh/multi' + require 'readline' + require 'chef/json_compat' + end + + + def self.get_common_options + + option :host, + :short => "-H HOST", + :long => "--host HOST", + :description => "The host to create the VM on" + + option :vsphere_user, + :short => "-u USERNAME", + :long => "--user USERNAME", + :description => "The username for the host" + + option :password, + :short => "-p PASSWORD", + :long => "--password PASSWORD", + :description => "The password for the host" + + option :datacenter, + :short => "-d DATACENTER", + :long => "--datacenter DATACENTER", + :description => "The Datacenter to create the VM in" + + option :path, + :short => "-P SOAP_PATH", + :long => "--path SOAP_PATH", + :description => "The SOAP endpoint path", + :proc => Proc.new { |p| Chef::Config[:knife][:path] = p }, + :default => "/sdk" + + option :port, + :short => "-p PORT", + :long => "--port PORT", + :description => "The VI SDK port number to use", + :proc => Proc.new { |p| Chef::Config[:knife][:port] = p }, + :default => 443 + + option :use_ssl, + :short => "-s USE_SSL", + :long => "--ssl USE_SSL", + :description => "Whether to use SSL connection", + :default => true + + option :insecure, + :short => "-i USE_INSECURE_SSL", + :long => "--insecure USE_INSECURE_SSL", + :description => "Determines whether SSL certificate verification is skipped", + :default => true + + end + + def get_vim_connection + + conn_opts = { + :host => config[:host] || Chef::Config[:knife][:vsphere_host], + :path => config[:path], + :port => config[:port], + :use_ssl => config[:ssl], + :user => config[:vsphere_user] || Chef::Config[:knife][:vsphere_user], + :password => config[:password] || Chef::Config[:knife][:vsphere_pass], + :insecure => config[:insecure] + } + + # opt :insecure, "don't verify ssl certificate", :short => 'k', :default => (ENV['RBVMOMI_INSECURE'] == '1') + # opt :debug, "Log SOAP messages", :short => 'd', :default => (ENV['RBVMOMI_DEBUG'] || false) + + vim = RbVmomi::VIM.connect conn_opts + return vim + end + end + end +end diff --git a/lib/chef/knife/vsphere_template_list.rb b/lib/chef/knife/vsphere_template_list.rb new file mode 100644 index 00000000..44a7f455 --- /dev/null +++ b/lib/chef/knife/vsphere_template_list.rb @@ -0,0 +1,51 @@ +# +# Author:: Ezra Pagel () +# License:: Apache License, Version 2.0 +# +# 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 'chef/knife' +require 'chef/knife/VsphereBaseCommand' + +class Chef + class Knife + class VsphereTemplateList < VsphereBaseCommand + + banner "knife vsphere template list" + + get_common_options + + + def run + + $stdout.sync = true + + vim = get_vim_connection + + dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc] + dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found" + vmFolder = dc.vmFolder + + vms = vmFolder.childEntity.grep(RbVmomi::VIM::VirtualMachine) + if (!vms.nil?) + templates = vms.find_all{ |v| !v.config.nil? && v.config.template == true } + if (!templates.nil?) + templates.each do |vm| + puts "#{ui.color("Template Name", :cyan)}: #{vm.name}" + end + end + end + end + end + end +end diff --git a/lib/chef/knife/vsphere_vm_clone.rb b/lib/chef/knife/vsphere_vm_clone.rb new file mode 100644 index 00000000..63cafafd --- /dev/null +++ b/lib/chef/knife/vsphere_vm_clone.rb @@ -0,0 +1,71 @@ +# +# Author:: Ezra Pagel () +# License:: Apache License, Version 2.0 +# +# 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 'chef/knife' +require 'chef/knife/VsphereBaseCommand' +require 'rbvmomi' + +class Chef + class Knife + class VsphereVmClone < VsphereBaseCommand + + banner "knife vsphere vm clone (options)" + + get_common_options + + option :template, + :short => "-t TEMPLATE", + :long => "--template TEMPLATE", + :description => "The template to create the VM from" + + option :vmname, + :short => "-N VMNAME", + :long => "--vmname VMNAME", + :description => "The name for the new virtual machine" + + def run + + $stdout.sync = true + + vim = get_vim_connection + + dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc] + dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found" + vmFolder = dc.vmFolder + hosts = dc.hostFolder.children + rp = hosts.first.resourcePool + + template = config[:template] or abort "source template name required" + vmname = config[:vmname] or abort "destination vm name required" + + puts "searching for template #{template}" + + src_vm = dc.find_vm(template) or abort "VM not found" + + rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp) + spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec, + :powerOn => false, + :template => false) + + task = src_vm.CloneVM_Task(:folder => src_vm.parent, :name => vmname, :spec => spec) + puts "Cloning template #{template} to new VM #{vmname}" + task.wait_for_completion + puts "Finished creating virtual machine #{vmname}" + + end + end + end +end diff --git a/lib/chef/knife/vsphere_vm_delete.rb b/lib/chef/knife/vsphere_vm_delete.rb new file mode 100644 index 00000000..4ff4d113 --- /dev/null +++ b/lib/chef/knife/vsphere_vm_delete.rb @@ -0,0 +1,54 @@ +# +# Author:: Ezra Pagel () +# License:: Apache License, Version 2.0 +# +# 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 'chef/knife' +require 'chef/knife/VsphereBaseCommand' +require 'rbvmomi' + +class Chef + class Knife + class VsphereVmDelete < VsphereBaseCommand + + banner "knife vsphere vm delete (options)" + + get_common_options + + option :vmname, + :short => "-N VMNAME", + :long => "--vmname VMNAME", + :description => "The name of the virtual machine to delete" + + def run + + $stdout.sync = true + + vmname = config[:vmname] or abort "virtual machine name required" + + vim = get_vim_connection + + dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc] + dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found" + + vm = dc.find_vm(vmname) or abort "VM not found" + + vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == "poweredOff" + vm.UnregisterVM + puts "Finished unregistering virtual machine #{vmname}" + + end + end + end +end diff --git a/lib/chef/knife/vsphere_vm_list.rb b/lib/chef/knife/vsphere_vm_list.rb new file mode 100644 index 00000000..24fad772 --- /dev/null +++ b/lib/chef/knife/vsphere_vm_list.rb @@ -0,0 +1,45 @@ +# +# Author:: Ezra Pagel () +# License:: Apache License, Version 2.0 +# +# 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 'chef/knife' +require 'chef/knife/VsphereBaseCommand' + +class Chef + class Knife + class VsphereVmList < VsphereBaseCommand + + banner "knife vsphere vm list" + + get_common_options + + + def run + + $stdout.sync = true + + vim = get_vim_connection + + dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc] + dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found" + vmFolder = dc.vmFolder + + vmFolder.childEntity.grep(RbVmomi::VIM::VirtualMachine).each do |vm| + puts "#{ui.color("VM Name", :cyan)}: #{vm.name}" + end + end + end + end +end diff --git a/lib/knife-vsphere/version.rb b/lib/knife-vsphere/version.rb new file mode 100644 index 00000000..5e109c29 --- /dev/null +++ b/lib/knife-vsphere/version.rb @@ -0,0 +1,4 @@ +module KnifeVsphere + VERSION = "0.1.1" +end + diff --git a/pkg/knife-vsphere-0.1.1.gem b/pkg/knife-vsphere-0.1.1.gem new file mode 100644 index 0000000000000000000000000000000000000000..fa4bc3ba68f56b0094220a4067c92c14a394ca7e GIT binary patch literal 5632 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 Di$-WH literal 0 HcmV?d00001