1// Copyright 2020 The go-github AUTHORS. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package github
7
8import (
9	"context"
10	"fmt"
11	"net/http"
12	"net/url"
13)
14
15// Artifact reprents a GitHub artifact.  Artifacts allow sharing
16// data between jobs in a workflow and provide storage for data
17// once a workflow is complete.
18//
19// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#artifacts
20type Artifact struct {
21	ID                 *int64     `json:"id,omitempty"`
22	NodeID             *string    `json:"node_id,omitempty"`
23	Name               *string    `json:"name,omitempty"`
24	SizeInBytes        *int64     `json:"size_in_bytes,omitempty"`
25	ArchiveDownloadURL *string    `json:"archive_download_url,omitempty"`
26	Expired            *bool      `json:"expired,omitempty"`
27	CreatedAt          *Timestamp `json:"created_at,omitempty"`
28	ExpiresAt          *Timestamp `json:"expires_at,omitempty"`
29}
30
31// ArtifactList represents a list of GitHub artifacts.
32//
33// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#artifacts
34type ArtifactList struct {
35	TotalCount *int64      `json:"total_count,omitempty"`
36	Artifacts  []*Artifact `json:"artifacts,omitempty"`
37}
38
39// ListArtifacts lists all artifacts that belong to a repository.
40//
41// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-artifacts-for-a-repository
42func (s *ActionsService) ListArtifacts(ctx context.Context, owner, repo string, opts *ListOptions) (*ArtifactList, *Response, error) {
43	u := fmt.Sprintf("repos/%v/%v/actions/artifacts", owner, repo)
44	u, err := addOptions(u, opts)
45	if err != nil {
46		return nil, nil, err
47	}
48
49	req, err := s.client.NewRequest("GET", u, nil)
50	if err != nil {
51		return nil, nil, err
52	}
53
54	artifactList := new(ArtifactList)
55	resp, err := s.client.Do(ctx, req, artifactList)
56	if err != nil {
57		return nil, resp, err
58	}
59
60	return artifactList, resp, nil
61}
62
63// ListWorkflowRunArtifacts lists all artifacts that belong to a workflow run.
64//
65// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-workflow-run-artifacts
66func (s *ActionsService) ListWorkflowRunArtifacts(ctx context.Context, owner, repo string, runID int64, opts *ListOptions) (*ArtifactList, *Response, error) {
67	u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/artifacts", owner, repo, runID)
68	u, err := addOptions(u, opts)
69	if err != nil {
70		return nil, nil, err
71	}
72
73	req, err := s.client.NewRequest("GET", u, nil)
74	if err != nil {
75		return nil, nil, err
76	}
77
78	artifactList := new(ArtifactList)
79	resp, err := s.client.Do(ctx, req, artifactList)
80	if err != nil {
81		return nil, resp, err
82	}
83
84	return artifactList, resp, nil
85}
86
87// GetArtifact gets a specific artifact for a workflow run.
88//
89// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-an-artifact
90func (s *ActionsService) GetArtifact(ctx context.Context, owner, repo string, artifactID int64) (*Artifact, *Response, error) {
91	u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v", owner, repo, artifactID)
92
93	req, err := s.client.NewRequest("GET", u, nil)
94	if err != nil {
95		return nil, nil, err
96	}
97
98	artifact := new(Artifact)
99	resp, err := s.client.Do(ctx, req, artifact)
100	if err != nil {
101		return nil, resp, err
102	}
103
104	return artifact, resp, nil
105}
106
107// DownloadArtifact gets a redirect URL to download an archive for a repository.
108//
109// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#download-an-artifact
110func (s *ActionsService) DownloadArtifact(ctx context.Context, owner, repo string, artifactID int64, followRedirects bool) (*url.URL, *Response, error) {
111	u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v/zip", owner, repo, artifactID)
112
113	resp, err := s.getDownloadArtifactFromURL(ctx, u, followRedirects)
114	if err != nil {
115		return nil, nil, err
116	}
117
118	if resp.StatusCode != http.StatusFound {
119		return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
120	}
121	parsedURL, err := url.Parse(resp.Header.Get("Location"))
122	return parsedURL, newResponse(resp), nil
123}
124
125func (s *ActionsService) getDownloadArtifactFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
126	req, err := s.client.NewRequest("GET", u, nil)
127	if err != nil {
128		return nil, err
129	}
130
131	var resp *http.Response
132	// Use http.DefaultTransport if no custom Transport is configured
133	req = withContext(ctx, req)
134	if s.client.client.Transport == nil {
135		resp, err = http.DefaultTransport.RoundTrip(req)
136	} else {
137		resp, err = s.client.client.Transport.RoundTrip(req)
138	}
139	if err != nil {
140		return nil, err
141	}
142	resp.Body.Close()
143
144	// If redirect response is returned, follow it
145	if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
146		u = resp.Header.Get("Location")
147		resp, err = s.getDownloadArtifactFromURL(ctx, u, false)
148	}
149	return resp, err
150}
151
152// DeleteArtifact deletes a workflow run artifact.
153//
154// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-an-artifact
155func (s *ActionsService) DeleteArtifact(ctx context.Context, owner, repo string, artifactID int64) (*Response, error) {
156	u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v", owner, repo, artifactID)
157
158	req, err := s.client.NewRequest("DELETE", u, nil)
159	if err != nil {
160		return nil, err
161	}
162
163	return s.client.Do(ctx, req, nil)
164}
165