foreman_digitalocean 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71d307b35d4985bdb21c214352ca5268d3cfc23b
4
- data.tar.gz: ff5a0e763bc0ef05ff8b1c78cdbafdacc7d54f75
3
+ metadata.gz: 09968431a3ccdbbd31a0dde996cfff6358a2a254
4
+ data.tar.gz: d044e4f386bd33594bcab16953e3380f2e334de7
5
5
  SHA512:
6
- metadata.gz: 172bddb13099be4ad44d1da245e73710cb8abce581d195dc873a55a494a68fd9f3199e700d6240157bd8ab049e1a0822afe2768fc87aa74bc69c8953af8116a0
7
- data.tar.gz: 71874e1b273f4b9cec7a05c1165c9ca46e4828c2dcc71f801fc9649ecba2e9b5b095daf114438fb0bbdf62bd918cbff279a9fd741aff0cfb54774ed25fae473b
6
+ metadata.gz: 83c7f182dfabf192159f2c2678f9aafceaece54ba60f9d9a19d397456d54c6c969a588242b25df274b5e349e3dd21bd77dff86324c867a9df36e06715ca94a7d
7
+ data.tar.gz: 9a861c05403d615412a39530860cca00cc2193bd921aaee5ea9d63dd2b34fd2315e5208ab46d78685720a30b8803b3289a0258bb658dc38cd0af7d12bf336f8a
@@ -0,0 +1,33 @@
1
+ module DigitaloceanImagesHelper
2
+ def digitalocean_image_field(f)
3
+ images = @compute_resource.available_images
4
+ images.each { |image| image.name = image.id if image.name.nil? }
5
+ select_f f, :uuid, images.to_a.sort_by(&:full_name),
6
+ :id, :full_name, {}, :label => _('Image')
7
+ end
8
+
9
+ def select_image(f, compute_resource)
10
+ images = possible_images(compute_resource, nil, nil)
11
+
12
+ select_f(f,
13
+ :image_id,
14
+ images,
15
+ :id,
16
+ :slug,
17
+ { :include_blank => (images.empty? || images.size == 1) ? false : _('Please select an image') },
18
+ { :label => ('Image'), :disabled => images.empty? } )
19
+ end
20
+
21
+ def select_region(f, compute_resource)
22
+ regions = compute_resource.regions
23
+ f.object.region = compute_resource.region
24
+ select_f(f,
25
+ :region,
26
+ regions,
27
+ :slug,
28
+ :slug,
29
+ {},
30
+ :label => ('Region'),
31
+ :disabled => compute_resource.images.empty?)
32
+ end
33
+ end
@@ -23,9 +23,12 @@ module FogExtensions
23
23
  # Attempt guessing arch based on the name from digital ocean
24
24
  def arch
25
25
  requires :os_version
26
- os_version.end_with?("x64") ? "x86_64" : ( os_version.end_with?("x32") ? "i386" : nil )
26
+ if os_version.end_with?("x64")
27
+ "x86_64"
28
+ elsif os_version.end_with?("x32")
29
+ "i386"
30
+ end
27
31
  end
28
-
29
32
  end
30
33
  end
31
34
  end
@@ -3,6 +3,8 @@ module FogExtensions
3
3
  module Server
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ attr_accessor :image_id
7
+
6
8
  def identity_to_s
7
9
  identity.to_s
8
10
  end
@@ -44,6 +46,10 @@ module FogExtensions
44
46
  [public_ip_address, private_ip_address].flatten.select(&:present?)
45
47
  end
46
48
 
49
+ def state
50
+ requires :status
51
+ @state ||= status
52
+ end
47
53
  end
48
54
  end
49
55
  end
@@ -0,0 +1,16 @@
1
+ module ForemanDigitalocean
2
+ module Concerns
3
+ module HostManagedExtensions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Rails 4 does not provide dynamic finders for delegated methods and
8
+ # the SSH orchestrate compute method uses them.
9
+ def self.find_by_ip(ip)
10
+ nic = Nic::Base.find_by_ip(ip)
11
+ nic.host if nic.present?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,24 +1,25 @@
1
1
  module ForemanDigitalocean
2
2
  class Digitalocean < ComputeResource
3
+ alias_attribute :api_key, :password
4
+ alias_attribute :region, :url
5
+
3
6
  has_one :key_pair, :foreign_key => :compute_resource_id, :dependent => :destroy
4
7
  delegate :flavors, :to => :client
5
8
 
6
- validates :user, :password, :presence => true
9
+ validates :api_key, :presence => true
7
10
  before_create :test_connection
8
11
 
9
12
  after_create :setup_key_pair
10
13
  after_destroy :destroy_key_pair
11
14
 
12
-
13
- # Not sure why it would need a url, but OK (copied from ec2)
14
- alias_attribute :region, :url
15
+ attr_accessible :region, :api_key
15
16
 
16
17
  def to_label
17
18
  "#{name} (#{provider_friendly_name})"
18
19
  end
19
20
 
20
21
  def provided_attributes
21
- super.merge({ :uuid => :identity_to_s, :ip => :public_ip_address })
22
+ super.merge(:uuid => :identity_to_s, :ip => :public_ip_address)
22
23
  end
23
24
 
24
25
  def self.model_name
@@ -35,8 +36,9 @@ module ForemanDigitalocean
35
36
  raise(ActiveRecord::RecordNotFound)
36
37
  end
37
38
 
38
- def create_vm(args = { })
39
+ def create_vm(args = {})
39
40
  args["ssh_keys"] = [ssh_key] if ssh_key
41
+ args['image'] = args['image_id']
40
42
  super(args)
41
43
  rescue Fog::Errors::Error => e
42
44
  logger.error "Unhandled DigitalOcean error: #{e.class}:#{e.message}\n " + e.backtrace.join("\n ")
@@ -48,13 +50,13 @@ module ForemanDigitalocean
48
50
  end
49
51
 
50
52
  def regions
51
- return [] if user.blank? || password.blank?
53
+ return [] if api_key.blank?
52
54
  client.regions
53
55
  end
54
56
 
55
57
  def test_connection(options = {})
56
58
  super
57
- errors[:user].empty? and errors[:password].empty? and regions.count
59
+ errors[:password].empty? && regions.count
58
60
  rescue Excon::Errors::Unauthorized => e
59
61
  errors[:base] << e.response.body
60
62
  rescue Fog::Errors::Error => e
@@ -63,12 +65,12 @@ module ForemanDigitalocean
63
65
 
64
66
  def destroy_vm(uuid)
65
67
  vm = find_vm_by_uuid(uuid)
66
- vm.destroy if vm.present?
68
+ vm.delete if vm.present?
67
69
  true
68
70
  end
69
71
 
70
72
  # not supporting update at the moment
71
- def update_required?(old_attrs, new_attrs)
73
+ def update_required?(*)
72
74
  false
73
75
  end
74
76
 
@@ -77,7 +79,8 @@ module ForemanDigitalocean
77
79
  end
78
80
 
79
81
  def associated_host(vm)
80
- Host.authorized(:view_hosts, Host).where(:ip => [vm.public_ip_address, vm.private_ip_address]).first
82
+ Host.authorized(:view_hosts, Host).
83
+ where(:ip => [vm.public_ip_address, vm.private_ip_address]).first
81
84
  end
82
85
 
83
86
  def user_data_supported?
@@ -85,7 +88,7 @@ module ForemanDigitalocean
85
88
  end
86
89
 
87
90
  def default_region_name
88
- @default_region_name ||= client.regions.get(region.to_i).try(:name)
91
+ @default_region_name ||= client.regions[region.to_i].try(:name)
89
92
  rescue Excon::Errors::Unauthorized => e
90
93
  errors[:base] << e.response.body
91
94
  end
@@ -95,25 +98,24 @@ module ForemanDigitalocean
95
98
  def client
96
99
  @client ||= Fog::Compute.new(
97
100
  :provider => "DigitalOcean",
98
- :digitalocean_client_id => user,
99
- :digitalocean_api_key => password,
101
+ :version => 'V2',
102
+ :digitalocean_token => api_key
100
103
  )
101
104
  end
102
105
 
103
106
  def vm_instance_defaults
104
107
  super.merge(
105
- :flavor_id => client.flavors.first.id
108
+ :size => client.flavors.first.slug
106
109
  )
107
110
  end
108
111
 
109
-
110
- # this method creates a new key pair for each new DigitalOcean compute resource
111
- # it should create the key and upload it to DigitalOcean
112
+ # Creates a new key pair for each new DigitalOcean compute resource
113
+ # After creating the key, it uploads it to DigitalOcean
112
114
  def setup_key_pair
113
115
  public_key, private_key = generate_key
114
116
  key_name = "foreman-#{id}#{Foreman.uuid}"
115
- key = client.create_ssh_key key_name, public_key
116
- KeyPair.create! :name => key_name, :compute_resource_id => self.id, :secret => private_key
117
+ client.create_ssh_key key_name, public_key
118
+ KeyPair.create! :name => key_name, :compute_resource_id => id, :secret => private_key
117
119
  rescue => e
118
120
  logger.warn "failed to generate key pair"
119
121
  logger.error e.message
@@ -134,24 +136,18 @@ module ForemanDigitalocean
134
136
 
135
137
  def ssh_key
136
138
  @ssh_key ||= begin
137
- key = client.list_ssh_keys.data[:body]["ssh_keys"].select{|i| i["name"] == key_pair.name}.first
138
- if key
139
- #the vm creator expects objects which respond to id, OpenStruct is the shortest solution.
140
- OpenStruct.new(key)
141
- else
142
- nil
143
- end
139
+ key = client.list_ssh_keys.data[:body]["ssh_keys"].find { |i| i["name"] == key_pair.name }
140
+ key['id'] if key.present?
144
141
  end
145
142
  end
146
143
 
147
144
  def generate_key
148
145
  key = OpenSSL::PKey::RSA.new 2048
149
146
  type = key.ssh_type
150
- data = [ key.to_blob ].pack('m0')
147
+ data = [key.to_blob].pack('m0')
151
148
 
152
149
  openssh_format_public_key = "#{type} #{data}"
153
150
  [openssh_format_public_key, key.to_pem]
154
151
  end
155
-
156
152
  end
157
153
  end
@@ -1,11 +1,14 @@
1
- <%= text_f f, :user, :label => _("Client ID") %>
2
- <%= password_f f, :password, :label => _("API Key") %>
3
-
1
+ <%= password_f f, :api_key, :label => _("API Key"), :unset => unset_password? %>
4
2
  <% regions = f.object.regions rescue [] %>
5
3
 
6
4
  <div id='region_selection'>
7
- <%= select_f(f, :region, regions, :id, :name, {}, {:label => _('Default Region'), :disabled => regions.empty?,
8
- :help_inline => link_to_function(regions.empty? ? _("Load Regions") : _("Test Connection"), "testConnection(this)",
9
- :class => "btn + #{regions.empty? ? "btn-default" : "btn-success"}",
10
- :'data-url' => test_connection_compute_resources_path) + image_tag('/assets/spinner.gif', :id => 'test_connection_indicator', :class => 'hide').html_safe }) %>
5
+ <%= select_f(
6
+ f, :region, regions, :slug, :name, {},
7
+ :label => _('Default Region'), :disabled => regions.empty?,
8
+ :help_inline => link_to_function(
9
+ regions.empty? ? _("Load Regions") : _("Test Connection"), "testConnection(this)",
10
+ :class => "btn + #{regions.empty? ? "btn-default" : "btn-success"}",
11
+ :'data-url' => test_connection_compute_resources_path) +
12
+ spinner('', :id => 'test_connection_indicator',
13
+ :class => 'hide').html_safe) %>
11
14
  </div>
@@ -1,12 +1,7 @@
1
- <%= select_f f, :flavor_id, compute_resource.flavors, :id, :name, {}, {:label => _('Flavor')} %>
2
- <%
3
- arch ||= nil
4
- os ||= nil
5
- images = possible_images(compute_resource, arch, os)
6
- images = compute_resource.available_images if images.empty?
7
- regions = compute_resource.regions
8
- f.object.region_id = compute_resource.region
9
- %>
10
-
11
- <div id='image_selection'><%= select_f f, :image_id, images, :uuid, :name, { :include_blank => (images.empty? || images.size == 1) ? false : _('Please Select an Image') }, { :label => ('Image'), :disabled => images.empty? } %></div>
12
- <div id='region_selection'><%= select_f f, :region_id, regions, :id, :name, {}, { :label => ('Region'), :disabled => images.empty?} %></div>
1
+ <%= select_f f, :size, compute_resource.flavors, :slug, :slug, {}, {:label => _('Flavor')} %>
2
+ <div id='image_selection'>
3
+ <%= select_image(f, compute_resource) %>
4
+ </div>
5
+ <div id='region_selection'>
6
+ <%= select_region(f, compute_resource) %>
7
+ </div>
@@ -12,9 +12,9 @@
12
12
  <% @vms.each do |vm| %>
13
13
  <tr>
14
14
  <td><%= link_to_if_authorized vm.name, hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity).merge(:auth_object => @compute_resource, :authorizer => authorizer) %></td>
15
- <td><%= vm.image.name if vm.image.present? %></td>
16
- <td><%= vm.flavor.name %></td>
17
- <td><%= vm.region.name %></td>
15
+ <td><%= vm.image['slug'] if vm.image.present? %></td>
16
+ <td><%= vm.size['slug'] %></td>
17
+ <td><%= vm.region['slug'] %></td>
18
18
  <td> <span <%= vm_power_class(vm.ready?) %>> <%= vm_state(vm) %></span> </td>
19
19
  <td>
20
20
  <%= action_buttons(
@@ -1,3 +1,6 @@
1
- <%= text_f f, :username, :value => @image.username || "root", :help_inline => _("The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc") %>
2
- <%= image_field(f) %>
1
+ <%= text_f f,
2
+ :username,
3
+ :value => @image.username || "root",
4
+ :help_inline => _("The user that is used to ssh into the instance, normally cloud-user, ec2-user, ubuntu, root etc") %>
5
+ <%= digitalocean_image_field(f) %>
3
6
  <%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input (e.g. via cloud-init)?") %>
@@ -2,11 +2,11 @@ require 'fast_gettext'
2
2
  require 'gettext_i18n_rails'
3
3
 
4
4
  module ForemanDigitalocean
5
- # Inherit from the Rails module of the parent app (Foreman), not the plugin.
6
- # Thus, inherits from ::Rails::Engine and not from Rails::Engine
7
5
  class Engine < ::Rails::Engine
8
6
  engine_name 'foreman_digitalocean'
9
7
 
8
+ config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
9
+
10
10
  initializer 'foreman_digitalocean.register_gettext', :after => :load_config_initializers do
11
11
  locale_dir = File.join(File.expand_path('../../..', __FILE__), 'locale')
12
12
  locale_domain = 'foreman_digitalocean'
@@ -14,19 +14,35 @@ module ForemanDigitalocean
14
14
  Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
15
15
  end
16
16
 
17
- initializer 'foreman_digitalocean.register_plugin', :after => :finisher_hook do
17
+ initializer 'foreman_digitalocean.register_plugin', :before => :finisher_hook do
18
18
  Foreman::Plugin.register :foreman_digitalocean do
19
19
  requires_foreman '>= 1.8'
20
20
  compute_resource ForemanDigitalocean::Digitalocean
21
21
  end
22
22
  end
23
- end
24
23
 
25
- require 'fog/digitalocean'
26
- require 'fog/digitalocean/models/compute/image'
27
- require 'fog/digitalocean/models/compute/server'
28
- require File.expand_path('../../../app/models/concerns/fog_extensions/digitalocean/server', __FILE__)
29
- require File.expand_path('../../../app/models/concerns/fog_extensions/digitalocean/image', __FILE__)
30
- Fog::Compute::DigitalOcean::Image.send(:include, FogExtensions::DigitalOcean::Image)
31
- Fog::Compute::DigitalOcean::Server.send(:include, FogExtensions::DigitalOcean::Server)
24
+ rake_tasks do
25
+ load "#{ForemanDigitalocean::Engine.root}/lib/foreman_digitalocean/tasks/test.rake"
26
+ end
27
+
28
+ config.to_prepare do
29
+ require 'fog/digitalocean'
30
+ require 'fog/digitalocean/compute_v2'
31
+ require 'fog/digitalocean/models/compute_v2/image'
32
+ require 'fog/digitalocean/models/compute_v2/server'
33
+ require File.expand_path(
34
+ '../../../app/models/concerns/fog_extensions/digitalocean/server',
35
+ __FILE__)
36
+ require File.expand_path(
37
+ '../../../app/models/concerns/fog_extensions/digitalocean/image',
38
+ __FILE__)
39
+
40
+ Fog::Compute::DigitalOceanV2::Image.send :include,
41
+ FogExtensions::DigitalOcean::Image
42
+ Fog::Compute::DigitalOceanV2::Server.send :include,
43
+ FogExtensions::DigitalOcean::Server
44
+ ::Host::Managed.send :include,
45
+ ForemanDigitalocean::Concerns::HostManagedExtensions
46
+ end
47
+ end
32
48
  end
@@ -0,0 +1,44 @@
1
+ require File.expand_path("../engine", File.dirname(__FILE__))
2
+ namespace :test do
3
+ desc "Run the plugin unit test suite."
4
+ task :digitalocean => ['db:test:prepare'] do
5
+ test_task = Rake::TestTask.new('digitalocean_test_task') do |t|
6
+ t.libs << ["test", "#{ForemanDigitalocean::Engine.root}/test"]
7
+ t.test_files = [
8
+ "#{ForemanDigitalocean::Engine.root}/test/**/*_test.rb"
9
+ ]
10
+ t.verbose = true
11
+ end
12
+
13
+ Rake::Task[test_task.name].invoke
14
+ end
15
+ end
16
+
17
+ namespace :digitalocean do
18
+ task :rubocop do
19
+ begin
20
+ require 'rubocop/rake_task'
21
+ RuboCop::RakeTask.new(:rubocop_digitalocean) do |task|
22
+ task.patterns = ["#{ForemanDigitalocean::Engine.root}/app/**/*.rb",
23
+ "#{ForemanDigitalocean::Engine.root}/lib/**/*.rb",
24
+ "#{ForemanDigitalocean::Engine.root}/test/**/*.rb"]
25
+ end
26
+ rescue
27
+ puts "Rubocop not loaded."
28
+ end
29
+
30
+ Rake::Task['rubocop_digitalocean'].invoke
31
+ end
32
+ end
33
+
34
+ Rake::Task[:test].enhance do
35
+ Rake::Task['test:digitalocean'].invoke
36
+ end
37
+
38
+ load 'tasks/jenkins.rake'
39
+ if Rake::Task.task_defined?(:'jenkins:unit')
40
+ Rake::Task["jenkins:unit"].enhance do
41
+ Rake::Task['test:digitalocean'].invoke
42
+ Rake::Task['digitalocean:rubocop'].invoke
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module ForemanDigitalocean
2
- VERSION = '0.2.1'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -0,0 +1,13 @@
1
+ FactoryGirl.define do
2
+ factory :container_resource, :class => ComputeResource do
3
+ sequence(:name) { |n| "compute_resource#{n}" }
4
+
5
+ trait :digitalocean do
6
+ provider 'Digitalocean'
7
+ api_key 'asampleapikey'
8
+ region 'everywhere'
9
+ end
10
+
11
+ factory :digitalocean_cr, :class => ForemanDigitalocean::Digitalocean, :traits => [:digitalocean]
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ # This calls the main test_helper in Foreman core
2
+ require 'test_helper'
3
+
4
+ # Add plugin to FactoryGirl's paths
5
+ FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
6
+ FactoryGirl.reload
@@ -0,0 +1,18 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ForemanDigitalocean::DigitaloceanTest < ActiveSupport::TestCase
4
+ should validate_presence_of(:api_key)
5
+ should allow_mass_assignment_of(:region)
6
+ should allow_mass_assignment_of(:api_key)
7
+ should delegate_method(:flavors).to(:client)
8
+ should have_one(:key_pair)
9
+
10
+ setup { Fog.mock! }
11
+ teardown { Fog.unmock! }
12
+
13
+ test 'ssh key pair gets created after its saved' do
14
+ digitalocean = FactoryGirl.build(:digitalocean_cr)
15
+ digitalocean.expects(:setup_key_pair)
16
+ digitalocean.save
17
+ end
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_digitalocean
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tommy McNeely, Daniel Lobato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-27 00:00:00.000000000 Z
11
+ date: 2016-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -33,8 +33,10 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - LICENSE
35
35
  - README.md
36
+ - app/helpers/digitalocean_images_helper.rb
36
37
  - app/models/concerns/fog_extensions/digitalocean/image.rb
37
38
  - app/models/concerns/fog_extensions/digitalocean/server.rb
39
+ - app/models/foreman_digitalocean/concerns/host_managed_extensions.rb
38
40
  - app/models/foreman_digitalocean/digitalocean.rb
39
41
  - app/views/api/v1/compute_resources/digitalocean.json
40
42
  - app/views/api/v2/compute_resources/digitalocean.json
@@ -46,8 +48,12 @@ files:
46
48
  - app/views/images/form/_digitalocean.html.erb
47
49
  - lib/foreman_digitalocean.rb
48
50
  - lib/foreman_digitalocean/engine.rb
51
+ - lib/foreman_digitalocean/tasks/test.rake
49
52
  - lib/foreman_digitalocean/version.rb
50
53
  - locale/Makefile
54
+ - test/factories/compute_resources.rb
55
+ - test/test_plugin_helper.rb
56
+ - test/unit/foreman_digitalocean/digitalocean_test.rb
51
57
  homepage: http://github.com/theforeman/foreman-digitalocean
52
58
  licenses:
53
59
  - GPL-3
@@ -72,4 +78,7 @@ rubygems_version: 2.2.2
72
78
  signing_key:
73
79
  specification_version: 4
74
80
  summary: Provision and manage DigitalOcean droplets from Foreman
75
- test_files: []
81
+ test_files:
82
+ - test/factories/compute_resources.rb
83
+ - test/test_plugin_helper.rb
84
+ - test/unit/foreman_digitalocean/digitalocean_test.rb