foreman_google 0.0.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -4
  3. data/app/controllers/concerns/foreman/controller/parameters/compute_resource_extension.rb +21 -0
  4. data/app/controllers/foreman_google/api/v2/apipie_extensions.rb +16 -0
  5. data/app/controllers/foreman_google/api/v2/compute_resources_extensions.rb +22 -0
  6. data/app/lib/foreman_google/google_compute_adapter.rb +178 -0
  7. data/app/lib/google_cloud_compute/compute_attributes.rb +98 -0
  8. data/app/lib/google_cloud_compute/compute_collection.rb +23 -0
  9. data/app/lib/google_extensions/attached_disk.rb +22 -0
  10. data/app/models/concerns/foreman_google/host_managed_extensions.rb +11 -0
  11. data/app/models/foreman_google/gce.rb +102 -38
  12. data/app/models/foreman_google/google_compute.rb +68 -58
  13. data/app/views/compute_resources/form/_gce.html.erb +10 -0
  14. data/app/views/compute_resources/show/_gce.html.erb +0 -0
  15. data/app/views/compute_resources_vms/form/gce/_base.html.erb +18 -0
  16. data/app/views/compute_resources_vms/form/gce/_volume.html.erb +5 -0
  17. data/app/views/compute_resources_vms/index/_gce.html.erb +26 -0
  18. data/app/views/compute_resources_vms/show/_gce.html.erb +21 -0
  19. data/app/views/images/form/_gce.html.erb +3 -0
  20. data/db/migrate/20220331113745_foreman_gce_to_foreman_google_gce.rb +24 -0
  21. data/lib/foreman_google/engine.rb +10 -2
  22. data/lib/foreman_google/version.rb +1 -1
  23. data/locale/action_names.rb +6 -0
  24. data/locale/en/foreman_google.edit.po +116 -0
  25. data/locale/en/foreman_google.po +74 -2
  26. data/locale/en/foreman_google.po.time_stamp +0 -0
  27. data/locale/foreman_google.pot +112 -8
  28. data/locale/gemspec.rb +1 -1
  29. data/package.json +7 -7
  30. data/test/fixtures/disks_delete.json +14 -0
  31. data/test/fixtures/disks_get.json +13 -0
  32. data/test/fixtures/disks_insert.json +12 -0
  33. data/test/fixtures/instance.json +1 -1
  34. data/test/fixtures/instance_insert.json +15 -0
  35. data/test/fixtures/instance_list.json +86 -0
  36. data/test/fixtures/instance_set_disk_auto_delete.json +14 -0
  37. data/test/fixtures/operation_error.json +26 -0
  38. data/test/fixtures/operation_get.json +13 -0
  39. data/test/models/foreman_google/gce_test.rb +43 -5
  40. data/test/models/foreman_google/google_compute_test.rb +90 -32
  41. data/test/unit/foreman_google/google_compute_adapter_test.rb +103 -4
  42. data/test/unit/google_extensions/attached_disk_test.rb +17 -0
  43. data/webpack/global_index.js +2 -13
  44. data/webpack/legacy.js +16 -0
  45. metadata +66 -16
  46. data/app/controllers/concerns/foreman_google/temporary_prepend_path.rb +0 -16
  47. data/lib/foreman_google/google_compute_adapter.rb +0 -91
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfa941ac3f2b5a130802d66a7bf5966b42facf344f5020d63ebd3371e79c403f
4
- data.tar.gz: b6d0a39fb9d62e91532a2bfdc157cf79d4ca785826804889055125540d963b04
3
+ metadata.gz: ea94c8410bd480a5d037871dfdbcc479c4a6b241ae0c02ab47f506f4b52215e4
4
+ data.tar.gz: a0f2fc8d3b5eac53bde9bbf0cf263feccc2e15e142ba5a0acb27383ad8b77872
5
5
  SHA512:
6
- metadata.gz: 4d7a4a449407e4af774f68722b53310c00712a9240c52e1f9e81bad216cbe9989b28e4bdd93d52c93dd52afdce7bf956cbcd5c221ef5f73c65aeeb57d4fd5ea9
7
- data.tar.gz: '008c0c232479d20066e04f6a4066d418120f93c97c26aaaad33a274581f8ecbdeed2acc568f5ae04c211314fb33ab04de58ab6767d6935789d7e1ff311a781ab'
6
+ metadata.gz: 1bbfeec72844bb5dbb055c912552c5929e0ac758d09ff53065d24b29ae2bfc3ae830724fcf79fc9a9f3d1302041dd315775935ca739e4b8caf5916233846cd62
7
+ data.tar.gz: 885348d92eb06c1cc78c14d98b9826e6591683dd1ae561c42ad8d91ec2d1859db6d2b4b4509d908b16d63e8eca0f6eacf861908b764fcb40d9caa396e89cb69a
data/README.md CHANGED
@@ -3,13 +3,25 @@
3
3
  Foreman plugin for Google Compute Engine.
4
4
 
5
5
  ## Installation
6
-
7
- See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
6
+ ```shell
7
+ foreman-installer --enable-foreman-plugin-google
8
+ ```
9
+ Or see [Plugins documentation](https://www.theforeman.org/plugins/#2.Installation)
8
10
  for how to install Foreman plugins
9
11
 
10
12
  ## Usage
11
-
12
- *TODO: Usage here*
13
+ * Create an account and project at [console.cloud.google.com](https://console.cloud.google.com)
14
+ * In _API & Services > Credentials_ create new service account (role: `Editor`)
15
+ * On account detail page go to _Keys_ and create new `JSON` key
16
+ * In Foreman, go to _Infrastructure > Compute Resources_ and create Compute Resource
17
+ ```
18
+ name: <your-name>
19
+ provider: Google
20
+ Google Project ID: <your-project-id>
21
+ Client Email: service account's email
22
+ Certificate Path: JSON file
23
+ Zone: select the zone you want
24
+ ```
13
25
 
14
26
  ## Contributing
15
27
 
@@ -0,0 +1,21 @@
1
+ module Foreman
2
+ module Controller
3
+ module Parameters
4
+ module ComputeResourceExtension
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def compute_resource_params_filter
9
+ super.tap do |filter|
10
+ filter.permit :email,
11
+ :key_pair,
12
+ :key_path,
13
+ :project,
14
+ :zone
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module ForemanGoogle
2
+ module Api
3
+ module V2
4
+ module ApipieExtensions
5
+ extend Apipie::DSL::Concern
6
+
7
+ update_api(:create, :update) do
8
+ param :compute_resource, Hash do
9
+ param :key_path, String, desc: N_('Certificate path, for GCE only')
10
+ param :zone, String, desc: N_('Zone, for GCE only')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ module ForemanGoogle
2
+ module Api
3
+ module V2
4
+ module ComputeResourcesExtensions
5
+ extend ActiveSupport::Concern
6
+
7
+ # rubocop:disable Rails/LexicallyScopedActionFilter
8
+ included do
9
+ before_action :read_key, only: [:create]
10
+ end
11
+ # rubocop:enable Rails/LexicallyScopedActionFilter
12
+
13
+ private
14
+
15
+ def read_key
16
+ return unless compute_resource_params['provider'] == 'GCE'
17
+ params[:compute_resource][:password] = File.read(params['compute_resource'].delete('key_path'))
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,178 @@
1
+ require 'google-cloud-compute'
2
+
3
+ # rubocop:disable Rails/SkipsModelValidations, Metrics/ClassLength
4
+ module ForemanGoogle
5
+ class GoogleComputeAdapter
6
+ def initialize(auth_json_string:)
7
+ @auth_json = JSON.parse(auth_json_string)
8
+ end
9
+
10
+ def project_id
11
+ @auth_json['project_id']
12
+ end
13
+
14
+ # ------ RESOURCES ------
15
+
16
+ def insert_instance(zone, attrs = {})
17
+ response = resource_client('instances').insert(project: project_id, zone: zone, instance_resource: attrs)
18
+ operation_attrs = { zone: zone, operation: response.operation.id.to_s }
19
+
20
+ wait_for do
21
+ get('zone_operations', operation_attrs).status == :DONE
22
+ end
23
+
24
+ e = get('zone_operations', operation_attrs).error
25
+
26
+ return response unless e
27
+
28
+ raise ::Google::Cloud::Error, e.errors.first.message
29
+ end
30
+
31
+ # Returns an Google::Instance identified by instance_identity within given zone.
32
+ # @param zone [String] eighter full url or just zone name
33
+ # @param instance_identity [String] eighter an instance name or its id
34
+ def instance(zone, instance_identity)
35
+ get('instances', instance: instance_identity, zone: zone)
36
+ end
37
+
38
+ def instances(zone, **attrs)
39
+ list('instances', zone: zone, **attrs)
40
+ end
41
+
42
+ def zones
43
+ list('zones')
44
+ end
45
+
46
+ def networks
47
+ list('networks')
48
+ end
49
+
50
+ def machine_types(zone)
51
+ list('machine_types', zone: zone)
52
+ end
53
+
54
+ def start(zone, instance_identity)
55
+ manage_instance(:start, zone: zone, instance: instance_identity)
56
+ end
57
+
58
+ def stop(zone, instance_identity)
59
+ manage_instance(:stop, zone: zone, instance: instance_identity)
60
+ end
61
+
62
+ def delete_instance(zone, instance_identity)
63
+ manage_instance(:delete, zone: zone, instance: instance_identity)
64
+ end
65
+
66
+ # Setting filter to '(deprecated.state != "DEPRECATED") AND (deprecated.state != "OBSOLETE")'
67
+ # doesn't work and returns empty array, no idea what is happening there
68
+ def images(filter: nil)
69
+ projects = [project_id] + all_projects
70
+ all_images = projects.map { |project| list_images(project, filter: filter) }
71
+ all_images.flatten.reject(&:deprecated)
72
+ end
73
+
74
+ def image(uuid)
75
+ images.find { |img| img.id == uuid }
76
+ end
77
+
78
+ def insert_disk(zone, disk_attrs = {})
79
+ insert('disks', zone, disk_resource: disk_attrs)
80
+ end
81
+
82
+ def disk(zone, name)
83
+ get('disks', disk: name, zone: zone)
84
+ end
85
+
86
+ def delete_disk(zone, disk_name)
87
+ delete('disks', zone, disk: disk_name)
88
+ end
89
+
90
+ def set_disk_auto_delete(zone, instance_identity)
91
+ instance = instance(zone, instance_identity)
92
+ instance.disks.each do |disk|
93
+ manage_instance :set_disk_auto_delete, zone: zone,
94
+ device_name: disk.device_name,
95
+ instance: instance_identity,
96
+ auto_delete: true
97
+ end
98
+ end
99
+
100
+ def serial_port_output(zone, instance_identity)
101
+ manage_instance(:get_serial_port_output, zone: zone, instance: instance_identity)
102
+ end
103
+
104
+ def wait_for
105
+ timeout = 60
106
+ duration = 0
107
+ start = Time.zone.now
108
+
109
+ loop do
110
+ break if yield
111
+
112
+ raise "The specified wait_for timeout (#{timeout} seconds) was exceeded" if duration > timeout
113
+
114
+ sleep(1)
115
+ duration = Time.zone.now - start
116
+ end
117
+
118
+ { duration: duration }
119
+ end
120
+
121
+ private
122
+
123
+ def list(resource_name, **opts)
124
+ response = resource_client(resource_name).list(project: project_id, **opts).response
125
+ response.items
126
+ rescue ::Google::Cloud::Error => e
127
+ raise Foreman::WrappedException.new(e, 'Cannot list Google resource %s', resource_name)
128
+ end
129
+
130
+ def get(resource_name, **opts)
131
+ resource_client(resource_name).get(project: project_id, **opts)
132
+ rescue Google::Cloud::NotFoundError => e
133
+ Foreman::Logging.exception("Could not fetch Google instance [#{opts[:instance]}]", e)
134
+ raise ActiveRecord::RecordNotFound
135
+ rescue ::Google::Cloud::Error => e
136
+ raise Foreman::WrappedException.new(e, 'Could not fetch Google resource %s', resource_name)
137
+ end
138
+
139
+ def insert(resource_name, zone, **opts)
140
+ resource_client(resource_name).insert(project: project_id, zone: zone, **opts)
141
+ rescue ::Google::Cloud::Error => e
142
+ raise Foreman::WrappedException.new(e, 'Could not create Google resource %s', resource_name)
143
+ end
144
+
145
+ def delete(resource_name, zone, **opts)
146
+ resource_client(resource_name).delete(project: project_id, zone: zone, **opts)
147
+ rescue ::Google::Cloud::Error => e
148
+ raise Foreman::WrappedException.new(e, 'Could not delete Google resource %s', resource_name)
149
+ end
150
+
151
+ def list_images(project, **opts)
152
+ resource_name = 'images'
153
+ response = resource_client(resource_name).list(project: project, **opts).response
154
+ response.items
155
+ rescue ::Google::Cloud::Error => e
156
+ raise Foreman::WrappedException.new(e, 'Cannot list Google resource %s', resource_name)
157
+ end
158
+
159
+ def manage_instance(action, **opts)
160
+ resource_client('instances').send(action, project: project_id, **opts)
161
+ rescue ::Google::Cloud::Error => e
162
+ raise Foreman::WrappedException.new(e, 'Could not %s Google resource %s', action.to_s, resource_name)
163
+ end
164
+
165
+ def resource_client(resource_name)
166
+ ::Google::Cloud::Compute.public_send(resource_name) do |config|
167
+ config.credentials = @auth_json
168
+ end
169
+ end
170
+
171
+ def all_projects
172
+ %w[centos-cloud cos-cloud coreos-cloud debian-cloud opensuse-cloud
173
+ rhel-cloud rhel-sap-cloud suse-cloud suse-sap-cloud
174
+ ubuntu-os-cloud windows-cloud windows-sql-cloud].freeze
175
+ end
176
+ end
177
+ end
178
+ # rubocop:enable Rails/SkipsModelValidations, Metrics/ClassLength
@@ -0,0 +1,98 @@
1
+ module GoogleCloudCompute
2
+ class ComputeAttributes
3
+ def initialize(client)
4
+ @client = client
5
+ end
6
+
7
+ def for_new(args)
8
+ name = parameterize_name(args[:name])
9
+ network = args[:network] || 'default'
10
+ associate_external_ip = ActiveModel::Type::Boolean.new.cast(args[:associate_external_ip])
11
+
12
+ { name: name, hostname: name,
13
+ machine_type: args[:machine_type],
14
+ network: network, associate_external_ip: associate_external_ip,
15
+ network_interfaces: construct_network(network, associate_external_ip, args[:network_interfaces] || []),
16
+ image_id: args[:image_id],
17
+ volumes: construct_volumes(name, args[:image_id], args[:volumes]),
18
+ metadata: construct_metadata(args) }
19
+ end
20
+
21
+ def for_create(instance)
22
+ {
23
+ name: instance.name,
24
+ machine_type: "zones/#{instance.zone}/machineTypes/#{instance.machine_type}",
25
+ disks: instance.volumes.map.with_index { |vol, i| { source: "zones/#{instance.zone}/disks/#{vol.device_name}", boot: i.zero? } },
26
+ network_interfaces: instance.network_interfaces,
27
+ metadata: instance.metadata,
28
+ }
29
+ end
30
+
31
+ def for_instance(instance)
32
+ {
33
+ name: instance.name, hostname: instance.name,
34
+ creation_timestamp: instance.creation_timestamp.to_datetime,
35
+ zone_name: instance.zone.split('/').last,
36
+ machine_type: instance.machine_type,
37
+ network: instance.network_interfaces[0].network.split('/').last,
38
+ network_interfaces: instance.network_interfaces,
39
+ volumes: instance.disks, metadata: instance.metadata
40
+ }
41
+ end
42
+
43
+ private
44
+
45
+ def parameterize_name(name)
46
+ name&.parameterize || "foreman-#{Time.now.to_i}"
47
+ end
48
+
49
+ def construct_network(network_name, associate_external_ip, network_interfaces)
50
+ # handle network_interface for external ip
51
+ # assign ephemeral external IP address using associate_external_ip
52
+ if associate_external_ip
53
+ network_interfaces = [{ network: 'global/networks/default' }] if network_interfaces.empty?
54
+ access_config = { name: 'External NAT', type: 'ONE_TO_ONE_NAT' }
55
+
56
+ # Note - no support for external_ip from foreman
57
+ # access_config[:nat_ip] = external_ip if external_ip
58
+ network_interfaces[0][:access_configs] = [access_config]
59
+ return network_interfaces
60
+ end
61
+
62
+ network = "https://compute.googleapis.com/compute/v1/projects/#{@client.project_id}/global/networks/#{network_name}"
63
+ [{ network: network }]
64
+ end
65
+
66
+ def load_image(image_id)
67
+ return unless image_id
68
+
69
+ @client.image(image_id.to_i)
70
+ end
71
+
72
+ def construct_volumes(vm_name, image_id, volumes = [])
73
+ return [Google::Cloud::Compute::V1::AttachedDisk.new(disk_size_gb: 20)] if volumes.empty?
74
+
75
+ image = load_image(image_id)
76
+
77
+ attached_disks = volumes.map.with_index do |vol_attrs, i|
78
+ name = "#{vm_name}-disk#{i + 1}"
79
+ size = (vol_attrs[:size_gb] || vol_attrs[:disk_size_gb]).to_i
80
+
81
+ Google::Cloud::Compute::V1::AttachedDisk.new(device_name: name, disk_size_gb: size)
82
+ end
83
+
84
+ attached_disks.first.source = image&.self_link if image&.self_link
85
+ attached_disks
86
+ end
87
+
88
+ # Note - GCE only supports cloud-init for Container Optimized images and
89
+ # for custom images with cloud-init setup
90
+ def construct_metadata(args)
91
+ ssh_keys = { key: 'ssh-keys', value: "#{args[:username]}:#{args[:public_key]}" }
92
+
93
+ return { items: [ssh_keys] } if args[:user_data].blank?
94
+
95
+ { items: [ssh_keys, { key: 'user-data', value: args[:user_data] }] }
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,23 @@
1
+ module GoogleCloudCompute
2
+ class ComputeCollection
3
+ include Enumerable
4
+
5
+ def initialize(client, zone, attrs)
6
+ instances = client.instances(zone, attrs)
7
+ @virtual_machines = instances.map do |vm|
8
+ ForemanGoogle::GoogleCompute.new client: client,
9
+ zone: zone,
10
+ identity: vm.id,
11
+ instance: vm
12
+ end
13
+ end
14
+
15
+ def each(&block)
16
+ @virtual_machines.each(&block)
17
+ end
18
+
19
+ def all(_opts = {})
20
+ @virtual_machines
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module GoogleExtensions
2
+ module AttachedDisk
3
+ def persisted?
4
+ end
5
+
6
+ def id
7
+ end
8
+
9
+ def _delete
10
+ end
11
+
12
+ def insert_attrs
13
+ attrs = { name: device_name, size_gb: disk_size_gb }
14
+ attrs[:source_image] = source if source.present?
15
+ attrs
16
+ end
17
+
18
+ def size_gb
19
+ disk_size_gb
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module ForemanGoogle
2
+ module HostManagedExtensions
3
+ def ip_addresses
4
+ vm&.ip_addresses || []
5
+ end
6
+
7
+ def vm_ip_address
8
+ vm&.vm_ip_address
9
+ end
10
+ end
11
+ end
@@ -1,42 +1,33 @@
1
1
  require 'foreman_google/google_compute_adapter'
2
2
 
3
+ # rubocop:disable Rails/InverseOf, Metrics/ClassLength
3
4
  module ForemanGoogle
4
5
  class GCE < ::ComputeResource
5
- def self.available?
6
- true
7
- end
8
-
9
- def self.provider_friendly_name
10
- 'Google'
11
- end
6
+ has_one :key_pair, foreign_key: :compute_resource_id, dependent: :destroy
7
+ before_create :setup_key_pair
8
+ validates :password, :zone, presence: true
12
9
 
13
- def user_data_supported?
10
+ def self.available?
14
11
  true
15
12
  end
16
13
 
17
- def test_connection(options = {})
18
- end
19
-
20
14
  def to_label
15
+ "#{name} (#{zone}-#{provider_friendly_name})"
21
16
  end
22
17
 
23
18
  def capabilities
24
19
  %i[image new_volume]
25
20
  end
26
21
 
22
+ def provided_attributes
23
+ super.merge({ ip: :vm_ip_address })
24
+ end
25
+
27
26
  def zones
28
27
  client.zones.map(&:name)
29
28
  end
30
29
  alias_method :available_zones, :zones
31
30
 
32
- def zone
33
- url
34
- end
35
-
36
- def zone=(zone)
37
- self.url = zone
38
- end
39
-
40
31
  def networks
41
32
  client.networks.map(&:name)
42
33
  end
@@ -50,17 +41,12 @@ module ForemanGoogle
50
41
  end
51
42
  alias_method :available_flavors, :machine_types
52
43
 
53
- def disks
44
+ def zone
45
+ url
54
46
  end
55
47
 
56
- # def interfaces_attrs_name
57
- # super # :interfaces
58
- # end
59
-
60
- # This should return interface compatible with Fog::Server
61
- # implemented by ForemanGoogle::Compute
62
- def find_vm_by_uuid(uuid)
63
- GoogleCompute.new(client: client, zone: zone, identity: uuid.to_s)
48
+ def zone=(zone)
49
+ self.url = zone
64
50
  end
65
51
 
66
52
  def new_vm(args = {})
@@ -73,35 +59,85 @@ module ForemanGoogle
73
59
  GoogleCompute.new(client: client, zone: zone, args: vm_args)
74
60
  end
75
61
 
62
+ def create_vm(args = {})
63
+ ssh_args = { username: find_os_image(args[:image_id])&.username, public_key: key_pair.public }
64
+ vm = new_vm(args.merge(ssh_args))
65
+
66
+ vm.create_volumes
67
+ vm.create_instance
68
+ vm.set_disk_auto_delete
69
+
70
+ find_vm_by_uuid vm.hostname
71
+ rescue ::Google::Cloud::Error => e
72
+ vm.destroy_volumes
73
+ raise Foreman::WrappedException.new(e, 'Cannot insert instance!')
74
+ end
75
+
76
+ def find_vm_by_uuid(uuid)
77
+ GoogleCompute.new(client: client, zone: zone, identity: uuid.to_s)
78
+ end
79
+
76
80
  def destroy_vm(uuid)
81
+ client.set_disk_auto_delete(zone, uuid)
82
+ client.delete_instance(zone, uuid)
83
+ rescue ActiveRecord::RecordNotFound
84
+ # if the VM does not exists, we don't really care.
85
+ true
77
86
  end
78
87
 
79
- def create_volumes(args)
88
+ def available_images(filter: filter_for_images)
89
+ client.images(filter: filter)
80
90
  end
81
91
 
82
- def create_vm(args = {})
83
- new_vm(args)
84
- create_volumes(args)
85
- # TBD
92
+ def filter_for_images
93
+ @filter_for_images ||= nil
86
94
  end
87
95
 
88
- def vm_options(args)
96
+ def self.model_name
97
+ ComputeResource.model_name
89
98
  end
90
99
 
91
- def new_volume(attrs = {})
100
+ def setup_key_pair
101
+ require 'sshkey'
102
+
103
+ key = ::SSHKey.generate
104
+ build_key_pair name: "foreman-#{id}#{Foreman.uuid}", secret: key.private_key, public: key.ssh_public_key
105
+ end
106
+
107
+ def self.provider_friendly_name
108
+ 'Google'
92
109
  end
93
110
 
94
- def normalize_vm_attrs(vm_attrs)
111
+ def user_data_supported?
112
+ true
113
+ end
114
+
115
+ def new_volume(attrs = {})
116
+ default_attrs = { disk_size_gb: 20 }
117
+ Google::Cloud::Compute::V1::AttachedDisk.new(**attrs.merge(default_attrs))
95
118
  end
96
119
 
97
120
  def console(uuid)
121
+ vm = find_vm_by_uuid(uuid)
122
+
123
+ if vm.ready?
124
+ {
125
+ 'output' => vm.serial_port_output, 'timestamp' => Time.now.utc,
126
+ :type => 'log', :name => vm.name
127
+ }
128
+ else
129
+ raise ::Foreman::Exception,
130
+ N_('console is not available at this time because the instance is powered off')
131
+ end
98
132
  end
99
133
 
100
134
  def associated_host(vm)
135
+ associate_by('ip', [vm.public_ip_address, vm.private_ip_address])
101
136
  end
102
137
 
103
- def available_images(filter: nil)
104
- client.images(filter: filter)
138
+ def vms(attrs = {})
139
+ filtered_attrs = attrs.except(:eager_loading)
140
+ GoogleCloudCompute::ComputeCollection.new(client, zone, filtered_attrs)
105
141
  end
106
142
 
107
143
  # ----# Google specific #-----
@@ -110,10 +146,38 @@ module ForemanGoogle
110
146
  client.project_id
111
147
  end
112
148
 
149
+ def vm_ready(vm)
150
+ vm.wait_for do
151
+ vm.reload
152
+ vm.ready?
153
+ end
154
+ end
155
+
113
156
  private
114
157
 
115
158
  def client
116
159
  @client ||= ForemanGoogle::GoogleComputeAdapter.new(auth_json_string: password)
117
160
  end
161
+
162
+ def set_vm_volumes_attributes(vm, vm_attrs)
163
+ return vm_attrs unless vm.respond_to?(:volumes)
164
+
165
+ vm_attrs[:volumes_attributes] = Hash[vm.volumes.each_with_index.map { |volume, idx| [idx.to_s, volume.to_h] }]
166
+
167
+ vm_attrs
168
+ end
169
+
170
+ def find_os_image(uuid)
171
+ os_image = images.find_by(uuid: uuid)
172
+ gce_image = client.image(uuid.to_i)
173
+
174
+ raise ::Foreman::Exception, N_('Missing an image for operating system!') if os_image.nil?
175
+ if gce_image.nil?
176
+ raise ::Foreman::Exception, N_("GCE image [#{uuid.to_i}] for #{os_image.name} image not found in the cloud!")
177
+ end
178
+
179
+ os_image
180
+ end
118
181
  end
119
182
  end
183
+ # rubocop:enable Rails/InverseOf, Metrics/ClassLength