├── .rubocop.yml ├── box ├── metadata.json ├── linode.box ├── linode-debian-7.5.box ├── metadata-debian-7.5.json ├── README.md └── Vagrantfile ├── test ├── cookbooks │ └── test │ │ └── recipes │ │ └── default.rb ├── scripts │ └── provision.sh ├── test.sh ├── test_id_rsa.pub ├── test_id_rsa └── Vagrantfile ├── .rspec ├── lib ├── vagrant-linode │ ├── version.rb │ ├── actions │ │ ├── is_stopped.rb │ │ ├── is_created.rb │ │ ├── message_not_off.rb │ │ ├── message_off.rb │ │ ├── message_already_off.rb │ │ ├── message_not_created.rb │ │ ├── message_already_active.rb │ │ ├── list_plans.rb │ │ ├── list_datacenters.rb │ │ ├── list_distributions.rb │ │ ├── list_images.rb │ │ ├── list_kernels.rb │ │ ├── list_servers.rb │ │ ├── reload.rb │ │ ├── create_image.rb │ │ ├── setup_hostname.rb │ │ ├── destroy.rb │ │ ├── power_on.rb │ │ ├── power_off.rb │ │ ├── connect_linode.rb │ │ ├── read_state.rb │ │ ├── setup_sudo.rb │ │ ├── modify_provision_path.rb │ │ ├── list_volumes.rb │ │ ├── read_ssh_info.rb │ │ ├── setup_user.rb │ │ ├── rebuild.rb │ │ └── create.rb │ ├── commands │ │ ├── rebuild.rb │ │ ├── list_volumes.rb │ │ ├── plans.rb │ │ ├── kernels.rb │ │ ├── servers.rb │ │ ├── networks.rb │ │ ├── list_images.rb │ │ ├── create_image.rb │ │ ├── datacenters.rb │ │ ├── distributions.rb │ │ ├── volumes.rb │ │ ├── images.rb │ │ └── root.rb │ ├── helpers │ │ ├── normalizer.rb │ │ ├── waiter.rb │ │ ├── result.rb │ │ └── client.rb │ ├── client_wrapper.rb │ ├── plugin.rb │ ├── services │ │ └── volume_manager.rb │ ├── errors.rb │ ├── provider.rb │ ├── config.rb │ └── actions.rb └── vagrant-linode.rb ├── .gitignore ├── Gemfile ├── spec ├── spec_helper.rb └── vagrant-linode │ ├── actions │ ├── list_plans_spec.rb │ └── list_distributions_spec.rb │ ├── services │ └── volume_manager_spec.rb │ └── config_spec.rb ├── CHANGELOG.md ├── Rakefile ├── vagrant-linode.gemspec ├── LICENSE.txt ├── .rubocop_todo.yml ├── locales └── en.yml └── README.md /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | -------------------------------------------------------------------------------- /box/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "linode" 3 | } 4 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/default.rb: -------------------------------------------------------------------------------- 1 | log 'Testing 1 2 3!' 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format doc 2 | --require spec_helper 3 | --order random 4 | -------------------------------------------------------------------------------- /test/scripts/provision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'Testing 1 2 3!' 4 | -------------------------------------------------------------------------------- /box/linode.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/displague/vagrant-linode/HEAD/box/linode.box -------------------------------------------------------------------------------- /box/linode-debian-7.5.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/displague/vagrant-linode/HEAD/box/linode-debian-7.5.box -------------------------------------------------------------------------------- /lib/vagrant-linode/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | VERSION = '0.4.1' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | Vagrantfile 16 | .vagrant 17 | !test/Vagrantfile 18 | test/tmp 19 | test/version_tmp 20 | test/.vagrant 21 | tmp 22 | vendor 23 | *.un~ 24 | *.orig 25 | salt 26 | -------------------------------------------------------------------------------- /box/metadata-debian-7.5.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "displague/debian-7.5", 3 | "description": "This box creates Debian 7.5 on a Linode", 4 | "versions": [{ 5 | "version": "0.1.0", 6 | "providers": [{ 7 | "name": "linode", 8 | "url": "https://github.com/displague/vagrant-linode/raw/master/box/linode.box", 9 | "distribution": "debian 7.5" 10 | }] 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | # if ! bundle exec vagrant box list | grep linode 1>/dev/null; then 2 | # bundle exec vagrant box add linode box/linode.box 3 | # fi 4 | 5 | cd test 6 | 7 | bundle exec vagrant up --provider=linode 8 | bundle exec vagrant up 9 | bundle exec vagrant provision 10 | bundle exec vagrant rebuild 11 | bundle exec vagrant halt 12 | bundle exec vagrant destroy 13 | 14 | cd .. 15 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/is_stopped.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class IsStopped 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:result] = env[:machine].state.id == :off 11 | 12 | @app.call(env) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/is_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class IsCreated 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:result] = env[:machine].state.id != :not_created 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/message_not_off.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class MessageNotOff 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_linode.info.not_off")) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/test_id_rsa.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNsNvFhqrcA0MLNT5z4EKmI815ILCP09LoiBQ10/m1nNovczmX6LLmtLSWyPDjbb0B0Xmg7VM4bN5+8iSod3/d96LOxQXO+9Iz7K08Jp0yQ8D5seKFYYk/KMsMtYIXmQej2B03EINzvZw+YZYmqbhh3BYDw+CTZfyBLHeSW605aD69auRtf8sQ1obyvm6T+0ga8MdX8JlAVkRQOjoQIngOKfiYuUFssZoTSzBBK2ER4+Wuj+SzZ4nZNx46j2pxORCchohvpZQRN/KUOSXXP8+IO37tDCYAuYS21aPOs69SgnoUDsfceLjV16zrApRiO0I3ZH5u9H1v1I2P0FeP6WOb test@vagrant-linode 2 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/message_off.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class MessageOff 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_linode.info.off", :status => :off)) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/message_already_off.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class MessageAlreadyOff 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_linode.info.already_off", :status => :off)) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/message_not_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class MessageNotCreated 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_linode.info.not_created", :status => :not_created)) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/message_already_active.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class MessageAlreadyActive 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | env[:ui].info(I18n.t("vagrant_linode.info.already_active", :status => :active)) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '>= 2.2.0' 3 | 4 | group :development do 5 | # We depend on Vagrant for development, but we don't add it as a 6 | # gem dependency because we expect to be installed within the 7 | # Vagrant environment itself using `vagrant plugin`. 8 | gem 'vagrant', git: 'https://github.com/mitchellh/vagrant' 9 | gem 'coveralls', require: false 10 | gem 'pry' 11 | end 12 | 13 | group :test do 14 | gem 'rspec-its' 15 | end 16 | 17 | group :plugins do 18 | gemspec 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/rebuild.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module VagrantPlugins 4 | module Linode 5 | module Commands 6 | class Rebuild < Vagrant.plugin('2', :command) 7 | def execute 8 | opts = OptionParser.new do |o| 9 | o.banner = 'Usage: vagrant rebuild [vm-name]' 10 | end 11 | 12 | argv = parse_options(opts) 13 | 14 | with_target_vms(argv) do |machine| 15 | machine.action(:rebuild) 16 | end 17 | 18 | 0 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/list_volumes.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class ListVolumes < Vagrant.plugin('2', :command) 5 | def execute 6 | opts = OptionParser.new do |o| 7 | o.banner = 'Usage: vagrant linode volumes list [options]' 8 | end 9 | 10 | argv = parse_options(opts) 11 | return unless argv 12 | 13 | with_target_vms(argv) do |machine| 14 | machine.action(:list_volumes) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_plans.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListPlans 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | linode_api = env[:linode_api] 11 | env[:ui].info ('%-36s %s' % ['Plan ID', 'Plan Name']) 12 | linode_api.avail.linodeplans.sort_by(&:planid).each do |plan| 13 | env[:ui].info ('%-36s %s' % [plan.planid, plan.label]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/plans.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Plans < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode plans [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_plans') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /box/README.md: -------------------------------------------------------------------------------- 1 | # Vagrant Linode Cloud Example Box 2 | 3 | Vagrant providers each require a custom provider-specific box format. 4 | This folder shows the example contents of a box for the `linode` provider. 5 | To turn this into a box: 6 | 7 | ``` 8 | $ tar cvzf linode.box ./metadata.json ./Vagrantfile 9 | ``` 10 | 11 | This box works by using Vagrant's built-in Vagrantfile merging to setup 12 | defaults for Linode. These defaults can easily be overwritten by higher-level 13 | Vagrantfiles (such as project root Vagrantfiles). 14 | 15 | # Test 16 | 17 | ``` 18 | vagrant box add --name linode_test linode.box 19 | ``` 20 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/kernels.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Kernels < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode kernels [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_kernels') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/servers.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Servers < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode servers [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_servers') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/networks.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Networks < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode networks [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_networks') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/list_images.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class ListImages < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode images list [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_images') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/create_image.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class CreateImage < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode images create [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('create_image') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/datacenters.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Datacenters < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode datacenters [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_datacenters') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/distributions.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Distributions < Vagrant.plugin('2', :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = 'Usage: vagrant linode distributions [options]' 9 | end 10 | 11 | argv = parse_options(opts) 12 | return unless argv 13 | 14 | with_target_vms(argv, provider: :linode) do |machine| 15 | machine.action('list_distributions') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-linode/helpers/normalizer.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Helpers 4 | module Normalizer 5 | def normalize_plan_label(plan_label) 6 | # if config plan is "Linode x" instead of "Linode xGB", look for "(x/1024)GB instead", when x >= 1024 7 | plan_label_has_size = plan_label.match(/(\d{4,})$/) 8 | if plan_label_has_size 9 | plan_size = plan_label_has_size.captures.first.to_i 10 | plan_label.sub(/(\d{4,})$/, "#{plan_size / 1024}GB") 11 | else 12 | plan_label 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_datacenters.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListDatacenters 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | linode_api = env[:linode_api] 11 | env[:ui].info ('%-15s %-36s %s' % ['Datacenter ID', 'Location', 'Abbr']) 12 | linode_api.avail.datacenters.sort_by(&:datacenterid).each do |dc| 13 | env[:ui].info ('%-15s %-36s %s' % [dc.datacenterid, dc.location, dc.abbr]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/client_wrapper.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module Linode 5 | class ClientWrapper 6 | def initialize(client, logger) 7 | @client = client 8 | @logger = logger 9 | end 10 | 11 | def method_missing(method, *args, &block) 12 | result = @client.send(method, *args, &block) 13 | 14 | if result.is_a? LinodeAPI::Retryable 15 | self.class.new(result, @logger) 16 | else 17 | result 18 | end 19 | rescue ::LinodeAPI::APIError => e 20 | @logger.error e.details.inspect 21 | raise 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_distributions.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListDistributions 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | linode_api = env[:linode_api] 11 | env[:ui].info ('%-4s %-6s %s' % ['ID', 'Size', 'Distribution Name']) 12 | linode_api.avail.distributions.sort_by(&:distributionid).each do |dist| 13 | env[:ui].info ('%-4s %-6s %s' % [dist.distributionid, dist.minimagesize, dist.label]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_images.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListImages 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | linode_api = env[:linode_api] 11 | env[:ui].info ('%-10s %-22s %-10s %s' % ['Image ID', 'Created', 'Size (MB)', 'Image Label']) 12 | linode_api.image.list.sort_by(&:imageid).each do |img| 13 | env[:ui].info ('%-10s %-22s %-10s %s' % [img.imageid, img.create_dt, img.minsize, img.label]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_kernels.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListKernels 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | linode_api = env[:linode_api] 11 | env[:ui].info ('%-4s %-6s %-6s %s' % ['ID', 'IsXen', 'IsKVM', 'Kernel Name']) 12 | linode_api.avail.kernels(isxen: nil, iskvm: 1).sort_by(&:kernelid).each do |kernel| 13 | env[:ui].info ('%-4s %-6s %-6s %s' % [kernel.kernelid, kernel.isxen, kernel.iskvm, kernel.label]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_servers.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListServers 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | linode_api = env[:linode_api] 11 | env[:ui].info ('%-8s %-30s %-20s %-10s %-9s' % ['LinodeID', 'Label', 'DataCenter', 'Plan', 'Status']) 12 | linode_api.linode.list.sort_by(&:imageid).each do |ln| 13 | env[:ui].info ('%-8s %-30s %-20s %-10s %-9s' % [ln.linodeid, ln.label, ln.datacenterid, ln.planid, ln.status]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-linode/helpers/waiter.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Helpers 4 | module Waiter 5 | include Vagrant::Util::Retryable 6 | def wait_for_event(env, id) 7 | retryable(tries: 120, sleep: 10) do 8 | # stop waiting if interrupted 9 | next if env[:interrupted] 10 | # check action status 11 | result = env[:linode_api].linode.job.list(jobid: id, linodeid: env[:machine].id) 12 | result = result[0] if result.is_a?(Array) 13 | 14 | yield result if block_given? 15 | fail 'not ready' if result['host_finish_dt'] > '' 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] != 'false' 2 | require 'simplecov' 3 | require 'coveralls' 4 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ 5 | SimpleCov::Formatter::HTMLFormatter, 6 | Coveralls::SimpleCov::Formatter 7 | ]) 8 | SimpleCov.start 9 | 10 | # Normally classes are lazily loaded, so any class without a test 11 | # is missing from the report. This ensures they show up so we can 12 | # see uncovered methods. 13 | require 'vagrant' 14 | Dir['lib/**/*.rb'].each do|file| 15 | require_string = file.match(/lib\/(.*)\.rb/)[1] 16 | require require_string 17 | end 18 | end 19 | 20 | require 'pry' 21 | require 'rspec/its' 22 | 23 | I18n.load_path << 'locales/en.yml' 24 | I18n.reload! 25 | -------------------------------------------------------------------------------- /box/Vagrantfile: -------------------------------------------------------------------------------- 1 | # vi: set ft=ruby et ts=4 sw=4 tw=80 : 2 | # -*- mode: ruby -*- 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.provider :linode do |linode, override| 6 | if ENV['LINODE_API_KEY'] 7 | override.api_key = ENV['LINODE_API_KEY'] 8 | end 9 | 10 | if ENV['LINODE_SSH_KEY_LOCATION'] 11 | override.ssh.private_key_path = ENV['LINODE_SSH_KEY_LOCATION'] 12 | end 13 | 14 | if ENV['LINODE_SSH_USER'] 15 | override.ssh.username = ENV['LINODE_SSH_USER'] 16 | end 17 | 18 | unless ENV['LINODE_NFS_FUNCTIONAL'] 19 | override.nfs.functional = false 20 | end 21 | 22 | linode.datacenter = "dallas" 23 | linode.distribution = "Ubuntu 14.04 LTS" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/reload.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | require 'vagrant-linode/helpers/waiter' 3 | 4 | module VagrantPlugins 5 | module Linode 6 | module Actions 7 | class Reload 8 | include Helpers::Waiter 9 | 10 | def initialize(app, env) 11 | @app = app 12 | @machine = env[:machine] 13 | @logger = Log4r::Logger.new('vagrant::linode::reload') 14 | end 15 | 16 | def call(env) 17 | @client = env[:linode_api] 18 | # submit reboot linode request 19 | result = @client.linode.reboot(linodeid: @machine.id) 20 | 21 | # wait for request to complete 22 | env[:ui].info I18n.t('vagrant_linode.info.reloading') 23 | wait_for_event(env, result['jobid']) 24 | @app.call(env) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/create_image.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class CreateImage 5 | def initialize(app, _env) 6 | @app = app 7 | @machine = _env[:machine] 8 | end 9 | 10 | def call(env) 11 | linode_api = env[:linode_api] 12 | env[:ui].info ('%-36s %-36s %-10s %-10s' % ['Image ID', 'Disk Label', 'Disk ID', 'Job ID']) 13 | linode_api.linode.disk.list(:linodeid => @machine.id).each do |disk| 14 | next if disk.type == 'swap' 15 | img = linode_api.linode.disk.imagize :linodeid => disk.linodeid, :diskid => disk.diskid, :description => 'Imagized with Vagrant' 16 | env[:ui].info ('%-36s %-36s %-10s %-10s' % [img.imageid, disk.label, disk.diskid, img.jobid]) 17 | end 18 | @app.call(env) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/setup_hostname.rb: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | module VagrantPlugins 5 | module Linode 6 | module Actions 7 | class SetupHostname 8 | 9 | def initialize(app, env) 10 | @app = app 11 | @machine = env[:machine] 12 | @logger = Log4r::Logger.new('vagrant::linode::setup_hostname') 13 | end 14 | 15 | def call(env) 16 | # Check if setup is enabled 17 | return @app.call(env) unless @machine.provider_config.setup? 18 | 19 | # Set Hostname 20 | if @machine.config.vm.hostname 21 | env[:ui].info I18n.t('vagrant_linode.info.modifying_host', name: @machine.config.vm.hostname) 22 | 23 | @machine.communicate.execute(<<-BASH) 24 | sudo echo -n #{@machine.config.vm.hostname} > /etc/hostname; 25 | sudo hostname -F /etc/hostname 26 | BASH 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/destroy.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | 3 | module VagrantPlugins 4 | module Linode 5 | module Actions 6 | class Destroy 7 | def initialize(app, env) 8 | @app = app 9 | @machine = env[:machine] 10 | @logger = Log4r::Logger.new('vagrant::linode::destroy') 11 | end 12 | 13 | def call(env) 14 | @client = env[:linode_api] 15 | # submit destroy linode request 16 | begin 17 | @client.linode.delete(linodeid: @machine.id, skipchecks: true) 18 | rescue RuntimeError => e 19 | raise unless e.message.include? 'Object not found' 20 | end 21 | 22 | env[:ui].info I18n.t('vagrant_linode.info.destroying') 23 | 24 | # set the machine id to nil to cleanup local vagrant state 25 | @machine.id = nil 26 | 27 | @app.call(env) 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.4.1 4 | 5 | * fixes handling of old plan labels [#88](https://github.com/displague/vagrant-linode/issues/88) 6 | * adds a custom User-Agent token to support usage analytics 7 | 8 | ## v0.4.0 9 | 10 | * added support for Linode Volumes 11 | * added `vagrant linode volumes list` command 12 | 13 | ## v0.3.0 14 | 15 | * fixes for Vagrant 2.0.1 (now requires Ruby 2.2.0+) 16 | * xen-only kernels are no longer available (Fixes KVM Grub options) 17 | * rebuild command warns / quits if Linode is not powered down 18 | 19 | ## v0.2.8 20 | 21 | * fixes for Vagrant 1.9 22 | 23 | ## v0.2.7 24 | 25 | * Update default plan (and ways to set it) and default distro 26 | 27 | ## v0.2.6 28 | 29 | * added StackScript support 30 | 31 | ## v0.2.5 32 | 33 | * destroy works when linode is already deleted 34 | 35 | ## v0.2.4 36 | 37 | * fixed the box image 38 | 39 | ## v0.2.3 40 | 41 | * fixed rsync before provision on startup 42 | 43 | ## v0.2.2 44 | 45 | * fixed provision on startup 46 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/power_on.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | require 'vagrant-linode/helpers/waiter' 3 | 4 | module VagrantPlugins 5 | module Linode 6 | module Actions 7 | class PowerOn 8 | include Helpers::Waiter 9 | 10 | def initialize(app, env) 11 | @app = app 12 | @machine = env[:machine] 13 | @logger = Log4r::Logger.new('vagrant::linode::power_on') 14 | end 15 | 16 | def call(env) 17 | @client = env[:linode_api] 18 | # submit power on linode request 19 | result = @client.linode.boot(linodeid: @machine.id) 20 | 21 | # wait for request to complete 22 | env[:ui].info I18n.t('vagrant_linode.info.powering_on') 23 | wait_for_event(env, result['jobid']) 24 | 25 | # refresh linode state with provider 26 | Provider.linode(@machine, refresh: true) 27 | 28 | @app.call(env) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/power_off.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | require 'vagrant-linode/helpers/waiter' 3 | # TODO: --force 4 | module VagrantPlugins 5 | module Linode 6 | module Actions 7 | 8 | class PowerOff 9 | include Helpers::Waiter 10 | def initialize(app, env) 11 | @app = app 12 | @machine = env[:machine] 13 | @logger = Log4r::Logger.new('vagrant::linode::power_off') 14 | end 15 | 16 | def call(env) 17 | @client = env[:linode_api] 18 | # submit power off linode request 19 | result = @client.linode.shutdown(linodeid: @machine.id) 20 | 21 | # wait for request to complete 22 | env[:ui].info I18n.t('vagrant_linode.info.powering_off') 23 | wait_for_event(env, result['jobid']) 24 | 25 | # refresh linode state with provider 26 | Provider.linode(@machine, refresh: true) 27 | 28 | @app.call(env) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'rspec/core/rake_task' 4 | 5 | # Immediately sync all stdout so that tools like buildbot can 6 | # immediately load in the output. 7 | $stdout.sync = true 8 | $stderr.sync = true 9 | 10 | # Change to the directory of this file. 11 | Dir.chdir(File.expand_path('../', __FILE__)) 12 | 13 | # This installs the tasks that help with gem creation and 14 | # publishing. 15 | namespace :gem do 16 | Bundler::GemHelper.install_tasks 17 | end 18 | 19 | # Install the `spec` task so that we can run tests. 20 | RSpec::Core::RakeTask.new 21 | 22 | # Default task is to run the unit tests 23 | task default: 'spec' 24 | 25 | # require 'bundler/gem_helper' 26 | 27 | task :test do 28 | result = sh 'bash test/test.sh' 29 | 30 | if result 31 | puts 'Success!' 32 | else 33 | puts 'Failure!' 34 | exit 1 35 | end 36 | end 37 | 38 | def env 39 | %w(LINODE_CLIENT_ID LINODE_API_KEY VAGRANT_LOG).reduce('') do |acc, key| 40 | acc += "#{key}=#{ENV[key] || 'error'} " 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/connect_linode.rb: -------------------------------------------------------------------------------- 1 | require 'log4r' 2 | require "vagrant-linode/client_wrapper" 3 | 4 | module VagrantPlugins 5 | module Linode 6 | module Actions 7 | # This action connects to Linode, verifies credentials work, and 8 | # puts the Linode connection object into the `:linode_api` key 9 | # in the environment. 10 | class ConnectLinode 11 | def initialize(app, _env) 12 | @app = app 13 | @logger = Log4r::Logger.new('vagrant_linode::action::connect_linode') 14 | end 15 | 16 | def call(env) 17 | # Get the configs 18 | config = env[:machine].provider_config 19 | api_key = config.api_key 20 | api_url = config.api_url 21 | 22 | params = { 23 | apikey: api_key, 24 | endpoint: api_url 25 | } 26 | 27 | @logger.info('Connecting to Linode api_url...') 28 | 29 | linode = ClientWrapper.new(::LinodeAPI::Retryable.new(params), env[:ui]) 30 | env[:linode_api] = linode 31 | 32 | @app.call(env) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /vagrant-linode.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'vagrant-linode/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'vagrant-linode' 8 | gem.version = VagrantPlugins::Linode::VERSION 9 | gem.licenses = ['MIT'] 10 | gem.authors = ['Marques Johansson', 'Jonathan Leal'] 11 | gem.email = ['marques@linode.com', 'jleal@linode.com'] 12 | gem.description = 'Enables Vagrant to manage Linode linodes' 13 | gem.homepage = 'https://www.github.com/displague/vagrant-linode' 14 | gem.summary = gem.description 15 | 16 | gem.add_runtime_dependency 'linodeapi', '~> 2.0.3' 17 | gem.add_runtime_dependency 'log4r', '~> 1.1' 18 | 19 | gem.add_development_dependency 'rake', '~> 12.0' 20 | gem.add_development_dependency 'rspec', '~> 3.5' 21 | 22 | gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 23 | gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } 24 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 25 | gem.require_paths = ['lib'] 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 John Bender 2 | Copyright (c) 2013 Shawn Dahlen 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /lib/vagrant-linode/helpers/result.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Helpers 4 | class Result 5 | def initialize(body) 6 | @result = body 7 | end 8 | 9 | def [](key) 10 | @result[key.to_s] 11 | end 12 | 13 | def find_id(sub_obj, search) #:ssh_keys, {:name => 'ijin (vagrant)'} 14 | find(sub_obj, search)['id'] 15 | end 16 | 17 | def find(sub_obj, search) 18 | key = search.keys.first #:slug 19 | value = search[key].to_s # sfo1 20 | key = key.to_s # slug 21 | 22 | result = @result[sub_obj.to_s].reduce(nil) do |result, obj| 23 | obj[key] == value ? obj : result 24 | end 25 | 26 | result || error(sub_obj, key, value) 27 | end 28 | 29 | def error(sub_obj, key, value) 30 | fail(Errors::ResultMatchErro r, key: key, 31 | value: value, 32 | collection_name: sub_obj.to_s, 33 | sub_obj: @result[sub_obj.to_s]) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/read_state.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ReadState 5 | def initialize(app, env) 6 | @app = app 7 | @machine = env[:machine] 8 | @logger = Log4r::Logger.new('vagrant_linode::action::read_state') 9 | end 10 | 11 | def call(env) 12 | env[:machine_state] = read_state(env[:linode_api], @machine) 13 | @logger.info "Machine state is '#{env[:machine_state]}'" 14 | @app.call(env) 15 | end 16 | 17 | def read_state(_linode, machine) 18 | return :not_created if machine.id.nil? 19 | server = Provider.linode(machine) 20 | return :not_created if server.nil? 21 | status = server[:status] 22 | return :not_created if status.nil? 23 | states = { 24 | '' => :not_created, 25 | '-2' => :boot_failed, 26 | '-1' => :being_created, 27 | '0' => :brand_new, 28 | '1' => :active, # running 29 | '2' => :off, # powered off 30 | '3' => :shutting_down 31 | } 32 | states[status.to_s] 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/vagrant-linode/plugin.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'vagrant' 3 | rescue LoadError 4 | raise 'The Linode provider must be run within Vagrant.' 5 | end 6 | 7 | # This is a sanity check to make sure no one is attempting to install 8 | # this into an early Vagrant version. 9 | if Vagrant::VERSION < '1.1.0' 10 | fail 'Linode provider is only compatible with Vagrant 1.1+' 11 | end 12 | 13 | module VagrantPlugins 14 | module Linode 15 | class Plugin < Vagrant.plugin('2') 16 | name 'Linode' 17 | description <<-DESC 18 | This plugin installs a provider that allows Vagrant to manage 19 | machines using Linode's API. 20 | DESC 21 | 22 | config(:linode, :provider) do 23 | require_relative 'config' 24 | Config 25 | end 26 | 27 | provider(:linode, parallel: true) do 28 | Linode.init_i18n 29 | Linode.init_logging 30 | 31 | require_relative 'provider' 32 | Provider 33 | end 34 | 35 | command(:linode) do 36 | require_relative 'commands/root' 37 | Commands::Root 38 | end 39 | 40 | command(:rebuild) do 41 | require_relative 'commands/rebuild' 42 | Commands::Rebuild 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/setup_sudo.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class SetupSudo 5 | def initialize(app, env) 6 | @app = app 7 | @machine = env[:machine] 8 | @logger = Log4r::Logger.new('vagrant::linode::setup_sudo') 9 | end 10 | 11 | def call(env) 12 | # check if setup is enabled 13 | return @app.call(env) unless @machine.provider_config.setup? 14 | 15 | # override ssh username to root 16 | user = @machine.config.ssh.username 17 | @machine.config.ssh.username = 'root' 18 | 19 | # check for guest name available in Vagrant 1.2 first 20 | guest_name = @machine.guest.name if @machine.guest.respond_to?(:name) 21 | guest_name ||= @machine.guest.to_s.downcase 22 | 23 | case guest_name 24 | when /redhat/ 25 | env[:ui].info I18n.t('vagrant_linode.info.modifying_sudo') 26 | 27 | # disable tty requirement for sudo 28 | @machine.communicate.execute(<<-'BASH') 29 | sed -i'.bk' -e 's/\(Defaults\s\+requiretty\)/# \1/' /etc/sudoers 30 | BASH 31 | end 32 | 33 | # reset ssh username 34 | @machine.config.ssh.username = user 35 | 36 | @app.call(env) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/modify_provision_path.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ModifyProvisionPath 5 | def initialize(app, env) 6 | @app = app 7 | @machine = env[:machine] 8 | @logger = 9 | Log4r::Logger.new('vagrant::linode::modify_provision_path') 10 | end 11 | 12 | def call(env) 13 | # check if provisioning is enabled 14 | enabled = true 15 | enabled = env[:provision_enabled] if env.key?(:provision_enabled) 16 | return @app.call(env) unless enabled 17 | 18 | username = @machine.ssh_info[:username] 19 | 20 | # change ownership of the provisioning path recursively to the 21 | # ssh user 22 | # 23 | # TODO submit patch to vagrant to set appropriate permissions 24 | # based on ssh username 25 | @machine.config.vm.provisioners.each do |provisioner| 26 | cfg = provisioner.config 27 | path = cfg.upload_path if cfg.respond_to? :upload_path 28 | path = cfg.provisioning_path if cfg.respond_to? :provisioning_path 29 | @machine.communicate.sudo("chown -R #{username} #{path}", 30 | error_check: false) 31 | end 32 | 33 | @app.call(env) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/list_volumes.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class ListVolumes 5 | def initialize(app, _env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | api = env[:linode_api] 11 | logger = env[:ui] 12 | machine = env[:machine] 13 | 14 | remote_volumes = api.volume.list 15 | volume_definitions = machine.provider_config.volumes 16 | 17 | volume_definitions.each do |volume| 18 | volume_label = "#{machine.name}_#{volume[:label]}" 19 | remote_volume = remote_volumes.find { |v| v.label == volume_label } 20 | 21 | if remote_volume.nil? 22 | logger.info format_volume(volume_label, "does not exist") 23 | next 24 | end 25 | 26 | logger.info format_volume(volume_label, volume_state(machine, remote_volume)) 27 | end 28 | 29 | @app.call(env) 30 | end 31 | 32 | private 33 | 34 | def volume_state(machine, volume) 35 | if volume.linodeid.to_s == machine.id 36 | "attached" 37 | elsif volume.linodeid == 0 38 | "detached" 39 | else 40 | "attached to other VM" 41 | end 42 | end 43 | 44 | def format_volume(label, state) 45 | "volume \"%s\": %s" % [label, state] 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/vagrant-linode/actions/list_plans_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vagrant-linode/actions/list_plans' 3 | 4 | describe VagrantPlugins::Linode::Actions::ListPlans do 5 | let(:app) { lambda { |_env| } } 6 | let(:ui) { Vagrant::UI::Silent.new } 7 | let(:plans) do 8 | Fog.mock! 9 | Fog::Compute.new(provider: :linode, 10 | linode_datacenter: :dallas, 11 | linode_api_key: 'anything', 12 | linode_username: 'anything').plans 13 | end 14 | let(:compute_connection) { double('fog connection') } 15 | let(:env) do 16 | { 17 | linode_compute: compute_connection, 18 | ui: ui 19 | } 20 | end 21 | 22 | subject(:action) { described_class.new(app, env) } 23 | 24 | before do 25 | allow(compute_connection).to receive(:plans).and_return plans 26 | end 27 | 28 | it 'get plans from Fog' do 29 | expect(compute_connection).to receive(:plans).and_return plans 30 | action.call(env) 31 | end 32 | 33 | it 'writes a sorted, formatted plan table to Vagrant::UI' do 34 | header_line = '%-36s %s' % ['Plan ID', 'Plan Name'] 35 | expect(ui).to receive(:info).with(header_line) 36 | plans.sort_by(&:id).each do |plan| 37 | formatted_line = '%-36s %s' % [plan.id, plan.name] 38 | expect(ui).to receive(:info).with formatted_line 39 | end 40 | action.call(env) 41 | end 42 | 43 | it 'continues the middleware chain' do 44 | expect(app).to receive(:call).with(env) 45 | action.call(env) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/vagrant-linode/actions/list_distributions_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vagrant-linode/actions/list_distributions' 3 | 4 | describe VagrantPlugins::Linode::Actions::ListImages do 5 | let(:app) { lambda { |_env| } } 6 | let(:ui) { Vagrant::UI::Silent.new } 7 | let(:distributions) do 8 | Fog.mock! 9 | Fog::Compute.new(provider: :linode, 10 | linode_region: :dfw, 11 | linode_api_key: 'anything', 12 | linode_username: 'anything').distributions 13 | end 14 | let(:compute_connection) { double('fog connection') } 15 | let(:env) do 16 | { 17 | linode_compute: compute_connection, 18 | ui: ui 19 | } 20 | end 21 | 22 | subject(:action) { described_class.new(app, env) } 23 | 24 | before do 25 | allow(compute_connection).to receive(:distributions).and_return distributions 26 | end 27 | 28 | it 'get distributions from Fog' do 29 | expect(compute_connection).to receive(:distributions).and_return distributions 30 | action.call(env) 31 | end 32 | 33 | it 'writes a sorted, formatted image table to Vagrant::UI' do 34 | header_line = '%-36s %s' % ['Image ID', 'Image Name'] 35 | expect(ui).to receive(:info).with(header_line) 36 | distributions.sort_by(&:name).each do |image| 37 | formatted_line = '%-36s %s' % [image.id.to_s, image.name] 38 | expect(ui).to receive(:info).with formatted_line 39 | end 40 | action.call(env) 41 | end 42 | 43 | it 'continues the middleware chain' do 44 | expect(app).to receive(:call).with(env) 45 | action.call(env) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/read_ssh_info.rb: -------------------------------------------------------------------------------- 1 | require 'log4r' 2 | 3 | module VagrantPlugins 4 | module Linode 5 | module Actions 6 | # This action reads the SSH info for the machine and puts it into the 7 | # `:machine_ssh_info` key in the environment. 8 | class ReadSSHInfo 9 | def initialize(app, _env) 10 | @env = _env 11 | @app = app 12 | @machine = _env[:machine] 13 | @logger = Log4r::Logger.new('vagrant_linode::action::read_ssh_info') 14 | end 15 | 16 | def call(env) 17 | env[:machine_ssh_info] = read_ssh_info(env[:linode_api], @machine) 18 | 19 | @app.call(env) 20 | end 21 | 22 | def read_ssh_info(_linode, machine) 23 | return nil if machine.id.nil? 24 | server = Provider.linode(machine, refresh: true) 25 | @env[:ui].info "Machine State ID: %s" % [machine.state.id] 26 | 27 | #return nil if machine.state.id != :active # @todo this seems redundant to the next line. may be more correct. 28 | if server.nil? 29 | # The machine can't be found 30 | @logger.info("Machine couldn't be found, assuming it got destroyed.") 31 | machine.id = nil 32 | return nil 33 | end 34 | 35 | public_network = server.network.find { |network| network['ispublic'] == 1 } 36 | @env[:ui].info "IP Address: %s" % public_network['ipaddress'] 37 | 38 | { 39 | host: public_network['ipaddress'], 40 | port: '22', 41 | username: 'root', 42 | private_key_path: machine.config.ssh.private_key_path 43 | } 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/test_id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAzbDbxYaq3ANDCzU+c+BCpiPNeSCwj9PS6IgUNdP5tZzaL3M5 3 | l+iy5rS0lsjw4229AdF5oO1TOGzefvIkqHd/3feizsUFzvvSM+ytPCadMkPA+bHi 4 | hWGJPyjLDLWCF5kHo9gdNxCDc72cPmGWJqm4YdwWA8Pgk2X8gSx3klutOWg+vWrk 5 | bX/LENaG8r5uk/tIGvDHV/CZQFZEUDo6ECJ4Din4mLlBbLGaE0swQSthEePlro/k 6 | s2eJ2TceOo9qcTkQnIaIb6WUETfylDkl1z/PiDt+7QwmALmEttWjzrOvUoJ6FA7H 7 | 3Hi41des6wKUYjtCN2R+bvR9b9SNj9BXj+ljmwIDAQABAoIBAA0ObOTc53uPuXG8 8 | r3orgg+JtkE6EfsPNxQLjzzbd75PdooMhlteKfz6+3uWxbOqA5VZ9p6Acgfi4Tyt 9 | oiYPb85nKa52UygQVAd3vodS7CeEpXs0D2zoBA4+SKVF4DwfOpzr2u7j3XQ7VO+g 10 | wicyHsIXdk5G4Lp6fsy0ReLEbvp1xknODE6y7mXfnFjV0SfbrB9vBYfljjD3aDmT 11 | nrAK1EMx1OLqh9p0fnHYS2d7q8kw6XE8DRVS/XCQEfqAmNr6ZPizVWrUWe9AN+dA 12 | 31atcA+UE+mOTnN/AL2BGE0Dndi8+ZUONxKkBKOdY2tIRPUbap8BANyXDJstFdFH 13 | ZTQlPgECgYEA+YF0NKbQ2/+TOPyLa988pJRiq6tCw1kYf98pwtyUbqVYCeldkCGc 14 | yNGSePvN0bhJrC5knI/05ia31VjQ7gtKZAz8RjnvfMCJyyYR8Dg00KvbUHm6NEWS 15 | Xw+5pz9Uhblaee9QmJ+oQ6VBZPZb98g+tBTTJDEucW61y1LCGp+BVUUCgYEA0wtz 16 | W0yjTBBmnTIgrDP0jDItT27zXl0M9KCU4VCB9NqpENq3lWrQVM4bWML1+WOm3NCR 17 | yQnX4n53QFqxerOEVrVt0hOiGwgttrltsjPrErydA0XkKMIzr7bAD2JUpeJWInro 18 | wuCQAoEzYv3HFtryuc0s7hTq5Xeph0MihBsWM18CgYBSC/bZpY2C+r0//RQf6e34 19 | NO9pgkzXDkJXMlx6Pqz04Zxczge9cMAs7XWcITmiYFahrzPYpCIlWNAU8TrrPH0+ 20 | /2Ip+b0+KdZmHmPBucnsYMci5JSNwd8LMZGcZN/3hWcyN7cqKT5c2Efz2muNxKSR 21 | 9VMlUKL0HDLd5J39wTv3fQKBgQDGrQDr8jnIYag4U/huJHsTgCknnkt9ihuoL4P4 22 | mNG+sBp4w24QO33kWCNmbCMjo6xyM+cKWzng/y1EaBysZlMvTZ0VJ2Z0DD78xZN/ 23 | L2EdQnKNoj4oIKqHwIMN+IO3pltwGkUFMGJh+T9m8YF7AqN+RqkFeKupWf0+WPUl 24 | aFp+AQKBgQDhTM46yr+6TGanepYon6XlTjFCtXEzdfVIP9GLoGQakKukX1XAJgX3 25 | QYzjsQp4Ig26dXTjTd8y+0Wwf8tuSeQzBE1ZJK/EjFgP/je7NEMMfRgB06Tcnzhs 26 | s5hIxs8t9OB/Um6DtWGMA0myQsNt4jcLcINvsEQhNR3bk2iBIPwofA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /lib/vagrant-linode/services/volume_manager.rb: -------------------------------------------------------------------------------- 1 | require "vagrant-linode/errors" 2 | 3 | module VagrantPlugins 4 | module Linode 5 | module Services 6 | class VolumeManager 7 | def initialize(machine, api, logger) 8 | @machine = machine 9 | @volumes_api = api 10 | @logger = logger 11 | end 12 | 13 | def perform 14 | volume_definitions.each do |volume| 15 | raise Errors::VolumeLabelMissing if volume[:label].to_s.empty? 16 | 17 | volume_name = "#{@machine.name}_#{volume[:label]}" 18 | 19 | remote_volume = remote_volumes.find { |v| v.label == volume_name } 20 | if remote_volume 21 | attach_volume(remote_volume) 22 | else 23 | create_and_attach_volume(volume_name, volume[:size]) 24 | end 25 | end 26 | end 27 | 28 | private 29 | 30 | def volume_definitions 31 | @machine.provider_config.volumes 32 | end 33 | 34 | def remote_volumes 35 | @_remote_volumes ||= @volumes_api.list 36 | end 37 | 38 | def attach_volume(volume) 39 | @volumes_api.update( 40 | volumeid: volume.volumeid, 41 | linodeid: @machine.id 42 | ) 43 | @logger.info "volume #{volume.label} attached" 44 | end 45 | 46 | def create_and_attach_volume(label, size) 47 | raise Errors::VolumeSizeMissing unless size.to_i > 0 48 | 49 | @volumes_api.create( 50 | label: label, 51 | size: size, 52 | linodeid: @machine.id 53 | ) 54 | @logger.info "volume #{label} created and attached" 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/vagrant-linode/services/volume_manager_spec.rb: -------------------------------------------------------------------------------- 1 | require "vagrant-linode/services/volume_manager" 2 | 3 | describe VagrantPlugins::Linode::Services::VolumeManager do 4 | subject { described_class.new(machine, client, logger) } 5 | let(:provider_config) { double(:config, volumes: [{label: "testvolume", size: 3}]) } 6 | let(:machine) { double(:machine, id: 123, name: "test", provider_config: provider_config) } 7 | let(:logger) { double(:logger, info: nil) } 8 | let(:remote_volumes) { [double(:volume, volumeid: 234, size: 3, label: "test_testvolume")] } 9 | let(:client) { double(:api, list: remote_volumes) } 10 | 11 | describe "#perform" do 12 | context "when the volume label is not specified" do 13 | let(:provider_config) { double(:config, volumes: [{size: 3}]) } 14 | it "raises an error" do 15 | expect { subject.perform }.to raise_error "You must specify a volume label." 16 | end 17 | end 18 | 19 | context "when the remote volume does not exist" do 20 | let(:remote_volumes) { [] } 21 | it "creates the volume bound to the linode" do 22 | expect(client).to receive(:create).with(label: "test_testvolume", size: 3, linodeid: 123) 23 | subject.perform 24 | end 25 | 26 | context "when the size is not specified" do 27 | let(:provider_config) { double(:config, volumes: [{label: "testvolume"}]) } 28 | it "raises an error" do 29 | expect { subject.perform }.to raise_error "For volumes that need to be created the size has to be specified." 30 | end 31 | end 32 | end 33 | 34 | context "when the remote volume exists" do 35 | let(:remote_volumes) { [double(:volume, volumeid: 234, size: 3, label: "test_testvolume")] } 36 | it "attaches the volume to the machine" do 37 | expect(client).to receive(:update).with(volumeid: 234, linodeid: 123) 38 | subject.perform 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/vagrant-linode/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Errors 4 | class LinodeError < Vagrant::Errors::VagrantError 5 | error_namespace('vagrant_linode.errors') 6 | end 7 | 8 | class APIStatusError < LinodeError 9 | error_key(:api_status) 10 | end 11 | 12 | class DiskSize < LinodeError 13 | error_key(:disk_size) 14 | end 15 | 16 | class DistroMatch < LinodeError 17 | error_key(:distro_match) 18 | end 19 | 20 | class DatacenterMatch < LinodeError 21 | error_key(:datacenter_match) 22 | end 23 | 24 | class ImageMatch < LinodeError 25 | error_key(:image_match) 26 | end 27 | 28 | class KernelMatch < LinodeError 29 | error_key(:kernel_match) 30 | end 31 | 32 | class JSONError < LinodeError 33 | error_key(:json) 34 | end 35 | 36 | class ResultMatchError < LinodeError 37 | error_key(:result_match) 38 | end 39 | 40 | class CertificateError < LinodeError 41 | error_key(:certificate) 42 | end 43 | 44 | class LocalIPError < LinodeError 45 | error_key(:local_ip) 46 | end 47 | 48 | class PlanID < LinodeError 49 | error_key(:plan_id) 50 | end 51 | 52 | class PublicKeyError < LinodeError 53 | error_key(:public_key) 54 | end 55 | 56 | class RsyncError < LinodeError 57 | error_key(:rsync) 58 | end 59 | 60 | class StackscriptMatch < LinodeError 61 | error_key(:stackscript_match) 62 | end 63 | 64 | class StackscriptUDFFormat < LinodeError 65 | error_key(:stackscript_udf_responses) 66 | end 67 | 68 | class VolumeSizeMissing < LinodeError 69 | error_key(:volume_size_missing) 70 | end 71 | 72 | class VolumeLabelMissing < LinodeError 73 | error_key(:volume_label_missing) 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/volumes.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Volumes < Vagrant.plugin('2', :command) 5 | def initialize(argv, env) 6 | @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) 7 | 8 | @subcommands = Vagrant::Registry.new 9 | @subcommands.register(:list) do 10 | require File.expand_path('../list_volumes', __FILE__) 11 | ListVolumes 12 | end 13 | 14 | super(argv, env) 15 | end 16 | 17 | def execute 18 | if @main_args.include?('-h') || @main_args.include?('--help') 19 | # Print the help for all the rackspace commands. 20 | return help 21 | end 22 | 23 | command_class = @subcommands.get(@sub_command.to_sym) if @sub_command 24 | return help if !command_class || !@sub_command 25 | @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") 26 | 27 | # Initialize and execute the command class 28 | command_class.new(@sub_args, @env).execute 29 | end 30 | 31 | def help 32 | opts = OptionParser.new do |opts| 33 | opts.banner = 'Usage: vagrant linode volumes []' 34 | opts.separator '' 35 | opts.separator 'Available subcommands:' 36 | 37 | # Add the available subcommands as separators in order to print them 38 | # out as well. 39 | keys = [] 40 | @subcommands.each { |key, _value| keys << key.to_s } 41 | 42 | keys.sort.each do |key| 43 | opts.separator " #{key}" 44 | end 45 | 46 | opts.separator '' 47 | opts.separator 'For help on any individual subcommand run `vagrant linode volumes -h`' 48 | end 49 | 50 | @env.ui.info(opts.help, prefix: false) 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure('2') do |config| 5 | config.ssh.username = 'tester' 6 | config.ssh.private_key_path = './test_id_rsa' 7 | 8 | config.vm.synced_folder '.', '/vagrant', disabled: true 9 | 10 | config.vm.provider :linode do |provider, override| 11 | override.vm.box = 'linode' 12 | override.vm.box_url = 'https://github.com/displague/vagrant-linode/raw/master/box/linode.box' 13 | provider.api_key = ENV['LINODE_API_KEY'] 14 | provider.ssh_key_name = 'Test Key' 15 | provider.distribution = 'Debian 8' 16 | provider.datacenter = 'newark' 17 | provider.plan = '2048' 18 | provider.label = 'vagrant-'+Time.new.strftime("%F%T").gsub(/[^0-9]/,'') 19 | 20 | # Disk Image Sizes (Optional configuration) 21 | 22 | # Main Disk Image Size 23 | # [str] ( Full Allocation - Swap ) if nil 24 | provider.xvda_size = '2048' 25 | 26 | # Swap Image Size 27 | # [str] 256 if nil 28 | provider.swap_size = '256' 29 | 30 | # Kernel Image ID 31 | # [str] 138 if nil 32 | # provider.kernel_id = '138' 33 | provider.kernel = 'Latest 64 bit' 34 | 35 | # Networking (Optional Configuration) 36 | 37 | # Enable private networking 38 | # [boolean] disabled if nil 39 | # provider.private_networking = true 40 | end 41 | 42 | config.vm.provision :shell, path: 'scripts/provision.sh' 43 | 44 | config.vm.provision :chef_solo do |chef| 45 | chef.cookbooks_path = 'cookbooks' 46 | chef.add_recipe 'test' 47 | end 48 | 49 | # Linode Specific Configurations 50 | config.vm.define :ubuntu do |ubuntu| 51 | ubuntu.vm.provider :linode do |provider| 52 | provider.distribution = 'Ubuntu 14.04 LTS' 53 | 54 | # Optional Settings 55 | provider.label = 'Vagrant-Ubuntu' 56 | end 57 | end 58 | 59 | config.vm.define :centos do |centos| 60 | centos.vm.provider :linode do |provider| 61 | provider.distribution = 'CentOS 7' 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/vagrant-linode.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | require 'vagrant-linode/plugin' 4 | 5 | module VagrantPlugins 6 | module Linode 7 | lib_path = Pathname.new(File.expand_path('../vagrant-linode', __FILE__)) 8 | autoload :Errors, lib_path.join('errors') 9 | 10 | # This initializes the i18n load path so that the plugin-specific 11 | # translations work. 12 | def self.init_i18n 13 | I18n.load_path << File.expand_path('locales/en.yml', source_root) 14 | I18n.reload! 15 | end 16 | 17 | # This initializes the logging so that our logs are outputted at 18 | # the same level as Vagrant core logs. 19 | def self.init_logging 20 | # Initialize logging 21 | level = nil 22 | begin 23 | level = Log4r.const_get(ENV['VAGRANT_LOG'].upcase) 24 | rescue NameError 25 | # This means that the logging constant wasn't found, 26 | # which is fine. We just keep `level` as `nil`. But 27 | # we tell the user. 28 | level = nil 29 | end 30 | 31 | # Some constants, such as "true" resolve to booleans, so the 32 | # above error checking doesn't catch it. This will check to make 33 | # sure that the log level is an integer, as Log4r requires. 34 | level = nil unless level.is_a?(Integer) 35 | 36 | # Set the logging level on all "vagrant" namespaced 37 | # logs as long as we have a valid level. 38 | if level 39 | logger = Log4r::Logger.new('vagrant_linode') 40 | logger.outputters = Log4r::Outputter.stderr 41 | logger.level = level 42 | logger = nil 43 | end 44 | end 45 | 46 | # This returns the path to the source of this plugin. 47 | # 48 | # @return [Pathname] 49 | def self.source_root 50 | @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) 51 | end 52 | 53 | def self.public_key(private_key_path) 54 | File.read("#{private_key_path}.pub") 55 | rescue 56 | raise Errors::PublicKeyError, path: "#{private_key_path}.pub" 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/images.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Commands 4 | class Images < Vagrant.plugin('2', :command) 5 | def initialize(argv, env) 6 | @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) 7 | 8 | @subcommands = Vagrant::Registry.new 9 | @subcommands.register(:list) do 10 | require File.expand_path('../list_images', __FILE__) 11 | ListImages 12 | end 13 | @subcommands.register(:create) do 14 | require File.expand_path('../create_image', __FILE__) 15 | CreateImage 16 | end 17 | 18 | super(argv, env) 19 | end 20 | 21 | def execute 22 | if @main_args.include?('-h') || @main_args.include?('--help') 23 | # Print the help for all the rackspace commands. 24 | return help 25 | end 26 | 27 | command_class = @subcommands.get(@sub_command.to_sym) if @sub_command 28 | return help if !command_class || !@sub_command 29 | @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") 30 | 31 | # Initialize and execute the command class 32 | command_class.new(@sub_args, @env).execute 33 | end 34 | 35 | def help 36 | opts = OptionParser.new do |opts| 37 | opts.banner = 'Usage: vagrant linode images []' 38 | opts.separator '' 39 | opts.separator 'Available subcommands:' 40 | 41 | # Add the available subcommands as separators in order to print them 42 | # out as well. 43 | keys = [] 44 | @subcommands.each { |key, _value| keys << key.to_s } 45 | 46 | keys.sort.each do |key| 47 | opts.separator " #{key}" 48 | end 49 | 50 | opts.separator '' 51 | opts.separator 'For help on any individual subcommand run `vagrant linode images -h`' 52 | end 53 | 54 | @env.ui.info(opts.help, prefix: false) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/vagrant-linode/helpers/client.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/result' 2 | require 'vagrant-linode/version' 3 | require 'linodeapi' 4 | require 'json' 5 | require 'vagrant/util/retryable' 6 | 7 | include Vagrant::Util::Retryable 8 | 9 | module VagrantPlugins 10 | module Linode 11 | module Helpers 12 | # module Client 13 | # def client 14 | # def wait_for_event(env, id) 15 | # retryable(tries: 120, sleep: 10) do 16 | # # stop waiting if interrupted 17 | # next if env[:interrupted] 18 | # # check action status 19 | # result = @client.linode.job.list(jobid: id, linodeid: env[:machine].id) 20 | # result = result[0] if result.is_a?(Array) 21 | # 22 | # yield result if block_given? 23 | # fail 'not ready' if result['host_finish_dt'] > '' 24 | # end 25 | # end 26 | # linodeapi = ::LinodeAPI::Raw.new(apikey: @machine.provider_config.api_key, 27 | # endpoint: @machine.provider_config.api_url || nil) 28 | # # linodeapi.wait_for_event = wait_for_event 29 | # # linodeapi.extend wait_for_event 30 | # end 31 | # end 32 | 33 | class ApiClient 34 | include Vagrant::Util::Retryable 35 | 36 | def initialize(machine) 37 | @logger = Log4r::Logger.new('vagrant::linode::apiclient') 38 | @config = machine.provider_config 39 | @client = ::LinodeAPI::Retryable.new(apikey: @config.api_key, 40 | endpoint: @config.api_url || nil, 41 | user_agent_prefix: "vagrant-linode/#{VagrantPlugins::Linode::VERSION}") 42 | end 43 | 44 | attr_reader :client 45 | 46 | def wait_for_event(env, id) 47 | retryable(tries: 120, sleep: 10) do 48 | # stop waiting if interrupted 49 | next if env[:interrupted] 50 | 51 | # check action status 52 | result = @client.linode.job.list(jobid: id, linodeid: env[:machine].id) 53 | 54 | yield result if block_given? 55 | fail 'not ready' if result['host_finish_dt'] > '' 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/setup_user.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | module Actions 4 | class SetupUser 5 | def initialize(app, env) 6 | @app = app 7 | @machine = env[:machine] 8 | @logger = Log4r::Logger.new('vagrant::linode::setup_user') 9 | end 10 | 11 | def call(env) 12 | # check if setup is enabled 13 | return @app.call(env) unless @machine.provider_config.setup? 14 | 15 | # check if a username has been specified 16 | return @app.call(env) unless @machine.config.ssh.username 17 | 18 | # override ssh username to root temporarily 19 | user = @machine.config.ssh.username 20 | @machine.config.ssh.username = 'root' 21 | 22 | env[:ui].info I18n.t('vagrant_linode.info.creating_user', user: user) 23 | 24 | # create user account 25 | @machine.communicate.execute(<<-BASH) 26 | if ! (grep ^#{user}: /etc/passwd); then 27 | useradd -m -s /bin/bash #{user}; 28 | fi 29 | BASH 30 | 31 | # grant user sudo access with no password requirement 32 | @machine.communicate.execute(<<-BASH) 33 | if ! (grep #{user} /etc/sudoers); then 34 | echo "#{user} ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers; 35 | else 36 | sed -i -e "/#{user}/ s/=.*/=(ALL:ALL) NOPASSWD: ALL/" /etc/sudoers; 37 | fi 38 | BASH 39 | 40 | # create the .ssh directory in the users home 41 | @machine.communicate.execute("su #{user} -c 'mkdir -p ~/.ssh'") 42 | 43 | # add the specified key to the authorized keys file 44 | path = @machine.config.ssh.private_key_path 45 | path = path[0] if path.is_a?(Array) 46 | path = File.expand_path(path, @machine.env.root_path) 47 | pub_key = Linode.public_key(path) 48 | @machine.communicate.execute(<<-BASH) 49 | if ! grep '#{pub_key}' /home/#{user}/.ssh/authorized_keys; then 50 | echo '#{pub_key}' >> /home/#{user}/.ssh/authorized_keys; 51 | fi 52 | BASH 53 | 54 | # reset username 55 | @machine.config.ssh.username = user 56 | 57 | @app.call(env) 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/vagrant-linode/commands/root.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/actions' 2 | 3 | module VagrantPlugins 4 | module Linode 5 | module Commands 6 | class Root < Vagrant.plugin('2', :command) 7 | def self.synopsis 8 | 'query Linode for available images or plans' 9 | end 10 | 11 | def initialize(argv, env) 12 | @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) 13 | 14 | @subcommands = Vagrant::Registry.new 15 | @subcommands.register(:images) do 16 | require File.expand_path('../images', __FILE__) 17 | Images 18 | end 19 | @subcommands.register(:plans) do 20 | require File.expand_path('../plans', __FILE__) 21 | Plans 22 | end 23 | @subcommands.register(:distributions) do 24 | require File.expand_path('../distributions', __FILE__) 25 | Distributions 26 | end 27 | @subcommands.register(:kernels) do 28 | require File.expand_path('../kernels', __FILE__) 29 | Kernels 30 | end 31 | @subcommands.register(:datacenters) do 32 | require File.expand_path('../datacenters', __FILE__) 33 | Datacenters 34 | end 35 | @subcommands.register(:networks) do 36 | require File.expand_path('../networks', __FILE__) 37 | Networks 38 | end 39 | @subcommands.register(:servers) do 40 | require File.expand_path('../servers', __FILE__) 41 | Servers 42 | end 43 | @subcommands.register(:volumes) do 44 | require File.expand_path('../volumes', __FILE__) 45 | Volumes 46 | end 47 | 48 | super(argv, env) 49 | end 50 | 51 | def execute 52 | if @main_args.include?('-h') || @main_args.include?('--help') 53 | # Print the help for all the linode commands. 54 | return help 55 | end 56 | 57 | command_class = @subcommands.get(@sub_command.to_sym) if @sub_command 58 | return help if !command_class || !@sub_command 59 | @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") 60 | 61 | # Initialize and execute the command class 62 | command_class.new(@sub_args, @env).execute 63 | end 64 | 65 | def help 66 | opts = OptionParser.new do |opts| 67 | opts.banner = 'Usage: vagrant linode []' 68 | opts.separator '' 69 | opts.separator 'Available subcommands:' 70 | 71 | # Add the available subcommands as separators in order to print them 72 | # out as well. 73 | keys = [] 74 | @subcommands.each { |key, _value| keys << key.to_s } 75 | 76 | keys.sort.each do |key| 77 | opts.separator " #{key}" 78 | end 79 | 80 | opts.separator '' 81 | opts.separator 'For help on any individual subcommand run `vagrant linode -h`' 82 | end 83 | 84 | @env.ui.info(opts.help, prefix: false) 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2014-12-02 23:23:38 -0500 using RuboCop version 0.27.1. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | 8 | # Offense count: 1 9 | # Configuration parameters: AlignWith, SupportedStyles. 10 | Lint/EndAlignment: 11 | Enabled: false 12 | 13 | # Offense count: 12 14 | Lint/ParenthesesAsGroupedExpression: 15 | Enabled: false 16 | 17 | # Offense count: 3 18 | Lint/ShadowingOuterLocalVariable: 19 | Enabled: false 20 | 21 | # Offense count: 1 22 | Lint/UnderscorePrefixedVariableName: 23 | Enabled: false 24 | 25 | # Offense count: 1 26 | Lint/UnreachableCode: 27 | Enabled: false 28 | 29 | # Offense count: 14 30 | Lint/UselessAssignment: 31 | Enabled: false 32 | 33 | # Offense count: 18 34 | Metrics/AbcSize: 35 | Max: 200 36 | 37 | # Offense count: 1 38 | # Configuration parameters: CountComments. 39 | Metrics/ClassLength: 40 | Max: 139 41 | 42 | # Offense count: 4 43 | Metrics/CyclomaticComplexity: 44 | Max: 27 45 | 46 | # Offense count: 66 47 | # Configuration parameters: AllowURI, URISchemes. 48 | Metrics/LineLength: 49 | Max: 138 50 | 51 | # Offense count: 26 52 | # Configuration parameters: CountComments. 53 | Metrics/MethodLength: 54 | Max: 120 55 | 56 | # Offense count: 4 57 | Metrics/PerceivedComplexity: 58 | Max: 33 59 | 60 | # Offense count: 1 61 | Style/AccessorMethodName: 62 | Enabled: false 63 | 64 | # Offense count: 2 65 | # Configuration parameters: Keywords. 66 | Style/CommentAnnotation: 67 | Enabled: false 68 | 69 | # Offense count: 51 70 | Style/Documentation: 71 | Enabled: false 72 | 73 | # Offense count: 1 74 | # Cop supports --auto-correct. 75 | Style/ElseAlignment: 76 | Enabled: false 77 | 78 | # Offense count: 1 79 | # Configuration parameters: Exclude. 80 | Style/FileName: 81 | Enabled: false 82 | 83 | # Offense count: 16 84 | # Configuration parameters: EnforcedStyle, SupportedStyles. 85 | Style/FormatString: 86 | Enabled: false 87 | 88 | # Offense count: 1 89 | # Configuration parameters: MinBodyLength. 90 | Style/GuardClause: 91 | Enabled: false 92 | 93 | # Offense count: 4 94 | # Cop supports --auto-correct. 95 | # Configuration parameters: EnforcedStyle, SupportedStyles. 96 | Style/HashSyntax: 97 | Enabled: false 98 | 99 | # Offense count: 2 100 | # Configuration parameters: MaxLineLength. 101 | Style/IfUnlessModifier: 102 | Enabled: false 103 | 104 | # Offense count: 42 105 | # Cop supports --auto-correct. 106 | Style/IndentationConsistency: 107 | Enabled: false 108 | 109 | # Offense count: 3 110 | # Cop supports --auto-correct. 111 | # Configuration parameters: Width. 112 | Style/IndentationWidth: 113 | Enabled: false 114 | 115 | # Offense count: 2 116 | Style/Lambda: 117 | Enabled: false 118 | 119 | # Offense count: 2 120 | # Cop supports --auto-correct. 121 | Style/LeadingCommentSpace: 122 | Enabled: false 123 | 124 | # Offense count: 3 125 | # Cop supports --auto-correct. 126 | # Configuration parameters: EnforcedStyle, SupportedStyles. 127 | Style/MultilineOperationIndentation: 128 | Enabled: false 129 | 130 | # Offense count: 1 131 | # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. 132 | Style/Next: 133 | Enabled: false 134 | 135 | # Offense count: 2 136 | # Configuration parameters: MaxSlashes. 137 | Style/RegexpLiteral: 138 | Enabled: false 139 | 140 | # Offense count: 2 141 | Style/SelfAssignment: 142 | Enabled: false 143 | 144 | # Offense count: 5 145 | # Cop supports --auto-correct. 146 | Style/SpaceInsideParens: 147 | Enabled: false 148 | 149 | # Offense count: 4 150 | # Cop supports --auto-correct. 151 | # Configuration parameters: EnforcedStyle, SupportedStyles. 152 | Style/StringLiterals: 153 | Enabled: false 154 | 155 | # Offense count: 10 156 | # Cop supports --auto-correct. 157 | Style/Tab: 158 | Enabled: false 159 | 160 | # Offense count: 1 161 | # Cop supports --auto-correct. 162 | # Configuration parameters: WordRegex. 163 | Style/WordArray: 164 | MinSize: 5 165 | -------------------------------------------------------------------------------- /lib/vagrant-linode/provider.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | require 'vagrant-linode/actions' 3 | 4 | module VagrantPlugins 5 | module Linode 6 | class Provider < Vagrant.plugin('2', :provider) 7 | def initialize(machine) 8 | @machine = machine 9 | end 10 | 11 | # This class method caches status for all linodes within 12 | # the Linode account. A specific linode's status 13 | # may be refreshed by passing :refresh => true as an option. 14 | def self.linode(machine, opts = {}) 15 | client = Helpers::ApiClient.new(machine).client 16 | 17 | # @todo how do I reuse VagrantPlugins::Linode::Actions::ConnectLinode ? 18 | # ..and nuke the helper 19 | # client = env[:linode_api] 20 | 21 | # load status of linodes if it has not been done before 22 | unless @linodes 23 | @linodes = client.linode.list.each { |l| l.network = client.linode.ip.list linodeid: l.linodeid } 24 | end 25 | 26 | if opts[:refresh] && machine.id 27 | # refresh the linode status for the given machine 28 | @linodes.delete_if { |d| d['linodeid'].to_s == machine.id } 29 | linode = client.linode.list(linodeid: machine.id).first 30 | linode.network = client.linode.ip.list linodeid: linode['linodeid'] 31 | @linodes << linode 32 | elsif machine.id 33 | # lookup linode status for the given machine 34 | linode = @linodes.find { |d| d['linodeid'].to_s == machine.id } 35 | end 36 | 37 | # if lookup by id failed, check for a linode with a matching name 38 | # and set the id to ensure vagrant stores locally 39 | # TODO allow the user to configure this behavior 40 | unless linode 41 | name = machine.config.vm.hostname || machine.name 42 | linode = @linodes.find { |d| d['label'] == name.to_s } 43 | machine.id = linode['linodeid'].to_s if linode 44 | end 45 | 46 | linode ||= { status: :not_created } 47 | end 48 | 49 | # Attempt to get the action method from the Action class if it 50 | # exists, otherwise return nil to show that we don't support the 51 | # given action. 52 | def action(name) 53 | action_method = "action_#{name}" 54 | return Actions.send(action_method) if Actions.respond_to?(action_method) 55 | nil 56 | end 57 | 58 | # This method is called if the underying machine ID changes. Providers 59 | # can use this method to load in new data for the actual backing 60 | # machine or to realize that the machine is now gone (the ID can 61 | # become `nil`). No parameters are given, since the underlying machine 62 | # is simply the machine instance given to this object. And no 63 | # return value is necessary. 64 | def machine_id_changed 65 | if @machine.id 66 | Provider.linode(@machine, refresh: true) 67 | end 68 | end 69 | 70 | # This should return a hash of information that explains how to 71 | # SSH into the machine. If the machine is not at a point where 72 | # SSH is even possible, then `nil` should be returned. 73 | # 74 | # The general structure of this returned hash should be the 75 | # following: 76 | # 77 | # { 78 | # :host => "1.2.3.4", 79 | # :port => "22", 80 | # :username => "mitchellh", 81 | # :private_key_path => "/path/to/my/key" 82 | # } 83 | # 84 | # **Note:** Vagrant only supports private key based authenticatonion, 85 | # mainly for the reason that there is no easy way to exec into an 86 | # `ssh` prompt with a password, whereas we can pass a private key 87 | # via commandline. 88 | def ssh_info 89 | env = @machine.action('read_ssh_info') 90 | env[:machine_ssh_info] 91 | end 92 | 93 | # This should return the state of the machine within this provider. 94 | # The state must be an instance of {MachineState}. Please read the 95 | # documentation of that class for more information. 96 | def state 97 | env = @machine.action('read_state') 98 | state_id = env[:machine_state] 99 | 100 | short = I18n.t("vagrant_linode.states.short_#{state_id}") 101 | long = I18n.t("vagrant_linode.states.long_#{state_id}") 102 | 103 | Vagrant::MachineState.new(state_id, short, long) 104 | end 105 | 106 | def to_s 107 | id = @machine.id.nil? ? 'new' : @machine.id 108 | "Linode (#{id})" 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib/vagrant-linode/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Linode 3 | class Config < Vagrant.plugin('2', :config) 4 | attr_accessor :token # deprecated 5 | attr_accessor :api_key 6 | attr_accessor :api_url 7 | attr_accessor :distributionid 8 | attr_accessor :distribution 9 | attr_accessor :imageid 10 | attr_accessor :image 11 | attr_accessor :datacenterid 12 | attr_accessor :datacenter 13 | attr_accessor :planid 14 | attr_accessor :plan 15 | attr_accessor :paymentterm 16 | attr_accessor :private_networking 17 | attr_accessor :ca_path 18 | attr_accessor :ssh_key_name 19 | attr_accessor :setup 20 | attr_accessor :stackscriptid 21 | attr_accessor :stackscript 22 | attr_accessor :stackscript_udf_responses 23 | attr_accessor :xvda_size 24 | attr_accessor :swap_size 25 | attr_accessor :kernelid 26 | attr_accessor :kernel 27 | attr_accessor :label 28 | attr_accessor :group 29 | attr_accessor :volumes 30 | 31 | alias_method :setup?, :setup 32 | 33 | def initialize 34 | # @logger = Log4r::Logger.new('vagrant::linode::config') 35 | 36 | @token = UNSET_VALUE 37 | @api_key = UNSET_VALUE 38 | @api_url = UNSET_VALUE 39 | @distributionid = UNSET_VALUE 40 | @distribution = UNSET_VALUE 41 | @stackscriptid = UNSET_VALUE 42 | @stackscript = UNSET_VALUE 43 | @stackscript_udf_responses = UNSET_VALUE 44 | @imageid = UNSET_VALUE 45 | @image = UNSET_VALUE 46 | @datacenterid = UNSET_VALUE 47 | @datacenter = UNSET_VALUE 48 | @planid = UNSET_VALUE 49 | @plan = UNSET_VALUE 50 | @paymentterm = UNSET_VALUE 51 | @private_networking = UNSET_VALUE 52 | @ca_path = UNSET_VALUE 53 | @ssh_key_name = UNSET_VALUE 54 | @setup = UNSET_VALUE 55 | @xvda_size = UNSET_VALUE 56 | @swap_size = UNSET_VALUE 57 | @kernelid = UNSET_VALUE 58 | @kernel = UNSET_VALUE 59 | @label = UNSET_VALUE 60 | @group = UNSET_VALUE 61 | @volumes = UNSET_VALUE 62 | end 63 | 64 | def finalize! 65 | @api_key = ENV['LINODE_API_KEY'] if @api_key == UNSET_VALUE 66 | @token = ENV['LINODE_TOKEN'] if @token == UNSET_VALUE 67 | @api_key = @token if ((@api_key == nil) and (@token != nil)) 68 | @api_url = ENV['LINODE_URL'] if @api_url == UNSET_VALUE 69 | @imageid = nil if @imageid == UNSET_VALUE 70 | @image = nil if @image == UNSET_VALUE 71 | @distributionid = nil if @distributionid == UNSET_VALUE 72 | @distribution = nil if @distribution == UNSET_VALUE 73 | @distribution = 'Ubuntu 16.04 LTS' if @distribution.nil? and @distributionid.nil? and @imageid.nil? and @image.nil? 74 | @stackscriptid = nil if @stackscriptid == UNSET_VALUE 75 | @stackscript = nil if @stackscript == UNSET_VALUE 76 | @stackscript_udf_responses = nil if @stackscript_udf_responses == UNSET_VALUE 77 | @datacenterid = nil if @datacenterid == UNSET_VALUE 78 | @datacenter = nil if @datacenter == UNSET_VALUE 79 | @datacenter = 'dallas' if @datacenter.nil? and @datacenterid.nil? 80 | @planid = nil if @planid == UNSET_VALUE 81 | @plan = nil if @plan == UNSET_VALUE 82 | @planid = '1' if @plan.nil? and @planid.nil? 83 | @paymentterm = '1' if @paymentterm == UNSET_VALUE 84 | @private_networking = false if @private_networking == UNSET_VALUE 85 | @ca_path = nil if @ca_path == UNSET_VALUE 86 | @ssh_key_name = 'Vagrant' if @ssh_key_name == UNSET_VALUE 87 | @setup = true if @setup == UNSET_VALUE 88 | @xvda_size = true if @xvda_size == UNSET_VALUE 89 | @swap_size = '256' if @swap_size == UNSET_VALUE 90 | @kernelid = nil if @kernelid == UNSET_VALUE 91 | @kernel = nil if @kernel == UNSET_VALUE 92 | @kernel = 'Latest 64 bit' if @kernel.nil? and @kernelid.nil? 93 | @label = false if @label == UNSET_VALUE 94 | @group = false if @group == UNSET_VALUE 95 | @volumes = [] if @volumes == UNSET_VALUE 96 | end 97 | 98 | def validate(machine) 99 | errors = [] 100 | errors << I18n.t('vagrant_linode.config.api_key') unless @api_key 101 | # Log4r::Logger.new('vagrant_linode.config.token') if @token 102 | # env[:ui].info I18n.t('vagrant_linode.config.token') if @token 103 | # errors << I18n.t('vagrant_linode.config.token') if @token 104 | key = machine.config.ssh.private_key_path 105 | key = key[0] if key.is_a?(Array) 106 | if !key 107 | errors << I18n.t('vagrant_linode.config.private_key') 108 | elsif !File.file?(File.expand_path("#{key}.pub", machine.env.root_path)) 109 | errors << I18n.t('vagrant_linode.config.public_key', key: "#{key}.pub") 110 | end 111 | 112 | if @distributionid and @distribution 113 | errors << I18n.t('vagrant_linode.config.distributionid_or_distribution') 114 | end 115 | 116 | if @stackscriptid and @stackscript 117 | errors << I18n.t('vagrant_linode.config.stackscriptid_or_stackscript') 118 | end 119 | 120 | if @datacenterid and @datacenter 121 | errors << I18n.t('vagrant_linode.config.datacenterid_or_datacenter') 122 | end 123 | 124 | if @kernelid and @kernel 125 | errors << I18n.t('vagrant_linode.config.kernelid_or_kernel') 126 | end 127 | 128 | if @planid and @plan 129 | errors << I18n.t('vagrant_linode.config.planid_or_plan') 130 | end 131 | 132 | if @imageid and @image 133 | errors << I18n.t('vagrant_linode.config.imageid_or_image') 134 | end 135 | 136 | if (@distribution or @distributionid) and (@imageid or @image) 137 | errors << I18n.t('vagrant_linode.config.distribution_or_image') 138 | end 139 | 140 | if !@volumes.is_a? Array 141 | errors << I18n.t("vagrant_linode.config.volumes") 142 | end 143 | 144 | { 'Linode Provider' => errors } 145 | end 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_linode: 3 | info: 4 | off: "Linode is off" 5 | not_off: "Linode must be powered off" 6 | not_created: "Linode has not been created" 7 | already_active: "Linode is already active" 8 | already_off: "Linode is already off" 9 | creating: "Creating a new linode..." 10 | created: "Created a new linode... %{linodeid}, https://manager.linode.com/linodes/dashboard/%{label}" 11 | booting: "Booting Linode %{linodeid} ..." 12 | linode_ip: "Assigned IP address: %{ip}" 13 | linode_private_ip: "Private IP address: %{ip}" 14 | destroying: "Destroying the linode..." 15 | powering_off: "Powering off the linode..." 16 | powering_on: "Powering on the linode..." 17 | rebuilding: "Rebuilding the linode..." 18 | reloading: "Rebooting the linode..." 19 | creating_user: "Creating user account: %{user}..." 20 | modifying_sudo: "Modifying sudoers file to remove tty requirement..." 21 | modifying_host: "Modifying hostname: %{name}..." 22 | using_key: "Using existing SSH key: %{name}" 23 | creating_key: "Creating new SSH key: %{name}..." 24 | trying_rsync_install: "Rsync not found, attempting to install with yum..." 25 | rsyncing: "Rsyncing folder: %{hostpath} => %{guestpath}..." 26 | rsync_missing: "The rsync executable was not found in the current path." 27 | states: 28 | short_not_created: "Not Created" 29 | short_boot_failed: "Boot Failed" 30 | short_being_created: "Being Created" 31 | short_brand_new: "Brand New" 32 | short_active: "Active" 33 | short_off: "Powered Off" 34 | short_shutting_down: "Shutting Down" 35 | long_not_created: "Linode has not been created" 36 | long_boot_failed: "Linode boot failed" 37 | long_being_created: "Linode is created" 38 | long_brand_new: "Linode is brand new" 39 | long_active: "Linode is active" 40 | long_off: "Linode is powered off" 41 | long_shutting_down: "Linode is shutting down" 42 | config: 43 | api_key: "API Key is required" 44 | token: "Config option token is deprecated. Use api_key." 45 | private_key: "SSH private key path is required" 46 | public_key: "SSH public key not found: %{key}" 47 | disk_too_large: "Disk Images use more drive space than plan allocates" 48 | planid_or_plan: "Use either planid or plan, not both" 49 | distributionid_or_distribution: "Use either distributionid or distribution, not both" 50 | stackscriptid_or_stackscript: "Use either stackscriptid or stackscript, not both" 51 | datacenterid_or_datacenter: "Use either datacenterid or datacenter, not both" 52 | kernelid_or_kernel: "Use either kernelid or kernel, not both" 53 | imageid_or_image: "Use either imageid or image, not both" 54 | distribution_or_image: "Distribution can not be specified with Image options" 55 | volumes: "Volumes must be an array of disks" 56 | errors: 57 | public_key: |- 58 | There was an issue reading the public key at: 59 | 60 | Path: %{path} 61 | 62 | Please check the file's permissions. 63 | api_status: |- 64 | There was an issue with the request made to the Linode 65 | API at: 66 | 67 | Path: %{path} 68 | URI Params: %{params} 69 | 70 | The response status from the API was: 71 | 72 | Status: %{status} 73 | Response: %{response} 74 | rsync: |- 75 | There was an error when attemping to rsync a share folder. 76 | Please inspect the error message below for more info. 77 | 78 | Host path: %{hostpath} 79 | Guest path: %{guestpath} 80 | Error: %{stderr} 81 | json: |- 82 | There was an issue with the JSON response from the Linode 83 | API at: 84 | 85 | Path: %{path} 86 | URI Params: %{params} 87 | 88 | The response JSON from the API was: 89 | 90 | Response: %{response} 91 | result_match: |- 92 | The result collection for %{collection_name}: 93 | 94 | %{sub_obj} 95 | 96 | Contained no object with the value "%{value}" for the the 97 | key "%{key}". 98 | 99 | Please ensure that the configured value exists in the collection. 100 | certificate: |- 101 | The secure connection to the Linode API has failed. Please 102 | ensure that your local certificates directory is defined in the 103 | provider config. 104 | 105 | config.vm.provider :linode do |vm| 106 | vm.ca_path = "/path/to/ssl/ca/cert.crt" 107 | end 108 | 109 | This is generally caused by the OpenSSL configuration associated 110 | with the Ruby install being unaware of the system specific ca 111 | certs. 112 | local_ip: |- 113 | The Linode provider was unable to determine the host's IP. 114 | datacenter_match: !- 115 | The provider does not support your configurations chosen Datacenter ( %{datacenter} ). 116 | Supported datacenters can be found at the following url - https://www.linode.com/api/utility/avail.datacenters 117 | distro_match: !- 118 | The provider does not support your configurations chosen Distribution ( %{distro} ). 119 | Supported distributions can be found at the following url - https://www.linode.com/distributions 120 | image_match: !- 121 | The provider does not support your configurations chosen Image ( %{image} ). 122 | Available images can be found at https://manager.linode.com/images/ or with "vagrant linode image list" 123 | kernel_match: !- 124 | The provider does not support your configurations chosen Kernel ( %{kernel} ). 125 | Supported kernels can be found at the following url - https://www.linode.com/kernels 126 | disk_size: !- 127 | The space which you have specified for your Disk Images is too large for your 128 | plans allocations. Current = %{current}MB, Max = %{max} 129 | plan_id: !- 130 | The plan which you have specified ( %{plan} ) is not available at this time, 131 | for more information regarding plans review the following url - https://www.linode.com/pricing 132 | stackscript_match: !- 133 | The provider does not have your chosen Stackscript ( %{stackscript} ). 134 | Supported distributions can be found at the following url - https://manager.linode.com/stackscripts 135 | stackscript_udf_responses: !- 136 | The stackscript UDF responses object provided is of the wrong type. It should be a Hash. 137 | volume_size_missing: For volumes that need to be created the size has to be specified. 138 | volume_label_missing: You must specify a volume label. 139 | -------------------------------------------------------------------------------- /spec/vagrant-linode/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'vagrant-linode/config' 3 | 4 | describe VagrantPlugins::Linode::Config do 5 | describe 'defaults' do 6 | let(:vagrant_public_key) { Vagrant.source_root.join('keys/vagrant.pub') } 7 | 8 | subject do 9 | super().tap(&:finalize!) 10 | end 11 | 12 | its(:api_key) { should be_nil } 13 | its(:api_url) { should be_nil } 14 | its(:distribution) { should eq(/Ubuntu/) } 15 | its(:datacenter) { should eq(/dallas/) } 16 | its(:plan) { should eq(/2048/) } 17 | its(:paymentterm) { should eq(/1/) } 18 | its(:private_networking) { should eq(/Ubuntu/) } 19 | its(:ca_path) { should eql(vagrant_public_key) } 20 | its(:ssh_key_name) { should eq(/Vagrant/) } 21 | its(:setup) { should eq(true) } 22 | its(:xvda_size) { should eq(true) } 23 | its(:swap_size) { should eq(256) } 24 | end 25 | 26 | describe 'overriding defaults' do 27 | [:api_key, 28 | :api_url, 29 | :distribution, 30 | :plan, 31 | :paymentterm, 32 | :private_networking, 33 | :ca_path, 34 | :ssh_key_name, 35 | :setup, 36 | :xvda_size, 37 | :swap_size].each do |attribute| 38 | it "should not default #{attribute} if overridden" do 39 | subject.send("#{attribute}=".to_sym, 'foo') 40 | subject.finalize! 41 | subject.send(attribute).should == 'foo' 42 | end 43 | end 44 | 45 | it 'should not default plan if overridden' do 46 | plan = 'Linode 2048' 47 | subject.send(:plan, plan) 48 | subject.finalize! 49 | subject.send(:plan).should include(plan) 50 | end 51 | 52 | end 53 | 54 | describe 'validation' do 55 | let(:machine) { double('machine') } 56 | let(:validation_errors) { subject.validate(machine)['Linode Provider'] } 57 | let(:error_message) { double('error message') } 58 | 59 | before(:each) do 60 | machine.stub_chain(:env, :root_path).and_return '/' 61 | subject.api_key = 'bar' 62 | end 63 | 64 | subject do 65 | super().tap(&:finalize!) 66 | end 67 | 68 | context 'with invalid key' do 69 | it 'should raise an error' do 70 | subject.nonsense1 = true 71 | subject.nonsense2 = false 72 | I18n.should_receive(:t).with('vagrant.config.common.bad_field', 73 | fields: 'nonsense1, nonsense2') 74 | .and_return error_message 75 | validation_errors.first.should == error_message 76 | end 77 | end 78 | context 'with good values' do 79 | it 'should validate' do 80 | validation_errors.should be_empty 81 | end 82 | end 83 | 84 | context 'the API key' do 85 | it 'should error if not given' do 86 | subject.api_key = nil 87 | I18n.should_receive(:t).with('vagrant_linode.config.api_key').and_return error_message 88 | validation_errors.first.should == error_message 89 | end 90 | end 91 | 92 | context 'the public key path' do 93 | it "should have errors if the key doesn't exist" do 94 | subject.public_key_path = 'missing' 95 | I18n.should_receive(:t).with('vagrant_linode.config.public_key_not_found').and_return error_message 96 | validation_errors.first.should == error_message 97 | end 98 | it 'should not have errors if the key exists with an absolute path' do 99 | subject.public_key_path = File.expand_path 'locales/en.yml', Dir.pwd 100 | validation_errors.should be_empty 101 | end 102 | it 'should not have errors if the key exists with a relative path' do 103 | machine.stub_chain(:env, :root_path).and_return '.' 104 | subject.public_key_path = 'locales/en.yml' 105 | validation_errors.should be_empty 106 | end 107 | end 108 | 109 | context 'the username' do 110 | it 'should error if not given' do 111 | subject.username = nil 112 | I18n.should_receive(:t).with('vagrant_linode.config.username_required').and_return error_message 113 | validation_errors.first.should == error_message 114 | end 115 | end 116 | 117 | [:linode_compute_url, :linode_auth_url].each do |url| 118 | context "the #{url}" do 119 | it 'should not validate if the URL is invalid' do 120 | subject.send "#{url}=", 'baz' 121 | I18n.should_receive(:t).with('vagrant_linode.config.invalid_uri', key: url, uri: 'baz').and_return error_message 122 | validation_errors.first.should == error_message 123 | end 124 | end 125 | end 126 | end 127 | 128 | describe 'linode_auth_url' do 129 | it 'should return UNSET_VALUE if linode_auth_url and linode_region are UNSET' do 130 | subject.linode_auth_url.should == VagrantPlugins::Linode::Config::UNSET_VALUE 131 | end 132 | it 'should return UNSET_VALUE if linode_auth_url is UNSET and linode_region is :ord' do 133 | subject.linode_region = :ord 134 | subject.linode_auth_url.should == VagrantPlugins::Linode::Config::UNSET_VALUE 135 | end 136 | it 'should return UK Authentication endpoint if linode_auth_url is UNSET and linode_region is :lon' do 137 | subject.linode_region = :lon 138 | subject.linode_auth_url.should == Fog::Linode::UK_AUTH_ENDPOINT 139 | end 140 | it 'should return custom endpoint if supplied and linode_region is :lon' do 141 | my_endpoint = 'http://custom-endpoint.com' 142 | subject.linode_region = :lon 143 | subject.linode_auth_url = my_endpoint 144 | subject.linode_auth_url.should == my_endpoint 145 | end 146 | it 'should return custom endpoint if supplied and linode_region is UNSET' do 147 | my_endpoint = 'http://custom-endpoint.com' 148 | subject.linode_auth_url = my_endpoint 149 | subject.linode_auth_url.should == my_endpoint 150 | end 151 | end 152 | 153 | describe 'lon_region?' do 154 | it 'should return false if linode_region is UNSET_VALUE' do 155 | subject.linode_region = VagrantPlugins::Linode::Config::UNSET_VALUE 156 | subject.send(:lon_region?).should be_false 157 | end 158 | it 'should return false if linode_region is nil' do 159 | subject.linode_region = nil 160 | subject.send(:lon_region?).should be_false 161 | end 162 | it 'should return false if linode_region is :ord' do 163 | subject.linode_region = :ord 164 | subject.send(:lon_region?).should be_false 165 | end 166 | it "should return true if linode_region is 'lon'" do 167 | subject.linode_region = 'lon' 168 | subject.send(:lon_region?).should be_true 169 | end 170 | it 'should return true if linode_Region is :lon' do 171 | subject.linode_region = :lon 172 | subject.send(:lon_region?).should be_true 173 | end 174 | end 175 | 176 | describe 'network' do 177 | it 'should remove SERVICE_NET_ID if :service_net is detached' do 178 | subject.send(:network, :service_net, attached: false) 179 | subject.send(:networks).should_not include(VagrantPlugins::Linode::Config::SERVICE_NET_ID) 180 | end 181 | 182 | it 'should not allow duplicate networks' do 183 | net_id = 'deadbeef-0000-0000-0000-000000000000' 184 | subject.send(:network, net_id) 185 | subject.send(:network, net_id) 186 | subject.send(:networks).count(net_id).should == 1 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | require 'vagrant/action/builder' 4 | 5 | module VagrantPlugins 6 | module Linode 7 | module Actions 8 | include Vagrant::Action::Builtin 9 | 10 | def self.action_destroy 11 | Vagrant::Action::Builder.new.tap do |builder| 12 | builder.use ConfigValidate 13 | builder.use Call, IsCreated do |env, b| 14 | if !env[:result] 15 | b.use MessageNotCreated 16 | else 17 | b.use Call, DestroyConfirm do |env2, b2| 18 | if env2[:result] 19 | b2.use ConnectLinode 20 | b2.use Destroy 21 | b2.use ProvisionerCleanup if defined?(ProvisionerCleanup) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | end 28 | 29 | # This action is called to read the SSH info of the machine. The 30 | # resulting state is expected to be put into the `:machine_ssh_info` 31 | # key. 32 | def self.action_read_ssh_info 33 | Vagrant::Action::Builder.new.tap do |b| 34 | b.use ConfigValidate 35 | b.use ConnectLinode 36 | b.use ReadSSHInfo 37 | end 38 | end 39 | 40 | def self.action_read_state 41 | Vagrant::Action::Builder.new.tap do |b| 42 | b.use ConfigValidate 43 | b.use ConnectLinode 44 | b.use ReadState 45 | end 46 | end 47 | 48 | def self.action_ssh 49 | Vagrant::Action::Builder.new.tap do |builder| 50 | builder.use ConfigValidate 51 | builder.use Call, IsCreated do |env, b| 52 | if env[:result] 53 | b.use Call, IsStopped do |env2, b2| 54 | if env2[:result] 55 | b2.use MessageOff 56 | else 57 | b2.use SSHExec 58 | end 59 | end 60 | else 61 | b.use MessageNotCreated 62 | end 63 | end 64 | end 65 | end 66 | 67 | def self.action_ssh_run 68 | Vagrant::Action::Builder.new.tap do |builder| 69 | builder.use ConfigValidate 70 | builder.use Call, IsCreated do |env, b| 71 | if env[:result] 72 | b.use SSHRun 73 | else 74 | b.use Call, IsStopped do |env2, b2| 75 | if env2[:result] 76 | b2.use MessageOff 77 | else 78 | b2.use MessageNotCreated 79 | end 80 | end 81 | end 82 | end 83 | end 84 | end 85 | 86 | def self.action_provision 87 | Vagrant::Action::Builder.new.tap do |builder| 88 | builder.use ConfigValidate 89 | builder.use Call, IsCreated do |env, b| 90 | if env[:result] 91 | b.use Call, IsStopped do |env2, b2| 92 | if env2[:result] 93 | b2.use MessageOff 94 | else 95 | b2.use Provision 96 | b2.use ModifyProvisionPath 97 | b2.use SyncedFolders 98 | end 99 | end 100 | else 101 | b.use MessageNotCreated 102 | end 103 | end 104 | end 105 | end 106 | 107 | def self.action_up 108 | Vagrant::Action::Builder.new.tap do |builder| 109 | builder.use ConfigValidate 110 | builder.use Call, IsCreated do |env, b| 111 | if env[:result] 112 | b.use Call, IsStopped do |env2, b2| 113 | if env2[:result] 114 | b2.use Provision 115 | b2.use SyncedFolders 116 | b2.use MessageOff 117 | b2.use ConnectLinode 118 | b2.use PowerOn 119 | else 120 | b2.use MessageAlreadyActive 121 | end 122 | end 123 | else 124 | b.use Provision 125 | b.use SyncedFolders 126 | b.use MessageNotCreated 127 | b.use ConnectLinode 128 | b.use Create 129 | b.use SetupSudo 130 | b.use SetupUser 131 | b.use SetupHostname 132 | end 133 | end 134 | end 135 | end 136 | 137 | def self.action_halt 138 | Vagrant::Action::Builder.new.tap do |builder| 139 | builder.use ConfigValidate 140 | builder.use Call, IsCreated do |env, b1| 141 | if env[:result] 142 | b1.use Call, IsStopped do |env2, b2| 143 | if env2[:result] 144 | b2.use MessageAlreadyOff 145 | else 146 | b2.use ConnectLinode 147 | b2.use PowerOff 148 | end 149 | end 150 | else 151 | b1.use MessageNotCreated 152 | end 153 | end 154 | end 155 | end 156 | 157 | def self.action_reload 158 | Vagrant::Action::Builder.new.tap do |builder| 159 | builder.use ConfigValidate 160 | builder.use Call, IsCreated do |env, b| 161 | if env[:result] 162 | b.use Call, IsStopped do |env2, b2| 163 | if env2[:result] 164 | b2.use MessageOff 165 | else 166 | b2.use ConnectLinode 167 | b2.use Reload 168 | b2.use Provision 169 | end 170 | end 171 | else 172 | b.use MessageNotCreated 173 | end 174 | end 175 | end 176 | end 177 | 178 | def self.action_rebuild 179 | Vagrant::Action::Builder.new.tap do |builder| 180 | builder.use ConfigValidate 181 | builder.use Call, IsCreated do |env, b| 182 | if env[:result] 183 | b.use Call, IsStopped do |env2, b2| 184 | if env2[:result] 185 | b2.use ConnectLinode 186 | b2.use Rebuild 187 | b2.use SetupSudo 188 | b2.use SetupUser 189 | b2.use SetupHostname 190 | b2.use Provision 191 | else 192 | b2.use MessageNotOff 193 | end 194 | end 195 | else 196 | b2.use MessageNotCreated 197 | end 198 | end 199 | end 200 | end 201 | 202 | # Extended actions 203 | def self.action_create_image 204 | Vagrant::Action::Builder.new.tap do |b| 205 | b.use ConfigValidate # is this per machine? 206 | b.use ConnectLinode 207 | b.use CreateImage 208 | end 209 | end 210 | 211 | def self.action_list_images 212 | Vagrant::Action::Builder.new.tap do |b| 213 | # b.use ConfigValidate # is this per machine? 214 | b.use ConnectLinode 215 | b.use ListImages 216 | end 217 | end 218 | 219 | def self.action_list_servers 220 | Vagrant::Action::Builder.new.tap do |b| 221 | # b.use ConfigValidate # is this per machine? 222 | b.use ConnectLinode 223 | b.use ListServers 224 | end 225 | end 226 | 227 | def self.action_list_plans 228 | Vagrant::Action::Builder.new.tap do |b| 229 | # b.use ConfigValidate # is this per machine? 230 | b.use ConnectLinode 231 | b.use ListPlans 232 | end 233 | end 234 | 235 | def self.action_list_datacenters 236 | Vagrant::Action::Builder.new.tap do |b| 237 | # b.use ConfigValidate # is this per machine? 238 | b.use ConnectLinode 239 | b.use ListDatacenters 240 | end 241 | end 242 | 243 | def self.action_list_distributions 244 | Vagrant::Action::Builder.new.tap do |b| 245 | # b.use ConfigValidate # is this per machine? 246 | b.use ConnectLinode 247 | b.use ListDistributions 248 | end 249 | end 250 | 251 | def self.action_list_kernels 252 | Vagrant::Action::Builder.new.tap do |b| 253 | # b.use ConfigValidate # is this per machine? 254 | b.use ConnectLinode 255 | b.use ListKernels 256 | end 257 | end 258 | 259 | def self.action_list_volumes 260 | Vagrant::Action::Builder.new.tap do |b| 261 | # b.use ConfigValidate # is this per machine? 262 | b.use ConnectLinode 263 | b.use ListVolumes 264 | end 265 | end 266 | 267 | action_root = Pathname.new(File.expand_path('../actions', __FILE__)) 268 | autoload :ConnectLinode, action_root.join('connect_linode') 269 | autoload :ReadState, action_root.join('read_state') 270 | autoload :Create, action_root.join('create') 271 | autoload :IsCreated, action_root.join('is_created') 272 | autoload :IsStopped, action_root.join('is_stopped') 273 | autoload :MessageAlreadyActive, action_root.join('message_already_active') 274 | autoload :MessageAlreadyOff, action_root.join('message_already_off') 275 | autoload :MessageNotOff, action_root.join('message_not_off') 276 | autoload :MessageNotCreated, action_root.join('message_not_created') 277 | autoload :MessageOff, action_root.join('message_off') 278 | autoload :ModifyProvisionPath, action_root.join('modify_provision_path') 279 | autoload :PowerOff, action_root.join('power_off') 280 | autoload :PowerOn, action_root.join('power_on') 281 | autoload :Destroy, action_root.join('destroy') 282 | autoload :Reload, action_root.join('reload') 283 | autoload :Rebuild, action_root.join('rebuild') 284 | autoload :SetupHostname, action_root.join('setup_hostname') 285 | autoload :SetupUser, action_root.join('setup_user') 286 | autoload :SetupSudo, action_root.join('setup_sudo') 287 | autoload :ReadSSHInfo, action_root.join("read_ssh_info") 288 | autoload :ListServers, action_root.join('list_servers') 289 | autoload :CreateImage, action_root.join('create_image') 290 | autoload :ListImages, action_root.join('list_images') 291 | autoload :ListPlans, action_root.join('list_plans') 292 | autoload :ListDistributions, action_root.join('list_distributions') 293 | autoload :ListKernels, action_root.join('list_kernels') 294 | autoload :ListDatacenters, action_root.join('list_datacenters') 295 | autoload :ListVolumes, action_root.join('list_volumes') 296 | end 297 | end 298 | end 299 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Linode Vagrant Provider 2 | ============================== 3 | 4 | `vagrant-linode` is a provider plugin for Vagrant that supports the 5 | management of [Linode](https://www.linode.com/) linodes 6 | (instances). 7 | 8 | Current features include: 9 | - create and destroy linodes 10 | - power on and off linodes 11 | - rebuild a linode 12 | - provision a linode with the shell or other provisioners 13 | - setup a SSH public key for authentication 14 | - create a new user account during linode creation 15 | - setup hostname during creation 16 | 17 | The provider has been tested with Vagrant 1.6.3+ using Ubuntu 14.04 LTS and 18 | Debian 7.5+ guest operating systems. 19 | 20 | Install 21 | ------- 22 | Installation of the provider couldn't be easier: 23 | 24 | ```bash 25 | vagrant plugin install vagrant-linode 26 | ``` 27 | 28 | Configure 29 | --------- 30 | Once the provider has been installed, you will need to configure your project 31 | to use it. The most basic `Vagrantfile` to create a linode on Linode 32 | is shown below (with most of the available options included but commented out): 33 | 34 | ```ruby 35 | Vagrant.configure('2') do |config| 36 | 37 | config.vm.provider :linode do |provider, override| 38 | override.ssh.private_key_path = '~/.ssh/id_rsa' 39 | override.vm.box = 'linode/ubuntu1404' 40 | 41 | provider.api_key = 'API_KEY' 42 | provider.distribution = 'Ubuntu 18.04 LTS' 43 | provider.datacenter = 'newark' 44 | provider.plan = 'Linode 2GB' # This will work 45 | # provider.plan = 'Linode 2048' # This will still work 46 | # provider.plan = 'Linode 2' # This may work, but may be ambiguous 47 | # provider.planid = 48 | # provider.paymentterm = <*1*,12,24> 49 | # provider.datacenterid = 50 | # provider.image = 51 | # provider.imageid = 52 | # provider.kernel = 53 | # provider.kernelid = 54 | # provider.private_networking = 55 | # provider.stackscript = # Not Supported Yet 56 | # provider.stackscriptid = # Not Supported Yet 57 | # provider.distributionid = 58 | end 59 | end 60 | ``` 61 | 62 | Please note the following: 63 | - You *must* specify the `override.ssh.private_key_path` to enable authentication 64 | with the linode. The provider will create a new Linode SSH key using 65 | your public key which is assumed to be the `private_key_path` with a *.pub* 66 | extension. 67 | - You *must* specify your Linode Personal Access Token. This may be 68 | found on the control panel within the *my profile* > *API Keys* section. 69 | 70 | **Supported Configuration Attributes** 71 | 72 | The following attributes are available to further configure the provider: 73 | - `provider.distribution` - A string representing the distribution to use when 74 | creating a new linode (e.g. `Debian 8.1`). The available options may 75 | be found on [Linode's Supported Distributions](https://www.linode.com/distributions) page. 76 | It defaults to `Ubuntu 14.04 LTS`. 77 | - `provider.datacenter` - A string representing the datacenter to create the new 78 | linode in. It defaults to `dallas`. 79 | - `provider.plan` - A string representing the size to use when creating a 80 | new linode (e.g. `Linode 4096`). It defaults to `Linode 2048`. 81 | - `provider.private_networking` - A boolean flag indicating whether to enable 82 | a private network interface. It defaults to `false`. 83 | - `provider.ssh_key_name` - A string representing the name to use when creating 84 | a Linode SSH key for linode authentication. It defaults to `Vagrant`. 85 | - `provider.setup` - A boolean flag indicating whether to setup a new user 86 | account and modify sudo to disable tty requirement. It defaults to `true`. 87 | If you are using a tool like [packer](https://packer.io) to create 88 | reusable snapshots with user accounts already provisioned, set to `false`. 89 | - `provider.label` - A string representing the Linode label to assign when 90 | creating a new linode 91 | - `provider.group` - A string representing the Linode's Display group to assign 92 | when creating a new linode 93 | 94 | The provider will create a new user account with the specified SSH key for 95 | authorization if `config.ssh.username` is set and the `provider.setup` 96 | attribute is `true`. 97 | 98 | ### provider.plan 99 | 100 | Each Linode Tier has been assigned a Plan Identification Number. 101 | Current (April 2019) Plan-ID table follows: 102 | 103 | | Plan ID | Plan Name | 104 | |:------- |:-------------- | 105 | | 1 | Nanode 1GB | 106 | | 2 | Linode 2GB | 107 | | 3 | Linode 4GB | 108 | | 4 | Linode 8GB | 109 | | 5 | Linode 16GB | 110 | | 6 | Linode 32GB | 111 | | 7 | Linode 64GB | 112 | | 8 | Linode 96GB | 113 | | 9 | Linode 128GB | 114 | | 10 | Linode 192GB | 115 | | 11 | Linode 24GB | 116 | | 12 | Linode 48GB | 117 | | 13 | Linode 90GB | 118 | | 14 | Linode 150GB | 119 | | 15 | Linode 300GB | 120 | | 16 | Dedicated 4GB | 121 | | 17 | Dedicated 8GB | 122 | | 18 | Dedicated 16GB | 123 | | 19 | Dedicated 32GB | 124 | | 20 | Dedicated 64GB | 125 | | 21 | Dedicated 96GB | 126 | 127 | This can be obtained through vagrant with: 128 | ``` 129 | vagrant linode plans 130 | ``` 131 | 132 | Or using curl: 133 | ``` 134 | curl -X POST "https://api.linode.com/?api_action=avail.linodeplans" \ 135 | --data-ascii api_key="$LINODE_API_KEY" \ 136 | 2>/dev/null | jq '.DATA [] | .PLANID,.LABEL' 137 | ``` 138 | 139 | More detail: [Linode API - Plans](https://www.linode.com/api/utility/avail.linodeplans) 140 | 141 | ### provider.datacenter 142 | 143 | Each region has been specified with a Data Center ID. 144 | Current (Feb 2017) Datacenter-ID table is: 145 | 146 | | DatacenterID | Datacenter | Location | 147 | |:------- |:------ |:--------------------| 148 | | 4 | atlanta | Atlanta, GA, USA | 149 | | 2 | dallas | Dallas, TX, USA | 150 | | 3 | fremont | Fremont, CA, USA | 151 | | 7 | london | London, England, UK | 152 | | 6 | newark | Newark, NJ, USA | 153 | | 8 | tokyo | Tokyo, JP | 154 | | 9 | singapore | Singapore, SGP | 155 | | 10 | frankfurt | Frankfurt, DE | 156 | |   11         | shinagawa1 | Tokyo 2, JP | 157 | 158 | You can find latest datacenter ID number using Vagrant subcommands: 159 | 160 | ``` 161 | vagrant linode datacenters 162 | ``` 163 | 164 | Or directly through the API: 165 | 166 | 167 | ``` 168 | curl -X POST "https://api.linode.com/?api_action=avail.datacenters" \ 169 | --data-ascii api_key="$LINODE_API_KEY" \ 170 | 2>/dev/null | jq '.DATA [] | .DATACENTERID,.ABBR,.LOCATION' 171 | ``` 172 | 173 | More detail: [Linode API - Datacenters](https://www.linode.com/api/utility/avail.datacenters) 174 | 175 | ### provider.kernel 176 | 177 | The kernel can be specified using the *kernelid* provider parameter, or with *kernel* which 178 | will use a partial text match. 179 | 180 | ``` 181 | curl -X POST "https://api.linode.com/?api_action=avail.kernels" \ 182 | --data-ascii api_key="$LINODE_API_KEY" \ 183 | 2>/dev/null | jq '.DATA [] | .KERNELID,.LABEL' 184 | ``` 185 | 186 | More detail: [Linode API - Kernels](https://www.linode.com/api/utility/avail.kernels) 187 | 188 | ### provider.volumes - [Volume Handling](https://www.linode.com/docs/platform/how-to-use-block-storage-with-your-linode/) 189 | 190 | The plugin can create and attach additional volumes when creating Linodes. `vagrant rebuild` calls will rebuild the VM only and reattach the volume afterwards without losing the contents. 191 | 192 | ```rb 193 | config.vm.provider :linode do |linode| 194 | linode.plan = "Linode 2048" 195 | linode.volumes = [ 196 | {label: "extra_volume", size: 1}, 197 | ] 198 | end 199 | ``` 200 | 201 | NOTES: 202 | * The volume needs to be formatted and mounted inside the VM either manually or by a StackScript, etc. 203 | * The plugin doesn't do any volume metadata management. If a volume is renamed the next `vagrant up` call will create a new one. 204 | * Running `vagrant destroy` will **NOT** destroy the volumes. 205 | 206 | ### nfs.functional 207 | 208 | The sync provider, NFS, has been disabled to make rsync easier to use. To enable NFS, 209 | run Vagrant with an environment variable `LINODE_NFS_FUNCTIONAL=1`. This will require 210 | a bit more configuration between the Linode and the Vagrant host. 211 | 212 | Run 213 | --- 214 | After creating your project's `Vagrantfile` with the required configuration 215 | attributes described above, you may create a new linode with the following 216 | command: 217 | 218 | $ vagrant up --provider=linode 219 | 220 | This command will create a new linode, setup your SSH key for authentication, 221 | create a new user account, and run the provisioners you have configured. 222 | 223 | The environment variable `VAGRANT_DEFAULT_PROVIDER` can be set to `linode` to avoid sending `--provider=linode` on each `vagrant up`. 224 | 225 | **Supported Commands** 226 | 227 | The provider supports the following Vagrant sub-commands: 228 | - `vagrant destroy` - Destroys the linode instance. 229 | - `vagrant ssh` - Logs into the linode instance using the configured user 230 | account. 231 | - `vagrant halt` - Powers off the linode instance. 232 | - `vagrant provision` - Runs the configured provisioners and rsyncs any 233 | specified `config.vm.synced_folder`. (see https://docs.vagrantup.com/v2/synced-folders/rsync.html) 234 | - `vagrant reload` - Reboots the linode instance. 235 | - `vagrant rebuild` - Destroys the linode instance and recreates it with the 236 | same IP address which was previously assigned. 237 | - `vagrant status` - Outputs the status (active, off, not created) for the 238 | linode instance. 239 | - `vagrant linode` - Offers Linode resource listing options for datacenters, 240 | distributions, images, networks, plans, and servers 241 | 242 | 243 | More Docs and Tools 244 | ------------------- 245 | [Linode Guides and Tutorials - Using Vagrant to Manage Linode Environments](https://linode.com/docs/applications/configuration-management/vagrant-linode-environments) 246 | [Puphpet - Online Vagrantfile Generator](https://puphpet.com/#vagrantfile-linode) 247 | 248 | Contribute 249 | ---------- 250 | To contribute, clone the repository, and use [Bundler](http://gembundler.com) 251 | to install dependencies: 252 | 253 | $ bundle 254 | 255 | To run the provider's tests, first install vagrant [as shown here](https://www.vagrantup.com/downloads.html) and then use rake: 256 | 257 | $ bundle exec rake test 258 | 259 | You can now make modifications. Running `vagrant` within the Bundler 260 | environment will ensure that plugins installed in your Vagrant 261 | environment are not loaded. 262 | 263 | ### Building and Publishing 264 | 265 | ``` 266 | vi lib/vagrant-linode/version.rb 267 | vi CHANGELOG.md 268 | git commit -m 'version 0.1.2' lib/vagrant-linode/version.rb CHANGELOG.md 269 | git tag -s v0.1.2 270 | git push --tags origin master 271 | gem build vagrant-linode.gemspec 272 | gem push vagrant-linode-0.1.2.gem 273 | ``` 274 | 275 | [![Join the chat at https://gitter.im/displague/vagrant-linode](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/displague/vagrant-linode?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 276 | [![Code Climate](https://codeclimate.com/github/displague/vagrant-linode/badges/gpa.svg)](https://codeclimate.com/github/displague/vagrant-linode) 277 | [![Test Coverage](https://codeclimate.com/github/displague/vagrant-linode/badges/coverage.svg)](https://codeclimate.com/github/displague/vagrant-linode) 278 | [![Gem Version](https://badge.fury.io/rb/vagrant-linode.svg)](http://badge.fury.io/rb/vagrant-linode) 279 | [![Dependency Status](https://gemnasium.com/displague/vagrant-linode.svg)](https://gemnasium.com/displague/vagrant-linode) 280 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-green.svg)](https://tldrlegal.com/license/mit-license) 281 | 282 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/rebuild.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | require 'vagrant-linode/helpers/normalizer' 3 | require 'vagrant-linode/helpers/waiter' 4 | require 'vagrant-linode/errors' 5 | require 'vagrant-linode/services/volume_manager' 6 | 7 | module VagrantPlugins 8 | module Linode 9 | module Actions 10 | class Rebuild 11 | include Vagrant::Util::Retryable 12 | include VagrantPlugins::Linode::Helpers::Normalizer 13 | include VagrantPlugins::Linode::Helpers::Waiter 14 | 15 | def initialize(app, env) 16 | @app = app 17 | @machine = env[:machine] 18 | @logger = Log4r::Logger.new('vagrant::linode::rebuild') 19 | end 20 | 21 | def call(env) 22 | @client = env[:linode_api] 23 | ssh_key_id = env[:machine].config.ssh.private_key_path 24 | ssh_key_id = ssh_key_id[0] if ssh_key_id.is_a?(Array) 25 | if ssh_key_id 26 | pubkey = File.read(File.expand_path("#{ssh_key_id}.pub")) 27 | end 28 | 29 | if @machine.provider_config.root_pass 30 | root_pass = @machine.provider_config.root_pass 31 | else 32 | root_pass = Digest::SHA2.new.update(@machine.provider_config.api_key).to_s 33 | end 34 | 35 | if @machine.provider_config.stackscript 36 | stackscripts = @client.stackscript.list + @client.avail.stackscripts 37 | stackscript = stackscripts.find { |s| s.label.downcase == @machine.provider_config.stackscript.to_s.downcase } 38 | fail(Errors::StackscriptMatch, stackscript: @machine.provider_config.stackscript.to_s) if stackscript.nil? 39 | stackscript_id = stackscript.stackscriptid || nil 40 | else 41 | stackscript_id = @machine.provider_config.stackscriptid 42 | end 43 | 44 | stackscript_udf_responses = @machine.provider_config.stackscript_udf_responses 45 | 46 | if stackscript_udf_responses and !stackscript_udf_responses.is_a?(Hash) 47 | fail(Errors::StackscriptUDFFormat, format: stackscript_udf_responses.class.to_s) 48 | else 49 | stackscript_udf_responses = @machine.provider_config.stackscript_udf_responses or {} 50 | end 51 | 52 | if @machine.provider_config.distribution 53 | distributions = @client.avail.distributions 54 | distribution = distributions.find { |d| d.label.downcase.include? @machine.provider_config.distribution.downcase } 55 | fail(Errors::DistroMatch, distro: @machine.provider_config.distribution.to_s) if distribution.nil? 56 | distribution_id = distribution.distributionid || nil 57 | else 58 | distribution_id = @machine.provider_config.distributionid 59 | end 60 | 61 | if @machine.provider_config.imageid 62 | distribution_id = nil 63 | images = @client.image.list 64 | image = images.find { |i| i.imageid == @machine.provider_config.imageid } 65 | fail Errors::ImageMatch, image: @machine.provider_config.imageid.to_s if image.nil? 66 | image_id = image.imageid || nil 67 | elsif @machine.provider_config.image 68 | distribution_id = nil 69 | images = @client.image.list 70 | image = images.find { |i| i.label.downcase.include? @machine.provider_config.image.downcase } 71 | fail Errors::ImageMatch, image: @machine.provider_config.image.to_s if image.nil? 72 | image_id = image.imageid || nil 73 | end 74 | 75 | if @machine.provider_config.kernel 76 | kernels = @client.avail.kernels(isxen: nil, iskvm: 1) 77 | kernel = kernels.find { |k| k.label.downcase.include? @machine.provider_config.kernel.downcase } 78 | raise( Errors::KernelMatch, kernel: @machine.provider_config.kernel.to_s ) if kernel == nil 79 | kernel_id = kernel.kernelid || nil 80 | else 81 | kernel_id = @machine.provider_config.kernelid 82 | end 83 | 84 | if @machine.provider_config.datacenter 85 | datacenters = @client.avail.datacenters 86 | datacenter = datacenters.find { |d| d.abbr == @machine.provider_config.datacenter } 87 | fail Errors::DatacenterMatch, datacenter: @machine.provider_config.datacenter if datacenter.nil? 88 | datacenter_id = datacenter.datacenterid 89 | else 90 | datacenters = @client.avail.datacenters 91 | datacenter = datacenters.find { |d| d.datacenterid == @machine.provider_config.datacenterid } 92 | fail Errors::DatacenterMatch, datacenter: @machine.provider_config.datacenter if datacenter.nil? 93 | datacenter_id = datacenter.datacenterid 94 | end 95 | 96 | if @machine.provider_config.plan 97 | plan_label = normalize_plan_label(@machine.provider_config.plan) 98 | plans = @client.avail.linodeplans 99 | plan = plans.find { |p| p.label.include? plan_label } 100 | fail Errors::PlanID, plan: @machine.provider_config.plan if plan.nil? 101 | plan_id = plan.planid 102 | else 103 | plans = @client.avail.linodeplans 104 | plan = plans.find { |p| p.planid.to_i == @machine.provider_config.planid.to_i } 105 | fail Errors::PlanID, plan: @machine.provider_config.planid if plan.nil? 106 | plan_id = @machine.provider_config.planid 107 | end 108 | 109 | ### Disk Images 110 | xvda_size, swap_size, disk_sanity = @machine.provider_config.xvda_size, @machine.provider_config.swap_size, true 111 | 112 | # Sanity checks for disk size 113 | if xvda_size != true 114 | disk_sanity = false if ( xvda_size.to_i + swap_size.to_i) > ( plan['disk'].to_i * 1024) 115 | end 116 | 117 | # throw if disk sizes are too large 118 | if xvda_size == true 119 | xvda_size = ( ( plan['disk'].to_i * 1024) - swap_size.to_i) 120 | elsif disk_sanity == false 121 | fail Errors::DiskSize, current: (xvda_size.to_i + swap_size.to_i), max: ( plan['disk'].to_i * 1024) 122 | end 123 | 124 | env[:ui].info I18n.t('vagrant_linode.info.powering_off') 125 | 126 | shutdownjob = @client.linode.shutdown( 127 | linodeid: @machine.id 128 | ) 129 | wait_for_event(env, shutdownjob['jobid']) 130 | 131 | env[:ui].info I18n.t('vagrant_linode.info.destroying') 132 | 133 | diskList = @client.linode.disk.list( 134 | linodeid: @machine.id 135 | ) 136 | 137 | diskList.each do |diskEntry| 138 | diskDeleteResult = @client.linode.disk.delete( 139 | linodeid: @machine.id, 140 | diskid: diskEntry['diskid'] 141 | ) 142 | 143 | job = diskDeleteResult['jobid'] 144 | 145 | jobStatus = @client.linode.job.list( 146 | linodeid: @machine.id, 147 | jobid: job 148 | ) 149 | 150 | while jobStatus[0]['host_finish_dt'].nil? || jobStatus[0]['host_finish_dt'].empty? do 151 | sleep(5) 152 | jobStatus = @client.linode.job.list( 153 | linodeid: @machine.id, 154 | jobid: job 155 | ) 156 | end 157 | end 158 | 159 | configList = @client.linode.config.list( 160 | linodeid: @machine.id 161 | ) 162 | 163 | configList.each do |configEntry| 164 | configDeleteResult = @client.linode.config.delete( 165 | linodeid: @machine.id, 166 | configid: configEntry['configid'] 167 | ) 168 | end 169 | 170 | env[:ui].info I18n.t('vagrant_linode.info.creating') 171 | 172 | if stackscript_id 173 | swap = @client.linode.disk.create( 174 | linodeid: @machine.id, 175 | label: 'Vagrant swap', 176 | type: 'swap', 177 | size: swap_size 178 | ) 179 | 180 | disk = @client.linode.disk.createfromstackscript( 181 | linodeid: @machine.id, 182 | stackscriptid: stackscript_id, 183 | stackscriptudfresponses: JSON.dump(stackscript_udf_responses), 184 | distributionid: distribution_id, 185 | label: 'Vagrant Disk Distribution ' + distribution_id.to_s + ' Linode ' + @machine.id.to_s, 186 | type: 'ext4', 187 | size: xvda_size, 188 | rootsshkey: pubkey, 189 | rootpass: root_pass 190 | ) 191 | elsif distribution_id 192 | swap = @client.linode.disk.create( 193 | linodeid: @machine.id, 194 | label: 'Vagrant swap', 195 | type: 'swap', 196 | size: swap_size 197 | ) 198 | 199 | disk = @client.linode.disk.createfromdistribution( 200 | linodeid: @machine.id, 201 | distributionid: distribution_id, 202 | label: 'Vagrant Disk Distribution ' + distribution_id.to_s + ' Linode ' + @machine.id.to_s, 203 | type: 'ext4', 204 | size: xvda_size, 205 | rootsshkey: pubkey, 206 | rootpass: root_pass 207 | ) 208 | elsif image_id 209 | disk = @client.linode.disk.createfromimage( 210 | linodeid: @machine.id, 211 | imageid: image_id, 212 | label: 'Vagrant Disk Image (' + image_id.to_s + ') for ' + @machine.id.to_s, 213 | size: xvda_size, 214 | rootsshkey: pubkey, 215 | rootpass: root_pass 216 | ) 217 | 218 | swap = @client.linode.disk.create( 219 | linodeid: @machine.id, 220 | label: 'Vagrant swap', 221 | type: 'swap', 222 | size: swap_size 223 | ) 224 | end 225 | 226 | config = @client.linode.config.create( 227 | linodeid: @machine.id, 228 | label: 'Vagrant Config', 229 | disklist: "#{disk['diskid']},#{swap['diskid']}", 230 | kernelid: kernel_id 231 | ) 232 | 233 | # @todo: allow provisioning to set static configuration for networking 234 | if @machine.provider_config.private_networking 235 | private_network = @client.linode.ip.addprivate linodeid: @machine.id 236 | end 237 | 238 | label = @machine.provider_config.label 239 | label = label || @machine.name if @machine.name != 'default' 240 | label = label || get_server_name 241 | 242 | group = @machine.provider_config.group 243 | group = "" if @machine.provider_config.group == false 244 | 245 | Services::VolumeManager.new(@machine, @client.volume, env[:ui]).perform 246 | 247 | result = @client.linode.update( 248 | linodeid: @machine.id, 249 | label: label, 250 | lpm_displaygroup: group 251 | ) 252 | 253 | env[:ui].info I18n.t('vagrant_linode.info.booting', linodeid: @machine.id) 254 | 255 | bootjob = @client.linode.boot linodeid: @machine.id 256 | # sleep 1 until ! @client.linode.job.list(:linodeid => @machine.id, :jobid => bootjob['jobid'], :pendingonly => 1).length 257 | wait_for_event(env, bootjob['jobid']) 258 | 259 | # refresh linode state with provider and output ip address 260 | linode = Provider.linode(@machine, refresh: true) 261 | public_network = linode.network.find { |network| network['ispublic'] == 1 } 262 | env[:ui].info I18n.t('vagrant_linode.info.linode_ip', ip: public_network['ipaddress']) 263 | 264 | if private_network 265 | env[:ui].info I18n.t('vagrant_linode.info.linode_private_ip', ip: private_network['ipaddress']) 266 | end 267 | 268 | # wait for ssh to be ready 269 | switch_user = @machine.provider_config.setup? 270 | user = @machine.config.ssh.username 271 | if switch_user 272 | @machine.config.ssh.username = 'root' 273 | @machine.config.ssh.password = root_pass 274 | end 275 | 276 | retryable(tries: 25, sleep: 10) do # @todo bump tries when this is solid 277 | next if env[:interrupted] 278 | fail 'not ready' unless @machine.communicate.ready? 279 | end 280 | 281 | @machine.config.ssh.username = user 282 | 283 | @app.call(env) 284 | end 285 | 286 | def get_server_name 287 | "vagrant_linode-#{rand.to_s.split('.')[1]}" 288 | end 289 | end 290 | end 291 | end 292 | end 293 | -------------------------------------------------------------------------------- /lib/vagrant-linode/actions/create.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-linode/helpers/client' 2 | require 'vagrant-linode/helpers/normalizer' 3 | require 'vagrant-linode/helpers/waiter' 4 | require 'vagrant-linode/errors' 5 | require 'vagrant-linode/services/volume_manager' 6 | 7 | module VagrantPlugins 8 | module Linode 9 | module Actions 10 | class Create 11 | include Vagrant::Util::Retryable 12 | include VagrantPlugins::Linode::Helpers::Normalizer 13 | include VagrantPlugins::Linode::Helpers::Waiter 14 | 15 | def initialize(app, env) 16 | @app = app 17 | @machine = env[:machine] 18 | @logger = Log4r::Logger.new('vagrant::linode::create') 19 | end 20 | 21 | def call(env) 22 | @client = env[:linode_api] 23 | ssh_key_id = env[:machine].config.ssh.private_key_path 24 | ssh_key_id = ssh_key_id[0] if ssh_key_id.is_a?(Array) 25 | if ssh_key_id 26 | pubkey = File.read(File.expand_path("#{ssh_key_id}.pub")) 27 | end 28 | 29 | if @machine.provider_config.root_pass 30 | root_pass = @machine.provider_config.root_pass 31 | else 32 | root_pass = Digest::SHA2.new.update(@machine.provider_config.api_key).to_s 33 | end 34 | 35 | if @machine.provider_config.stackscript 36 | stackscripts = @client.stackscript.list + @client.avail.stackscripts 37 | stackscript = stackscripts.find { |s| s.label.downcase == @machine.provider_config.stackscript.to_s.downcase } 38 | fail(Errors::StackscriptMatch, stackscript: @machine.provider_config.stackscript.to_s) if stackscript.nil? 39 | stackscript_id = stackscript.stackscriptid || nil 40 | else 41 | stackscript_id = @machine.provider_config.stackscriptid 42 | end 43 | 44 | stackscript_udf_responses = @machine.provider_config.stackscript_udf_responses 45 | 46 | if stackscript_udf_responses and !stackscript_udf_responses.is_a?(Hash) 47 | fail(Errors::StackscriptUDFFormat, format: stackscript_udf_responses.class.to_s) 48 | else 49 | stackscript_udf_responses = @machine.provider_config.stackscript_udf_responses or {} 50 | end 51 | 52 | if @machine.provider_config.distribution 53 | distributions = @client.avail.distributions 54 | distribution = distributions.find { |d| d.label.downcase.include? @machine.provider_config.distribution.downcase } 55 | fail(Errors::DistroMatch, distro: @machine.provider_config.distribution.to_s) if distribution.nil? 56 | distribution_id = distribution.distributionid || nil 57 | else 58 | distribution_id = @machine.provider_config.distributionid 59 | end 60 | 61 | if @machine.provider_config.imageid 62 | distribution_id = nil 63 | images = @client.image.list 64 | image = images.find { |i| i.imageid == @machine.provider_config.imageid } 65 | fail Errors::ImageMatch, image: @machine.provider_config.imageid.to_s if image.nil? 66 | image_id = image.imageid || nil 67 | elsif @machine.provider_config.image 68 | distribution_id = nil 69 | images = @client.image.list 70 | image = images.find { |i| i.label.downcase.include? @machine.provider_config.image.downcase } 71 | fail Errors::ImageMatch, image: @machine.provider_config.image.to_s if image.nil? 72 | image_id = image.imageid || nil 73 | end 74 | 75 | if @machine.provider_config.kernel 76 | kernels = @client.avail.kernels(isxen: nil, iskvm: 1) 77 | kernel = kernels.find { |k| k.label.downcase.include? @machine.provider_config.kernel.downcase } 78 | raise( Errors::KernelMatch, kernel: @machine.provider_config.kernel.to_s ) if kernel == nil 79 | kernel_id = kernel.kernelid || nil 80 | else 81 | kernel_id = @machine.provider_config.kernelid 82 | end 83 | 84 | if @machine.provider_config.datacenter 85 | datacenters = @client.avail.datacenters 86 | datacenter = datacenters.find { |d| d.abbr == @machine.provider_config.datacenter } 87 | fail Errors::DatacenterMatch, datacenter: @machine.provider_config.datacenter if datacenter.nil? 88 | datacenter_id = datacenter.datacenterid 89 | else 90 | datacenters = @client.avail.datacenters 91 | datacenter = datacenters.find { |d| d.datacenterid == @machine.provider_config.datacenterid } 92 | fail Errors::DatacenterMatch, datacenter: @machine.provider_config.datacenter if datacenter.nil? 93 | datacenter_id = datacenter.datacenterid 94 | end 95 | 96 | if @machine.provider_config.plan 97 | plan_label = normalize_plan_label(@machine.provider_config.plan) 98 | plans = @client.avail.linodeplans 99 | plan = plans.find { |p| p.label.include? plan_label } 100 | fail Errors::PlanID, plan: @machine.provider_config.plan if plan.nil? 101 | plan_id = plan.planid 102 | else 103 | plans = @client.avail.linodeplans 104 | plan = plans.find { |p| p.planid.to_i == @machine.provider_config.planid.to_i } 105 | fail Errors::PlanID, plan: @machine.provider_config.planid if plan.nil? 106 | plan_id = @machine.provider_config.planid 107 | end 108 | 109 | ### Disk Images 110 | disk_size = plan['disk'].to_i * 1024 111 | xvda_size, swap_size, xvdc_size = @machine.provider_config.xvda_size, @machine.provider_config.swap_size, @machine.provider_config.xvdc_size 112 | 113 | swap_size = swap_size.to_i 114 | xvda_size = xvda_size == true ? disk_size - swap_size : xvda_size.to_i 115 | xvdc_size = (xvdc_size.is_a?(Vagrant::Config::V2::DummyConfig) or xvdc_size == true) ? (disk_size - swap_size - xvda_size).abs : xvdc_size.to_i 116 | 117 | if ( xvda_size + swap_size + xvdc_size) > disk_size 118 | fail Errors::DiskSize, current: (xvda_size + swap_size + xvdc_size), max: disk_size 119 | end 120 | 121 | env[:ui].info I18n.t('vagrant_linode.info.creating') 122 | 123 | # submit new linode request 124 | result = @client.linode.create( 125 | planid: plan_id, 126 | datacenterid: datacenter_id, 127 | paymentterm: @machine.provider_config.paymentterm || 1 128 | ) 129 | @machine.id = result['linodeid'].to_s 130 | env[:ui].info I18n.t('vagrant_linode.info.created', linodeid: @machine.id, label: (@machine.provider_config.label or "linode#{@machine.id}")) 131 | 132 | # @client.linode.job.list(:linodeid => @machine.id, :pendingonly => 1) 133 | # assign the machine id for reference in other commands 134 | 135 | disklist = [] 136 | 137 | if stackscript_id 138 | disk = @client.linode.disk.createfromstackscript( 139 | linodeid: @machine.id, 140 | stackscriptid: stackscript_id, 141 | stackscriptudfresponses: JSON.dump(stackscript_udf_responses), 142 | distributionid: distribution_id, 143 | label: 'Vagrant Disk Distribution ' + distribution_id.to_s + ' Linode ' + @machine.id, 144 | type: 'ext4', 145 | size: xvda_size, 146 | rootsshkey: pubkey, 147 | rootpass: root_pass 148 | ) 149 | disklist.push(disk['diskid']) 150 | elsif distribution_id 151 | disk = @client.linode.disk.createfromdistribution( 152 | linodeid: @machine.id, 153 | distributionid: distribution_id, 154 | label: 'Vagrant Disk Distribution ' + distribution_id.to_s + ' Linode ' + @machine.id, 155 | type: 'ext4', 156 | size: xvda_size, 157 | rootsshkey: pubkey, 158 | rootpass: root_pass 159 | ) 160 | disklist.push(disk['diskid']) 161 | elsif image_id 162 | disk = @client.linode.disk.createfromimage( 163 | linodeid: @machine.id, 164 | imageid: image_id, 165 | label: 'Vagrant Disk Image (' + image_id.to_s + ') for ' + @machine.id, 166 | size: xvda_size, 167 | rootsshkey: pubkey, 168 | rootpass: root_pass 169 | ) 170 | disklist.push(disk['diskid']) 171 | else 172 | disklist.push('') 173 | end 174 | 175 | if swap_size > 0 176 | swap = @client.linode.disk.create( 177 | linodeid: @machine.id, 178 | label: 'Vagrant swap', 179 | type: 'swap', 180 | size: swap_size 181 | ) 182 | disklist.push(swap['diskid']) 183 | else 184 | disklist.push('') 185 | end 186 | 187 | if xvdc_size > 0 188 | xvdc_type = @machine.provider_config.xvdc_type.is_a?(Vagrant::Config::V2::DummyConfig) ? "raw" : @machine.provider_config.xvdc_type 189 | xvdc = @client.linode.disk.create( 190 | linodeid: @machine.id, 191 | label: 'Vagrant Leftover Disk Linode ' + @machine.id, 192 | type: xvdc_type, 193 | size: xvdc_size, 194 | ) 195 | disklist.push(xvdc['diskid']) 196 | else 197 | disklist.push('') 198 | end 199 | 200 | config = @client.linode.config.create( 201 | linodeid: @machine.id, 202 | label: 'Vagrant Config', 203 | disklist: disklist.join(','), 204 | kernelid: kernel_id 205 | ) 206 | 207 | # @todo: allow provisioning to set static configuration for networking 208 | if @machine.provider_config.private_networking 209 | private_network = @client.linode.ip.addprivate linodeid: @machine.id 210 | end 211 | 212 | label = @machine.provider_config.label 213 | label = label || @machine.name if @machine.name != 'default' 214 | label = label || get_server_name 215 | 216 | group = @machine.provider_config.group 217 | group = "" if @machine.provider_config.group == false 218 | 219 | Services::VolumeManager.new(@machine, @client.volume, env[:ui]).perform 220 | 221 | result = @client.linode.update( 222 | linodeid: @machine.id, 223 | label: label, 224 | lpm_displaygroup: group 225 | ) 226 | 227 | env[:ui].info I18n.t('vagrant_linode.info.booting', linodeid: @machine.id) 228 | 229 | bootjob = @client.linode.boot linodeid: @machine.id 230 | # sleep 1 until ! @client.linode.job.list(:linodeid => @machine.id, :jobid => bootjob['jobid'], :pendingonly => 1).length 231 | wait_for_event(env, bootjob['jobid']) 232 | 233 | # refresh linode state with provider and output ip address 234 | linode = Provider.linode(@machine, refresh: true) 235 | public_network = linode.network.find { |network| network['ispublic'] == 1 } 236 | env[:ui].info I18n.t('vagrant_linode.info.linode_ip', ip: public_network['ipaddress']) 237 | 238 | if private_network 239 | env[:ui].info I18n.t('vagrant_linode.info.linode_private_ip', ip: private_network['ipaddress']) 240 | end 241 | 242 | # wait for ssh to be ready 243 | switch_user = @machine.provider_config.setup? 244 | user = @machine.config.ssh.username 245 | if switch_user 246 | @machine.config.ssh.username = 'root' 247 | @machine.config.ssh.password = root_pass 248 | end 249 | 250 | retryable(tries: 25, sleep: 10) do # @todo bump tries when this is solid 251 | next if env[:interrupted] 252 | fail 'not ready' unless @machine.communicate.ready? 253 | end 254 | 255 | @machine.config.ssh.username = user 256 | 257 | @app.call(env) 258 | end 259 | 260 | # Both the recover and terminate are stolen almost verbatim from 261 | # the Vagrant AWS provider up action 262 | # def recover(env) 263 | # print YAML::dump env['vagrant_error'] 264 | # return if env['vagrant.error'].is_a?(Vagrant::Errors::VagrantError) 265 | # if @machine.state.id != -1 266 | # terminate(env) 267 | # end 268 | # end 269 | 270 | # generate a random name if server name is empty 271 | def get_server_name 272 | "vagrant_linode-#{rand.to_s.split('.')[1]}" 273 | end 274 | 275 | def terminate(env) 276 | destroy_env = env.dup 277 | destroy_env.delete(:interrupted) 278 | destroy_env[:config_validate] = false 279 | destroy_env[:force_confirm_destroy] = true 280 | env[:action_runner].run(Actions.destroy, destroy_env) 281 | end 282 | end 283 | end 284 | end 285 | end 286 | --------------------------------------------------------------------------------