Welcome to mirror list, hosted at ThFree Co, Russian Federation.

download-downstream-artifact.rb « scripts - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 23c400a9add16fbd7dfec41d0468c7012454b6b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/usr/bin/env ruby

# frozen_string_literal: true

require 'gitlab'

require_relative 'api/default_options'

# This class allows an upstream job to fetch an artifact from a job in a downstream pipeline.
#
# Until https://gitlab.com/gitlab-org/gitlab/-/issues/285100 is resolved it's not straightforward for an upstream
# pipeline to use artifacts from a downstream pipeline. There is a workaround for parent-child pipelines (see the issue)
# but it relies on CI_MERGE_REQUEST_REF_PATH so it doesn't work for multi-project pipelines.
#
# This uses the Jobs API to get pipeline bridges (trigger jobs) and the Job artifacts API to download artifacts.
# - https://docs.gitlab.com/ee/api/jobs.html#list-pipeline-trigger-jobs
# - https://docs.gitlab.com/ee/api/job_artifacts.html
#
# Note: This class also works for parent-child pipelines within the same project, it's just not necessary in that case.
class DownloadDownstreamArtifact
  def initialize(options)
    @upstream_project = options.fetch(:upstream_project, API::DEFAULT_OPTIONS[:project])
    @upstream_pipeline_id = options.fetch(:upstream_pipeline_id, API::DEFAULT_OPTIONS[:pipeline_id])
    @downstream_project = options.fetch(:downstream_project, API::DEFAULT_OPTIONS[:project])
    @downstream_job_name = options.fetch(:downstream_job_name)
    @trigger_job_name = options.fetch(:trigger_job_name)
    @downstream_artifact_path = options.fetch(:downstream_artifact_path)
    @output_artifact_path = options.fetch(:output_artifact_path)

    unless options.key?(:api_token)
      raise ArgumentError, 'PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE is required to access downstream pipelines'
    end

    api_token = options.fetch(:api_token)

    @client = Gitlab.client(
      endpoint: options.fetch(:endpoint, API::DEFAULT_OPTIONS[:endpoint]),
      private_token: api_token
    )
  end

  def execute
    unless downstream_pipeline
      abort("Could not find downstream pipeline triggered via #{trigger_job_name} in project #{downstream_project}")
    end

    unless downstream_job
      abort("Could not find job with name '#{downstream_job_name}' in #{downstream_pipeline['web_url']}")
    end

    puts "Fetching scores artifact from downstream pipeline triggered via #{trigger_job_name}..."
    puts "Downstream pipeline is #{downstream_pipeline['web_url']}."
    puts %(Downstream job "#{downstream_job_name}": #{downstream_job['web_url']}.)

    path = downstream_artifact_path.sub('DOWNSTREAM_JOB_ID', downstream_job.id.to_s)
    puts %(Fetching artifact "#{path}" from #{downstream_job_name}...)

    download_and_save_artifact(path)

    puts "Artifact saved as #{output_artifact_path} ..."
  end

  def self.options_from_env
    API::DEFAULT_OPTIONS.merge({
      upstream_project: API::DEFAULT_OPTIONS[:project],
      upstream_pipeline_id: API::DEFAULT_OPTIONS[:pipeline_id],
      downstream_project: ENV.fetch('DOWNSTREAM_PROJECT', API::DEFAULT_OPTIONS[:project]),
      downstream_job_name: ENV['DOWNSTREAM_JOB_NAME'],
      trigger_job_name: ENV['TRIGGER_JOB_NAME'],
      downstream_artifact_path: ENV['DOWNSTREAM_JOB_ARTIFACT_PATH'],
      output_artifact_path: ENV['OUTPUT_ARTIFACT_PATH']
    }).except(:project, :pipeline_id)
  end

  private

  attr_reader :downstream_artifact_path,
    :output_artifact_path,
    :downstream_job_name,
    :trigger_job_name,
    :upstream_project,
    :downstream_project,
    :upstream_pipeline_id,
    :client

  def bridge
    @bridge ||= client
      .pipeline_bridges(upstream_project, upstream_pipeline_id, per_page: 100)
      .auto_paginate
      .find { |job| job.name.include?(trigger_job_name) }
  end

  def downstream_pipeline
    @downstream_pipeline ||=
      if bridge&.downstream_pipeline.nil?
        nil
      else
        client.pipeline(downstream_project, bridge.downstream_pipeline.id)
      end
  end

  def downstream_job
    @downstream_job ||= client
      .pipeline_jobs(downstream_project, downstream_pipeline.id)
      .find { |job| job.name.include?(downstream_job_name) }
  end

  def download_and_save_artifact(job_artifact_path)
    file_response = client.download_job_artifact_file(downstream_project, downstream_job.id, job_artifact_path)

    file_response.respond_to?(:read) || abort("Could not download artifact. Request returned: #{file_response}")

    File.write(output_artifact_path, file_response.read)
  end
end

if $PROGRAM_NAME == __FILE__
  options = DownloadDownstreamArtifact.options_from_env

  DownloadDownstreamArtifact.new(options).execute
end