diff --git a/.fixtures.yml b/.fixtures.yml index e6a230c..6471712 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,8 +1,9 @@ fixtures: repositories: - stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git" apt: "https://github.com/puppetlabs/puppetlabs-apt.git" + concat: "https://github.com/syseleven/puppetlabs-concat.git" docker: "https://github.com/puppetlabs/puppetlabs-docker.git" + stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git" yumrepo_core: repo: https://github.com/puppetlabs/puppetlabs-yumrepo_core.git puppet_version: ">= 6.0.0" diff --git a/.sync.yml b/.sync.yml index 17e654f..1d14457 100644 --- a/.sync.yml +++ b/.sync.yml @@ -18,3 +18,6 @@ Gemfile: optional: ':test': - gem: 'webmock' +spec/spec_helper.rb: + spec_overrides: + - require 'spec_helper_local' diff --git a/REFERENCE.md b/REFERENCE.md index 862d69e..f2ba41c 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -18,7 +18,21 @@ _Private Classes_ **Defined types** -* [`gitlab_ci_runner::runner`](#gitlab_ci_runnerrunner): This module installs and configures Gitlab CI Runners. +* [`gitlab_ci_runner::runner`](#gitlab_ci_runnerrunner): This configures a Gitlab CI runner. + +**Functions** + +* [`gitlab_ci_runner::register`](#gitlab_ci_runnerregister): A function that registers a Gitlab runner on a Gitlab instance. Be careful, this will be triggered on noop runs as well! +* [`gitlab_ci_runner::register_to_file`](#gitlab_ci_runnerregister_to_file): A function that registers a Gitlab runner on a Gitlab instance, if it doesn't already exist, _and_ saves the retrived authentication token to +* [`gitlab_ci_runner::to_toml`](#gitlab_ci_runnerto_toml): Convert a data structure and output to TOML. +* [`gitlab_ci_runner::unregister`](#gitlab_ci_runnerunregister): A function that unregisters a Gitlab runner from a Gitlab instance. Be careful, this will be triggered on noop runs as well! + +**Data types** + +* [`Gitlab_ci_runner::Log_format`](#gitlab_ci_runnerlog_format): Gitlab Runner log format configuration +* [`Gitlab_ci_runner::Log_level`](#gitlab_ci_runnerlog_level): Gitlab Runner log level configuration +* [`Gitlab_ci_runner::Register`](#gitlab_ci_runnerregister): A struct of all possible additionl options for gitlab_ci_runner::register +* [`Gitlab_ci_runner::Register_parameter`](#gitlab_ci_runnerregister_parameter): A enum containing a possible keys used for Gitlab runner registrations **Tasks** @@ -81,43 +95,43 @@ Limits how many jobs globally can be run concurrently. The most upper limit of j Default value: `undef` -##### `builds_dir` +##### `log_level` -Data type: `Optional[String]` +Data type: `Optional[Gitlab_ci_runner::Log_level]` -Absolute path to a directory where builds will be stored in context of selected executor (Locally, Docker, SSH). +Log level (options: debug, info, warn, error, fatal, panic). Note that this setting has lower priority than level set by command line argument --debug, -l or --log-level Default value: `undef` -##### `cache_dir` +##### `log_format` -Data type: `Optional[String]` +Data type: `Optional[Gitlab_ci_runner::Log_format]` -Absolute path to a directory where build caches will be stored in context of selected executor (locally, Docker, SSH). If the docker executor is used, this directory needs to be included in its volumes parameter. +Log format (options: runner, text, json). Note that this setting has lower priority than format set by command line argument --log-format Default value: `undef` -##### `metrics_server` +##### `check_interval` -Data type: `Optional[Pattern[/.*:.+/]]` +Data type: `Optional[Integer]` -(Deprecated) [host]: to enable metrics server as described in https://docs.gitlab.com/runner/monitoring/README.html#configuration-of-the-metrics-http-server. +defines the interval length, in seconds, between new jobs check. The default value is 3; if set to 0 or lower, the default value will be used. Default value: `undef` -##### `listen_address` +##### `sentry_dsn` -Data type: `Optional[Pattern[/.*:.+/]]` +Data type: `Optional[String]` -Address (:) on which the Prometheus metrics HTTP server should be listening. +Enable tracking of all system level errors to sentry. Default value: `undef` -##### `sentry_dsn` +##### `listen_address` -Data type: `Optional[String]` +Data type: `Optional[Pattern[/.*:.+/]]` -Enable tracking of all system level errors to sentry. +Address (:) on which the Prometheus metrics HTTP server should be listening. Default value: `undef` @@ -163,7 +177,7 @@ Default value: 'https://packages.gitlab.com' ##### `repo_keyserver` -Data type: `Optional[Stdlib::Fqdn]` +Data type: `Stdlib::Fqdn` The keyserver which should be used to get the repository key. @@ -181,18 +195,67 @@ Default value: '/etc/gitlab-runner/config.toml' ### gitlab_ci_runner::runner -This module installs and configures Gitlab CI Runners. +This configures a Gitlab CI runner. #### Examples -##### Simple runner registration +##### Add a simple runner + +```puppet +gitlab_ci_runner::runner { 'testrunner': + config => { + 'url' => 'https://gitlab.com', + 'token' => '123456789abcdefgh', # Note this is different from the registration token used by `gitlab-runner register` + 'executor' => 'shell', + }, +} +``` + +##### Add a autoscaling runner with DigitalOcean as IaaS ```puppet -gitlab_ci_runner::runner { example_runner: +gitlab_ci_runner::runner { 'autoscale-runner': config => { - 'registration-token' => 'gitlab-token', - 'url' => 'https://gitlab.com', - 'tag-list' => 'docker,aws', + url => 'https://gitlab.com', + token => 'RUNNER_TOKEN', # Note this is different from the registration token used by `gitlab-runner register` + name => 'autoscale-runner', + executor => 'docker+machine', + limit => 10, + docker => { + image => 'ruby:2.6', + }, + machine => { + OffPeakPeriods => [ + '* * 0-9,18-23 * * mon-fri *', + '* * * * * sat,sun *', + ], + OffPeakIdleCount => 1, + OffPeakIdleTime => 1200, + IdleCount => 5, + IdleTime => 600, + MaxBuilds => 100, + MachineName => 'auto-scale-%s', + MachineDriver => 'digitalocean', + MachineOptions => [ + 'digitalocean-image=coreos-stable', + 'digitalocean-ssh-user=core', + 'digitalocean-access-token=DO_ACCESS_TOKEN', + 'digitalocean-region=nyc2', + 'digitalocean-size=4gb', + 'digitalocean-private-networking', + 'engine-registry-mirror=http://10.11.12.13:12345', + ], + }, + cache => { + 'Type' => 's3', + s3 => { + ServerAddress => 's3-eu-west-1.amazonaws.com', + AccessKey => 'AMAZON_S3_ACCESS_KEY', + SecretKey => 'AMAZON_S3_SECRET_KEY', + BucketName => 'runner', + Insecure => false, + }, + }, }, } ``` @@ -207,30 +270,242 @@ Data type: `Hash` Hash with configuration options. See https://docs.gitlab.com/runner/configuration/advanced-configuration.html for all possible options. +If you omit the 'name' configuration, we will automatically use the $title of this define class. + +## Functions + +### gitlab_ci_runner::register -##### `ensure` +Type: Ruby 4.x API -Data type: `Enum['present', 'absent']` +A function that registers a Gitlab runner on a Gitlab instance. Be careful, this will be triggered on noop runs as well! -If the runner should be 'present' or 'absent'. Will register/unregister the runner from Gitlab. +#### Examples + +##### Using it as a replacement for the Bolt 'register_runner' task + +```puppet +puppet apply -e "notice(gitlab_ci_runner::register('https://gitlab.com', 'registration-token'))" +``` -Default value: 'present' +#### `gitlab_ci_runner::register(Stdlib::HTTPUrl $url, String[1] $token, Optional[Gitlab_ci_runner::Register] $additional_options)` + +A function that registers a Gitlab runner on a Gitlab instance. Be careful, this will be triggered on noop runs as well! + +Returns: `Struct[{ id => Integer[1], token => String[1], }]` Returns a hash with the runner id and authentcation token + +##### Examples + +###### Using it as a replacement for the Bolt 'register_runner' task + +```puppet +puppet apply -e "notice(gitlab_ci_runner::register('https://gitlab.com', 'registration-token'))" +``` + +##### `url` + +Data type: `Stdlib::HTTPUrl` + +The url to your Gitlab instance. Please only provide the host part (e.g https://gitlab.com) + +##### `token` + +Data type: `String[1]` + +Registration token. + +##### `additional_options` + +Data type: `Optional[Gitlab_ci_runner::Register]` + +A hash with all additional configuration options for that runner + +### gitlab_ci_runner::register_to_file + +Type: Ruby 4.x API + +A function that registers a Gitlab runner on a Gitlab instance, if it doesn't already exist, +_and_ saves the retrived authentication token to a file. This is helpful for Deferred functions. + +#### Examples + +##### Using it as a Deferred function + +```puppet +gitlab_ci_runner::runner { 'testrunner': + config => { + 'url' => 'https://gitlab.com', + 'token' => Deferred('gitlab_ci_runner::register_runner_to_file', [$config['url'], $config['registration-token'], 'testrunner']) } + 'executor' => 'shell', + }, +} +``` + +#### `gitlab_ci_runner::register_to_file(String[1] $url, String[1] $regtoken, String[1] $runner_name, Optional[Hash] $additional_options, Optional[String[1]] $filename)` + +A function that registers a Gitlab runner on a Gitlab instance, if it doesn't already exist, +_and_ saves the retrived authentication token to a file. This is helpful for Deferred functions. + +Returns: `String[1]` Returns the authentication token + +##### Examples + +###### Using it as a Deferred function + +```puppet +gitlab_ci_runner::runner { 'testrunner': + config => { + 'url' => 'https://gitlab.com', + 'token' => Deferred('gitlab_ci_runner::register_runner_to_file', [$config['url'], $config['registration-token'], 'testrunner']) } + 'executor' => 'shell', + }, +} +``` + +##### `url` + +Data type: `String[1]` + +The url to your Gitlab instance. Please only provide the host part (e.g https://gitlab.com) + +##### `regtoken` + +Data type: `String[1]` + +Registration token. ##### `runner_name` Data type: `String[1]` -The name of the runner. +The name of the runner. Use as identifier for the retrived auth token. + +##### `filename` + +Data type: `Optional[String[1]]` + +The filename where the token should be saved. + +##### `additional_options` + +Data type: `Optional[Hash]` + +A hash with all additional configuration options for that runner + +### gitlab_ci_runner::to_toml + +Type: Ruby 4.x API + +Convert a data structure and output to TOML. + +#### Examples + +##### How to output TOML to a file + +```puppet +file { '/tmp/config.toml': + ensure => file, + content => to_toml($myhash), +} +``` + +#### `gitlab_ci_runner::to_toml(Hash $data)` + +The gitlab_ci_runner::to_toml function. + +Returns: `String` Converted data as TOML string + +##### Examples + +###### How to output TOML to a file + +```puppet +file { '/tmp/config.toml': + ensure => file, + content => to_toml($myhash), +} +``` + +##### `data` + +Data type: `Hash` + +Data structure which needs to be converted into TOML + +### gitlab_ci_runner::unregister + +Type: Ruby 4.x API + +A function that unregisters a Gitlab runner from a Gitlab instance. Be careful, this will be triggered on noop runs as well! + +#### Examples + +##### Using it as a replacement for the Bolt 'unregister_runner' task + +```puppet +puppet apply -e "notice(gitlab_ci_runner::unregister('https://gitlab.com', 'runner-auth-token'))" +``` + +#### `gitlab_ci_runner::unregister(Stdlib::HTTPUrl $url, String[1] $token)` + +A function that unregisters a Gitlab runner from a Gitlab instance. Be careful, this will be triggered on noop runs as well! + +Returns: `Struct[{ status => Enum['success'], }]` Returns a hash with the runner id and authentcation token + +##### Examples -Default value: $title +###### Using it as a replacement for the Bolt 'unregister_runner' task -##### `binary` +```puppet +puppet apply -e "notice(gitlab_ci_runner::unregister('https://gitlab.com', 'runner-auth-token'))" +``` + +##### `url` + +Data type: `Stdlib::HTTPUrl` + +The url to your Gitlab instance. Please only provide the host part (e.g https://gitlab.com) + +##### `token` Data type: `String[1]` -The name of the Gitlab runner binary. +Runners authentication token. -Default value: 'gitlab-runner' +## Data types + +### Gitlab_ci_runner::Log_format + +Gitlab Runner log format configuration + +Alias of `Enum['runner', 'text', 'json']` + +### Gitlab_ci_runner::Log_level + +Gitlab Runner log level configuration + +Alias of `Enum['debug', 'info', 'warn', 'error', 'fatal', 'panic']` + +### Gitlab_ci_runner::Register + +A struct of all possible additionl options for gitlab_ci_runner::register + +Alias of `Struct[{ + Optional[description] => String[1], + Optional[info] => Hash[String[1],String[1]], + Optional[active] => Boolean, + Optional[locked] => Boolean, + Optional[run_untagged] => Boolean, + Optional[tag_list] => Array[String[1]], + Optional[access_level] => Enum['not_protected', 'ref_protected'], + Optional[maximum_timeout] => Integer, +}]` + +### Gitlab_ci_runner::Register_parameter + +A enum containing a possible keys used for Gitlab runner registrations + +Alias of `Enum['description', 'info', 'active', 'locked', 'run_untagged', 'run-untagged', 'tag_list', 'tag-list', 'access_level', 'access-level', 'maximum_timeout', 'maximum-timeout']` ## Tasks diff --git a/lib/puppet/functions/gitlab_ci_runner/register.rb b/lib/puppet/functions/gitlab_ci_runner/register.rb new file mode 100644 index 0000000..ede8c23 --- /dev/null +++ b/lib/puppet/functions/gitlab_ci_runner/register.rb @@ -0,0 +1,24 @@ +require_relative '../../../puppet_x/gitlab/runner.rb' + +# A function that registers a Gitlab runner on a Gitlab instance. Be careful, this will be triggered on noop runs as well! +Puppet::Functions.create_function(:'gitlab_ci_runner::register') do + # @param url The url to your Gitlab instance. Please only provide the host part (e.g https://gitlab.com) + # @param token Registration token. + # @param additional_options A hash with all additional configuration options for that runner + # @return [Struct[{ id => Integer[1], token => String[1], }]] Returns a hash with the runner id and authentcation token + # @example Using it as a replacement for the Bolt 'register_runner' task + # puppet apply -e "notice(gitlab_ci_runner::register('https://gitlab.com', 'registration-token'))" + # + dispatch :register do + param 'Stdlib::HTTPUrl', :url + param 'String[1]', :token + optional_param 'Gitlab_ci_runner::Register', :additional_options + return_type 'Struct[{ id => Integer[1], token => String[1], }]' + end + + def register(url, token, additional_options = {}) + PuppetX::Gitlab::Runner.register(url, additional_options.merge('token' => token)) + rescue Net::HTTPError => e + raise "Gitlab runner failed to register: #{e.message}" + end +end diff --git a/lib/puppet/functions/gitlab_ci_runner/register_to_file.rb b/lib/puppet/functions/gitlab_ci_runner/register_to_file.rb new file mode 100644 index 0000000..142275b --- /dev/null +++ b/lib/puppet/functions/gitlab_ci_runner/register_to_file.rb @@ -0,0 +1,46 @@ +require_relative '../../../puppet_x/gitlab/runner.rb' + +# A function that registers a Gitlab runner on a Gitlab instance, if it doesn't already exist, +# _and_ saves the retrived authentication token to a file. This is helpful for Deferred functions. +Puppet::Functions.create_function(:'gitlab_ci_runner::register_to_file') do + # @param url The url to your Gitlab instance. Please only provide the host part (e.g https://gitlab.com) + # @param regtoken Registration token. + # @param runner_name The name of the runner. Use as identifier for the retrived auth token. + # @param filename The filename where the token should be saved. + # @param additional_options A hash with all additional configuration options for that runner + # @return [String] Returns the authentication token + # @example Using it as a Deferred function + # gitlab_ci_runner::runner { 'testrunner': + # config => { + # 'url' => 'https://gitlab.com', + # 'token' => Deferred('gitlab_ci_runner::register_runner_to_file', [$config['url'], $config['registration-token'], 'testrunner']) } + # 'executor' => 'shell', + # }, + # } + # + dispatch :register_to_file do + # We use only core data types because others aren't synced to the agent. + param 'String[1]', :url + param 'String[1]', :regtoken + param 'String[1]', :runner_name + optional_param 'Hash', :additional_options + optional_param 'String[1]', :filename + return_type 'String[1]' + end + + def register_to_file(url, regtoken, runner_name, additional_options = {}, filename = "/etc/gitlab-runner/auth-token-#{runner_name}") + if File.exist?(filename) + authtoken = File.read(filename).strip + else + begin + authtoken = PuppetX::Gitlab::Runner.register(url, additional_options.merge('token' => regtoken))['token'] + File.write(filename, authtoken) + File.chmod(0o400, filename) + rescue Net::HTTPError => e + raise "Gitlab runner failed to register: #{e.message}" + end + end + + authtoken + end +end diff --git a/lib/puppet/functions/gitlab_ci_runner/to_toml.rb b/lib/puppet/functions/gitlab_ci_runner/to_toml.rb new file mode 100644 index 0000000..51494d5 --- /dev/null +++ b/lib/puppet/functions/gitlab_ci_runner/to_toml.rb @@ -0,0 +1,21 @@ +require_relative '../../../puppet_x/gitlab/dumper.rb' +# @summary Convert a data structure and output to TOML. +# +# @example How to output TOML to a file +# file { '/tmp/config.toml': +# ensure => file, +# content => to_toml($myhash), +# } +# +Puppet::Functions.create_function(:'gitlab_ci_runner::to_toml') do + # @param data Data structure which needs to be converted into TOML + # @return [String] Converted data as TOML string + dispatch :to_toml do + required_param 'Hash', :data + return_type 'String' + end + + def to_toml(data) + PuppetX::Gitlab::Dumper.new(data).toml_str + end +end diff --git a/lib/puppet/functions/gitlab_ci_runner/unregister.rb b/lib/puppet/functions/gitlab_ci_runner/unregister.rb new file mode 100644 index 0000000..bb59748 --- /dev/null +++ b/lib/puppet/functions/gitlab_ci_runner/unregister.rb @@ -0,0 +1,24 @@ +require_relative '../../../puppet_x/gitlab/runner.rb' + +# A function that unregisters a Gitlab runner from a Gitlab instance. Be careful, this will be triggered on noop runs as well! +Puppet::Functions.create_function(:'gitlab_ci_runner::unregister') do + # @summary A function that unregisters a Gitlab runner from a Gitlab instance. Be careful, this will be triggered on noop runs as well! + # @param url The url to your Gitlab instance. Please only provide the host part (e.g https://gitlab.com) + # @param token Runners authentication token. + # @return [Struct[{ id => Integer[1], token => String[1], }]] Returns a hash with the runner id and authentcation token + # @example Using it as a replacement for the Bolt 'unregister_runner' task + # puppet apply -e "notice(gitlab_ci_runner::unregister('https://gitlab.com', 'runner-auth-token'))" + # + dispatch :register do + param 'Stdlib::HTTPUrl', :url + param 'String[1]', :token + return_type "Struct[{ status => Enum['success'], }]" + end + + def register(url, token) + PuppetX::Gitlab::Runner.unregister(url, token: token) + { 'status' => 'success' } + rescue Net::HTTPError => e + raise "Gitlab runner failed to unregister: #{e.message}" + end +end diff --git a/lib/puppet_x/gitlab/dumper.rb b/lib/puppet_x/gitlab/dumper.rb new file mode 100644 index 0000000..8db271a --- /dev/null +++ b/lib/puppet_x/gitlab/dumper.rb @@ -0,0 +1,122 @@ +# The Dumper class was blindly copied from https://github.com/emancu/toml-rb/blob/v2.0.1/lib/toml-rb/dumper.rb +# This allows us to use the `to_toml` function as a `Deferred` function because the `toml-rb` gem is usually +# installed on the agent and the `Deferred` function gets evaluated before the catalog gets applied. This +# makes it in most scenarios impossible to install the gem before it is used. + +require 'date' + +module PuppetX + module Gitlab + class Dumper + attr_reader :toml_str + + def initialize(hash) + @toml_str = '' + + visit(hash, []) + end + + private + + def visit(hash, prefix, extra_brackets = false) + simple_pairs, nested_pairs, table_array_pairs = sort_pairs hash + + if prefix.any? && (simple_pairs.any? || hash.empty?) + print_prefix prefix, extra_brackets + end + + dump_pairs simple_pairs, nested_pairs, table_array_pairs, prefix + end + + def sort_pairs(hash) + nested_pairs = [] + simple_pairs = [] + table_array_pairs = [] + + hash.keys.sort.each do |key| + val = hash[key] + element = [key, val] + + if val.is_a? Hash + nested_pairs << element + elsif val.is_a?(Array) && val.first.is_a?(Hash) + table_array_pairs << element + else + simple_pairs << element + end + end + + [simple_pairs, nested_pairs, table_array_pairs] + end + + def dump_pairs(simple, nested, table_array, prefix = []) + # First add simple pairs, under the prefix + dump_simple_pairs simple + dump_nested_pairs nested, prefix + dump_table_array_pairs table_array, prefix + end + + def dump_simple_pairs(simple_pairs) + simple_pairs.each do |key, val| + key = quote_key(key) unless bare_key? key + @toml_str << "#{key} = #{to_toml(val)}\n" + end + end + + def dump_nested_pairs(nested_pairs, prefix) + nested_pairs.each do |key, val| + key = quote_key(key) unless bare_key? key + + visit val, prefix + [key], false + end + end + + def dump_table_array_pairs(table_array_pairs, prefix) + table_array_pairs.each do |key, val| + key = quote_key(key) unless bare_key? key + aux_prefix = prefix + [key] + + val.each do |child| + print_prefix aux_prefix, true + args = sort_pairs(child) << aux_prefix + + dump_pairs(*args) + end + end + end + + def print_prefix(prefix, extra_brackets = false) + new_prefix = prefix.join('.') + new_prefix = '[' + new_prefix + ']' if extra_brackets + + @toml_str += "[" + new_prefix + "]\n" # rubocop:disable Style/StringLiterals + end + + def to_toml(obj) + if obj.is_a?(Time) || obj.is_a?(DateTime) + obj.strftime('%Y-%m-%dT%H:%M:%SZ') + elsif obj.is_a?(Date) + obj.strftime('%Y-%m-%d') + elsif obj.is_a? Regexp + obj.inspect.inspect + elsif obj.is_a? String + obj.inspect.gsub(/\\(#[$@{])/, '\1') # rubocop:disable Style/RegexpLiteral + else + obj.inspect + end + end + + def bare_key?(key) + # rubocop:disable Style/DoubleNegation + # rubocop:disable Style/RegexpLiteral + !!key.to_s.match(/^[a-zA-Z0-9_-]*$/) + # rubocop:enable Style/DoubleNegation + # rubocop:enable Style/RegexpLiteral + end + + def quote_key(key) + '"' + key.gsub('"', '\\"') + '"' + end + end + end +end diff --git a/lib/puppet_x/gitlab/runner.rb b/lib/puppet_x/gitlab/runner.rb new file mode 100644 index 0000000..485b9ca --- /dev/null +++ b/lib/puppet_x/gitlab/runner.rb @@ -0,0 +1,56 @@ +require 'json' +require 'net/http' +require 'uri' + +module PuppetX + module Gitlab + module APIClient + def self.delete(url, options) + response = request(url, Net::HTTP::Delete, options) + validate(response) + + {} + end + + def self.post(url, options) + response = request(url, Net::HTTP::Post, options) + validate(response) + + JSON.parse(response.body) + end + + def self.request(url, http_method, options) + uri = URI.parse(url) + headers = { + 'Accept' => 'application/json', + 'Content-Type' => 'application/json' + } + + http = Net::HTTP.new(uri.host, uri.port) + if uri.scheme == 'https' + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + request = http_method.new(uri.request_uri, headers) + request.body = options.to_json + http.request(request) + end + + def self.validate(response) + raise Net::HTTPError.new(response.message, response) unless response.code.start_with?('2') + end + end + + module Runner + def self.register(host, options) + url = "#{host}/api/v4/runners" + PuppetX::Gitlab::APIClient.post(url, options) + end + + def self.unregister(host, options) + url = "#{host}/api/v4/runners" + PuppetX::Gitlab::APIClient.delete(url, options) + end + end + end +end diff --git a/manifests/config.pp b/manifests/config.pp index 16a81ba..a52e640 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -5,66 +5,41 @@ class gitlab_ci_runner::config ( $config_path = $gitlab_ci_runner::config_path, $concurrent = $gitlab_ci_runner::concurrent, - $metrics_server = $gitlab_ci_runner::metrics_server, - $listen_address = $gitlab_ci_runner::listen_address, - $builds_dir = $gitlab_ci_runner::builds_dir, - $cache_dir = $gitlab_ci_runner::cache_dir, + $log_level = $gitlab_ci_runner::log_level, + $log_format = $gitlab_ci_runner::log_format, + $check_interval = $gitlab_ci_runner::check_interval, $sentry_dsn = $gitlab_ci_runner::sentry_dsn, + $listen_address = $gitlab_ci_runner::listen_address, $package_name = $gitlab_ci_runner::package_name, ) { assert_private() - file { $config_path: # ensure config exists - ensure => 'present', - replace => 'no', - content => '', + concat { $config_path: + ensure => present, + owner => 'root', + group => 'root', + mode => '0444', + ensure_newline => true, } - if $concurrent { - file_line { 'gitlab-runner-concurrent': - path => $config_path, - line => "concurrent = ${concurrent}", - match => '^concurrent = \d+', - } - } - - if $metrics_server { - file_line { 'gitlab-runner-metrics_server': - path => $config_path, - line => "metrics_server = \"${metrics_server}\"", - match => '^metrics_server = .+', - } - } - - if $listen_address { - file_line { 'gitlab-runner-listen-address': - path => $config_path, - line => "listen_address = \"${listen_address}\"", - match => '^listen_address = .+', - } - } - - if $builds_dir { - file_line { 'gitlab-runner-builds_dir': - path => $config_path, - line => "builds_dir = \"${builds_dir}\"", - match => '^builds_dir = .+', - } - } + $global_options = { + concurrent => $concurrent, + log_level => $log_level, + log_format => $log_format, + check_interval => $check_interval, + sentry_dsn => $sentry_dsn, + listen_address => $listen_address, + }.filter |$key, $val| { $val =~ NotUndef } - if $cache_dir { - file_line { 'gitlab-runner-cache_dir': - path => $config_path, - line => "cache_dir = \"${cache_dir}\"", - match => '^cache_dir = .+', - } + concat::fragment { "${config_path} - header": + target => $config_path, + order => 0, + content => '# MANAGED BY PUPPET', } - if $sentry_dsn { - file_line { 'gitlab-runner-sentry_dsn': - path => $config_path, - line => "sentry_dsn = \"${sentry_dsn}\"", - match => '^sentry_dsn = .+', - } + concat::fragment { "${config_path} - global options": + target => $config_path, + order => 1, + content => gitlab_ci_runner::to_toml($global_options), } } diff --git a/manifests/init.pp b/manifests/init.pp index 821b88d..04f4c95 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -19,16 +19,16 @@ # The name of the 'xz' package. Needed for local docker installations. # @param concurrent # Limits how many jobs globally can be run concurrently. The most upper limit of jobs using all defined runners. 0 does not mean unlimited! -# @param builds_dir -# Absolute path to a directory where builds will be stored in context of selected executor (Locally, Docker, SSH). -# @param cache_dir -# Absolute path to a directory where build caches will be stored in context of selected executor (locally, Docker, SSH). If the docker executor is used, this directory needs to be included in its volumes parameter. -# @param metrics_server -# (Deprecated) [host]: to enable metrics server as described in https://docs.gitlab.com/runner/monitoring/README.html#configuration-of-the-metrics-http-server. -# @param listen_address -# Address (:) on which the Prometheus metrics HTTP server should be listening. +# @param log_level +# Log level (options: debug, info, warn, error, fatal, panic). Note that this setting has lower priority than level set by command line argument --debug, -l or --log-level +# @param log_format +# Log format (options: runner, text, json). Note that this setting has lower priority than format set by command line argument --log-format +# @param check_interval +# defines the interval length, in seconds, between new jobs check. The default value is 3; if set to 0 or lower, the default value will be used. # @param sentry_dsn # Enable tracking of all system level errors to sentry. +# @param listen_address +# Address (:) on which the Prometheus metrics HTTP server should be listening. # @param manage_docker # If docker should be installs (uses the puppetlabs-docker). # @param manage_repo @@ -45,22 +45,22 @@ # The path to the config file of Gitlab runner. # class gitlab_ci_runner ( - String $xz_package_name, # Defaults in module hieradata - Hash $runners = {}, - Hash $runner_defaults = {}, - Optional[Integer] $concurrent = undef, - Optional[String] $builds_dir = undef, - Optional[String] $cache_dir = undef, - Optional[Pattern[/.*:.+/]] $metrics_server = undef, - Optional[Pattern[/.*:.+/]] $listen_address = undef, - Optional[String] $sentry_dsn = undef, - Boolean $manage_docker = false, - Boolean $manage_repo = true, - String $package_ensure = installed, - String $package_name = 'gitlab-runner', - Stdlib::HTTPUrl $repo_base_url = 'https://packages.gitlab.com', - Optional[Stdlib::Fqdn] $repo_keyserver = undef, - String $config_path = '/etc/gitlab-runner/config.toml', + String $xz_package_name, # Defaults in module hieradata + Hash $runners = {}, + Hash $runner_defaults = {}, + Optional[Integer] $concurrent = undef, + Optional[Gitlab_ci_runner::Log_level] $log_level = undef, + Optional[Gitlab_ci_runner::Log_format] $log_format = undef, + Optional[Integer] $check_interval = undef, + Optional[String] $sentry_dsn = undef, + Optional[Pattern[/.*:.+/]] $listen_address = undef, + Boolean $manage_docker = false, + Boolean $manage_repo = true, + String $package_ensure = installed, + String $package_name = 'gitlab-runner', + Stdlib::HTTPUrl $repo_base_url = 'https://packages.gitlab.com', + Stdlib::Fqdn $repo_keyserver = undef, + String $config_path = '/etc/gitlab-runner/config.toml', ){ if $manage_docker { # workaround for cirunner issue #1617 @@ -94,16 +94,13 @@ $runners.each |$runner_name,$config| { $_config = merge($runner_defaults, $config) - $ensure = $_config['ensure'] $title = $_config['name'] ? { undef => $runner_name, default => $_config['name'], } gitlab_ci_runner::runner { $title: - ensure => $ensure, config => $_config - ['ensure', 'name'], - binary => $package_name, require => Class['gitlab_ci_runner::config'], notify => Class['gitlab_ci_runner::service'], } diff --git a/manifests/runner.pp b/manifests/runner.pp index 6ad7b85..67bbe3e 100644 --- a/manifests/runner.pp +++ b/manifests/runner.pp @@ -1,66 +1,111 @@ -# @summary This module installs and configures Gitlab CI Runners. +# @summary This configures a Gitlab CI runner. # -# @example Simple runner registration -# gitlab_ci_runner::runner { example_runner: +# @example Add a simple runner +# gitlab_ci_runner::runner { 'testrunner': +# config => { +# 'url' => 'https://gitlab.com', +# 'token' => '123456789abcdefgh', # Note this is different from the registration token used by `gitlab-runner register` +# 'executor' => 'shell', +# }, +# } +# +# @example Add a autoscaling runner with DigitalOcean as IaaS +# gitlab_ci_runner::runner { 'autoscale-runner': # config => { -# 'registration-token' => 'gitlab-token', -# 'url' => 'https://gitlab.com', -# 'tag-list' => 'docker,aws', +# url => 'https://gitlab.com', +# token => 'RUNNER_TOKEN', # Note this is different from the registration token used by `gitlab-runner register` +# name => 'autoscale-runner', +# executor => 'docker+machine', +# limit => 10, +# docker => { +# image => 'ruby:2.6', +# }, +# machine => { +# OffPeakPeriods => [ +# '* * 0-9,18-23 * * mon-fri *', +# '* * * * * sat,sun *', +# ], +# OffPeakIdleCount => 1, +# OffPeakIdleTime => 1200, +# IdleCount => 5, +# IdleTime => 600, +# MaxBuilds => 100, +# MachineName => 'auto-scale-%s', +# MachineDriver => 'digitalocean', +# MachineOptions => [ +# 'digitalocean-image=coreos-stable', +# 'digitalocean-ssh-user=core', +# 'digitalocean-access-token=DO_ACCESS_TOKEN', +# 'digitalocean-region=nyc2', +# 'digitalocean-size=4gb', +# 'digitalocean-private-networking', +# 'engine-registry-mirror=http://10.11.12.13:12345', +# ], +# }, +# cache => { +# 'Type' => 's3', +# s3 => { +# ServerAddress => 's3-eu-west-1.amazonaws.com', +# AccessKey => 'AMAZON_S3_ACCESS_KEY', +# SecretKey => 'AMAZON_S3_SECRET_KEY', +# BucketName => 'runner', +# Insecure => false, +# }, +# }, # }, # } # # @param config # Hash with configuration options. # See https://docs.gitlab.com/runner/configuration/advanced-configuration.html for all possible options. -# @param ensure -# If the runner should be 'present' or 'absent'. Will register/unregister the runner from Gitlab. -# @param runner_name -# The name of the runner. -# @param binary -# The name of the Gitlab runner binary. +# If you omit the 'name' configuration, we will automatically use the $title of this define class. # define gitlab_ci_runner::runner ( - Hash $config, - Enum['present', 'absent'] $ensure = 'present', - String[1] $runner_name = $title, - String[1] $binary = 'gitlab-runner', + Hash $config, ) { - # Ensure we have a unique runner name - $_config = { name => $runner_name } + $config - $_name = $_config['name'] + include gitlab_ci_runner - # To be able to use all parameters as command line arguments, - # we have to transform the configuration into something the gitlab-runner - # binary accepts: - # * Always prefix the options with '--' - # * Always join option names and values with '=' - # - # In the end, flatten thewhole array and join all elements with a space as delimiter - $__config = $_config.map |$item| { - # Ensure all keys use '-' instead of '_'. Needed for e.g. build_dir. - $key = regsubst($item[0], '_', '-', 'G') + $config_path = $gitlab_ci_runner::config_path + # $serverversion is empty on 'puppet apply' runs. Just use clientversion. + $_serverversion = getvar('serverversion') ? { + undef => $clientversion, + default => $serverversion, + } + $supports_deferred = (versioncmp($clientversion, '6.0') >= 0 and versioncmp($_serverversion, '6.0') >= 0) + + # Use title parameter if config hash doesn't contain one. + $_config = $config['name'] ? { + undef => $config + { name => $title }, + default => $config, + } - # If the value ($item[1]) is an Array multiple elements are added for each item - if $item[1] =~ Array { - $item[1].map |$nested| { - "--${key}=${nested}" - } + # Puppet < 6 doesn't include the Deferred type and will therefore + # fail with an compilation error while trying to load the type + if $supports_deferred { + if $_config['registration-token'] { + $register_additional_options = $config + .filter |$item| { $item[0] =~ Gitlab_ci_runner::Register_parameter } # Get all items use for the registration process + .reduce({}) |$memo, $item| { $memo + { regsubst($item[0], '-', '_', 'G') => $item[1] } } # Ensure all keys use '_' instead of '-' + + $deferred_call = Deferred('gitlab_ci_runner::register_to_file', [ $_config['url'], $_config['registration-token'], $_config['name'], $register_additional_options, ]) + + # Remove registration-token and add a 'token' key to the config with a Deferred function to get it. + $__config = ($_config - 'registration-token') + { 'token' => $deferred_call } - $register_additional_options } else { - "--${key}=${item[1]}" + $__config = $_config } - }.flatten.join(' ') - if $ensure == 'absent' { - # Execute gitlab ci multirunner unregister - exec {"Unregister_runner_${title}": - command => "/usr/bin/${binary} unregister -n ${_name}", - onlyif => "/bin/grep -F \'${_name}\' /etc/gitlab-runner/config.toml", + $content = $__config['token'] =~ Deferred ? { + true => Deferred('gitlab_ci_runner::to_toml', [{ runners => [ $__config ], }]), + false => gitlab_ci_runner::to_toml({ runners => [ $__config ], }), } } else { - # Execute gitlab ci multirunner register - exec {"Register_runner_${title}": - command => "/usr/bin/${binary} register -n ${__config}", - unless => "/bin/grep -F \'${_name}\' /etc/gitlab-runner/config.toml", - } + $content = gitlab_ci_runner::to_toml({ runners => [ $_config ], }) + } + + concat::fragment { "${config_path} - ${title}": + target => $config_path, + order => 2, + content => $content, } } diff --git a/metadata.json b/metadata.json index fc0a89e..0291dfb 100644 --- a/metadata.json +++ b/metadata.json @@ -12,6 +12,10 @@ "gitlab" ], "dependencies": [ + { + "name": "puppetlabs/concat", + "version_requirement": ">= 4.1.0 < 7.0.0" + }, { "name": "puppetlabs/stdlib", "version_requirement": ">= 4.13.0 < 7.0.0" diff --git a/spec/acceptance/class_spec.rb b/spec/acceptance/class_spec.rb new file mode 100644 index 0000000..b64a1cd --- /dev/null +++ b/spec/acceptance/class_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper_acceptance' + +describe 'gitlab_ci_runner class' do + context 'default parameters' do + it 'idempotently with no errors' do + pp = <<-EOS + include gitlab_ci_runner + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe package('gitlab-runner') do + it { is_expected.to be_installed } + end + + describe service('gitlab-runner') do + it { is_expected.to be_running } + it { is_expected.to be_enabled } + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain '# MANAGED BY PUPPET' } + end + end + + context 'concurrent => 20' do + it 'idempotently with no errors' do + pp = <<-EOS + class { 'gitlab_ci_runner': + concurrent => 20, + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain 'concurrent = 20' } + end + end + + context 'log_level => error' do + it 'idempotently with no errors' do + pp = <<-EOS + class { 'gitlab_ci_runner': + log_level => 'error', + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain 'log_level = "error"' } + end + end + + context 'log_format => text' do + it 'idempotently with no errors' do + pp = <<-EOS + class { 'gitlab_ci_runner': + log_format => 'text', + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain 'log_format = "text"' } + end + end + + context 'check_interval => 42' do + it 'idempotently with no errors' do + pp = <<-EOS + class { 'gitlab_ci_runner': + check_interval => 42, + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain 'check_interval = 42' } + end + end + + context 'sentry_dsn => https://123abc@localhost/1' do + it 'idempotently with no errors' do + pp = <<-EOS + class { 'gitlab_ci_runner': + sentry_dsn => 'https://123abc@localhost/1', + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain 'sentry_dsn = "https://123abc@localhost/1"' } + end + end + + context 'listen_address => localhost:9252' do + it 'idempotently with no errors' do + pp = <<-EOS + class { 'gitlab_ci_runner': + listen_address => 'localhost:9252', + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain 'listen_address = "localhost:9252"' } + end + end +end diff --git a/spec/acceptance/runner_spec.rb b/spec/acceptance/runner_spec.rb new file mode 100644 index 0000000..71560f4 --- /dev/null +++ b/spec/acceptance/runner_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper_acceptance' + +describe 'gitlab_ci_runner::runner define' do + context 'simple runner' do + it 'idempotently with no errors' do + pp = <<-EOS + gitlab_ci_runner::runner { 'testrunner': + config => { + 'url' => 'https://gitlab.com', + 'token' => '123456789abcdefgh', + 'executor' => 'shell', + }, + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe package('gitlab-runner') do + it { is_expected.to be_installed } + end + + describe service('gitlab-runner') do + it { is_expected.to be_running } + it { is_expected.to be_enabled } + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain '[[runners]]' } + it { is_expected.to contain 'name = "testrunner"' } + it { is_expected.to contain 'url = "https://gitlab.com"' } + it { is_expected.to contain 'token = "123456789abcdefgh"' } + it { is_expected.to contain 'executor = "shell"' } + end + end + + context 'autoscaling runner with DigitalOcean as IaaS' do + it 'idempotently with no errors' do + pp = <<-EOS + gitlab_ci_runner::runner { 'autoscale-runner': + config => { + url => 'https://gitlab.com', + token => '123456789abcdefgh', + name => 'autoscale-runner', + executor => 'docker+machine', + limit => 10, + docker => { + image => 'ruby:2.6', + }, + machine => { + 'OffPeakPeriods' => [ + '* * 0-9,18-23 * * mon-fri *', + '* * * * * sat,sun *', + ], + 'OffPeakIdleCount' => 1, + 'OffPeakIdleTime' => 1200, + 'IdleCount' => 5, + 'IdleTime' => 600, + 'MaxBuilds' => 100, + 'MachineName' => 'auto-scale-%s', + 'MachineDriver' => 'digitalocean', + 'MachineOptions' => [ + 'digitalocean-image=coreos-stable', + 'digitalocean-ssh-user=core', + 'digitalocean-access-token=DO_ACCESS_TOKEN', + 'digitalocean-region=nyc2', + 'digitalocean-size=4gb', + 'digitalocean-private-networking', + 'engine-registry-mirror=http://10.11.12.13:12345', + ], + }, + cache => { + 'Type' => 's3', + s3 => { + 'ServerAddress' => 's3-eu-west-1.amazonaws.com', + 'AccessKey' => 'AMAZON_S3_ACCESS_KEY', + 'SecretKey' => 'AMAZON_S3_SECRET_KEY', + 'BucketName' => 'runner', + 'Insecure' => false, + }, + }, + }, + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe package('gitlab-runner') do + it { is_expected.to be_installed } + end + + describe service('gitlab-runner') do + it { is_expected.to be_running } + it { is_expected.to be_enabled } + end + + describe file('/etc/gitlab-runner/config.toml') do + it { is_expected.to contain '[[runners]]' } + it { is_expected.to contain 'url = "https://gitlab.com"' } + it { is_expected.to contain 'token = "123456789abcdefgh"' } + it { is_expected.to contain 'name = "autoscale-runner"' } + it { is_expected.to contain 'executor = "docker+machine"' } + it { is_expected.to contain 'limit = 10' } + it { is_expected.to contain '[runners.docker]' } + it { is_expected.to contain 'image = "ruby:2.6"' } + it { is_expected.to contain '[runners.machine]' } + it { is_expected.to contain 'OffPeakPeriods = ["* * 0-9,18-23 * * mon-fri *", "* * * * * sat,sun *"]' } + it { is_expected.to contain 'OffPeakIdleCount = 1' } + it { is_expected.to contain 'OffPeakIdleTime = 1200' } + it { is_expected.to contain 'IdleCount = 5' } + it { is_expected.to contain 'IdleTime = 600' } + it { is_expected.to contain 'MaxBuilds = 100' } + it { is_expected.to contain 'MachineName = "auto-scale-%s"' } + it { is_expected.to contain 'MachineDriver = "digitalocean"' } + it { is_expected.to contain 'MachineOptions = ["digitalocean-image=coreos-stable", "digitalocean-ssh-user=core", "digitalocean-access-token=DO_ACCESS_TOKEN", "digitalocean-region=nyc2", "digitalocean-size=4gb", "digitalocean-private-networking", "engine-registry-mirror=http://10.11.12.13:12345"]' } + it { is_expected.to contain '[runners.cache]' } + it { is_expected.to contain 'Type = "s3"' } + it { is_expected.to contain '[runners.cache.s3]' } + it { is_expected.to contain 'ServerAddress = "s3-eu-west-1.amazonaws.com"' } + it { is_expected.to contain 'AccessKey = "AMAZON_S3_ACCESS_KEY"' } + it { is_expected.to contain 'SecretKey = "AMAZON_S3_SECRET_KEY"' } + it { is_expected.to contain 'BucketName = "runner"' } + it { is_expected.to contain 'Insecure = false' } + end + end +end diff --git a/spec/classes/gitlab_ci_runner_spec.rb b/spec/classes/gitlab_ci_runner_spec.rb index c817ef1..d8a8ccc 100644 --- a/spec/classes/gitlab_ci_runner_spec.rb +++ b/spec/classes/gitlab_ci_runner_spec.rb @@ -7,6 +7,14 @@ on_supported_os.each do |os, facts| context "on #{os}" do + before do + # Make 'gitlab_ci_runner::register_to_file' think that we already have a token on disk + # This ensure the function won't call a Gitlab server to try getting the auth token. + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with('/etc/gitlab-runner/auth-token-test_runner').and_return(true) + allow(File).to receive(:read).and_call_original + allow(File).to receive(:read).with('/etc/gitlab-runner/auth-token-test_runner').and_return('authtoken') + end let(:facts) do facts end @@ -36,106 +44,129 @@ that_notifies('Class[gitlab_ci_runner::service]') end it { is_expected.to contain_class('gitlab_ci_runner::service') } - it { is_expected.to contain_file('/etc/gitlab-runner/config.toml') } - it { is_expected.not_to contain_file_line('gitlab-runner-concurrent') } - it { is_expected.not_to contain_file_line('gitlab-runner-metrics_server') } - it { is_expected.not_to contain_file_line('gitlab-runner-builds_dir') } - it { is_expected.not_to contain_file_line('gitlab-runner-cache_dir') } + it do + is_expected.to contain_concat('/etc/gitlab-runner/config.toml'). + with( + ensure: 'present', + owner: 'root', + group: 'root', + mode: '0444', + ensure_newline: true + ) + end + + it do + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - header'). + with( + target: '/etc/gitlab-runner/config.toml', + order: 0, + content: '# MANAGED BY PUPPET' + ) + end context 'with concurrent => 10' do let(:params) do { - 'runner_defaults' => {}, - 'runners' => {}, 'concurrent' => 10 } end it do - is_expected.to contain_file_line('gitlab-runner-concurrent').with('path' => '/etc/gitlab-runner/config.toml', - 'line' => 'concurrent = 10', - 'match' => '^concurrent = \d+') + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - global options'). + with( + target: '/etc/gitlab-runner/config.toml', + order: 1, + content: %r{concurrent = 10} + ) end end - context 'with metrics_server => localhost:9252' do + + context 'with log_level => error' do let(:params) do { - 'runner_defaults' => {}, - 'runners' => {}, - 'metrics_server' => 'localhost:9252' + 'log_level' => 'error' } end it do - is_expected.to contain_file_line('gitlab-runner-metrics_server').with('path' => '/etc/gitlab-runner/config.toml', - 'line' => 'metrics_server = "localhost:9252"', - 'match' => '^metrics_server = .+') + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - global options'). + with( + target: '/etc/gitlab-runner/config.toml', + order: 1, + content: %r{log_level = "error"} + ) end end - context 'with listen_address => localhost:9252' do + + context 'with log_format => json' do let(:params) do { - 'runner_defaults' => {}, - 'runners' => {}, - 'listen_address' => 'localhost:9252' + 'log_format' => 'json' } end it do - is_expected.to contain_file_line('gitlab-runner-listen-address'). + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - global options'). with( - path: '/etc/gitlab-runner/config.toml', - line: 'listen_address = "localhost:9252"', - match: '^listen_address = .+' + target: '/etc/gitlab-runner/config.toml', + order: 1, + content: %r{log_format = "json"} ) end end - context 'with builds_dir => /tmp/builds_dir' do + + context 'with check_interval => 6' do let(:params) do { - 'runner_defaults' => {}, - 'runners' => {}, - 'builds_dir' => '/tmp/builds_dir' + 'check_interval' => 6 } end it do - is_expected.to contain_file_line('gitlab-runner-builds_dir').with('path' => '/etc/gitlab-runner/config.toml', - 'line' => 'builds_dir = "/tmp/builds_dir"', - 'match' => '^builds_dir = .+') + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - global options'). + with( + target: '/etc/gitlab-runner/config.toml', + order: 1, + content: %r{check_interval = 6} + ) end end - context 'with cache_dir => /tmp/cache_dir' do + + context 'with sentry_dsn => https://123abc@localhost/1' do let(:params) do { - 'runner_defaults' => {}, - 'runners' => {}, - 'cache_dir' => '/tmp/cache_dir' + 'sentry_dsn' => 'https://123abc@localhost/1' } end it do - is_expected.to contain_file_line('gitlab-runner-cache_dir').with('path' => '/etc/gitlab-runner/config.toml', - 'line' => 'cache_dir = "/tmp/cache_dir"', - 'match' => '^cache_dir = .+') + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - global options'). + with( + target: '/etc/gitlab-runner/config.toml', + order: 1, + content: %r{sentry_dsn = "https://123abc@localhost/1"} + ) end end - context 'with sentry_dsn => https://123abc@localhost/1' do + + context 'with listen_address => localhost:9252' do let(:params) do { - 'runner_defaults' => {}, - 'runners' => {}, - 'sentry_dsn' => 'https://123abc@localhost/1' + 'listen_address' => 'localhost:9252' } end it do - is_expected.to contain_file_line('gitlab-runner-sentry_dsn').with('path' => '/etc/gitlab-runner/config.toml', - 'line' => 'sentry_dsn = "https://123abc@localhost/1"', - 'match' => '^sentry_dsn = .+') + is_expected.to contain_concat__fragment('/etc/gitlab-runner/config.toml - global options'). + with( + target: '/etc/gitlab-runner/config.toml', + order: 1, + content: %r{listen_address = "localhost:9252"} + ) end end + context 'with ensure => present' do let(:params) do super().merge( diff --git a/spec/defines/runner_spec.rb b/spec/defines/runner_spec.rb index da461db..58dad67 100644 --- a/spec/defines/runner_spec.rb +++ b/spec/defines/runner_spec.rb @@ -5,85 +5,164 @@ context "on #{os}" do let(:facts) { facts } let(:title) { 'testrunner' } - let(:default_params) do - { - config: { - 'registration-token' => 'gitlab-token', - 'url' => 'https://gitlab.com' - } - } - end - context 'with default params' do - let(:params) { default_params } + context 'with simple shell runner' do + let(:params) do + { + config: { + url: 'https://gitlab.com', + token: '123456789abcdefgh', + executor: 'shell' + } + } + end it { is_expected.to compile.with_all_deps } it do - is_expected.to contain_exec('Register_runner_testrunner').with( - command: '/usr/bin/gitlab-runner register -n --name=testrunner --registration-token=gitlab-token --url=https://gitlab.com', - unless: "/bin/grep -F 'testrunner' /etc/gitlab-runner/config.toml" + verify_concat_fragment_exact_contents( + catalogue, + '/etc/gitlab-runner/config.toml - testrunner', + [ + '[[runners]]', + 'name = "testrunner"', + 'url = "https://gitlab.com"', + 'token = "123456789abcdefgh"', + 'executor = "shell"' + ] ) end end - context 'with ensure => absent' do + context 'with autoscaling runner with DigitalOcean as IaaS' do let(:params) do - default_params.merge(ensure: 'absent') + { + config: { + url: 'https://gitlab.com', + token: '123456789abcdefgh', + name: 'autoscale-runner', + executor: 'docker+machine', + limit: 10, + docker: { + image: 'ruby:2.6' + }, + machine: { + OffPeakPeriods: [ + '* * 0-9,18-23 * * mon-fri *', + '* * * * * sat,sun *' + ], + OffPeakIdleCount: 1, + OffPeakIdleTime: 1200, + IdleCount: 5, + IdleTime: 600, + MaxBuilds: 100, + MachineName: 'auto-scale-%s', + MachineDriver: 'digitalocean', + MachineOptions: [ + 'digitalocean-image=coreos-stable', + 'digitalocean-ssh-user=core', + 'digitalocean-access-token=DO_ACCESS_TOKEN', + 'digitalocean-region=nyc2', + 'digitalocean-size=4gb', + 'digitalocean-private-networking', + 'engine-registry-mirror=http://10.11.12.13:12345' + ] + }, + cache: { + Type: 's3', + s3: { + ServerAddress: 's3-eu-west-1.amazonaws.com', + AccessKey: 'AMAZON_S3_ACCESS_KEY', + SecretKey: 'AMAZON_S3_SECRET_KEY', + BucketName: 'runner', + Insecure: false + } + } + } + } end it { is_expected.to compile.with_all_deps } it do - is_expected.to contain_exec('Unregister_runner_testrunner').with( - command: '/usr/bin/gitlab-runner unregister -n testrunner', - onlyif: "/bin/grep -F 'testrunner' /etc/gitlab-runner/config.toml" + verify_concat_fragment_exact_contents( + catalogue, + '/etc/gitlab-runner/config.toml - testrunner', + [ + '[[runners]]', + 'url = "https://gitlab.com"', + 'token = "123456789abcdefgh"', + 'name = "autoscale-runner"', + 'executor = "docker+machine"', + 'limit = 10', + '[runners.docker]', + 'image = "ruby:2.6"', + '[runners.machine]', + 'OffPeakPeriods = ["* * 0-9,18-23 * * mon-fri *", "* * * * * sat,sun *"]', + 'OffPeakIdleCount = 1', + 'OffPeakIdleTime = 1200', + 'IdleCount = 5', + 'IdleTime = 600', + 'MaxBuilds = 100', + 'MachineName = "auto-scale-%s"', + 'MachineDriver = "digitalocean"', + 'MachineOptions = ["digitalocean-image=coreos-stable", "digitalocean-ssh-user=core", "digitalocean-access-token=DO_ACCESS_TOKEN", "digitalocean-region=nyc2", "digitalocean-size=4gb", "digitalocean-private-networking", "engine-registry-mirror=http://10.11.12.13:12345"]', + '[runners.cache]', + 'Type = "s3"', + '[runners.cache.s3]', + 'ServerAddress = "s3-eu-west-1.amazonaws.com"', + 'AccessKey = "AMAZON_S3_ACCESS_KEY"', + 'SecretKey = "AMAZON_S3_SECRET_KEY"', + 'BucketName = "runner"', + 'Insecure = false' + ] ) end end - context 'with binary => special-gitlab-runner' do - let(:params) { default_params.merge(binary: 'special-gitlab-runner') } - - context 'with ensure => present' do - it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_exec('Register_runner_testrunner').with_command(%r{^/usr/bin/special-gitlab-runner}) } - end - - context 'with ensure => absent' do - let(:params) do - super().merge(ensure: 'absent') - end - - it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_exec('Unregister_runner_testrunner').with_command(%r{^/usr/bin/special-gitlab-runner}) } - end - end - - context 'with config having a key containing _' do + context 'with name not included in config' do let(:params) do { config: { - build_dir: '/tmp' } } end it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_exec('Register_runner_testrunner').with_command(%r{--build-dir=/tmp}) } + + it do + verify_concat_fragment_exact_contents( + catalogue, + '/etc/gitlab-runner/config.toml - testrunner', + [ + '[[runners]]', + 'name = "testrunner"' + ] + ) + end end - context 'with config having a key which has an array as value' do + context 'with name included in config' do let(:params) do { config: { - 'docker-volumes' => ['test0:/test0', 'test1:/test1'] + name: 'foo-runner' } } end it { is_expected.to compile.with_all_deps } - it { is_expected.to contain_exec('Register_runner_testrunner').with_command(%r{--docker-volumes=test0:/test0 --docker-volumes=test1:/test1}) } + + it do + verify_concat_fragment_exact_contents( + catalogue, + '/etc/gitlab-runner/config.toml - testrunner', + [ + '[[runners]]', + 'name = "foo-runner"' + ] + ) + end end end end diff --git a/spec/functions/register_spec.rb b/spec/functions/register_spec.rb new file mode 100644 index 0000000..24d93df --- /dev/null +++ b/spec/functions/register_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe 'gitlab_ci_runner::register' do + let(:url) { 'https://gitlab.example.org' } + let(:regtoken) { 'registration-token' } + let(:return_hash) do + { + 'id' => 1234, + 'token' => 'auth-token' + } + end + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('').and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('ftp://foooo.bar').and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('https://gitlab.com', 1234).and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('https://gitlab.com', 'registration-token', project: 1234).and_raise_error(ArgumentError) } + + it "calls 'PuppetX::Gitlab::Runner.register'" do + allow(PuppetX::Gitlab::Runner).to receive(:register).with(url, 'token' => regtoken).and_return(return_hash) + + is_expected.to run.with_params(url, regtoken).and_return(return_hash) + expect(PuppetX::Gitlab::Runner).to have_received(:register) + end + + it "passes additional args to 'PuppetX::Gitlab::Runner.register'" do + allow(PuppetX::Gitlab::Runner).to receive(:register).with(url, 'token' => regtoken, 'active' => false).and_return(return_hash) + + is_expected.to run.with_params(url, regtoken, 'active' => false).and_return(return_hash) + expect(PuppetX::Gitlab::Runner).to have_received(:register) + end +end diff --git a/spec/functions/register_to_file_spec.rb b/spec/functions/register_to_file_spec.rb new file mode 100644 index 0000000..7f22d23 --- /dev/null +++ b/spec/functions/register_to_file_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe 'gitlab_ci_runner::register_to_file' do + let(:url) { 'https://gitlab.example.org' } + let(:regtoken) { 'registration-token' } + let(:runner_name) { 'testrunner' } + let(:filename) { "/etc/gitlab-runner/auth-token-#{runner_name}" } + let(:return_hash) do + { + 'id' => 1234, + 'token' => 'auth-token' + } + end + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('').and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('ftp://foooo.bar').and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('https://gitlab.com', 1234).and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('https://gitlab.com', 'registration-token', project: 1234).and_raise_error(ArgumentError) } + + context 'uses an existing auth token from file' do + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with(filename).and_return(true) + allow(File).to receive(:read).and_call_original + allow(File).to receive(:read).with(filename).and_return(return_hash['token']) + end + + it { is_expected.to run.with_params(url, regtoken, runner_name).and_return(return_hash['token']) } + end + + context "retrieves from Gitlab and writes auth token to file if it doesn't exist" do + before do + allow(PuppetX::Gitlab::Runner).to receive(:register).with(url, 'token' => regtoken).and_return(return_hash) + allow(File).to receive(:write).with(filename, return_hash['token']) + allow(File).to receive(:chmod).with(0o400, filename) + end + + it { is_expected.to run.with_params(url, regtoken, runner_name).and_return(return_hash['token']) } + end +end diff --git a/spec/functions/to_toml_spec.rb b/spec/functions/to_toml_spec.rb new file mode 100644 index 0000000..3c4264d --- /dev/null +++ b/spec/functions/to_toml_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'gitlab_ci_runner::to_toml' do + context 'fails on invalid params' do + it { is_expected.not_to eq(nil) } + [ + nil, + '', + 'foobar', + 1, + true, + false, + [] + ].each do |value| + it { is_expected.to run.with_params(value).and_raise_error(ArgumentError) } + end + end + + context 'returns TOML string on valid params' do + it { is_expected.to run.with_params({}).and_return('') } + it { is_expected.to run.with_params(foo: 'bar').and_return("foo = \"bar\"\n") } + it { is_expected.to run.with_params(foo: { bar: 'baz' }).and_return("[foo]\nbar = \"baz\"\n") } + it { is_expected.to run.with_params(foo: %w[bar baz]).and_return("foo = [\"bar\", \"baz\"]\n") } + it { is_expected.to run.with_params(foo: [{ bar: {}, baz: {} }]).and_return("[[foo]]\n[foo.bar]\n[foo.baz]\n") } + end +end diff --git a/spec/functions/unregister_spec.rb b/spec/functions/unregister_spec.rb new file mode 100644 index 0000000..f8270f5 --- /dev/null +++ b/spec/functions/unregister_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require 'webmock/rspec' + +describe 'gitlab_ci_runner::unregister' do + let(:url) { 'https://gitlab.example.org' } + let(:authtoken) { 'registration-token' } + let(:return_hash) { { 'status' => 'success' } } + + it { is_expected.not_to eq(nil) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('').and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('ftp://foooo.bar').and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('https://gitlab.com', 1234).and_raise_error(ArgumentError) } + it { is_expected.to run.with_params('https://gitlab.com', 'registration-token', project: 1234).and_raise_error(ArgumentError) } + + it "calls 'PuppetX::Gitlab::Runner.unregister'" do + allow(PuppetX::Gitlab::Runner).to receive(:unregister).with(url, token: authtoken).and_return(return_hash) + + is_expected.to run.with_params(url, authtoken).and_return(return_hash) + expect(PuppetX::Gitlab::Runner).to have_received(:unregister) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b2b2704..84944fa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,3 +16,5 @@ end end end + +require 'spec_helper_local' diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb new file mode 100644 index 0000000..4987b1b --- /dev/null +++ b/spec/spec_helper_local.rb @@ -0,0 +1,6 @@ +# Taken from https://github.com/theforeman/puppet-puppet/blob/143199e1f529581f138fcd9c8edea2990ea4a69c/spec/spec_helper.rb +def verify_concat_fragment_exact_contents(subject, title, expected_lines) + is_expected.to contain_concat__fragment(title) + content = subject.resource('concat::fragment', title).send(:parameters)[:content] + expect(content.split(%r{\n}).reject { |line| line =~ %r{(^#|^$|^\s+#)} }).to match_array(expected_lines) +end diff --git a/spec/tasks/register_runner_spec.rb b/spec/tasks/register_runner_spec.rb index 519e052..6879039 100644 --- a/spec/tasks/register_runner_spec.rb +++ b/spec/tasks/register_runner_spec.rb @@ -4,25 +4,26 @@ describe RegisterRunnerTask do let(:params) do { - url: 'https://gitlab.example.org', - token: 'abcdef1234' + token: 'registrationtoken', + url: 'https://gitlab.example.org' } end - let(:task) { described_class.new } + let(:described_object) { described_class.new } - describe 'task' do + describe '.task' do it 'can register a runner' do - stub_request(:post, 'https://gitlab.example.org/api/v4/runners'). - with(body: { token: 'abcdef1234' }). - to_return(body: '{"id": 777, "token": "3bz5wqfDiYBhxoUNuGVu"}') - expect(task.task(params)).to eq('id' => 777, 'token' => '3bz5wqfDiYBhxoUNuGVu') + allow(PuppetX::Gitlab::Runner).to receive(:register). + with('https://gitlab.example.org', token: 'registrationtoken'). + and_return('id' => 777, 'token' => '3bz5wqfDiYBhxoUNuGVu') + + expect(described_object.task(params)).to eq('id' => 777, 'token' => '3bz5wqfDiYBhxoUNuGVu') end it 'can raise an error' do params.merge(token: 'invalid-token') stub_request(:post, 'https://gitlab.example.org/api/v4/runners'). to_return(status: [403, 'Forbidden']) - expect { task.task(params) }.to raise_error(TaskHelper::Error, %r{Gitlab runner failed to register: Forbidden}) + expect { described_object.task(params) }.to raise_error(TaskHelper::Error, %r{Gitlab runner failed to register: Forbidden}) end end end diff --git a/spec/tasks/unregister_runner_spec.rb b/spec/tasks/unregister_runner_spec.rb index be010c2..94438c6 100644 --- a/spec/tasks/unregister_runner_spec.rb +++ b/spec/tasks/unregister_runner_spec.rb @@ -8,21 +8,22 @@ token: 'abcdef1234' } end - let(:task) { described_class.new } + let(:described_object) { described_class.new } - describe 'task' do + describe '.task' do it 'can unregister a runner' do - stub_request(:delete, 'https://gitlab.example.org/api/v4/runners'). - with(body: { token: 'abcdef1234' }). - to_return(body: nil) - expect(task.task(params)).to eq(status: 'success') + allow(PuppetX::Gitlab::Runner).to receive(:unregister). + with('https://gitlab.example.org', token: 'abcdef1234'). + and_return({}) + + expect(described_object.task(params)).to eq(status: 'success') end it 'can raise an error' do params.merge(token: 'invalid-token') stub_request(:delete, 'https://gitlab.example.org/api/v4/runners'). to_return(status: [403, 'Forbidden']) - expect { task.task(params) }.to raise_error(TaskHelper::Error, %r{Gitlab runner failed to unregister: Forbidden}) + expect { described_object.task(params) }.to raise_error(TaskHelper::Error, %r{Gitlab runner failed to unregister: Forbidden}) end end end diff --git a/spec/type_aliases/log_format.rb b/spec/type_aliases/log_format.rb new file mode 100644 index 0000000..07a7c44 --- /dev/null +++ b/spec/type_aliases/log_format.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe 'Gitlab_ci_runner::Log_format' do + %w[runner text json].each do |value| + it { is_expected.to allow_value(value) } + end + + [:undef, 1, '', 'WARN', 'DEBUG1', true, false, 42]. each do |value| + it { is_expected.not_to allow_value(value) } + end +end diff --git a/spec/type_aliases/log_level.rb b/spec/type_aliases/log_level.rb new file mode 100644 index 0000000..f91f008 --- /dev/null +++ b/spec/type_aliases/log_level.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe 'Gitlab_ci_runner::Log_level' do + %w[debug info warn error fatal panic].each do |value| + it { is_expected.to allow_value(value) } + end + + [:undef, 1, '', 'WARN', 'DEBUG1', true, false, 42]. each do |value| + it { is_expected.not_to allow_value(value) } + end +end diff --git a/spec/unit/puppet_x/gitlab/runner_spec.rb b/spec/unit/puppet_x/gitlab/runner_spec.rb new file mode 100644 index 0000000..ef23c7c --- /dev/null +++ b/spec/unit/puppet_x/gitlab/runner_spec.rb @@ -0,0 +1,119 @@ +require 'spec_helper' +require 'webmock/rspec' +require_relative '../../../../lib/puppet_x/gitlab/runner.rb' + +module PuppetX::Gitlab + describe APIClient do + describe 'self.delete' do + it 'returns an empty hash' do + stub_request(:delete, 'https://example.org'). + with( + body: '{}', + headers: { + 'Accept' => 'application/json', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'Content-Type' => 'application/json', + 'User-Agent' => 'Ruby' + } + ). + to_return(status: 204, body: '', headers: {}) + + expect(described_class.delete('https://example.org', {})).to eq({}) + end + + it 'raises an exception on non 200 http code' do + stub_request(:delete, 'https://example.org'). + to_return(status: 403) + + expect { described_class.delete('https://example.org', {}) }.to raise_error(Net::HTTPError) + end + end + + describe 'self.post' do + it 'returns a hash' do + stub_request(:post, 'https://example.org'). + with( + body: '{}', + headers: { + 'Accept' => 'application/json', + 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'Content-Type' => 'application/json', + 'User-Agent' => 'Ruby' + } + ). + to_return(status: 201, body: '{ "id": "12345", "token": "6337ff461c94fd3fa32ba3b1ff4125" }', headers: {}) + + expect(described_class.post('https://example.org', {})).to eq('id' => '12345', 'token' => '6337ff461c94fd3fa32ba3b1ff4125') + end + + it 'raises an exception on non 200 http code' do + stub_request(:delete, 'https://example.org'). + to_return(status: 501) + + expect { described_class.delete('https://example.org', {}) }.to raise_error(Net::HTTPError) + end + end + + describe 'self.request' do + context 'when doing a request' do + before do + stub_request(:post, 'example.org') + described_class.request('http://example.org/', Net::HTTP::Post, {}) + end + + it 'uses Accept: application/json' do + expect(a_request(:post, 'example.org'). + with(headers: { 'Accept' => 'application/json' })). + to have_been_made + end + + it 'uses Content: application/json' do + expect(a_request(:post, 'example.org'). + with(headers: { 'Content-Type' => 'application/json' })). + to have_been_made + end + end + + context 'when doing a HTTPS request' do + before do + stub_request(:post, 'https://example.org/') + described_class.request('https://example.org/', Net::HTTP::Post, {}) + end + + it 'uses SSL if url contains https://' do + expect(a_request(:post, 'https://example.org/')).to have_been_made + end + end + end + end + + describe Runner do + describe 'self.register' do + before do + PuppetX::Gitlab::APIClient. + stub(:post). + with('https://gitlab.example.org/api/v4/runners', token: 'registrationtoken'). + and_return('id' => 1234, 'token' => '1234567890abcd') + end + let(:response) { described_class.register('https://gitlab.example.org', token: 'registrationtoken') } + + it 'returns a token' do + expect(response['token']).to eq('1234567890abcd') + end + end + + describe 'self.unregister' do + before do + PuppetX::Gitlab::APIClient. + stub(:delete). + with('https://gitlab.example.org/api/v4/runners', token: '1234567890abcd'). + and_return({}) + end + let(:response) { described_class.unregister('https://gitlab.example.org', token: '1234567890abcd') } + + it 'returns an empty hash' do + expect(response).to eq({}) + end + end + end +end diff --git a/tasks/register_runner.json b/tasks/register_runner.json index e9b7260..17046f4 100644 --- a/tasks/register_runner.json +++ b/tasks/register_runner.json @@ -1,6 +1,9 @@ { "description": "Registers a runner on a Gitlab instance.", - "files": ["ruby_task_helper/files/task_helper.rb"], + "files": [ + "ruby_task_helper/files/task_helper.rb", + "gitlab_ci_runner/lib/puppet_x/gitlab/runner.rb" + ], "input_method": "stdin", "parameters": { "url": { diff --git a/tasks/register_runner.rb b/tasks/register_runner.rb index cef32a1..85c33ed 100755 --- a/tasks/register_runner.rb +++ b/tasks/register_runner.rb @@ -1,31 +1,18 @@ #!/opt/puppetlabs/puppet/bin/ruby # frozen_string_literal: true -require 'json' -require 'net/http' -require 'uri' +require_relative '../lib/puppet_x/gitlab/runner.rb' require_relative '../../ruby_task_helper/files/task_helper.rb' class RegisterRunnerTask < TaskHelper def task(**kwargs) - host = kwargs[:url] + url = kwargs[:url] options = kwargs.reject { |key, _| %i[_task _installdir url].include?(key) } - uri = URI.parse("#{host}/api/v4/runners") - headers = { - 'Accept' => 'application/json', - 'Content-Type' => 'application/json' - } - http = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') - request = Net::HTTP::Post.new(uri.request_uri, headers) - request.body = options.to_json - response = http.request(request) - - if response.is_a?(Net::HTTPSuccess) - JSON.parse(response.body) - else - msg = "Gitlab runner failed to register: #{response.message}" - raise TaskHelper::Error.new(msg, 'bolt-plugin/gitlab-ci-runner-register-error') + begin + PuppetX::Gitlab::Runner.register(url, options) + rescue Net::HTTPError => e + raise TaskHelper::Error.new("Gitlab runner failed to register: #{e.message}", 'bolt-plugin/gitlab-ci-runner-register-error') end end end diff --git a/tasks/unregister_runner.json b/tasks/unregister_runner.json index 30ad51f..d931956 100644 --- a/tasks/unregister_runner.json +++ b/tasks/unregister_runner.json @@ -1,6 +1,9 @@ { "description": "Unregisters a runner from a Gitlab instance.", - "files": ["ruby_task_helper/files/task_helper.rb"], + "files": [ + "ruby_task_helper/files/task_helper.rb", + "gitlab_ci_runner/lib/puppet_x/gitlab/runner.rb" + ], "input_method": "stdin", "parameters": { "url": { diff --git a/tasks/unregister_runner.rb b/tasks/unregister_runner.rb index a7468a8..0614ba5 100755 --- a/tasks/unregister_runner.rb +++ b/tasks/unregister_runner.rb @@ -1,33 +1,19 @@ #!/opt/puppetlabs/puppet/bin/ruby # frozen_string_literal: true -require 'json' -require 'net/http' -require 'uri' +require_relative '../lib/puppet_x/gitlab/runner.rb' require_relative '../../ruby_task_helper/files/task_helper.rb' class UnregisterRunnerTask < TaskHelper def task(**kwargs) - host = kwargs[:url] + url = kwargs[:url] options = kwargs.reject { |key, _| %i[_task _installdir url].include?(key) } - uri = URI.parse("#{host}/api/v4/runners") - headers = { - 'Accept' => 'application/json', - 'Content-Type' => 'application/json' - } - http = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') - request = Net::HTTP::Delete.new(uri.request_uri, headers) - request.body = options.to_json - response = http.request(request) - - if response.is_a?(Net::HTTPSuccess) - { - status: 'success' - } - else - msg = "Gitlab runner failed to unregister: #{response.message}" - raise TaskHelper::Error.new(msg, 'bolt-plugin/gitlab-ci-runner-unregister-error') + begin + PuppetX::Gitlab::Runner.unregister(url, options) + { status: 'success' } + rescue Net::HTTPError => e + raise TaskHelper::Error.new("Gitlab runner failed to unregister: #{e.message}", 'bolt-plugin/gitlab-ci-runner-unregister-error') end end end diff --git a/types/log_format.pp b/types/log_format.pp new file mode 100644 index 0000000..6a5ca44 --- /dev/null +++ b/types/log_format.pp @@ -0,0 +1,2 @@ +# @summary Gitlab Runner log format configuration +type Gitlab_ci_runner::Log_format = Enum['runner', 'text', 'json'] diff --git a/types/log_level.pp b/types/log_level.pp new file mode 100644 index 0000000..58090e7 --- /dev/null +++ b/types/log_level.pp @@ -0,0 +1,2 @@ +# @summary Gitlab Runner log level configuration +type Gitlab_ci_runner::Log_level = Enum['debug', 'info', 'warn', 'error', 'fatal', 'panic'] diff --git a/types/register.pp b/types/register.pp new file mode 100644 index 0000000..09bc9de --- /dev/null +++ b/types/register.pp @@ -0,0 +1,11 @@ +# @summary A struct of all possible additionl options for gitlab_ci_runner::register +type Gitlab_ci_runner::Register = Struct[{ + Optional[description] => String[1], + Optional[info] => Hash[String[1],String[1]], + Optional[active] => Boolean, + Optional[locked] => Boolean, + Optional[run_untagged] => Boolean, + Optional[tag_list] => Array[String[1]], + Optional[access_level] => Enum['not_protected', 'ref_protected'], + Optional[maximum_timeout] => Integer, +}] diff --git a/types/register_parameter.pp b/types/register_parameter.pp new file mode 100644 index 0000000..40f8eac --- /dev/null +++ b/types/register_parameter.pp @@ -0,0 +1,2 @@ +# @summary A enum containing a possible keys used for Gitlab runner registrations +type Gitlab_ci_runner::Register_parameter = Enum['description', 'info', 'active', 'locked', 'run_untagged', 'run-untagged', 'tag_list', 'tag-list', 'access_level', 'access-level', 'maximum_timeout', 'maximum-timeout']