foreman_google 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|