foreman_digitalocean 0.2.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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