beaker-google 1.2.0 → 1.3.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/release.yml +41 -0
  3. data/.github/workflows/ci.yml +11 -8
  4. data/.github/workflows/release.yml +91 -17
  5. data/.rubocop.yml +2 -0
  6. data/.rubocop_todo.yml +20 -0
  7. data/CHANGELOG.md +32 -0
  8. data/Gemfile +8 -0
  9. data/README.md +2 -2
  10. data/Rakefile +7 -0
  11. data/beaker-google.gemspec +9 -6
  12. data/lib/beaker/hypervisor/google_compute.rb +4 -4
  13. data/lib/beaker/hypervisor/google_compute_helper.rb +15 -5
  14. data/lib/beaker-google/version.rb +1 -1
  15. data/spec/classes/beaker/google_compute_constants_and_defaults_spec.rb +141 -0
  16. data/spec/classes/beaker/google_compute_helper_api_version_spec.rb +37 -0
  17. data/spec/classes/beaker/google_compute_helper_configuration_validation_spec.rb +100 -0
  18. data/spec/classes/beaker/google_compute_helper_constants_and_defaults_spec.rb +66 -0
  19. data/spec/classes/beaker/google_compute_helper_edge_cases_spec.rb +173 -0
  20. data/spec/classes/beaker/google_compute_helper_error_handling_spec.rb +105 -0
  21. data/spec/classes/beaker/google_compute_helper_spec.rb +368 -0
  22. data/spec/classes/beaker/google_compute_helper_utilities_spec.rb +95 -0
  23. data/spec/classes/beaker/google_compute_initialization_spec.rb +52 -0
  24. data/spec/classes/beaker/google_compute_provision_spec.rb +68 -0
  25. data/spec/classes/beaker/google_compute_spec.rb +95 -0
  26. data/spec/classes/beaker/google_compute_ssh_key_management_spec.rb +115 -0
  27. data/spec/classes/beaker/google_spec.rb +68 -0
  28. data/spec/classes/beaker-google/beaker_google_version_spec.rb +19 -0
  29. data/spec/classes/beaker_integration_spec.rb +67 -0
  30. data/spec/spec_helper.rb +45 -0
  31. data/spec/support/google_compute_shared_setup.rb +138 -0
  32. data/spec/support/shared_mocks.rb +89 -0
  33. metadata +59 -15
  34. data/.github/workflows/codeql-analysis.yml +0 -70
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Beaker::GoogleComputeHelper do
6
+ let(:logger) { instance_double(Beaker::Logger) }
7
+ let(:base_options) { { logger: logger, gce_project: 'test-project', gce_ports: [] } }
8
+
9
+ def mock_google_services
10
+ # Mock the authentication and service initialization
11
+ allow(Google::Auth).to receive(:get_application_default).and_return(double)
12
+
13
+ compute_service = double
14
+ allow(compute_service).to receive(:authorization=)
15
+ allow(compute_service).to receive(:request_options).and_return(Google::Apis::RequestOptions.new)
16
+ allow(Google::Apis::ComputeV1::ComputeService).to receive(:new).and_return(compute_service)
17
+
18
+ oslogin_service = double
19
+ allow(oslogin_service).to receive(:authorization=)
20
+ allow(Google::Apis::OsloginV1::CloudOSLoginService).to receive(:new).and_return(oslogin_service)
21
+
22
+ client_options = double
23
+ allow(client_options).to receive(:application_name=)
24
+ allow(client_options).to receive(:application_version=)
25
+ allow(Google::Apis::ClientOptions).to receive(:default).and_return(client_options)
26
+ end
27
+
28
+ describe 'initialization validation' do
29
+ context 'when project is not provided' do
30
+ let(:options) { base_options.merge(gce_project: nil) }
31
+
32
+ it 'raises an error' do
33
+ expect do
34
+ described_class.new(options)
35
+ end.to raise_error('You must specify a gce_project for Google Compute Engine instances!')
36
+ end
37
+ end
38
+
39
+ context 'when project is provided' do
40
+ let(:options) { base_options }
41
+
42
+ it 'does not raise an error' do
43
+ mock_google_services
44
+
45
+ expect do
46
+ described_class.new(options)
47
+ end.not_to raise_error
48
+ end
49
+ end
50
+ end
51
+
52
+ describe 'port validation' do
53
+ context 'when port format is invalid' do
54
+ let(:options) { base_options.merge(gce_ports: ['22']) }
55
+
56
+ it 'raises an error' do
57
+ expect do
58
+ described_class.new(options)
59
+ end.to raise_error(%r{Invalid format for port 22. Should be 'port/proto'})
60
+ end
61
+ end
62
+
63
+ context 'when port protocol is invalid' do
64
+ let(:options) { base_options.merge(gce_ports: ['22/invalid']) }
65
+
66
+ it 'raises an error' do
67
+ expect do
68
+ described_class.new(options)
69
+ end.to raise_error(%r{Invalid value 'invalid' for protocol in '22/invalid'. Must be one of})
70
+ end
71
+ end
72
+
73
+ context 'when port format is valid' do
74
+ let(:options) { base_options.merge(gce_ports: ['22/tcp', '80/tcp']) }
75
+
76
+ it 'does not raise an error' do
77
+ mock_google_services
78
+
79
+ expect do
80
+ described_class.new(options)
81
+ end.not_to raise_error
82
+ end
83
+ end
84
+
85
+ context 'when all valid protocols are used' do
86
+ let(:valid_protocols) { described_class::VALID_PROTOS }
87
+
88
+ before do
89
+ mock_google_services
90
+ end
91
+
92
+ it 'accepts all valid protocols' do
93
+ valid_protocols.each do |proto|
94
+ options = base_options.merge(gce_ports: ["80/#{proto}"])
95
+ expect { described_class.new(options) }.not_to raise_error
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Beaker::GoogleComputeHelper do
6
+ context 'when testing constants and defaults' do
7
+ let(:base_options) do
8
+ {
9
+ gce_project: 'test-project',
10
+ gce_zone: 'us-central1-a',
11
+ gce_network: 'default',
12
+ logger: instance_double(Beaker::Logger, debug: nil, info: nil, warn: nil, error: nil),
13
+ }
14
+ end
15
+
16
+ describe 'Google Compute Helper constants' do
17
+ it 'defines AUTH_URL constant' do
18
+ expect(described_class::AUTH_URL).to eq('https://www.googleapis.com/auth/compute')
19
+ end
20
+
21
+ it 'defines API_VERSION constant' do
22
+ expect(described_class::API_VERSION).to eq('v1')
23
+ end
24
+
25
+ it 'defines BASE_URL constant' do
26
+ expect(described_class::BASE_URL).to eq('https://www.googleapis.com/compute/v1/projects/')
27
+ end
28
+
29
+ it 'defines DEFAULT_ZONE_NAME constant' do
30
+ expect(described_class::DEFAULT_ZONE_NAME).to eq('us-central1-a')
31
+ end
32
+
33
+ it 'defines DEFAULT_MACHINE_TYPE constant' do
34
+ expect(described_class::DEFAULT_MACHINE_TYPE).to eq('e2-standard-4')
35
+ end
36
+
37
+ it 'defines DEFAULT_NETWORK_NAME constant' do
38
+ expect(described_class::DEFAULT_NETWORK_NAME).to eq('default')
39
+ end
40
+
41
+ it 'defines SLEEPWAIT constant' do
42
+ expect(described_class::SLEEPWAIT).to eq(5)
43
+ end
44
+
45
+ it 'defines valid protocols' do
46
+ expect(described_class::VALID_PROTOS).to eq(%w[tcp udp icmp esp ah ipip sctp])
47
+ .and be_frozen
48
+ end
49
+
50
+ it 'defines GCP auth scopes structure' do
51
+ auth_scopes = described_class::GCP_AUTH_SCOPE
52
+
53
+ expect(auth_scopes).to be_an(Array).and be_frozen.and have_attributes(length: 2)
54
+ .and include('https://www.googleapis.com/auth/compute')
55
+ .and include('https://www.googleapis.com/auth/cloud-platform.read-only')
56
+ end
57
+ end
58
+
59
+ describe 'Google Compute constants' do
60
+ it 'defines Windows image projects' do
61
+ expect(Beaker::GoogleCompute::WINDOWS_IMAGE_PROJECT).to eq(%w[windows-cloud windows-sql-cloud])
62
+ .and be_frozen
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Beaker::GoogleComputeHelper do
6
+ context 'when testing edge cases and integration' do
7
+ describe 'version loading' do
8
+ it 'loads version as a module constant' do
9
+ expect(BeakerGoogle::VERSION).to be_a(String)
10
+ .and match(/^\d+\.\d+\.\d+/)
11
+ end
12
+
13
+ it 'is accessible through the module' do
14
+ expect { BeakerGoogle::VERSION }.not_to raise_error
15
+ end
16
+ end
17
+
18
+ describe 'module structure' do
19
+ it 'defines the BeakerGoogle module' do
20
+ expect(BeakerGoogle).to be_a(Module)
21
+ end
22
+
23
+ it 'has const_get method' do
24
+ expect(BeakerGoogle).to respond_to(:const_get)
25
+ end
26
+
27
+ it 'has version information' do
28
+ expect(BeakerGoogle.const_get(:VERSION)).to be_a(String)
29
+ end
30
+ end
31
+
32
+ describe 'string processing edge cases' do
33
+ it 'handles nil values safely' do
34
+ expect(safe_string_process(nil)).to be_nil
35
+ end
36
+
37
+ it 'handles empty strings' do
38
+ expect(safe_string_process('')).to eq('')
39
+ end
40
+
41
+ it 'handles whitespace' do
42
+ expect(safe_string_process(' test ')).to eq('test')
43
+ end
44
+
45
+ it 'handles special characters' do
46
+ expect(safe_string_process('test@example.com')).to eq('test@example.com')
47
+ end
48
+
49
+ private
50
+
51
+ def safe_string_process(str)
52
+ return nil if str.nil?
53
+ return str if str.empty?
54
+
55
+ str.strip
56
+ end
57
+ end
58
+
59
+ describe 'environment variable handling' do
60
+ it 'handles missing environment variables gracefully' do
61
+ allow(ENV).to receive(:[]).with('NONEXISTENT_VAR').and_return(nil)
62
+ expect(ENV.fetch('NONEXISTENT_VAR', nil)).to be_nil
63
+ end
64
+
65
+ it 'handles empty environment variables' do
66
+ allow(ENV).to receive(:[]).with('EMPTY_VAR').and_return('')
67
+ allow(ENV).to receive(:fetch).with('EMPTY_VAR', nil).and_return('')
68
+ expect(ENV.fetch('EMPTY_VAR', nil)).to eq('')
69
+ end
70
+
71
+ it 'handles environment variables with spaces' do
72
+ allow(ENV).to receive(:[]).with('SPACE_VAR').and_return(' value ')
73
+ allow(ENV).to receive(:fetch).with('SPACE_VAR', nil).and_return(' value ')
74
+ expect(ENV.fetch('SPACE_VAR', nil)).to eq(' value ')
75
+ end
76
+ end
77
+
78
+ describe 'GoogleComputeHelper options processing' do
79
+ let(:logger) { instance_double(Beaker::Logger) }
80
+ let(:base_options) { { logger: logger, gce_project: 'test-project' } }
81
+
82
+ def mock_google_services
83
+ # Mock the authentication and service initialization
84
+ allow(Google::Auth).to receive(:get_application_default).and_return(double)
85
+
86
+ compute_service = double
87
+ allow(compute_service).to receive(:authorization=)
88
+ allow(compute_service).to receive(:request_options).and_return(Google::Apis::RequestOptions.new)
89
+ allow(Google::Apis::ComputeV1::ComputeService).to receive(:new).and_return(compute_service)
90
+
91
+ oslogin_service = double
92
+ allow(oslogin_service).to receive(:authorization=)
93
+ allow(Google::Apis::OsloginV1::CloudOSLoginService).to receive(:new).and_return(oslogin_service)
94
+
95
+ client_options = double
96
+ allow(client_options).to receive(:application_name=)
97
+ allow(client_options).to receive(:application_version=)
98
+ allow(Google::Apis::ClientOptions).to receive(:default).and_return(client_options)
99
+ end
100
+
101
+ before do
102
+ mock_google_services
103
+ allow(ENV).to receive(:fetch).and_call_original
104
+ allow(ENV).to receive(:fetch).with('BEAKER_gce_ports', '').and_return('')
105
+ end
106
+
107
+ it 'handles nil gce_ports configuration' do
108
+ options = base_options.merge(gce_ports: nil)
109
+
110
+ expect do
111
+ described_class.new(options)
112
+ end.not_to raise_error
113
+ end
114
+
115
+ it 'handles empty gce_ports configuration' do
116
+ options = base_options.merge(gce_ports: [])
117
+
118
+ mock_google_services
119
+
120
+ expect do
121
+ described_class.new(options)
122
+ end.not_to raise_error
123
+ end
124
+
125
+ it 'handles gce_ports with multiple valid entries' do
126
+ options = base_options.merge(gce_ports: ['22/tcp', '80/tcp', '443/tcp'])
127
+
128
+ mock_google_services
129
+
130
+ expect do
131
+ described_class.new(options)
132
+ end.not_to raise_error
133
+ end
134
+
135
+ def ports_for(env_value, configured)
136
+ allow(ENV).to receive(:fetch).with('BEAKER_gce_ports', '').and_return(env_value)
137
+ described_class.new(base_options.merge(gce_ports: configured)).options[:gce_ports]
138
+ end
139
+
140
+ it 'lets BEAKER_gce_ports replace configured gce_ports' do
141
+ expect(ports_for('8140/tcp, 443/tcp', ['80/tcp'])).to eq(['8140/tcp', '443/tcp'])
142
+ end
143
+
144
+ it 'uses BEAKER_gce_ports when gce_ports is not configured' do
145
+ expect(ports_for('8140/tcp', nil)).to eq(['8140/tcp'])
146
+ end
147
+
148
+ it 'keeps configured gce_ports when BEAKER_gce_ports is empty' do
149
+ expect(ports_for('', ['80/tcp'])).to eq(['80/tcp'])
150
+ end
151
+
152
+ it 'keeps configured gce_ports when BEAKER_gce_ports is only whitespace' do
153
+ expect(ports_for(' ', ['80/tcp'])).to eq(['80/tcp'])
154
+ end
155
+
156
+ it 'defaults to no ports when neither BEAKER_gce_ports nor gce_ports is set' do
157
+ expect(ports_for('', nil)).to eq([])
158
+ end
159
+
160
+ it 'validates ports taken from BEAKER_gce_ports' do
161
+ expect { ports_for('8140/bogus', ['80/tcp']) }.to raise_error(/Invalid value 'bogus' for protocol/)
162
+ end
163
+
164
+ it 'splits a comma-separated gce_ports string from the hosts file' do
165
+ expect(ports_for('', '80/tcp, 443/tcp')).to eq(['80/tcp', '443/tcp'])
166
+ end
167
+
168
+ it 'splits comma-separated entries inside a gce_ports list' do
169
+ expect(ports_for('', ['80/tcp,443/tcp', ' 8140/tcp '])).to eq(['80/tcp', '443/tcp', '8140/tcp'])
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Beaker::GoogleComputeHelper do
6
+ context 'when handling errors' do
7
+ describe 'GoogleComputeError' do
8
+ it 'can be raised with a custom message' do
9
+ expect do
10
+ raise Beaker::GoogleComputeHelper::GoogleComputeError, 'Custom error message'
11
+ end.to raise_error(Beaker::GoogleComputeHelper::GoogleComputeError, 'Custom error message')
12
+ end
13
+
14
+ it 'inherits from StandardError' do
15
+ expect(Beaker::GoogleComputeHelper::GoogleComputeError).to be < StandardError
16
+ end
17
+ end
18
+
19
+ describe 'GoogleComputeHelper configuration errors' do
20
+ let(:logger) { instance_double(Beaker::Logger) }
21
+ let(:base_options) { { logger: logger } }
22
+
23
+ before do
24
+ # Mock the authentication and service initialization
25
+ allow(Google::Auth).to receive(:get_application_default).and_return(double)
26
+
27
+ compute_service = double
28
+ allow(compute_service).to receive(:authorization=)
29
+ allow(compute_service).to receive(:request_options).and_return(Google::Apis::RequestOptions.new)
30
+ allow(Google::Apis::ComputeV1::ComputeService).to receive(:new).and_return(compute_service)
31
+
32
+ oslogin_service = double
33
+ allow(oslogin_service).to receive(:authorization=)
34
+ allow(Google::Apis::OsloginV1::CloudOSLoginService).to receive(:new).and_return(oslogin_service)
35
+
36
+ client_options = double
37
+ allow(client_options).to receive(:application_name=)
38
+ allow(client_options).to receive(:application_version=)
39
+ allow(Google::Apis::ClientOptions).to receive(:default).and_return(client_options)
40
+ end
41
+
42
+ it 'raises error when gce_project is missing' do
43
+ expect do
44
+ described_class.new(base_options.merge(gce_project: nil))
45
+ end.to raise_error('You must specify a gce_project for Google Compute Engine instances!')
46
+ end
47
+
48
+ it 'passes validation when gce_project is provided' do
49
+ options = base_options.merge(gce_project: 'test-project', gce_ports: [])
50
+
51
+ expect do
52
+ described_class.new(options)
53
+ end.not_to raise_error
54
+ end
55
+ end
56
+
57
+ describe 'GoogleComputeHelper port validation errors' do
58
+ let(:logger) { instance_double(Beaker::Logger) }
59
+ let(:base_options) { { logger: logger, gce_project: 'test-project' } }
60
+
61
+ before do
62
+ # Mock the authentication and service initialization
63
+ allow(Google::Auth).to receive(:get_application_default).and_return(double)
64
+
65
+ compute_service = double
66
+ allow(compute_service).to receive(:authorization=)
67
+ allow(compute_service).to receive(:request_options).and_return(Google::Apis::RequestOptions.new)
68
+ allow(Google::Apis::ComputeV1::ComputeService).to receive(:new).and_return(compute_service)
69
+
70
+ oslogin_service = double
71
+ allow(oslogin_service).to receive(:authorization=)
72
+ allow(Google::Apis::OsloginV1::CloudOSLoginService).to receive(:new).and_return(oslogin_service)
73
+
74
+ client_options = double
75
+ allow(client_options).to receive(:application_name=)
76
+ allow(client_options).to receive(:application_version=)
77
+ allow(Google::Apis::ClientOptions).to receive(:default).and_return(client_options)
78
+ end
79
+
80
+ it 'raises error for invalid protocol' do
81
+ options = base_options.merge(gce_ports: ['80/invalid'])
82
+
83
+ expect do
84
+ described_class.new(options)
85
+ end.to raise_error(%r{Invalid value 'invalid' for protocol in '80/invalid'})
86
+ end
87
+
88
+ it 'raises error for invalid port format' do
89
+ options = base_options.merge(gce_ports: ['invalid-port'])
90
+
91
+ expect do
92
+ described_class.new(options)
93
+ end.to raise_error(/Invalid format for port invalid-port/)
94
+ end
95
+
96
+ it 'accepts valid protocols' do
97
+ options = base_options.merge(gce_ports: ['80/tcp', '443/udp'])
98
+
99
+ expect do
100
+ described_class.new(options)
101
+ end.not_to raise_error
102
+ end
103
+ end
104
+ end
105
+ end