diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/api/deployments.rb | 82 | ||||
-rw-r--r-- | lib/api/members.rb | 21 | ||||
-rw-r--r-- | lib/api/settings.rb | 1 | ||||
-rw-r--r-- | lib/gitlab.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/ci/ansi2json.rb | 12 | ||||
-rw-r--r-- | lib/gitlab/ci/ansi2json/converter.rb | 131 | ||||
-rw-r--r-- | lib/gitlab/ci/ansi2json/line.rb | 93 | ||||
-rw-r--r-- | lib/gitlab/ci/ansi2json/parser.rb | 200 | ||||
-rw-r--r-- | lib/gitlab/ci/ansi2json/state.rb | 98 | ||||
-rw-r--r-- | lib/gitlab/ci/ansi2json/style.rb | 84 | ||||
-rw-r--r-- | lib/gitlab/cycle_analytics/summary/deploy.rb | 2 | ||||
-rw-r--r-- | lib/gitlab/diff/position_collection.rb | 18 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/renderer.rb | 11 | ||||
-rw-r--r-- | lib/gitlab/graphql/docs/templates/default.md.haml | 3 | ||||
-rw-r--r-- | lib/gitlab/health_checks/checks.rb | 14 | ||||
-rw-r--r-- | lib/gitlab/health_checks/probes/collection.rb (renamed from lib/gitlab/health_checks/probes/readiness.rb) | 7 | ||||
-rw-r--r-- | lib/gitlab/health_checks/probes/liveness.rb | 13 | ||||
-rw-r--r-- | lib/gitlab/metrics/exporter/base_exporter.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/metrics/exporter/web_exporter.rb | 2 | ||||
-rw-r--r-- | lib/tasks/gitlab/graphql.rake | 20 |
20 files changed, 771 insertions, 53 deletions
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index eb45df31ff9..da882547071 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -42,6 +42,88 @@ module API present deployment, with: Entities::Deployment end + + desc 'Creates a new deployment' do + detail 'This feature was introduced in GitLab 12.4' + success Entities::Deployment + end + params do + requires :environment, + type: String, + desc: 'The name of the environment to deploy to' + + requires :sha, + type: String, + desc: 'The SHA of the commit that was deployed' + + requires :ref, + type: String, + desc: 'The name of the branch or tag that was deployed' + + requires :tag, + type: Boolean, + desc: 'A boolean indicating if the deployment ran for a tag' + + requires :status, + type: String, + desc: 'The status of the deployment', + values: %w[running success failed canceled] + end + post ':id/deployments' do + authorize!(:create_deployment, user_project) + authorize!(:create_environment, user_project) + + environment = user_project + .environments + .find_or_create_by_name(params[:environment]) + + unless environment.persisted? + render_validation_error!(deployment) + end + + authorize!(:create_deployment, environment) + + service = ::Deployments::CreateService + .new(environment, current_user, declared_params) + + deployment = service.execute + + if deployment.persisted? + present(deployment, with: Entities::Deployment, current_user: current_user) + else + render_validation_error!(deployment) + end + end + + desc 'Updates an existing deployment' do + detail 'This feature was introduced in GitLab 12.4' + success Entities::Deployment + end + params do + requires :status, + type: String, + desc: 'The new status of the deployment', + values: %w[running success failed canceled] + end + put ':id/deployments/:deployment_id' do + authorize!(:read_deployment, user_project) + + deployment = user_project.deployments.find(params[:deployment_id]) + + authorize!(:update_deployment, deployment) + + if deployment.deployable + forbidden!('Deployments created using GitLab CI can not be updated using the API') + end + + service = ::Deployments::UpdateService.new(deployment, declared_params) + + if service.execute + present(deployment, with: Entities::Deployment, current_user: current_user) + else + render_validation_error!(deployment) + end + end end end end diff --git a/lib/api/members.rb b/lib/api/members.rb index 461ffe71a62..1d4616fed52 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -18,6 +18,7 @@ module API end params do optional :query, type: String, desc: 'A query string to search for members' + optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership' use :pagination end # rubocop: disable CodeReuse/ActiveRecord @@ -26,6 +27,7 @@ module API members = source.members.where.not(user_id: nil).includes(:user) members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present? + members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? members = paginate(members) present members, with: Entities::Member @@ -37,6 +39,7 @@ module API end params do optional :query, type: String, desc: 'A query string to search for members' + optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership' use :pagination end # rubocop: disable CodeReuse/ActiveRecord @@ -45,6 +48,7 @@ module API members = find_all_members(source_type, source) members = members.includes(:user).references(:user).merge(User.search(params[:query])) if params[:query].present? + members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? members = paginate(members) present members, with: Entities::Member @@ -68,6 +72,23 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Gets a member of a group or project, including those who gained membership through ancestor group' do + success Entities::Member + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the member' + end + # rubocop: disable CodeReuse/ActiveRecord + get ":id/members/all/:user_id" do + source = find_source(source_type, params[:id]) + + members = find_all_members(source_type, source) + member = members.find_by!(user_id: params[:user_id]) + + present member, with: Entities::Member + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Adds a member to a group or project.' do success Entities::Member end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index e4ef507228b..b7a471f14fe 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -101,6 +101,7 @@ module API optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.' optional :project_export_enabled, type: Boolean, desc: 'Enable project export' optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics' + optional :push_event_hooks_limit, type: Integer, desc: "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value." optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts' given recaptcha_enabled: ->(val) { val } do requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha' diff --git a/lib/gitlab.rb b/lib/gitlab.rb index 0cc9a6a5fb1..ad8e693ccbc 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -69,14 +69,14 @@ module Gitlab # means that checking the presence of the License class could result in # this method returning `false`, even for an EE installation. # - # The `IS_GITLAB_EE` is always `string` or `nil` + # The `FOSS_ONLY` is always `string` or `nil` # Thus the nil or empty string will result - # in using default value: true + # in using default value: false # # The behavior needs to be synchronised with # config/helpers/is_ee_env.js root.join('ee/app/models/license.rb').exist? && - (ENV['IS_GITLAB_EE'].to_s.empty? || Gitlab::Utils.to_boolean(ENV['IS_GITLAB_EE'])) + !%w[true 1].include?(ENV['FOSS_ONLY'].to_s) end def self.ee diff --git a/lib/gitlab/ci/ansi2json.rb b/lib/gitlab/ci/ansi2json.rb new file mode 100644 index 00000000000..79114d35916 --- /dev/null +++ b/lib/gitlab/ci/ansi2json.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Convert terminal stream to JSON +module Gitlab + module Ci + module Ansi2json + def self.convert(ansi, state = nil) + Converter.new.convert(ansi, state) + end + end + end +end diff --git a/lib/gitlab/ci/ansi2json/converter.rb b/lib/gitlab/ci/ansi2json/converter.rb new file mode 100644 index 00000000000..53adaf38b87 --- /dev/null +++ b/lib/gitlab/ci/ansi2json/converter.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Ansi2json + class Converter + def convert(stream, new_state) + @lines = [] + @state = State.new(new_state, stream.size) + + append = false + truncated = false + + cur_offset = stream.tell + if cur_offset > @state.offset + @state.offset = cur_offset + truncated = true + else + stream.seek(@state.offset) + append = @state.offset > 0 + end + + start_offset = @state.offset + + @state.set_current_line!(style: Style.new(@state.inherited_style)) + + stream.each_line do |line| + s = StringScanner.new(line) + convert_line(s) + end + + # This must be assigned before flushing the current line + # or the @current_line.offset will advance to the very end + # of the trace. Instead we want @last_line_offset to always + # point to the beginning of last line. + @state.set_last_line_offset + + flush_current_line + + OpenStruct.new( + lines: @lines, + state: @state.encode, + append: append, + truncated: truncated, + offset: start_offset, + size: stream.tell - start_offset, + total: stream.size + ) + end + + private + + def convert_line(scanner) + until scanner.eos? + + if scanner.scan(Gitlab::Regex.build_trace_section_regex) + handle_section(scanner) + elsif scanner.scan(/\e([@-_])(.*?)([@-~])/) + handle_sequence(scanner) + elsif scanner.scan(/\e(([@-_])(.*?)?)?$/) + break + elsif scanner.scan(/</) + @state.current_line << '<' + elsif scanner.scan(/\r?\n/) + # we advance the offset of the next current line + # so it does not start from \n + flush_current_line(advance_offset: scanner.matched_size) + else + @state.current_line << scanner.scan(/./m) + end + + @state.offset += scanner.matched_size + end + end + + def handle_sequence(scanner) + indicator = scanner[1] + commands = scanner[2].split ';' + terminator = scanner[3] + + # We are only interested in color and text style changes - triggered by + # sequences starting with '\e[' and ending with 'm'. Any other control + # sequence gets stripped (including stuff like "delete last line") + return unless indicator == '[' && terminator == 'm' + + @state.update_style(commands) + end + + def handle_section(scanner) + action = scanner[1] + timestamp = scanner[2] + section = scanner[3] + + section_name = sanitize_section_name(section) + + if action == "start" + handle_section_start(section_name, timestamp) + elsif action == "end" + handle_section_end(section_name, timestamp) + end + end + + def handle_section_start(section, timestamp) + flush_current_line unless @state.current_line.empty? + @state.open_section(section, timestamp) + end + + def handle_section_end(section, timestamp) + return unless @state.section_open?(section) + + flush_current_line unless @state.current_line.empty? + @state.close_section(section, timestamp) + + # ensure that section end is detached from the last + # line in the section + flush_current_line + end + + def flush_current_line(advance_offset: 0) + @lines << @state.current_line.to_h + + @state.set_current_line!(advance_offset: advance_offset) + end + + def sanitize_section_name(section) + section.to_s.downcase.gsub(/[^a-z0-9]/, '-') + end + end + end + end +end diff --git a/lib/gitlab/ci/ansi2json/line.rb b/lib/gitlab/ci/ansi2json/line.rb new file mode 100644 index 00000000000..173fb1df88e --- /dev/null +++ b/lib/gitlab/ci/ansi2json/line.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Ansi2json + # Line class is responsible for keeping the internal state of + # a log line and to finally serialize it as Hash. + class Line + # Line::Segment is a portion of a line that has its own style + # and text. Multiple segments make the line content. + class Segment + attr_accessor :text, :style + + def initialize(style:) + @text = +'' + @style = style + end + + def empty? + text.empty? + end + + def to_h + # Without force encoding to UTF-8 we could get an error + # when serializing the Hash to JSON. + # Encoding::UndefinedConversionError: + # "\xE2" from ASCII-8BIT to UTF-8 + { text: text.force_encoding('UTF-8') }.tap do |result| + result[:style] = style.to_s if style.set? + end + end + end + + attr_reader :offset, :sections, :segments, :current_segment, + :section_header, :section_duration + + def initialize(offset:, style:, sections: []) + @offset = offset + @segments = [] + @sections = sections + @section_header = false + @duration = nil + @current_segment = Segment.new(style: style) + end + + def <<(data) + @current_segment.text << data + end + + def style + @current_segment.style + end + + def empty? + @segments.empty? && @current_segment.empty? + end + + def update_style(ansi_commands) + @current_segment.style.update(ansi_commands) + end + + def add_section(section) + @sections << section + end + + def set_as_section_header + @section_header = true + end + + def set_section_duration(duration) + @section_duration = Time.at(duration.to_i).strftime('%M:%S') + end + + def flush_current_segment! + return if @current_segment.empty? + + @segments << @current_segment.to_h + @current_segment = Segment.new(style: @current_segment.style) + end + + def to_h + flush_current_segment! + + { offset: offset, content: @segments }.tap do |result| + result[:section] = sections.last if sections.any? + result[:section_header] = true if @section_header + result[:section_duration] = @section_duration if @section_duration + end + end + end + end + end +end diff --git a/lib/gitlab/ci/ansi2json/parser.rb b/lib/gitlab/ci/ansi2json/parser.rb new file mode 100644 index 00000000000..d428680fb2a --- /dev/null +++ b/lib/gitlab/ci/ansi2json/parser.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +# This Parser translates ANSI escape codes into human readable format. +# It considers color and format changes. +# Inspired by http://en.wikipedia.org/wiki/ANSI_escape_code +module Gitlab + module Ci + module Ansi2json + class Parser + # keys represent the trailing digit in color changing command (30-37, 40-47, 90-97. 100-107) + COLOR = { + 0 => 'black', # not that this is gray in the intense color table + 1 => 'red', + 2 => 'green', + 3 => 'yellow', + 4 => 'blue', + 5 => 'magenta', + 6 => 'cyan', + 7 => 'white' # not that this is gray in the dark (aka default) color table + }.freeze + + STYLE_SWITCHES = { + bold: 0x01, + italic: 0x02, + underline: 0x04, + conceal: 0x08, + cross: 0x10 + }.freeze + + def self.bold?(mask) + mask & STYLE_SWITCHES[:bold] != 0 + end + + def self.matching_formats(mask) + formats = [] + STYLE_SWITCHES.each do |text_format, flag| + formats << "term-#{text_format}" if mask & flag != 0 + end + + formats + end + + def initialize(command, ansi_stack = nil) + @command = command + @ansi_stack = ansi_stack + end + + def changes + if self.respond_to?("on_#{@command}") + send("on_#{@command}", @ansi_stack) # rubocop:disable GitlabSecurity/PublicSend + end + end + + # rubocop:disable Style/SingleLineMethods + def on_0(_) { reset: true } end + + def on_1(_) { enable: STYLE_SWITCHES[:bold] } end + + def on_3(_) { enable: STYLE_SWITCHES[:italic] } end + + def on_4(_) { enable: STYLE_SWITCHES[:underline] } end + + def on_8(_) { enable: STYLE_SWITCHES[:conceal] } end + + def on_9(_) { enable: STYLE_SWITCHES[:cross] } end + + def on_21(_) { disable: STYLE_SWITCHES[:bold] } end + + def on_22(_) { disable: STYLE_SWITCHES[:bold] } end + + def on_23(_) { disable: STYLE_SWITCHES[:italic] } end + + def on_24(_) { disable: STYLE_SWITCHES[:underline] } end + + def on_28(_) { disable: STYLE_SWITCHES[:conceal] } end + + def on_29(_) { disable: STYLE_SWITCHES[:cross] } end + + def on_30(_) { fg: fg_color(0) } end + + def on_31(_) { fg: fg_color(1) } end + + def on_32(_) { fg: fg_color(2) } end + + def on_33(_) { fg: fg_color(3) } end + + def on_34(_) { fg: fg_color(4) } end + + def on_35(_) { fg: fg_color(5) } end + + def on_36(_) { fg: fg_color(6) } end + + def on_37(_) { fg: fg_color(7) } end + + def on_38(stack) { fg: fg_color_256(stack) } end + + def on_39(_) { fg: fg_color(9) } end + + def on_40(_) { bg: bg_color(0) } end + + def on_41(_) { bg: bg_color(1) } end + + def on_42(_) { bg: bg_color(2) } end + + def on_43(_) { bg: bg_color(3) } end + + def on_44(_) { bg: bg_color(4) } end + + def on_45(_) { bg: bg_color(5) } end + + def on_46(_) { bg: bg_color(6) } end + + def on_47(_) { bg: bg_color(7) } end + + def on_48(stack) { bg: bg_color_256(stack) } end + + # TODO: all the x9 never get called? + def on_49(_) { fg: fg_color(9) } end + + def on_90(_) { fg: fg_color(0, 'l') } end + + def on_91(_) { fg: fg_color(1, 'l') } end + + def on_92(_) { fg: fg_color(2, 'l') } end + + def on_93(_) { fg: fg_color(3, 'l') } end + + def on_94(_) { fg: fg_color(4, 'l') } end + + def on_95(_) { fg: fg_color(5, 'l') } end + + def on_96(_) { fg: fg_color(6, 'l') } end + + def on_97(_) { fg: fg_color(7, 'l') } end + + def on_99(_) { fg: fg_color(9, 'l') } end + + def on_100(_) { fg: bg_color(0, 'l') } end + + def on_101(_) { fg: bg_color(1, 'l') } end + + def on_102(_) { fg: bg_color(2, 'l') } end + + def on_103(_) { fg: bg_color(3, 'l') } end + + def on_104(_) { fg: bg_color(4, 'l') } end + + def on_105(_) { fg: bg_color(5, 'l') } end + + def on_106(_) { fg: bg_color(6, 'l') } end + + def on_107(_) { fg: bg_color(7, 'l') } end + + def on_109(_) { fg: bg_color(9, 'l') } end + # rubocop:enable Style/SingleLineMethods + + def fg_color(color_index, prefix = nil) + term_color_class(color_index, ['fg', prefix]) + end + + def fg_color_256(command_stack) + xterm_color_class(command_stack, 'fg') + end + + def bg_color(color_index, prefix = nil) + term_color_class(color_index, ['bg', prefix]) + end + + def bg_color_256(command_stack) + xterm_color_class(command_stack, 'bg') + end + + def term_color_class(color_index, prefix) + color_name = COLOR[color_index] + return if color_name.nil? + + color_class(['term', prefix, color_name]) + end + + def xterm_color_class(command_stack, prefix) + # the 38 and 48 commands have to be followed by "5" and the color index + return unless command_stack.length >= 2 + return unless command_stack[0] == "5" + + command_stack.shift # ignore the "5" command + color_index = command_stack.shift.to_i + + return unless color_index >= 0 + return unless color_index <= 255 + + color_class(["xterm", prefix, color_index]) + end + + def color_class(segments) + [segments].flatten.compact.join('-') + end + end + end + end +end diff --git a/lib/gitlab/ci/ansi2json/state.rb b/lib/gitlab/ci/ansi2json/state.rb new file mode 100644 index 00000000000..db7a9035b8b --- /dev/null +++ b/lib/gitlab/ci/ansi2json/state.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# In this class we keep track of the state changes that the +# Converter makes as it scans through the log stream. +module Gitlab + module Ci + module Ansi2json + class State + attr_accessor :offset, :current_line, :inherited_style, :open_sections, :last_line_offset + + def initialize(new_state, stream_size) + @offset = 0 + @inherited_style = {} + @open_sections = {} + @stream_size = stream_size + + restore_state!(new_state) + end + + def encode + state = { + offset: @last_line_offset, + style: @current_line.style.to_h, + open_sections: @open_sections + } + Base64.urlsafe_encode64(state.to_json) + end + + def open_section(section, timestamp) + @open_sections[section] = timestamp + + @current_line.add_section(section) + @current_line.set_as_section_header + end + + def close_section(section, timestamp) + return unless section_open?(section) + + duration = timestamp.to_i - @open_sections[section].to_i + @current_line.set_section_duration(duration) + + @open_sections.delete(section) + end + + def section_open?(section) + @open_sections.key?(section) + end + + def set_current_line!(style: nil, advance_offset: 0) + new_line = Line.new( + offset: @offset + advance_offset, + style: style || @current_line.style, + sections: @open_sections.keys + ) + @current_line = new_line + end + + def set_last_line_offset + @last_line_offset = @current_line.offset + end + + def update_style(commands) + @current_line.flush_current_segment! + @current_line.update_style(commands) + end + + private + + def restore_state!(encoded_state) + state = decode_state(encoded_state) + + return unless state + return if state['offset'].to_i > @stream_size + + @offset = state['offset'].to_i if state['offset'] + @open_sections = state['open_sections'] if state['open_sections'] + + if state['style'] + @inherited_style = { + fg: state.dig('style', 'fg'), + bg: state.dig('style', 'bg'), + mask: state.dig('style', 'mask') + } + end + end + + def decode_state(state) + return unless state.present? + + decoded_state = Base64.urlsafe_decode64(state) + return unless decoded_state.present? + + JSON.parse(decoded_state) + end + end + end + end +end diff --git a/lib/gitlab/ci/ansi2json/style.rb b/lib/gitlab/ci/ansi2json/style.rb new file mode 100644 index 00000000000..2739ffdfa5d --- /dev/null +++ b/lib/gitlab/ci/ansi2json/style.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Ansi2json + class Style + attr_reader :fg, :bg, :mask + + def initialize(fg: nil, bg: nil, mask: 0) + @fg = fg + @bg = bg + @mask = mask + + update_formats + end + + def update(ansi_commands) + command = ansi_commands.shift + return unless command + + if changes = Gitlab::Ci::Ansi2json::Parser.new(command, ansi_commands).changes + apply_changes(changes) + end + + update(ansi_commands) + end + + def set? + @fg || @bg || @formats.any? + end + + def reset! + @fg = nil + @bg = nil + @mask = 0 + @formats = [] + end + + def ==(other) + self.to_h == other.to_h + end + + def to_s + [@fg, @bg, @formats].flatten.compact.join(' ') + end + + def to_h + { fg: @fg, bg: @bg, mask: @mask } + end + + private + + def apply_changes(changes) + case + when changes[:reset] + reset! + when changes[:fg] + @fg = changes[:fg] + when changes[:bg] + @bg = changes[:bg] + when changes[:enable] + @mask |= changes[:enable] + when changes[:disable] + @mask &= ~changes[:disable] + else + return + end + + update_formats + end + + def update_formats + # Most terminals show bold colored text in the light color variant + # Let's mimic that here + if @fg.present? && Gitlab::Ci::Ansi2json::Parser.bold?(@mask) + @fg = @fg.sub(/fg-([a-z]{2,}+)/, 'fg-l-\1') + end + + @formats = Gitlab::Ci::Ansi2json::Parser.matching_formats(@mask) + end + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/summary/deploy.rb b/lib/gitlab/cycle_analytics/summary/deploy.rb index 0691f3cd131..5ff8d881143 100644 --- a/lib/gitlab/cycle_analytics/summary/deploy.rb +++ b/lib/gitlab/cycle_analytics/summary/deploy.rb @@ -12,7 +12,7 @@ module Gitlab def value strong_memoize(:value) do - query = @project.deployments.where("created_at >= ?", @from) + query = @project.deployments.success.where("created_at >= ?", @from) query = query.where("created_at <= ?", @to) if @to query.count end diff --git a/lib/gitlab/diff/position_collection.rb b/lib/gitlab/diff/position_collection.rb index 59c60f77aaa..2112d347678 100644 --- a/lib/gitlab/diff/position_collection.rb +++ b/lib/gitlab/diff/position_collection.rb @@ -6,13 +6,13 @@ module Gitlab include Enumerable # collection - An array of Gitlab::Diff::Position - def initialize(collection, diff_head_sha) + def initialize(collection, diff_head_sha = nil) @collection = collection @diff_head_sha = diff_head_sha end def each(&block) - @collection.each(&block) + filtered_positions.each(&block) end def concat(positions) @@ -23,9 +23,21 @@ module Gitlab # positions (https://gitlab.com/gitlab-org/gitlab/issues/33271). def unfoldable select do |position| - position.unfoldable? && position.head_sha == @diff_head_sha + position.unfoldable? && valid_head_sha?(position) end end + + private + + def filtered_positions + @collection.select { |item| item.is_a?(Position) } + end + + def valid_head_sha?(position) + return true unless @diff_head_sha + + position.head_sha == @diff_head_sha + end end end end diff --git a/lib/gitlab/graphql/docs/renderer.rb b/lib/gitlab/graphql/docs/renderer.rb index f47a372aa19..41aef64f683 100644 --- a/lib/gitlab/graphql/docs/renderer.rb +++ b/lib/gitlab/graphql/docs/renderer.rb @@ -23,15 +23,12 @@ module Gitlab @parsed_schema = GraphQLDocs::Parser.new(schema, {}).parse end - def render - contents = @layout.render(self) - - write_file(contents) + def contents + # Render and remove an extra trailing new line + @contents ||= @layout.render(self).sub!(/\n(?=\Z)/, '') end - private - - def write_file(contents) + def write filename = File.join(@output_dir, 'index.md') FileUtils.mkdir_p(@output_dir) diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml index cc22d43ab4f..33acff38ef4 100644 --- a/lib/gitlab/graphql/docs/templates/default.md.haml +++ b/lib/gitlab/graphql/docs/templates/default.md.haml @@ -20,6 +20,3 @@ - type[:fields].each do |field| = "| `#{field[:name]}` | #{render_field_type(field[:type][:info])} | #{field[:description]} |" \ - - - diff --git a/lib/gitlab/health_checks/checks.rb b/lib/gitlab/health_checks/checks.rb deleted file mode 100644 index c4016c5fffd..00000000000 --- a/lib/gitlab/health_checks/checks.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module HealthChecks - CHECKS = [ - Gitlab::HealthChecks::DbCheck, - Gitlab::HealthChecks::Redis::RedisCheck, - Gitlab::HealthChecks::Redis::CacheCheck, - Gitlab::HealthChecks::Redis::QueuesCheck, - Gitlab::HealthChecks::Redis::SharedStateCheck, - Gitlab::HealthChecks::GitalyCheck - ].freeze - end -end diff --git a/lib/gitlab/health_checks/probes/readiness.rb b/lib/gitlab/health_checks/probes/collection.rb index 28abf490ffc..db3ef4834c2 100644 --- a/lib/gitlab/health_checks/probes/readiness.rb +++ b/lib/gitlab/health_checks/probes/collection.rb @@ -3,14 +3,13 @@ module Gitlab module HealthChecks module Probes - class Readiness + class Collection attr_reader :checks # This accepts an array of objects implementing `:readiness` # that returns `::Gitlab::HealthChecks::Result` - def initialize(*additional_checks) - @checks = ::Gitlab::HealthChecks::CHECKS - @checks += additional_checks + def initialize(*checks) + @checks = checks end def execute diff --git a/lib/gitlab/health_checks/probes/liveness.rb b/lib/gitlab/health_checks/probes/liveness.rb deleted file mode 100644 index b4d346e945e..00000000000 --- a/lib/gitlab/health_checks/probes/liveness.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module HealthChecks - module Probes - class Liveness - def execute - Probes::Status.new(200, status: 'ok') - end - end - end - end -end diff --git a/lib/gitlab/metrics/exporter/base_exporter.rb b/lib/gitlab/metrics/exporter/base_exporter.rb index 01d1ec9305e..7111835c85a 100644 --- a/lib/gitlab/metrics/exporter/base_exporter.rb +++ b/lib/gitlab/metrics/exporter/base_exporter.rb @@ -6,7 +6,7 @@ module Gitlab class BaseExporter < Daemon attr_reader :server - attr_accessor :additional_checks + attr_accessor :readiness_checks def enabled? settings.enabled @@ -73,11 +73,11 @@ module Gitlab end def readiness_probe - ::Gitlab::HealthChecks::Probes::Readiness.new(*additional_checks) + ::Gitlab::HealthChecks::Probes::Collection.new(*readiness_checks) end def liveness_probe - ::Gitlab::HealthChecks::Probes::Liveness.new + ::Gitlab::HealthChecks::Probes::Collection.new end def render_probe(probe, req, res) diff --git a/lib/gitlab/metrics/exporter/web_exporter.rb b/lib/gitlab/metrics/exporter/web_exporter.rb index 597ac289193..3940f6fa155 100644 --- a/lib/gitlab/metrics/exporter/web_exporter.rb +++ b/lib/gitlab/metrics/exporter/web_exporter.rb @@ -20,7 +20,7 @@ module Gitlab def initialize super - self.additional_checks = [ + self.readiness_checks = [ WebExporter::ExporterCheck.new(self), Gitlab::HealthChecks::PumaCheck, Gitlab::HealthChecks::UnicornCheck diff --git a/lib/tasks/gitlab/graphql.rake b/lib/tasks/gitlab/graphql.rake index fd8df015903..902f22684ee 100644 --- a/lib/tasks/gitlab/graphql.rake +++ b/lib/tasks/gitlab/graphql.rake @@ -11,10 +11,28 @@ namespace :gitlab do task compile_docs: :environment do renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options) - renderer.render + renderer.write puts "Documentation compiled." end + + desc 'GitLab | Check if GraphQL docs are up to date' + task check_docs: :environment do + renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options) + + doc = File.read(Rails.root.join(OUTPUT_DIR, 'index.md')) + + if doc == renderer.contents + puts "GraphQL documentation is up to date" + else + puts '#' * 10 + puts '#' + puts '# GraphQL documentation is outdated! Please update it by running `bundle exec rake gitlab:graphql:compile_docs`.' + puts '#' + puts '#' * 10 + abort + end + end end end |