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.
- checksums.yaml +4 -4
- data/README.md +16 -4
- data/app/controllers/concerns/foreman/controller/parameters/compute_resource_extension.rb +21 -0
- data/app/controllers/foreman_google/api/v2/apipie_extensions.rb +16 -0
- data/app/controllers/foreman_google/api/v2/compute_resources_extensions.rb +22 -0
- data/app/lib/foreman_google/google_compute_adapter.rb +178 -0
- data/app/lib/google_cloud_compute/compute_attributes.rb +98 -0
- data/app/lib/google_cloud_compute/compute_collection.rb +23 -0
- data/app/lib/google_extensions/attached_disk.rb +22 -0
- data/app/models/concerns/foreman_google/host_managed_extensions.rb +11 -0
- data/app/models/foreman_google/gce.rb +102 -38
- data/app/models/foreman_google/google_compute.rb +68 -58
- data/app/views/compute_resources/form/_gce.html.erb +10 -0
- data/app/views/compute_resources/show/_gce.html.erb +0 -0
- data/app/views/compute_resources_vms/form/gce/_base.html.erb +18 -0
- data/app/views/compute_resources_vms/form/gce/_volume.html.erb +5 -0
- data/app/views/compute_resources_vms/index/_gce.html.erb +26 -0
- data/app/views/compute_resources_vms/show/_gce.html.erb +21 -0
- data/app/views/images/form/_gce.html.erb +3 -0
- data/db/migrate/20220331113745_foreman_gce_to_foreman_google_gce.rb +24 -0
- data/lib/foreman_google/engine.rb +10 -2
- data/lib/foreman_google/version.rb +1 -1
- data/locale/action_names.rb +6 -0
- data/locale/en/foreman_google.edit.po +116 -0
- data/locale/en/foreman_google.po +74 -2
- data/locale/en/foreman_google.po.time_stamp +0 -0
- data/locale/foreman_google.pot +112 -8
- data/locale/gemspec.rb +1 -1
- data/package.json +7 -7
- data/test/fixtures/disks_delete.json +14 -0
- data/test/fixtures/disks_get.json +13 -0
- data/test/fixtures/disks_insert.json +12 -0
- data/test/fixtures/instance.json +1 -1
- data/test/fixtures/instance_insert.json +15 -0
- data/test/fixtures/instance_list.json +86 -0
- data/test/fixtures/instance_set_disk_auto_delete.json +14 -0
- data/test/fixtures/operation_error.json +26 -0
- data/test/fixtures/operation_get.json +13 -0
- data/test/models/foreman_google/gce_test.rb +43 -5
- data/test/models/foreman_google/google_compute_test.rb +90 -32
- data/test/unit/foreman_google/google_compute_adapter_test.rb +103 -4
- data/test/unit/google_extensions/attached_disk_test.rb +17 -0
- data/webpack/global_index.js +2 -13
- data/webpack/legacy.js +16 -0
- metadata +66 -16
- data/app/controllers/concerns/foreman_google/temporary_prepend_path.rb +0 -16
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea94c8410bd480a5d037871dfdbcc479c4a6b241ae0c02ab47f506f4b52215e4
|
4
|
+
data.tar.gz: a0f2fc8d3b5eac53bde9bbf0cf263feccc2e15e142ba5a0acb27383ad8b77872
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
*
|
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
|
@@ -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
|
-
|
6
|
-
|
7
|
-
|
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
|
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
|
44
|
+
def zone
|
45
|
+
url
|
54
46
|
end
|
55
47
|
|
56
|
-
|
57
|
-
|
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
|
88
|
+
def available_images(filter: filter_for_images)
|
89
|
+
client.images(filter: filter)
|
80
90
|
end
|
81
91
|
|
82
|
-
def
|
83
|
-
|
84
|
-
create_volumes(args)
|
85
|
-
# TBD
|
92
|
+
def filter_for_images
|
93
|
+
@filter_for_images ||= nil
|
86
94
|
end
|
87
95
|
|
88
|
-
def
|
96
|
+
def self.model_name
|
97
|
+
ComputeResource.model_name
|
89
98
|
end
|
90
99
|
|
91
|
-
def
|
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
|
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
|
104
|
-
|
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
|