# frozen_string_literal: true require "httparty" require "json" require "tty-config" require "cgi" require "gitlab" require_relative './utils/constants' module Dri TokenNotProvidedError = Class.new(StandardError) class ApiClient # rubocop:disable Metrics/ClassLength include Dri::Utils::Constants::ProjectIDs include Dri::Utils::Constants::Triage::Labels API_URL = "https://gitlab.com/api/v4" OPS_API_URL = "https://ops.gitlab.net/api/v4" def initialize(config, ops = false) @token = config.read.dig("settings", "token") @ops_token = config.read.dig("settings", "ops_token") if @token.nil? || @ops_token.nil? raise TokenNotProvidedError, "Gitlab API client cannot be initialized without both access tokens. " \ "Run `dri init` again or `dri profile --edit` to add an ops_token entry." end @ops_instance = ops end # Fetch all triaged failures # # @param [String] emoji # @param [String] state # @return [Array] def fetch_all_triaged_failures(emoji:, state:) project_ids = [GITLAB_PROJECT_ID, CUSTOMERSDOT_PROJECT_ID] failures = [] project_ids.each do |project_id| failures += fetch_triaged_failures(project_id: project_id, emoji: emoji, state: state) end failures end # Fetch triaged failures for a given project # # @param [Integer] project_id # @param [String] emoji # @param [String] state # @return [Array] def fetch_triaged_failures(project_id:, emoji:, state:) gitlab.issues( project_id, order_by: "updated_at", my_reaction_emoji: emoji, scope: "all", state: state ) end # Fetch triaged incidents # # @param [String] emoji def fetch_triaged_incidents(emoji:) gitlab.issues( INFRA_TEAM_PROD_PROJECT_ID, order_by: "updated_at", my_reaction_emoji: emoji, state: "all", labels: "incident" ) end # Fetch award emojis # # @param [Integer] issue_iid # @param [Integer] project_id # @return [Array] def fetch_awarded_emojis(issue_iid, project_id:) gitlab.award_emojis(project_id, issue_iid, "issue") end # Fetch failing testcases # # @param [String] pipeline # @param [String] state # @return [Array] def fetch_failing_testcases(pipeline, state:) gitlab.issues( TESTCASES_PROJECT_ID, labels: "#{pipeline}::failed", state: state, scope: "all", "not[labels]": QUARANTINE ).auto_paginate end # Fetch issues related to failing test cases # # @return [Array] def fetch_test_failure_issues(labels: FAILURE_NEW) gitlab.issues( GITLAB_PROJECT_ID, labels: labels, state: 'opened', scope: "all" ).auto_paginate end # Fetch related issue mrs # # @param [Integer] project_id # @param [Integer] issue_iid # @return [Array] def fetch_related_mrs(project_id, issue_iid) gitlab.related_issue_merge_requests(project_id, issue_iid) end # Fetch MRs # # @see https://docs.gitlab.com/ee/api/merge_requests.html for all passable options # # @param [Hash] options # @option options [String] state # @option options [String] order_by # @option options [String] sort # @option options [String] milestone # @option options [String] labels def fetch_mrs(project_id:, **options) gitlab.merge_requests(project_id, per_page: 100, **options).auto_paginate end # Fetch current triage issue # # @return [Gitlab::ObjectifiedHash] def fetch_current_triage_issue gitlab.issues(TRIAGE_PROJECT_ID, state: "opened", order_by: "updated_at") end # Create triage report note # # @param [Integer] iid # @param [String] body # @return [Gitlab::ObjectifiedHash] def post_triage_report_note(iid:, body:) gitlab.create_issue_note(TRIAGE_PROJECT_ID, iid, body) end # Update triage report note # # @param [Integer] iid # @param [Integer] note_id # @param [String] body # @return [Gitlab::ObjectifiedHash] def update_triage_report_note(iid:, note_id:, body:) gitlab.edit_issue_note(TRIAGE_PROJECT_ID, iid, note_id, body) end # Fetch all new failures # # @param [String] start_date # @param [String] end_date # @param [String] state # @return [Array] def fetch_all_new_failures(start_date:, end_date:, state:) project_ids = [GITLAB_PROJECT_ID, CUSTOMERSDOT_PROJECT_ID] failures = [] start_date_iso = start_date.strftime('%Y-%m-%dT00:00:00Z') end_date_iso = end_date.strftime('%Y-%m-%dT23:59:59Z') project_ids.each do |project_id| failures += fetch_new_failures( project_id: project_id, start_date: start_date_iso, end_date: end_date_iso, state: state ) end failures end # Fetch new failures for a given project # # @param [Integer] project_id # @param [String] start_date # @param [String] end_date # @param [String] state # @return [Array] def fetch_new_failures(project_id:, start_date:, end_date:, state:) gitlab.issues( project_id, labels: FAILURE_NEW, order_by: "created_at", sort: 'desc', state: state, scope: "all", created_after: start_date, created_before: end_date, per_page: 100 ) end # Fetch failure notes # # @param [Integer] project_id # @param [Integer] issue_iid # @return [Array] def fetch_failure_notes(project_id, issue_iid) gitlab.issue_notes(project_id, issue_iid, per_page: 100).auto_paginate end # Delete award emoji # # @param [Integer] issue_iid # @param [Integer] emoji_id # @param [Integer] project_id # @return [Gitlab::ObjectifiedHash] def delete_award_emoji(issue_iid, emoji_id:, project_id: GITLAB_PROJECT_ID) gitlab.delete_award_emoji( project_id, issue_iid, "issue", emoji_id ) end # Fetch feature flag log issues # # @param [String] date # @return [Array] def fetch_feature_flag_logs(date) gitlab.issues(FEATURE_FLAG_LOG_PROJECT_ID, created_after: date, per_page: 100).auto_paginate end # Fetch ongoing incidents # # @return [Array] def incidents gitlab.issues(INFRA_TEAM_PROD_PROJECT_ID, order_by: "updated_at", state: "opened", labels: "incident") end # Fetch pipelines # # @param [Integer] project_id # @return [Array] def pipelines(project_id:, options:, auto_paginate: false) if auto_paginate gitlab.pipelines(project_id, options).auto_paginate else gitlab.pipelines(project_id, options) end end # Fetch single pipeline # # @param [Integer] project_id # @param [Integer] pipeline_id # @return [] def pipeline(project_id, pipeline_id) gitlab.pipeline(project_id, pipeline_id) end # Fetch test report from a pipeline # # @param [Integer] project_id # @param [Integer] pipeline_id # @return [] def pipeline_test_report(project_id, pipeline_id) gitlab.pipeline_test_report(project_id, pipeline_id) end # Fetch pipeline bridges/downstream pipelines # # @param [Integer] project_id # @param [Integer] pipeline_id # @return [Array] def pipeline_bridges(project_id, pipeline_id, options = {}) gitlab.pipeline_bridges(project_id, pipeline_id, options).auto_paginate end # Fetch jobs from a pipeline # # @param [Integer] project_id # @param [Integer] pipeline_id # @return [Array] def pipeline_jobs(project_id, pipeline_id, options = {}) gitlab.pipeline_jobs(project_id, pipeline_id, options).auto_paginate end # Fetch runbooks from the runbooks project # # @param [Integer] project_id # @return [Array] def list_runbooks(project_id = RUNBOOKS_PROJECT_ID) tree = gitlab.tree(project_id, { recursive: true, ref: 'main' }).auto_paginate tree.select do |node| node.type == 'tree' && !node.name.start_with?('_') end end # Fetches file contents at # # @param [String] path # @param [String] ref # @param [Integer] project_id # @return [Gitlab::ObjectifiedHash] def get_file(path, ref:, project_id:) gitlab.get_file(project_id, path, ref) end private attr_reader :token, :ops_token # Gitlab client # # @return [Gitlab::Client] def gitlab if @ops_instance @ops_client ||= Gitlab.client( endpoint: OPS_API_URL, private_token: ops_token ) else @gitlab_client ||= Gitlab.client( endpoint: API_URL, private_token: token ) end end end end