From e78745e108f17691a91ecfae92c0a6d488b63d3a Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 3 Jun 2024 15:22:07 -0500 Subject: [PATCH 1/2] Cleanup for rubocop --- .github/workflows/ci.yml | 23 +- .gitignore | 1 + .rubocop.yml | 520 +------- .simplecov | 2 + Gemfile | 2 + Rakefile | 22 +- beaker-google.gemspec | 33 +- bin/beaker-google | 1 + lib/beaker-google/version.rb | 2 + lib/beaker/hypervisor/google.rb | 6 +- lib/beaker/hypervisor/google_compute.rb | 72 +- .../hypervisor/google_compute_helper.rb | 1170 +++++++++-------- 12 files changed, 699 insertions(+), 1155 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a5e52b..aacb07e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,18 +7,17 @@ env: BUNDLE_WITHOUT: release jobs: - # rubocop is currently not included in Gemfile - # rubocop: - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - name: Install Ruby 3.1 - # uses: ruby/setup-ruby@v1 - # with: - # ruby-version: "3.1" - # bundler-cache: true - # - name: Run rubocop - # run: bundle exec rake rubocop + rubocop: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Ruby 3.3 + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + bundler-cache: true + - name: Run rubocop + run: bundle exec rake rubocop test: runs-on: ubuntu-latest strategy: diff --git a/.gitignore b/.gitignore index d3deae5..b4767c7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ coverage .bundle .vendor _vendor +/vendor/ tmp/ doc # JetBrains IDEA diff --git a/.rubocop.yml b/.rubocop.yml index eb66290..c46d772 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,519 +1,3 @@ --- -require: -- rubocop-performance -- rubocop-rspec -AllCops: - DisplayCopNames: true - TargetRubyVersion: '2.7' - Include: - - "**/*.rb" - Exclude: - - bin/* - - ".vendor/**/*" - - "**/Gemfile" - - "**/Rakefile" - - pkg/**/* - - spec/fixtures/**/* - - vendor/**/* - - "**/Puppetfile" - - "**/Vagrantfile" - - "**/Guardfile" -Layout/LineLength: - Description: People have wide screens, use them. - Max: 200 -RSpec/BeforeAfterAll: - Description: Beware of using after(:all) as it may cause state to leak between tests. - A necessary evil in acceptance testing. - Exclude: - - spec/acceptance/**/*.rb -RSpec/HookArgument: - Description: Prefer explicit :each argument, matching existing module's style - EnforcedStyle: each -RSpec/DescribeSymbol: - Exclude: - - spec/unit/facter/**/*.rb -Style/BlockDelimiters: - Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to - be consistent then. - EnforcedStyle: braces_for_chaining -Style/ClassAndModuleChildren: - Description: Compact style reduces the required amount of indentation. - EnforcedStyle: compact -Style/EmptyElse: - Description: Enforce against empty else clauses, but allow `nil` for clarity. - EnforcedStyle: empty -Style/FormatString: - Description: Following the main puppet project's style, prefer the % format format. - EnforcedStyle: percent -Style/FormatStringToken: - Description: Following the main puppet project's style, prefer the simpler template - tokens over annotated ones. - EnforcedStyle: template -Style/Lambda: - Description: Prefer the keyword for easier discoverability. - EnforcedStyle: literal -Style/RegexpLiteral: - Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 - EnforcedStyle: percent_r -Style/TernaryParentheses: - Description: Checks for use of parentheses around ternary conditions. Enforce parentheses - on complex expressions for better readability, but seriously consider breaking - it up. - EnforcedStyle: require_parentheses_when_complex -Style/TrailingCommaInArguments: - Description: Prefer always trailing comma on multiline argument lists. This makes - diffs, and re-ordering nicer. - EnforcedStyleForMultiline: comma -Style/TrailingCommaInArrayLiteral: - Description: Prefer always trailing comma on multiline literals. This makes diffs, - and re-ordering nicer. - EnforcedStyleForMultiline: comma -Style/SymbolArray: - Description: Using percent style obscures symbolic intent of array's contents. - EnforcedStyle: brackets -RSpec/MessageSpies: - EnforcedStyle: receive -Style/Documentation: - Exclude: - - lib/puppet/parser/functions/**/* - - spec/**/* -Style/WordArray: - EnforcedStyle: brackets -Performance/AncestorsInclude: - Enabled: true -Performance/BigDecimalWithNumericArgument: - Enabled: true -Performance/BlockGivenWithExplicitBlock: - Enabled: true -Performance/CaseWhenSplat: - Enabled: true -Performance/ConstantRegexp: - Enabled: true -Performance/MethodObjectAsBlock: - Enabled: true -Performance/RedundantSortBlock: - Enabled: true -Performance/RedundantStringChars: - Enabled: true -Performance/ReverseFirst: - Enabled: true -Performance/SortReverse: - Enabled: true -Performance/Squeeze: - Enabled: true -Performance/StringInclude: - Enabled: true -Performance/Sum: - Enabled: true -Style/CollectionMethods: - Enabled: true -Style/MethodCalledOnDoEndBlock: - Enabled: true -Style/StringMethods: - Enabled: true -Bundler/InsecureProtocolSource: - Enabled: false -Gemspec/DuplicatedAssignment: - Enabled: false -Gemspec/OrderedDependencies: - Enabled: false -Gemspec/RequiredRubyVersion: - Enabled: false -Gemspec/RubyVersionGlobalsUsage: - Enabled: false -Layout/ArgumentAlignment: - Enabled: false -Layout/BeginEndAlignment: - Enabled: false -Layout/ClosingHeredocIndentation: - Enabled: false -Layout/EmptyComment: - Enabled: false -Layout/EmptyLineAfterGuardClause: - Enabled: false -Layout/EmptyLinesAroundArguments: - Enabled: false -Layout/EmptyLinesAroundAttributeAccessor: - Enabled: false -Layout/EndOfLine: - Enabled: false -Layout/FirstArgumentIndentation: - Enabled: false -Layout/HashAlignment: - Enabled: false -Layout/HeredocIndentation: - Enabled: false -Layout/LeadingEmptyLines: - Enabled: false -Layout/SpaceAroundMethodCallOperator: - Enabled: false -Layout/SpaceInsideArrayLiteralBrackets: - Enabled: false -Layout/SpaceInsideReferenceBrackets: - Enabled: false -Lint/BigDecimalNew: - Enabled: false -Lint/BooleanSymbol: - Enabled: false -Lint/ConstantDefinitionInBlock: - Enabled: false -Lint/DeprecatedOpenSSLConstant: - Enabled: false -Lint/DisjunctiveAssignmentInConstructor: - Enabled: false -Lint/DuplicateElsifCondition: - Enabled: false -Lint/DuplicateRequire: - Enabled: false -Lint/DuplicateRescueException: - Enabled: false -Lint/EmptyConditionalBody: - Enabled: false -Lint/EmptyFile: - Enabled: false -Lint/ErbNewArguments: - Enabled: false -Lint/FloatComparison: - Enabled: false -Lint/HashCompareByIdentity: - Enabled: false -Lint/IdentityComparison: - Enabled: false -Lint/InterpolationCheck: - Enabled: false -Lint/MissingCopEnableDirective: - Enabled: false -Lint/MixedRegexpCaptureTypes: - Enabled: false -Lint/NestedPercentLiteral: - Enabled: false -Lint/NonDeterministicRequireOrder: - Enabled: false -Lint/OrderedMagicComments: - Enabled: false -Lint/OutOfRangeRegexpRef: - Enabled: false -Lint/RaiseException: - Enabled: false -Lint/RedundantCopEnableDirective: - Enabled: false -Lint/RedundantRequireStatement: - Enabled: false -Lint/RedundantSafeNavigation: - Enabled: false -Lint/RedundantWithIndex: - Enabled: false -Lint/RedundantWithObject: - Enabled: false -Lint/RegexpAsCondition: - Enabled: false -Lint/ReturnInVoidContext: - Enabled: false -Lint/SafeNavigationConsistency: - Enabled: false -Lint/SafeNavigationWithEmpty: - Enabled: false -Lint/SelfAssignment: - Enabled: false -Lint/SendWithMixinArgument: - Enabled: false -Lint/ShadowedArgument: - Enabled: false -Lint/StructNewOverride: - Enabled: false -Lint/ToJSON: - Enabled: false -Lint/TopLevelReturnWithArgument: - Enabled: false -Lint/TrailingCommaInAttributeDeclaration: - Enabled: false -Lint/UnreachableLoop: - Enabled: false -Lint/UriEscapeUnescape: - Enabled: false -Lint/UriRegexp: - Enabled: false -Lint/UselessMethodDefinition: - Enabled: false -Lint/UselessTimes: - Enabled: false -Metrics/AbcSize: - Enabled: false -Metrics/BlockLength: - Enabled: false -Metrics/BlockNesting: - Enabled: false -Metrics/ClassLength: - Enabled: false -Metrics/CyclomaticComplexity: - Enabled: false -Metrics/MethodLength: - Enabled: false -Metrics/ModuleLength: - Enabled: false -Metrics/ParameterLists: - Enabled: false -Metrics/PerceivedComplexity: - Enabled: false -Migration/DepartmentName: - Enabled: false -Naming/AccessorMethodName: - Enabled: false -Naming/BlockParameterName: - Enabled: false -Naming/HeredocDelimiterCase: - Enabled: false -Naming/HeredocDelimiterNaming: - Enabled: false -Naming/MemoizedInstanceVariableName: - Enabled: false -Naming/MethodParameterName: - Enabled: false -Naming/RescuedExceptionsVariableName: - Enabled: false -Naming/VariableNumber: - Enabled: false -Performance/BindCall: - Enabled: false -Performance/DeletePrefix: - Enabled: false -Performance/DeleteSuffix: - Enabled: false -Performance/InefficientHashSearch: - Enabled: false -Performance/UnfreezeString: - Enabled: false -Performance/UriDefaultParser: - Enabled: false -RSpec/Be: - Enabled: false -RSpec/Capybara/CurrentPathExpectation: - Enabled: false -RSpec/Capybara/FeatureMethods: - Enabled: false -RSpec/Capybara/VisibilityMatcher: - Enabled: false -RSpec/ContextMethod: - Enabled: false -RSpec/ContextWording: - Enabled: false -RSpec/DescribeClass: - Enabled: false -RSpec/EmptyHook: - Enabled: false -RSpec/EmptyLineAfterExample: - Enabled: false -RSpec/EmptyLineAfterExampleGroup: - Enabled: false -RSpec/EmptyLineAfterHook: - Enabled: false -RSpec/ExampleLength: - Enabled: false -RSpec/ExampleWithoutDescription: - Enabled: false -RSpec/ExpectChange: - Enabled: false -RSpec/ExpectInHook: - Enabled: false -RSpec/FactoryBot/AttributeDefinedStatically: - Enabled: false -RSpec/FactoryBot/CreateList: - Enabled: false -RSpec/FactoryBot/FactoryClassName: - Enabled: false -RSpec/HooksBeforeExamples: - Enabled: false -RSpec/ImplicitBlockExpectation: - Enabled: false -RSpec/ImplicitSubject: - Enabled: false -RSpec/LeakyConstantDeclaration: - Enabled: false -RSpec/LetBeforeExamples: - Enabled: false -RSpec/MissingExampleGroupArgument: - Enabled: false -RSpec/MultipleExpectations: - Enabled: false -RSpec/MultipleMemoizedHelpers: - Enabled: false -RSpec/MultipleSubjects: - Enabled: false -RSpec/NestedGroups: - Enabled: false -RSpec/PredicateMatcher: - Enabled: false -RSpec/ReceiveCounts: - Enabled: false -RSpec/ReceiveNever: - Enabled: false -RSpec/RepeatedExampleGroupBody: - Enabled: false -RSpec/RepeatedExampleGroupDescription: - Enabled: false -RSpec/RepeatedIncludeExample: - Enabled: false -RSpec/ReturnFromStub: - Enabled: false -RSpec/SharedExamples: - Enabled: false -RSpec/StubbedMock: - Enabled: false -RSpec/UnspecifiedException: - Enabled: false -RSpec/VariableDefinition: - Enabled: false -RSpec/VoidExpect: - Enabled: false -RSpec/Yield: - Enabled: false -Security/Open: - Enabled: false -Style/AccessModifierDeclarations: - Enabled: false -Style/AccessorGrouping: - Enabled: false -Style/AsciiComments: - Enabled: false -Style/BisectedAttrAccessor: - Enabled: false -Style/CaseLikeIf: - Enabled: false -Style/ClassEqualityComparison: - Enabled: false -Style/ColonMethodDefinition: - Enabled: false -Style/CombinableLoops: - Enabled: false -Style/CommentedKeyword: - Enabled: false -Style/Dir: - Enabled: false -Style/DoubleCopDisableDirective: - Enabled: false -Style/EmptyBlockParameter: - Enabled: false -Style/EmptyLambdaParameter: - Enabled: false -Style/Encoding: - Enabled: false -Style/EvalWithLocation: - Enabled: false -Style/ExpandPathArguments: - Enabled: false -Style/ExplicitBlockArgument: - Enabled: false -Style/ExponentialNotation: - Enabled: false -Style/FloatDivision: - Enabled: false -Style/FrozenStringLiteralComment: - Enabled: false -Style/GlobalStdStream: - Enabled: false -Style/HashAsLastArrayItem: - Enabled: false -Style/HashLikeCase: - Enabled: false -Style/HashTransformKeys: - Enabled: false -Style/HashTransformValues: - Enabled: false -Style/IfUnlessModifier: - Enabled: false -Style/KeywordParametersOrder: - Enabled: false -Style/MinMax: - Enabled: false -Style/MixinUsage: - Enabled: false -Style/MultilineWhenThen: - Enabled: false -Style/NegatedUnless: - Enabled: false -Style/NumericPredicate: - Enabled: false -Style/OptionalBooleanParameter: - Enabled: false -Style/OrAssignment: - Enabled: false -Style/RandomWithOffset: - Enabled: false -Style/RedundantAssignment: - Enabled: false -Style/RedundantCondition: - Enabled: false -Style/RedundantConditional: - Enabled: false -Style/RedundantFetchBlock: - Enabled: false -Style/RedundantFileExtensionInRequire: - Enabled: false -Style/RedundantRegexpCharacterClass: - Enabled: false -Style/RedundantRegexpEscape: - Enabled: false -Style/RedundantSelfAssignment: - Enabled: false -Style/RedundantSort: - Enabled: false -Style/RescueStandardError: - Enabled: false -Style/SingleArgumentDig: - Enabled: false -Style/SlicingWithRange: - Enabled: false -Style/SoleNestedConditional: - Enabled: false -Style/StderrPuts: - Enabled: false -Style/StringConcatenation: - Enabled: false -Style/Strip: - Enabled: false -Style/SymbolProc: - Enabled: false -Style/TrailingBodyOnClass: - Enabled: false -Style/TrailingBodyOnMethodDefinition: - Enabled: false -Style/TrailingBodyOnModule: - Enabled: false -Style/TrailingCommaInHashLiteral: - Enabled: false -Style/TrailingMethodEndStatement: - Enabled: false -Style/UnpackFirst: - Enabled: false -Lint/DuplicateBranch: - Enabled: false -Lint/DuplicateRegexpCharacterClassElement: - Enabled: false -Lint/EmptyBlock: - Enabled: false -Lint/EmptyClass: - Enabled: false -Lint/NoReturnInBeginEndBlocks: - Enabled: false -Lint/ToEnumArguments: - Enabled: false -Lint/UnexpectedBlockArity: - Enabled: false -Lint/UnmodifiedReduceAccumulator: - Enabled: false -Performance/CollectionLiteralInLoop: - Enabled: false -Style/ArgumentsForwarding: - Enabled: false -Style/CollectionCompact: - Enabled: false -Style/DocumentDynamicEvalDefinition: - Enabled: false -Style/NegatedIfElseCondition: - Enabled: false -Style/NilLambda: - Enabled: false -Style/RedundantArgument: - Enabled: false -Style/SwapValues: - Enabled: false +inherit_gem: + voxpupuli-rubocop: rubocop.yml diff --git a/.simplecov b/.simplecov index fb80f65..734e00e 100644 --- a/.simplecov +++ b/.simplecov @@ -1,3 +1,5 @@ +# frozen_string_literal: true + SimpleCov.configure do add_filter 'spec/' add_filter 'vendor/' diff --git a/Gemfile b/Gemfile index 1c6cfc9..cfd4a35 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source ENV.fetch('GEM_SOURCE', 'https://rubygems.org') gemspec diff --git a/Rakefile b/Rakefile index 167a048..9ab1e8b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,13 @@ +# frozen_string_literal: true + require 'rspec/core/rake_task' +begin + require 'voxpupuli/rubocop/rake' +rescue LoadError + # the voxpupuli-rubocop gem is optional +end + ########################################################### # # Documentation Tasks @@ -13,12 +21,12 @@ def running?(cmdline) found = ps.lines.grep(/#{Regexp.quote(cmdline)}/) raise StandardError, "Found multiple YARD Servers. Don't know what to do." if found.length > 1 - yes = found.empty? ? false : true + yes = !found.empty? [yes, found.first] end def pid_from(output) - output.squeeze(' ').strip.split(' ')[1] + output.squeeze(' ').strip.split[1] end desc 'Start the documentation server in the foreground' @@ -44,7 +52,7 @@ namespace :docs do Dir.chdir(__dir__) output = `bundle exec yard doc` puts output - raise 'Errors/Warnings during yard documentation generation' if output =~ /\[warn\]|\[error\]/ + raise 'Errors/Warnings during yard documentation generation' if /\[warn\]|\[error\]/.match?(output) Dir.chdir(original_dir) end @@ -63,6 +71,7 @@ namespace :docs do end end + desc 'Alias for `background`' task(:bg) { Rake::Task['docs:background'].invoke } desc 'Check the status of the documentation server' @@ -84,10 +93,10 @@ namespace :docs do puts "Found a YARD Server running with pid #{pid}" `kill #{pid}` puts 'Stopping...' - yes, output = running?(DOCS_DAEMON) + yes, = running?(DOCS_DAEMON) if yes `kill -9 #{pid}` - yes, output = running?(DOCS_DAEMON) + yes, = running?(DOCS_DAEMON) if yes puts 'Could not Stop Server!' else @@ -106,9 +115,10 @@ begin require 'rubygems' require 'github_changelog_generator/task' rescue LoadError + # the github_changelog_generator gem is optional else GitHubChangelogGenerator::RakeTask.new :changelog do |config| - config.exclude_labels = %w{duplicate question invalid wontfix wont-fix skip-changelog} + config.exclude_labels = %w[duplicate question invalid wontfix wont-fix skip-changelog] config.user = 'voxpupuli' config.project = 'beaker-google' gem_version = Gem::Specification.load("#{config.project}.gemspec").version diff --git a/beaker-google.gemspec b/beaker-google.gemspec index 556de64..4492a87 100644 --- a/beaker-google.gemspec +++ b/beaker-google.gemspec @@ -1,35 +1,36 @@ -# -*- encoding: utf-8 -*- -$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path('lib', __dir__) require 'beaker-google/version' Gem::Specification.new do |s| - s.name = "beaker-google" + s.name = 'beaker-google' s.version = BeakerGoogle::VERSION - s.authors = ["Puppet", "Voxpupuli"] - s.email = ["voxpupuli@groups.io"] - s.homepage = "https://github.com/voxpupuli/beaker-google" - s.summary = %q{Beaker DSL Extension Helpers!} - s.description = %q{Google Compute Engine support for the Beaker acceptance testing tool.} + s.authors = %w[Puppet Voxpupuli] + s.email = ['voxpupuli@groups.io'] + s.homepage = 'https://github.com/voxpupuli/beaker-google' + s.summary = 'Beaker DSL Extension Helpers!' + s.description = 'Google Compute Engine support for the Beaker acceptance testing tool.' s.license = 'Apache2' s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - s.require_paths = ["lib"] + s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } + s.require_paths = ['lib'] - s.required_ruby_version = Gem::Requirement.new('>= 2.4') + s.required_ruby_version = Gem::Requirement.new('>= 2.7') # Testing dependencies - s.add_development_dependency 'rspec', '~> 3.0' - s.add_development_dependency 'rspec-its' s.add_development_dependency 'fakefs', '~> 2.4' + s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'rake', '~> 13.0' + s.add_development_dependency 'rspec', '~> 3.0' + s.add_development_dependency 'rspec-its' s.add_development_dependency 'simplecov' - s.add_development_dependency 'pry', '~> 0.10' + s.add_development_dependency 'voxpupuli-rubocop', '~> 2.7.0' # Documentation dependencies - s.add_development_dependency 'yard' s.add_development_dependency 'thin' + s.add_development_dependency 'yard' # Run time dependencies s.add_runtime_dependency 'stringify-hash', '~> 0.0.0' diff --git a/bin/beaker-google b/bin/beaker-google index 4b4c6b2..3b54395 100755 --- a/bin/beaker-google +++ b/bin/beaker-google @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require 'rubygems' unless defined?(Gem) require 'beaker-google' diff --git a/lib/beaker-google/version.rb b/lib/beaker-google/version.rb index 168c836..7d8a7c4 100644 --- a/lib/beaker-google/version.rb +++ b/lib/beaker-google/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module BeakerGoogle VERSION = '1.1.0' end diff --git a/lib/beaker/hypervisor/google.rb b/lib/beaker/hypervisor/google.rb index a45aaab..4e19fc1 100644 --- a/lib/beaker/hypervisor/google.rb +++ b/lib/beaker/hypervisor/google.rb @@ -1,4 +1,8 @@ +# frozen_string_literal: true + require 'beaker/hypervisor/google_compute' -class Beaker::Google < Beaker::GoogleCompute +module Beaker + class Google < Beaker::GoogleCompute + end end diff --git a/lib/beaker/hypervisor/google_compute.rb b/lib/beaker/hypervisor/google_compute.rb index a1bc589..f3c2737 100644 --- a/lib/beaker/hypervisor/google_compute.rb +++ b/lib/beaker/hypervisor/google_compute.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' module Beaker @@ -5,6 +7,8 @@ module Beaker class GoogleCompute < Beaker::Hypervisor SLEEPWAIT = 5 + WINDOWS_IMAGE_PROJECT = %w[windows-cloud windows-sql-cloud].freeze + # Do some reasonable sleuthing on the SSH public key for GCE ## @@ -15,9 +19,12 @@ class GoogleCompute < Beaker::Hypervisor # @raise [Error] if the private key can not be found def find_google_ssh_private_key private_keyfile = ENV.fetch('BEAKER_gce_ssh_public_key', - File.join(ENV.fetch('HOME', nil), '.ssh', 'google_compute_engine')) - private_keyfile = @options[:gce_ssh_private_key] if @options[:gce_ssh_private_key] && !File.exist?(private_keyfile) + File.join(Dir.home, '.ssh', 'google_compute_engine')) + if @options[:gce_ssh_private_key] && !File.exist?(private_keyfile) + private_keyfile = @options[:gce_ssh_private_key] + end raise("Could not find GCE Private SSH key at '#{keyfile}'") unless File.exist?(private_keyfile) + @options[:gce_ssh_private_key] = private_keyfile private_keyfile end @@ -33,6 +40,7 @@ def find_google_ssh_public_key public_keyfile = private_keyfile << '.pub' public_keyfile = @options[:gce_ssh_public_key] if @options[:gce_ssh_public_key] && !File.exist?(public_keyfile) raise("Could not find GCE Public SSH key at '#{keyfile}'") unless File.exist?(public_keyfile) + @options[:gce_ssh_public_key] = public_keyfile public_keyfile end @@ -89,25 +97,40 @@ def provision # set firewall to open pe ports network = @gce_helper.get_network - @external_firewall_name = test_group_identifier + '-external' + @external_firewall_name = "#{test_group_identifier}-external" # Always allow ssh from anywhere as it's needed for Beaker to run - @gce_helper.create_firewall(@external_firewall_name, network, allow: @options[:gce_ports] + ['22/tcp'], source_ranges: ['0.0.0.0/0'], target_tags: [test_group_identifier]) + @gce_helper.create_firewall( + @external_firewall_name, + network, + allow: @options[:gce_ports] + ['22/tcp'], + source_ranges: ['0.0.0.0/0'], + target_tags: [test_group_identifier], + ) @logger.debug("Created External Google Compute firewall #{@external_firewall_name}") # Create a firewall that opens everything between all the hosts in this test group - @internal_firewall_name = test_group_identifier + '-internal' + @internal_firewall_name = "#{test_group_identifier}-internal" internal_ports = ['1-65535/tcp', '1-65535/udp', '-1/icmp'] - @gce_helper.create_firewall(@internal_firewall_name, network, allow: internal_ports, source_tags: [test_group_identifier], target_tags: [test_group_identifier]) + @gce_helper.create_firewall( + @internal_firewall_name, + network, + allow: internal_ports, + source_tags: [test_group_identifier], + target_tags: [test_group_identifier], + ) @logger.debug("Created test group Google Compute firewall #{@internal_firewall_name}") @hosts.each do |host| machine_type_name = ENV.fetch('BEAKER_gce_machine_type', host['gce_machine_type']) raise "Must provide a machine type name in 'gce_machine_type'." if machine_type_name.nil? + # Get the GCE machine type object for this host machine_type = @gce_helper.get_machine_type(machine_type_name) - raise "Unable to find machine type named #{machine_type_name} in region #{@compute.default_zone}" if machine_type.nil? + if machine_type.nil? + raise "Unable to find machine type named #{machine_type_name} in region #{@compute.default_zone}" + end # Find the image to use to create the new VM. # Either `image` or `family` must be set in the configuration. Accepted formats @@ -122,7 +145,7 @@ def provision if host[:image] image_selector = host[:image] # Do we have a project name? - if %r{/}.match?(image_selector) + if image_selector.include?('/') image_project, image_name = image_selector.split('/')[0..1] else image_project = @gce_helper.options[:gce_project] @@ -133,7 +156,7 @@ def provision elsif host[:family] image_selector = host[:family] # Do we have a project name? - if %r{/}.match?(image_selector) + if image_selector.include?('/') image_project, family_name = image_selector.split('/') else image_project = @gce_helper.options[:gce_project] @@ -145,7 +168,7 @@ def provision raise('You must specify either :image or :family') end - unique_host_id = test_group_identifier + '-' + generate_host_name + unique_host_id = "#{test_group_identifier}-#{generate_host_name}" boot_size = host['volume_size'] || img.disk_size_gb @@ -160,8 +183,15 @@ def provision # add a new instance of the image operation = @gce_helper.create_instance(host['vmhostname'], img, machine_type, boot_size, host.name) unless operation.error.nil? - raise "Unable to create Google Compute Instance #{host.name}: [#{operation.error.errors[0].code}] #{operation.error.errors[0].message}" + raise "Unable to create Google Compute Instance #{ + host.name + }: [#{ + operation.error.errors[0].code + }] #{ + operation.error.errors[0].message + }" end + @logger.debug("Created Google Compute instance for #{host.name}: #{host['vmhostname']}") instance = @gce_helper.get_instance(host['vmhostname']) @@ -169,11 +199,11 @@ def provision @logger.debug("Added network tag #{test_group_identifier} to instance") # Make sure we have a non root/Adminsitor user to log in as - if host['user'] == "root" || host['user'] == "Administrator" || host['user'].empty? - initial_user = 'google_compute' - else - initial_user = host['user'] - end + initial_user = if host['user'] == 'root' || host['user'] == 'Administrator' || host['user'].empty? + 'google_compute' + else + host['user'] + end # add metadata to instance, if there is any to set # mdata = format_metadata @@ -181,18 +211,18 @@ def provision mdata = [ { key: 'ssh-keys', - value: "#{initial_user}:#{File.read(find_google_ssh_public_key).strip}" + value: "#{initial_user}:#{File.read(find_google_ssh_public_key).strip}", }, # For now oslogin needs to be disabled as there's no way to log in as root and it would # take too much work on beaker to add sudo support to everything { key: 'enable-oslogin', - value: 'FALSE' + value: 'FALSE', }, ] # Check for google's default windows images and turn on ssh if found - if image_project == "windows-cloud" || image_project == "windows-sql-cloud" + if WINDOWS_IMAGE_PROJECT.include?(image_project) # Turn on SSH on GCP's default windows images mdata << { key: 'enable-windows-ssh', @@ -200,12 +230,12 @@ def provision } mdata << { key: 'sysprep-specialize-script-cmd', - value: 'start /wait googet -noconfirm=true update && start /wait googet -noconfirm=true install google-compute-engine-ssh', + value: 'start /wait googet -noconfirm=true update && start /wait googet -noconfirm=true install google-compute-engine-ssh', # rubocop:disable Layout/LineLength } # Some versions of windows don't seem to add the OpenSSH directory to the path which prevents scp from working mdata << { key: 'sysprep-specialize-script-ps1', - value: '[Environment]::SetEnvironmentVariable( "PATH", "$ENV:PATH;C:\Program Files\OpenSSH", [EnvironmentVariableTarget]::Machine )', + value: '[Environment]::SetEnvironmentVariable( "PATH", "$ENV:PATH;C:\Program Files\OpenSSH", [EnvironmentVariableTarget]::Machine )', # rubocop:disable Layout/LineLength } end unless mdata.empty? diff --git a/lib/beaker/hypervisor/google_compute_helper.rb b/lib/beaker/hypervisor/google_compute_helper.rb index 0ebece0..df0cabf 100644 --- a/lib/beaker/hypervisor/google_compute_helper.rb +++ b/lib/beaker/hypervisor/google_compute_helper.rb @@ -10,636 +10,644 @@ # TODO: Figure out what to do about the timeout thing # TODO: Implement Google::Apis::RequestOptions on all calls (In lib/google/apis/options.rb) -# Beaker helper module for doing API level Google Compute Engine interaction. -class Beaker::GoogleComputeHelper - class GoogleComputeError < StandardError - end +module Beaker + # Beaker helper module for doing API level Google Compute Engine interaction. + class GoogleComputeHelper + class GoogleComputeError < StandardError + end - SLEEPWAIT = 5 + SLEEPWAIT = 5 - AUTH_URL = 'https://www.googleapis.com/auth/compute' - API_VERSION = 'v1' - BASE_URL = "https://www.googleapis.com/compute/#{API_VERSION}/projects/" - DEFAULT_ZONE_NAME = 'us-central1-a' - DEFAULT_MACHINE_TYPE = 'e2-standard-4' - DEFAULT_NETWORK_NAME = 'default' + AUTH_URL = 'https://www.googleapis.com/auth/compute' + API_VERSION = 'v1' + BASE_URL = "https://www.googleapis.com/compute/#{API_VERSION}/projects/" + DEFAULT_ZONE_NAME = 'us-central1-a' + DEFAULT_MACHINE_TYPE = 'e2-standard-4' + DEFAULT_NETWORK_NAME = 'default' - VALID_PROTOS = ['tcp', 'udp', 'icmp', 'esp', 'ah', 'ipip', 'sctp'] + VALID_PROTOS = %w[tcp udp icmp esp ah ipip sctp].freeze - GCP_AUTH_SCOPE = [ - Google::Apis::ComputeV1::AUTH_COMPUTE, - Google::Apis::OsloginV1::AUTH_CLOUD_PLATFORM_READ_ONLY, - ].freeze + GCP_AUTH_SCOPE = [ + ::Google::Apis::ComputeV1::AUTH_COMPUTE, + ::Google::Apis::OsloginV1::AUTH_CLOUD_PLATFORM_READ_ONLY, + ].freeze - ## - # Create a new instance of the Google Compute Engine helper object - # - def initialize(options) - @options = options - @logger = options[:logger] + ## + # Create a new instance of the Google Compute Engine helper object + # + def initialize(options) + @options = options + @logger = options[:logger] - set_client(Beaker::Version::STRING) + client(Beaker::Version::STRING) - # ::Google::Apis.logger = ::Logger.new(::STDERR) - # ::Google::Apis.logger.level = ::Logger::DEBUG - # ::Google::Apis.logger.level = ::Logger::WARN + # ::Google::Apis.logger = ::Logger.new(::STDERR) + # ::Google::Apis.logger.level = ::Logger::DEBUG + # ::Google::Apis.logger.level = ::Logger::WARN - @options[:gce_project] = ENV['BEAKER_gce_project'] if ENV['BEAKER_gce_project'] + @options[:gce_project] = ENV['BEAKER_gce_project'] if ENV['BEAKER_gce_project'] - @options[:gce_zone] = ENV.fetch('BEAKER_gce_zone', DEFAULT_ZONE_NAME) - @options[:gce_network] = ENV.fetch('BEAKER_gce_network', DEFAULT_NETWORK_NAME) - @options[:gce_subnetwork] = ENV.fetch('BEAKER_gce_subnetwork', nil) + @options[:gce_zone] = ENV.fetch('BEAKER_gce_zone', DEFAULT_ZONE_NAME) + @options[:gce_network] = ENV.fetch('BEAKER_gce_network', DEFAULT_NETWORK_NAME) + @options[:gce_subnetwork] = ENV.fetch('BEAKER_gce_subnetwork', nil) - @configure_ports = ENV.fetch('BEAKER_gce_ports', "").strip - # Split the ports based on commas, removing any empty values - @options[:gce_ports] = @configure_ports.split(/\s*?,\s*/).reject { |s| s.empty? } + @configure_ports = ENV.fetch('BEAKER_gce_ports', '').strip + # Split the ports based on commas, removing any empty values + @options[:gce_ports] = @configure_ports.split(/\s*?,\s*/).reject(&:empty?) - raise 'You must specify a gce_project for Google Compute Engine instances!' unless @options[:gce_project] + raise 'You must specify a gce_project for Google Compute Engine instances!' unless @options[:gce_project] - @options[:gce_ports].each do |port| - parts = port.split('/', 2) - raise "Invalid format for port #{port}. Should be 'port/proto'" unless parts.length == 2 - proto = parts[1] - raise "Invalid value '#{proto}' for protocol in '#{port}'. Must be one of '#{VALID_PROTOS.join("', '")}'" unless VALID_PROTOS.include? proto - end - authorizer = authenticate - @compute = ::Google::Apis::ComputeV1::ComputeService.new - @compute.authorization = authorizer + @options[:gce_ports].each do |port| + parts = port.split('/', 2) + raise "Invalid format for port #{port}. Should be 'port/proto'" unless parts.length == 2 - # Find the appropriate username to log into created instances - @cloudoslogin = Google::Apis::OsloginV1::CloudOSLoginService.new - @cloudoslogin.authorization = authorizer - end + proto = parts[1] + unless VALID_PROTOS.include? proto + raise "Invalid value '#{proto}' for protocol in '#{port}'. Must be one of '#{VALID_PROTOS.join("', '")}'" + end + end + authorizer = authenticate + @compute = ::Google::Apis::ComputeV1::ComputeService.new + @compute.authorization = authorizer - ## - # Determines the default Google Compute zone based upon options and - # defaults - # - # @return [String] The name of the zone - def default_zone - @options[:gce_zone] - end + # Find the appropriate username to log into created instances + @cloudoslogin = ::Google::Apis::OsloginV1::CloudOSLoginService.new + @cloudoslogin.authorization = authorizer + end - ## - # Get the region name from the provided zone. - # - # Assume that the region is the name of the zone without - # the final - and zone letter - # - # @return [String] The name of the region - def default_region - @options[:gce_zone].split('-')[0..1].join('-') - end + ## + # Determines the default Google Compute zone based upon options and + # defaults + # + # @return [String] The name of the zone + def default_zone + @options[:gce_zone] + end - ## - # Determines the default Google Compute network based upon defaults and - # options - # - # @return [String] The short name of the VPC network - def default_network - @options[:gce_network] - end + ## + # Get the region name from the provided zone. + # + # Assume that the region is the name of the zone without + # the final - and zone letter + # + # @return [String] The name of the region + def default_region + @options[:gce_zone].split('-')[0..1].join('-') + end - ## - # Find the username for ssh to use with this connection - # - # @return [String] The username for ssh - # - # @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys - # @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed. - # Additional data may be available in the error subclass and message. - def ssh_username - authorizer = @compute.authorization - # This is a bit of a hack based on what I found in a user (default application credentials) - # and a service account. There might be a better way of doing this. - case authorizer.class.to_s - when 'Google::Auth::UserRefreshCredentials' - authorizer.refresh! - userid = ::Google::Auth::IDTokens.verify_oidc(authorizer.id_token)['email'] - when 'Google::Auth::ServiceAccountCredentials' - userid = authorizer.issuer - else - raise 'Unknown type of credential' + ## + # Determines the default Google Compute network based upon defaults and + # options + # + # @return [String] The short name of the VPC network + def default_network + @options[:gce_network] end - userid = "users/#{userid}" unless userid.start_with? 'users/' - @cloudoslogin.get_user_login_profile(userid).posix_accounts[0].username - end - ## - # Infer the network that a given subnetwork is attached to - # - # @param [String] subnetwork_name The name of the subnetwork - # - # @return [String] The short name of the network - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def default_network_from_subnet(subnetwork_name) - subnetwork = @compute.get_subnetwork(@options[:gce_project], default_region, subnetwork_name) - m = %r{.*/networks/(?.*)\Z}.match subnetwork.network - nil if m.nil? - m['network_name'] - end + ## + # Find the username for ssh to use with this connection + # + # @return [String] The username for ssh + # + # @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys + # @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed. + # Additional data may be available in the error subclass and message. + def ssh_username + authorizer = @compute.authorization + # This is a bit of a hack based on what I found in a user (default application credentials) + # and a service account. There might be a better way of doing this. + case authorizer.class.to_s + when 'Google::Auth::UserRefreshCredentials' + authorizer.refresh! + userid = ::Google::Auth::IDTokens.verify_oidc(authorizer.id_token)['email'] + when 'Google::Auth::ServiceAccountCredentials' + userid = authorizer.issuer + else + raise 'Unknown type of credential' + end + userid = "users/#{userid}" unless userid.start_with? 'users/' + @cloudoslogin.get_user_login_profile(userid).posix_accounts[0].username + end - ## - # Determine the subnetwork to use for instances - # - # If the network is the 'default' network, get the 'default' subnetwork for the region. - # If no subnet is provided by the user, pick the first one out of the user-provided network - # - # @return [String] The name of the subnetwork that should be attached to the instances - def default_subnetwork - network_name = @options[:gce_network] - if network_name == 'default' - @options[:gce_subnetwork] ||= @compute.get_subnetwork(@options[:gce_project], default_region, 'default').name - elsif @options[:gce_subnetwork].nil? - # No subnet set, get the first subnet in our current region for the network - subnetwork = @compute.get_network(@options[:gce_project], network_name).subnetworks[0] - m = %r{.*/subnetworks/(?.*)\Z}.match subnetwork - raise "Unable to find a subnetwork in provided network #{network_name}" if m.nil? - - @options[:gce_subnetwork] = m['subnetwork_name'] + ## + # Infer the network that a given subnetwork is attached to + # + # @param [String] subnetwork_name The name of the subnetwork + # + # @return [String] The short name of the network + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def default_network_from_subnet(subnetwork_name) + subnetwork = @compute.get_subnetwork(@options[:gce_project], default_region, subnetwork_name) + m = %r{.*/networks/(?.*)\Z}.match subnetwork.network + nil if m.nil? + m['network_name'] end - @options[:gce_subnetwork] - end - ## - # Set the user-agent information for the application. - # - # @param version The version number of Beaker currently running - def set_client(version) - ::Google::Apis::ClientOptions.default.application_name = 'beaker-google' - ::Google::Apis::ClientOptions.default.application_version = version - end + ## + # Determine the subnetwork to use for instances + # + # If the network is the 'default' network, get the 'default' subnetwork for the region. + # If no subnet is provided by the user, pick the first one out of the user-provided network + # + # @return [String] The name of the subnetwork that should be attached to the instances + def default_subnetwork + network_name = @options[:gce_network] + if network_name == 'default' + @options[:gce_subnetwork] ||= @compute.get_subnetwork(@options[:gce_project], default_region, 'default').name + elsif @options[:gce_subnetwork].nil? + # No subnet set, get the first subnet in our current region for the network + subnetwork = @compute.get_network(@options[:gce_project], network_name).subnetworks[0] + m = %r{.*/subnetworks/(?.*)\Z}.match subnetwork + raise "Unable to find a subnetwork in provided network #{network_name}" if m.nil? + + @options[:gce_subnetwork] = m['subnetwork_name'] + end + @options[:gce_subnetwork] + end - ## - # Creates an authentication object to use in the various Google APIs - # - # This method currently supports using application credentials via the - # GOOGLE_APPLICATION_CREDENTIALS environment variable, and application default - # credentials. - # - # @return [Google::Auth::UserRefreshCredentials|Google::Auth::ServiceAccountCredentials] - # Authorization object to pass to Google APIs - def authenticate - if ENV['GOOGLE_APPLICATION_CREDENTIALS'] - ::Google::Auth::ServiceAccountCredentials.from_env(scope: GCP_AUTH_SCOPE) - else - # Fall back to default application auth - ::Google::Auth.get_application_default(GCP_AUTH_SCOPE) + ## + # Set the user-agent information for the application. + # + # @param version The version number of Beaker currently running + def client(version) + ::Google::Apis::ClientOptions.default.application_name = 'beaker-google' + ::Google::Apis::ClientOptions.default.application_version = version end - end - ## - # Find the correct image object for a given project and name - # - # @param [String] image_project The project that owns the requested image - # - # @param [String] name The name of the image in the project. This must - # be the exact name of the image - # - # @return [Google::Apis::ComputeV1::Image] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def get_image(project, name) - @compute.get_image(project, name) - end + ## + # Creates an authentication object to use in the various Google APIs + # + # This method currently supports using application credentials via the + # GOOGLE_APPLICATION_CREDENTIALS environment variable, and application default + # credentials. + # + # @return [Google::Auth::UserRefreshCredentials|Google::Auth::ServiceAccountCredentials] + # Authorization object to pass to Google APIs + def authenticate + if ENV['GOOGLE_APPLICATION_CREDENTIALS'] + ::Google::Auth::ServiceAccountCredentials.from_env(scope: GCP_AUTH_SCOPE) + else + # Fall back to default application auth + ::Google::Auth.get_application_default(GCP_AUTH_SCOPE) + end + end - ## - # Find the latest non-deprecated image in the given project and family - # - # @param [String] image_project The project that owns the requested image - # - # @param [String] family The name of the image family - # - # @return [Google::Apis::ComputeV1::Image] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def get_latest_image_from_family(image_project, family) - @compute.get_image_from_family(image_project, family) - end + ## + # Find the correct image object for a given project and name + # + # @param [String] image_project The project that owns the requested image + # + # @param [String] name The name of the image in the project. This must + # be the exact name of the image + # + # @return [Google::Apis::ComputeV1::Image] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def get_image(project, name) + @compute.get_image(project, name) + end - ## - # Determines the Google Compute machineType object based upon the selected - # gce_machine_type option - # - # @param [String] type_name The name of the type to get - # - # @return [Google::Apis::ComputeV1::MachineType] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def get_machine_type(type_name = DEFAULT_MACHINE_TYPE) - @compute.get_machine_type(@options[:gce_project], default_zone, type_name) - end + ## + # Find the latest non-deprecated image in the given project and family + # + # @param [String] image_project The project that owns the requested image + # + # @param [String] family The name of the image family + # + # @return [Google::Apis::ComputeV1::Image] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def get_latest_image_from_family(image_project, family) + @compute.get_image_from_family(image_project, family) + end - ## - # Determines the Google Compute network object in use for the current connection - # - # @return [Google::Apis::ComputeV1::Network] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def get_network(network_name = default_network) - @compute.get_network(@options[:gce_project], network_name) - end + ## + # Determines the Google Compute machineType object based upon the selected + # gce_machine_type option + # + # @param [String] type_name The name of the type to get + # + # @return [Google::Apis::ComputeV1::MachineType] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def get_machine_type(type_name = DEFAULT_MACHINE_TYPE) + @compute.get_machine_type(@options[:gce_project], default_zone, type_name) + end - ## - # Determines a list of existing Google Compute instances - # - # @return [Array[Google::Apis::ComputeV1::Instance]] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def list_instances - @compute.list_instances(@options[:gce_project], default_zone).items - end + ## + # Determines the Google Compute network object in use for the current connection + # + # @return [Google::Apis::ComputeV1::Network] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def get_network(network_name = default_network) + @compute.get_network(@options[:gce_project], network_name) + end - ## - # Determines a list of existing Google Compute disks - # - # @param [Integer] start The time when we started code execution, it is - # compared to Time.now to determine how many further code execution - # attempts remain - # - # @return [Array[Google::Apis::ComputeV1::Disk]] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def list_disks - @compute.list_disks(@options[:gce_project], default_zone).items - end + ## + # Determines a list of existing Google Compute instances + # + # @return [Array[Google::Apis::ComputeV1::Instance]] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def list_instances + @compute.list_instances(@options[:gce_project], default_zone).items + end - ## - # Determines a list of existing Google Compute firewalls - # - # @return [Array[Google::Apis::ComputeV1::Firewall]] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def list_firewalls - @compute.list_firewalls(@options[:gce_project], - filter: 'name != default-allow-internal AND name != default-ssh').items - end + ## + # Determines a list of existing Google Compute disks + # + # @param [Integer] start The time when we started code execution, it is + # compared to Time.now to determine how many further code execution + # attempts remain + # + # @return [Array[Google::Apis::ComputeV1::Disk]] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def list_disks + @compute.list_disks(@options[:gce_project], default_zone).items + end - ## - # Create a Google Compute firewall - # - # @param [String] name The name of the firewall to create - # - # @param [::Google::Apis::ComputeV1::Network] network The Google Compute networkin which to create - # the firewall - # - # @param [Array] allow List of ports to allow through the firewall. One of 'allow' or 'deny' must be specified, but not both. - # - # @param [Array] deny List of ports to deny through the firewall. One of 'allow' or 'deny' must be specified, but not both. - # - # @param [Array] source_ranges List of ranges in CIDR format to accept through the firewall. If neither 'source_ranges' - # or 'source_tags' is specified, GCP adds a default 'source_range' of '0.0.0.0/0' (allow all) - # - # @param [Array] source_tags List of network tags to accept through the firewall. If neither 'source_ranges' - # or 'source_tags' is specified, GCP adds a default 'source_range' of '0.0.0.0/0' (allow all) - # - # @param [Array] target_ranges List of ranges in CIDR format to apply this firewall. If neither 'target_ranges' - # or 'target_tags' is specified, the firewall applies to all hosts in the VPC - # - # @param [Array] target_tags List of network tags to apply this firewall. If neither 'target_ranges' - # or 'target_tags' is specified, the firewall applies to all hosts in the VPC - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def create_firewall(name, network, allow: [], deny: [], source_ranges: [], source_tags: [], target_ranges: [], target_tags: []) - allowed = [] - allow.each do |port| - parts = port.split('/', 2) - if parts[1] == 'tcp' || parts[1] == 'udp' || parts[1] == 'sctp' then - allowed << ::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: parts[1], ports: [parts[0]]) - else - allowed << ::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: parts[1]) - end + ## + # Determines a list of existing Google Compute firewalls + # + # @return [Array[Google::Apis::ComputeV1::Firewall]] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def list_firewalls + @compute.list_firewalls(@options[:gce_project], + filter: 'name != default-allow-internal AND name != default-ssh').items end - denied = [] - deny.each do |port| - parts = port.split('/', 2) - if parts[1] == 'tcp' || parts[1] == 'udp' || parts[1] == 'sctp' then - denied << ::Google::Apis::ComputeV1::Firewall::Denied.new(ip_protocol: parts[1], ports: [parts[0]]) - else - denied << ::Google::Apis::ComputeV1::Firewall::Denied.new(ip_protocol: parts[1]) + + ## + # Create a Google Compute firewall + # + # @param [String] name The name of the firewall to create + # + # @param [::Google::Apis::ComputeV1::Network] network The Google Compute networkin which to create + # the firewall + # + # @param [Array] allow List of ports to allow through the firewall. One of 'allow' or 'deny' must be + # specified, but not both. + # + # @param [Array] deny List of ports to deny through the firewall. One of 'allow' or 'deny' must be + # specified, but not both. + # + # @param [Array] source_ranges List of ranges in CIDR format to accept through the firewall. If neither + # 'source_ranges' or 'source_tags' is specified, GCP adds a default 'source_range' of '0.0.0.0/0' (allow all) + # + # @param [Array] source_tags List of network tags to accept through the firewall. If neither 'source_ranges' + # or 'source_tags' is specified, GCP adds a default 'source_range' of '0.0.0.0/0' (allow all) + # + # @param [Array] target_ranges List of ranges in CIDR format to apply this firewall. If neither + # 'target_ranges' or 'target_tags' is specified, the firewall applies to all hosts in the VPC + # + # @param [Array] target_tags List of network tags to apply this firewall. If neither 'target_ranges' or + # 'target_tags' is specified, the firewall applies to all hosts in the VPC + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def create_firewall(name, network, allow: [], deny: [], source_ranges: [], source_tags: [], target_ranges: [], + target_tags: []) + allowed = [] + allow.each do |port| + parts = port.split('/', 2) + allowed << if parts[1] == 'tcp' || parts[1] == 'udp' || parts[1] == 'sctp' + ::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: parts[1], ports: [parts[0]]) + else + ::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: parts[1]) + end end + denied = [] + deny.each do |port| + parts = port.split('/', 2) + denied << if parts[1] == 'tcp' || parts[1] == 'udp' || parts[1] == 'sctp' + ::Google::Apis::ComputeV1::Firewall::Denied.new(ip_protocol: parts[1], ports: [parts[0]]) + else + ::Google::Apis::ComputeV1::Firewall::Denied.new(ip_protocol: parts[1]) + end + end + + firewall_object = ::Google::Apis::ComputeV1::Firewall.new( + name: name, + direction: 'INGRESS', + network: network.self_link, + allowed: allowed, + denied: denied, + source_ranges: source_ranges, + source_tags: source_tags, + target_ranges: target_ranges, + target_tags: target_tags, + ) + operation = @compute.insert_firewall(@options[:gce_project], firewall_object) + @compute.wait_global_operation(@options[:gce_project], operation.name) end - firewall_object = ::Google::Apis::ComputeV1::Firewall.new( - name: name, - direction: 'INGRESS', - network: network.self_link, - allowed: allowed, - denied: denied, - source_ranges: source_ranges, - source_tags: source_tags, - target_ranges: target_ranges, - target_tags: target_tags, - ) - operation = @compute.insert_firewall(@options[:gce_project], firewall_object) - @compute.wait_global_operation(@options[:gce_project], operation.name) - end + ## + # Get the named firewall + # + # @param [String] name The name of the firewall + # + # @return [Google::Apis::ComputeV1::Firewall] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def get_firewall(name) + @compute.get_firewall(@options[:gce_project], name) + end - ## - # Get the named firewall - # - # @param [String] name The name of the firewall - # - # @return [Google::Apis::ComputeV1::Firewall] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def get_firewall(name) - firewall = @compute.get_firewall(@options[:gce_project], name) - end + ## + # Add a source range to the firewall. + # + # @param [String] name The name of the firewall + # + # @param [String] range The IP range in CIDR format to add to the firewall + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def add_firewall_source_range(name, range) + firewall = get_firewall(name) + firewall.source_ranges = [] if firewall.source_ranges.nil? + firewall.source_ranges << range + operation = @compute.patch_firewall(@options[:gce_project], name, firewall) + @compute.wait_global_operation(@options[:gce_project], operation.name) + end - ## - # Add a source range to the firewall. - # - # @param [String] name The name of the firewall - # - # @param [String] range The IP range in CIDR format to add to the firewall - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def add_firewall_source_range(name, range) - firewall = get_firewall(name) - firewall.source_ranges = [] if firewall.source_ranges.nil? - firewall.source_ranges << range - operation = @compute.patch_firewall(@options[:gce_project], name, firewall) - @compute.wait_global_operation(@options[:gce_project], operation.name) - end + ## + # Add an allowed port to the firewall + # + # @param [String] name The name of the firewall + # + # @param [String] port The port number to open on the firewall + # + # @param [String] proto The protocol of the port. This should be 'tcp' or 'udp' + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def add_firewall_port(name, port, proto) + firewall = get_firewall(name) + firewall.allowed = [] if firewall.allowed.nil? + firewall.allowed << ::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: proto, ports: [port]) + operation = @compute.patch_firewall(@options[:gce_project], name, firewall) + @compute.wait_global_operation(@options[:gce_project], operation.name) + end - ## - # Add an allowed port to the firewall - # - # @param [String] name The name of the firewall - # - # @param [String] port The port number to open on the firewall - # - # @param [String] proto The protocol of the port. This should be 'tcp' or 'udp' - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def add_firewall_port(name, port, proto) - firewall = get_firewall(name) - firewall.allowed = [] if firewall.allowed.nil? - firewall.allowed << ::Google::Apis::ComputeV1::Firewall::Allowed.new(ip_protocol: proto, ports: [port]) - operation = @compute.patch_firewall(@options[:gce_project], name, firewall) - @compute.wait_global_operation(@options[:gce_project], operation.name) - end + ## + # Add a taget_tag to an existing firewall + # + # @param [String] the name of the firewall to update + # + # @param [String] tag The target tag to add to the firewall + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def add_firewall_target_tag(name, tag) + firewall = @compute.get_firewall(@options[:gce_project], name) + firewall.target_tags = [] if firewall.target_tags.nil? + firewall.target_tags << tag + operation = @compute.patch_firewall(@options[:gce_project], name, firewall) + @compute.wait_global_operation(@options[:gce_project], operation.name) + end - ## - # Add a taget_tag to an existing firewall - # - # @param [String] the name of the firewall to update - # - # @param [String] tag The target tag to add to the firewall - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def add_firewall_target_tag(name, tag) - firewall = @compute.get_firewall(@options[:gce_project], name) - firewall.target_tags = [] if firewall.target_tags.nil? - firewall.target_tags << tag - operation = @compute.patch_firewall(@options[:gce_project], name, firewall) - @compute.wait_global_operation(@options[:gce_project], operation.name) - end + ## + # Add a source_tag to an existing firewall + # + # @param [String] the name of the firewall to update + # + # @param [String] tag The source tag to add to the firewall + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def add_firewall_source_tag(name, tag) + firewall = @compute.get_firewall(@options[:gce_project], name) + firewall.source_tags = [] if firewall.source_tags.nil? + firewall.source_tags << tag + operation = @compute.patch_firewall(@options[:gce_project], name, firewall) + @compute.wait_global_operation(@options[:gce_project], operation.name) + end - ## - # Add a source_tag to an existing firewall - # - # @param [String] the name of the firewall to update - # - # @param [String] tag The source tag to add to the firewall - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def add_firewall_source_tag(name, tag) - firewall = @compute.get_firewall(@options[:gce_project], name) - firewall.source_tags = [] if firewall.source_tags.nil? - firewall.source_tags << tag - operation = @compute.patch_firewall(@options[:gce_project], name, firewall) - @compute.wait_global_operation(@options[:gce_project], operation.name) - end + ## + # Create a Google Compute disk + # + # @param [String] name The name of the disk to create + # + # @param [String] img The existing disk image to clone for this image + # or nil to create a blank disk + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def create_disk(name, size, img = nil) + new_disk = ::Google::Apis::ComputeV1::Disk.new( + name: name, + size_gb: size, + source_image: img, + ) + operation = @compute.insert_disk(@options[:gce_project], @options[:gce_zone], new_disk) + @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) + end - ## - # Create a Google Compute disk - # - # @param [String] name The name of the disk to create - # - # @param [String] img The existing disk image to clone for this image - # or nil to create a blank disk - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def create_disk(name, size, img = nil) - new_disk = ::Google::Apis::ComputeV1::Disk.new( - name: name, - size_gb: size, - source_image: img, - ) - operation = @compute.insert_disk(@options[:gce_project], @options[:gce_zone], new_disk) - @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) - end + ## + # Create a Google Compute instance + # + # @param [String] name The name of the instance to create + # + # @param [Google::Apis::ComputeV1::Image] img The Google Compute image to use for instance creation + # + # @param [Google::Apis::ComputeV1::MachineType] machine_type The Google Compute Machine Type + # + # @param [Integer] disk_size The size of the boot disk for the new instance. Must be equal to or + # greater than the image disk's size + # + # @param [String] hostname The custom hostname to set in the OS of the instance + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def create_instance(name, img, machine_type, disk_size, hostname) + initialize_params = ::Google::Apis::ComputeV1::AttachedDiskInitializeParams.new( + disk_size_gb: disk_size, + source_image: img.self_link, + ) + disk_params = ::Google::Apis::ComputeV1::AttachedDisk.new( + boot: true, + auto_delete: true, + initialize_params: initialize_params, + ) + # attached_network = ::Google::Apis::ComputeV1::networkInterfaces.new() + tags = ::Google::Apis::ComputeV1::Tags.new( + items: [name], + ) + network_interface = ::Google::Apis::ComputeV1::NetworkInterface.new( + network: get_network(default_network).self_link, + subnetwork: @compute.get_subnetwork(@options[:gce_project], default_region, default_subnetwork).self_link, + # Create an AccessConfig to add a NAT IP to the host. + # TODO: Make this configurable + access_configs: [ + ::Google::Apis::ComputeV1::AccessConfig.new( + network_tier: 'STANDARD', + ), + ], + ) + + instance_opts = { + machine_type: machine_type.self_link, + name: name, + disks: [disk_params], + network_interfaces: [network_interface], + tags: tags, + } + + # use custom hostname if specified + if hostname && ENV.fetch('BEAKER_set_gce_hostname', false) + # The google api requires an FQDN for the custom hostname + valid_hostname = hostname.include?('.') ? hostname : "#{hostname}.beaker.test" + instance_opts[:hostname] = valid_hostname + end - ## - # Create a Google Compute instance - # - # @param [String] name The name of the instance to create - # - # @param [Google::Apis::ComputeV1::Image] img The Google Compute image to use for instance creation - # - # @param [Google::Apis::ComputeV1::MachineType] machine_type The Google Compute Machine Type - # - # @param [Integer] disk_size The size of the boot disk for the new instance. Must be equal to or - # greater than the image disk's size - # - # @param [String] hostname The custom hostname to set in the OS of the instance - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def create_instance(name, img, machine_type, disk_size, hostname) - initialize_params = ::Google::Apis::ComputeV1::AttachedDiskInitializeParams.new( - disk_size_gb: disk_size, - source_image: img.self_link, - ) - disk_params = ::Google::Apis::ComputeV1::AttachedDisk.new( - boot: true, - auto_delete: true, - initialize_params: initialize_params, - ) - # attached_network = ::Google::Apis::ComputeV1::networkInterfaces.new() - tags = ::Google::Apis::ComputeV1::Tags.new( - items: [name], - ) - network_interface = ::Google::Apis::ComputeV1::NetworkInterface.new( - network: get_network(default_network).self_link, - subnetwork: @compute.get_subnetwork(@options[:gce_project], default_region, default_subnetwork).self_link, - # Create an AccessConfig to add a NAT IP to the host. - # TODO: Make this configurable - access_configs: [ - ::Google::Apis::ComputeV1::AccessConfig.new( - network_tier: 'STANDARD', - ), - ], - ) - - instance_opts = { - :machine_type => machine_type.self_link, - :name => name, - :disks => [disk_params], - :network_interfaces => [network_interface], - :tags => tags, - } - - # use custom hostname if specified - if hostname && ENV.fetch('BEAKER_set_gce_hostname', false) - # The google api requires an FQDN for the custom hostname - hostname.include?('.') ? valid_hostname = hostname : valid_hostname = hostname + '.beaker.test' - instance_opts[:hostname] = valid_hostname - end - - new_instance = ::Google::Apis::ComputeV1::Instance.new(**instance_opts) + new_instance = ::Google::Apis::ComputeV1::Instance.new(**instance_opts) - operation = @compute.insert_instance(@options[:gce_project], @options[:gce_zone], new_instance) - @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) - end + operation = @compute.insert_instance(@options[:gce_project], @options[:gce_zone], new_instance) + @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) + end - ## - # Get the named instace from Google Compute Image - # - # @param [String] name The name of the instance - # - # @return [Google::Apis::ComputeV1::Instance] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def get_instance(name) - @compute.get_instance(@options[:gce_project], @options[:gce_zone], name) - end + ## + # Get the named instace from Google Compute Image + # + # @param [String] name The name of the instance + # + # @return [Google::Apis::ComputeV1::Instance] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def get_instance(name) + @compute.get_instance(@options[:gce_project], @options[:gce_zone], name) + end - ## - # Add a tag to a Google Compute Instance - # - # @param [String] name The name of the instance - # - # @param [String] tag The tag to add to the instance - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def add_instance_tag(name, tag) - instance = get_instance(name) - tags = instance.tags - tags.items << tag - operation = @compute.set_instance_tags(@options[:gce_project], @options[:gce_zone], name, tags) - @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) - end + ## + # Add a tag to a Google Compute Instance + # + # @param [String] name The name of the instance + # + # @param [String] tag The tag to add to the instance + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def add_instance_tag(name, tag) + instance = get_instance(name) + tags = instance.tags + tags.items << tag + operation = @compute.set_instance_tags(@options[:gce_project], @options[:gce_zone], name, tags) + @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) + end - ## - # Set key/value metadata pairs to a Google Compute instance - # - # This function replaces any existing items in the metadata hash! - # - # @param [String] name The name of the instance to set metadata - # - # @param [String] data An array of hashes to set ass metadata. Each array - # item should have a 'key' and 'value' key. - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def set_metadata_on_instance(name, data) - instance = @compute.get_instance(@options[:gce_project], @options[:gce_zone], name) - mdata = instance.metadata.dup - mdata.items = data - operation = @compute.set_instance_metadata(@options[:gce_project], @options[:gce_zone], name, mdata) - @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) - end + ## + # Set key/value metadata pairs to a Google Compute instance + # + # This function replaces any existing items in the metadata hash! + # + # @param [String] name The name of the instance to set metadata + # + # @param [String] data An array of hashes to set ass metadata. Each array + # item should have a 'key' and 'value' key. + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def set_metadata_on_instance(name, data) + instance = @compute.get_instance(@options[:gce_project], @options[:gce_zone], name) + mdata = instance.metadata.dup + mdata.items = data + operation = @compute.set_instance_metadata(@options[:gce_project], @options[:gce_zone], name, mdata) + @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) + end - ## - # Delete a Google Compute instance - # - # @param [String] name The name of the instance to delete - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def delete_instance(name) - operation = @compute.delete_instance(@options[:gce_project], default_zone, name) - @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) - end + ## + # Delete a Google Compute instance + # + # @param [String] name The name of the instance to delete + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def delete_instance(name) + operation = @compute.delete_instance(@options[:gce_project], default_zone, name) + @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) + end - ## - # Delete a Google Compute disk - # - # @param [String] name The name of the disk to delete - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def delete_disk(name) - operation = @compute.delete_disk(@options[:gce_project], default_zone, name) - @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) - end + ## + # Delete a Google Compute disk + # + # @param [String] name The name of the disk to delete + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def delete_disk(name) + operation = @compute.delete_disk(@options[:gce_project], default_zone, name) + @compute.wait_zone_operation(@options[:gce_project], @options[:gce_zone], operation.name) + end - ## - # Delete a Google Compute firewall - # - # @param [String] name The name of the firewall to delete - # - # @return [Google::Apis::ComputeV1::Operation] - # - # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried - # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification - # @raise [Google::Apis::AuthorizationError] Authorization is required - def delete_firewall(name) - operation = @compute.delete_firewall(@options[:gce_project], name) - @compute.wait_global_operation(@options[:gce_project], operation.name) + ## + # Delete a Google Compute firewall + # + # @param [String] name The name of the firewall to delete + # + # @return [Google::Apis::ComputeV1::Operation] + # + # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried + # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification + # @raise [Google::Apis::AuthorizationError] Authorization is required + def delete_firewall(name) + operation = @compute.delete_firewall(@options[:gce_project], name) + @compute.wait_global_operation(@options[:gce_project], operation.name) + end end end From 4569100df513257097e3f243217c3d6b654be265 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Thu, 6 Jun 2024 15:49:06 -0500 Subject: [PATCH 2/2] Add tests job By [request](https://github.com/voxpupuli/beaker-google/pull/50#discussion_r1630101815) --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aacb07e..eb6a924 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,3 +43,12 @@ jobs: # Currently no tests are defined in this gem # - name: Run spec tests # run: bundle exec rake + + tests: + needs: + - rubocop + - test + runs-on: ubuntu-latest + name: Test suite + steps: + - run: echo Test suite completed