Skip to content

Commit

Permalink
add artifact collection (#17)
Browse files Browse the repository at this point in the history
* add artifact collection

* handle close stream errors correctly

* Update vendor files

* Remove named returns

* collect artifacts when waitforbuilds is set to true
  • Loading branch information
DamienBitrise authored Jul 20, 2020
1 parent ce48df3 commit 7b09287
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 8 deletions.
135 changes: 135 additions & 0 deletions bitrise/bitrise.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"time"

Expand Down Expand Up @@ -46,6 +48,27 @@ type StartResponse struct {
TriggeredWorkflow string `json:"triggered_workflow"`
}

// BuildArtifactsResponse ...
type BuildArtifactsResponse struct {
ArtifactSlugs []BuildArtifactSlug `json:"data"`
}

// BuildArtifactSlug ...
type BuildArtifactSlug struct {
ArtifactSlug string `json:"slug"`
}

// BuildArtifactResponse ...
type BuildArtifactResponse struct {
Artifact BuildArtifact `json:"data"`
}

// BuildArtifact ...
type BuildArtifact struct {
DownloadURL string `json:"expiring_download_url"`
Title string `json:"title"`
}

// Environment ...
type Environment struct {
MappedTo string `json:"mapped_to"`
Expand Down Expand Up @@ -220,6 +243,118 @@ func (app App) StartBuild(workflow string, buildParams json.RawMessage, buildNum
return response, nil
}

// GetBuildArtifacts ...
func (build Build) GetBuildArtifacts(app App) (BuildArtifactsResponse, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v0.1/apps/%s/builds/%s/artifacts", app.BaseURL, app.Slug, build.Slug), nil)
if err != nil {
return BuildArtifactsResponse{}, nil
}
req.Header.Add("Authorization", "token "+app.AccessToken)

retryReq, err := retryablehttp.FromRequest(req)
if err != nil {
return BuildArtifactsResponse{}, fmt.Errorf("failed to create retryable request: %s", err)
}

retryClient := NewRetryableClient(app.IsDebugRetryTimings)

resp, err := retryClient.Do(retryReq)
if err != nil {
return BuildArtifactsResponse{}, nil
}

defer func() {
if cerr := resp.Body.Close(); cerr != nil && err == nil {
err = cerr
}
}()

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return BuildArtifactsResponse{}, nil
}

if resp.StatusCode < 200 || resp.StatusCode > 299 {
return BuildArtifactsResponse{}, fmt.Errorf("failed to get response, statuscode: %d, body: %s", resp.StatusCode, respBody)
}

var response BuildArtifactsResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return BuildArtifactsResponse{}, fmt.Errorf("failed to decode response, body: %s, error: %s", respBody, err)
}
return response, nil
}

// GetBuildArtifact ...
func (build Build) GetBuildArtifact(app App, artifactSlug string) (BuildArtifactResponse, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v0.1/apps/%s/builds/%s/artifacts/%s", app.BaseURL, app.Slug, build.Slug, artifactSlug), nil)
if err != nil {
return BuildArtifactResponse{}, nil
}
req.Header.Add("Authorization", "token "+app.AccessToken)

retryReq, err := retryablehttp.FromRequest(req)
if err != nil {
return BuildArtifactResponse{}, fmt.Errorf("failed to create retryable request: %s", err)
}

retryClient := NewRetryableClient(app.IsDebugRetryTimings)

resp, err := retryClient.Do(retryReq)
if err != nil {
return BuildArtifactResponse{}, nil
}

defer func() {
if cerr := resp.Body.Close(); cerr != nil && err == nil {
err = cerr
}
}()

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return BuildArtifactResponse{}, nil
}

if resp.StatusCode < 200 || resp.StatusCode > 299 {
return BuildArtifactResponse{}, fmt.Errorf("failed to get response, statuscode: %d, body: %s", resp.StatusCode, respBody)
}

var response BuildArtifactResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return BuildArtifactResponse{}, fmt.Errorf("failed to decode response, body: %s, error: %s", respBody, err)
}
return response, nil
}

// DownloadArtifact ...
func (artifact BuildArtifact) DownloadArtifact(filepath string) error {
resp, err := http.Get(artifact.DownloadURL)
if err != nil {
return err
}

defer func() {
if err := resp.Body.Close(); err != nil {
fmt.Println("Failed to close body, error:", err)
}
}()

out, err := os.Create(filepath)
if err != nil {
return err
}

defer func() {
if err := out.Close(); err != nil {
fmt.Println("Failed to close output stream, error:", err)
}
}()

_, err = io.Copy(out, resp.Body)
return err
}

// WaitForBuilds ...
func (app App) WaitForBuilds(buildSlugs []string, statusChangeCallback func(build Build)) error {
failed := false
Expand Down
37 changes: 29 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ const envBuildSlugs = "ROUTER_STARTED_BUILD_SLUGS"

// Config ...
type Config struct {
AppSlug string `env:"BITRISE_APP_SLUG,required"`
BuildSlug string `env:"BITRISE_BUILD_SLUG,required"`
BuildNumber string `env:"BITRISE_BUILD_NUMBER,required"`
AccessToken stepconf.Secret `env:"access_token,required"`
WaitForBuilds string `env:"wait_for_builds"`
Workflows string `env:"workflows,required"`
Environments string `env:"environment_key_list"`
IsVerboseLog bool `env:"verbose,required"`
AppSlug string `env:"BITRISE_APP_SLUG,required"`
BuildSlug string `env:"BITRISE_BUILD_SLUG,required"`
BuildNumber string `env:"BITRISE_BUILD_NUMBER,required"`
AccessToken stepconf.Secret `env:"access_token,required"`
WaitForBuilds string `env:"wait_for_builds"`
BuildArtifactsSavePath string `env:"build_artifacts_save_path"`
Workflows string `env:"workflows,required"`
Environments string `env:"environment_key_list"`
IsVerboseLog bool `env:"verbose,required"`
}

func failf(s string, a ...interface{}) {
Expand Down Expand Up @@ -86,6 +87,26 @@ func main() {
case 4:
log.Infof("- %s cancelled", build.TriggeredWorkflow)
}
if build.Status != 0 {
if strings.TrimSpace(cfg.BuildArtifactsSavePath) != "" {
artifactsResponse, err := build.GetBuildArtifacts(app)
if err != nil {
log.Warnf("failed to get build artifacts, error: %s", err)
}
for _, artifactSlug := range artifactsResponse.ArtifactSlugs {
artifactObj, err := build.GetBuildArtifact(app, artifactSlug.ArtifactSlug)
if err != nil {
log.Warnf("failed to get build artifact, error: %s", err)
}

downloadErr := artifactObj.Artifact.DownloadArtifact(strings.TrimSpace(cfg.BuildArtifactsSavePath) + artifactObj.Artifact.Title)
if downloadErr != nil {
log.Warnf("failed to download artifact, error: %s", downloadErr)
}
log.Donef("Downloaded: " + artifactObj.Artifact.Title + " to path " + strings.TrimSpace(cfg.BuildArtifactsSavePath))
}
}
}
}); err != nil {
failf("An error occoured: %s", err)
}
Expand Down
8 changes: 8 additions & 0 deletions step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ inputs:
value_options:
- "false"
- "true"
- build_artifacts_save_path:
opts:
title: Path to save the Artifacts in (leave empty to not save artifacts)
summary: This is where the artifacts will be saved to if wait_for_builds == true
description: |
This is where the artifacts will be saved to so you can access them in the workflow
is_required: false
is_sensitive: false
- verbose: "no"
opts:
title: Enable verbose log?
Expand Down

0 comments on commit 7b09287

Please sign in to comment.