Currently the deploy to bitrise, allows to download the apk or ipa generated from a link. This link changes based on job execution. How would I download the apk or ipa and run tests on it from a different build job without having to manually download the apk or ipa?
Hey!
I would recommend to use Bitrise API. Especially this part could be interesting:
http://devcenter.bitrise.io/api/v0.1/#get-appsapp-slugbuildsbuild-slugartifacts
Just wrote a small Go code to be able to download artifact with Bitrise API that you can use for downloading assets from a build.
To use you need to set these envs:
-
AF_ACCESS_TOKEN
: Your personal access token from security tab in Account Settings -
AF_APP_SLUG
: the app slug where the build is -
AF_BUILD_SLUG
: the build slug in which your artifact is -
AF_ARTIFACT_NAME
: the name of the artifact -
AF_DOWNLOAD_DIR
: the path to the directory where the asset will be downloaded
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"time"
)
const domain = "https://api.bitrise.io"
const apiVersion = "v0.1"
// Client Bitrise API client
type Client struct {
authToken string
httpClient http.Client
}
// Artifacts ...
type Artifacts struct {
Data []struct {
ArtifactType string `json:"artifact_type"`
IsPublicPageEnabled bool `json:"is_public_page_enabled"`
Slug string `json:"slug"`
Title string `json:"title"`
} `json:"data"`
Paging struct {
PageItemLimit int `json:"page_item_limit"`
TotalItemCount int `json:"total_item_count"`
} `json:"paging"`
}
// Artifact ...
type Artifact struct {
Data struct {
ArtifactType string `json:"artifact_type"`
ExpiringDownloadURL string `json:"expiring_download_url"`
IsPublicPageEnabled bool `json:"is_public_page_enabled"`
PublicInstallPageURL string `json:"public_install_page_url"`
Slug string `json:"slug"`
Title string `json:"title"`
} `json:"data"`
}
// New Create new Bitrise API client
func New(authToken string) Client {
return Client{
authToken: authToken,
httpClient: http.Client{Timeout: 20 * time.Second},
}
}
func (c Client) get(endpoint string) (*http.Response, error) {
url := fmt.Sprintf("%s/%s/%s", domain, apiVersion, endpoint)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return &http.Response{}, err
}
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.authToken))
resp, err := c.httpClient.Do(req)
return resp, err
}
// GetArtifactsForBuild ...
func (c Client) GetArtifactsForBuild(appSlug, buildSlug string) (art Artifacts, err error) {
requestPath := fmt.Sprintf("apps/%s/builds/%s/artifacts", appSlug, buildSlug)
resp, err := c.get(requestPath)
if err != nil {
return
}
defer responseBodyCloser(resp)
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
err = fmt.Errorf("failed to get artifacts with status code (%d) for [build_slug: %s, app_slug: %s]", resp.StatusCode, appSlug, buildSlug)
return
}
err = json.NewDecoder(resp.Body).Decode(&art)
return
}
// GetArtifactDetails ...
func (c Client) GetArtifactDetails(appSlug, buildSlug, artifactSlug string) (art Artifact, err error) {
requestPath := fmt.Sprintf("apps/%s/builds/%s/artifacts/%s", appSlug, buildSlug, artifactSlug)
resp, err := c.get(requestPath)
if err != nil {
return
}
defer responseBodyCloser(resp)
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
err = fmt.Errorf("failed to get artifact details with status code (%d) for [build_slug: %s, app_slug: %s]", resp.StatusCode, appSlug, buildSlug)
return
}
err = json.NewDecoder(resp.Body).Decode(&art)
return
}
// DownloadArtifact ...
func (c Client) DownloadArtifact(appSlug, buildSlug, artifactSlug string) (io.ReadCloser, error) {
artifact, err := c.GetArtifactDetails(appSlug, buildSlug, artifactSlug)
if err != nil {
return nil, err
}
resp, err := http.Get(artifact.Data.ExpiringDownloadURL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
func responseBodyCloser(resp *http.Response) {
if err := resp.Body.Close(); err != nil {
log.Printf(" [!] Failed to close response body: %+v", err)
}
}
func errNoEnv(env string) error {
return fmt.Errorf("environment variable (%s) is not set", env)
}
func mainE() error {
accessTokenKey := "AF_ACCESS_TOKEN"
accessToken := os.Getenv(accessTokenKey)
if accessToken == "" {
return errNoEnv(accessTokenKey)
}
appSlugKey := "AF_APP_SLUG"
appSlug := os.Getenv(appSlugKey)
if appSlug == "" {
return errNoEnv(appSlugKey)
}
buildSlugKey := "AF_BUILD_SLUG"
buildSlug := os.Getenv(buildSlugKey)
if buildSlug == "" {
return errNoEnv(buildSlugKey)
}
artifactNameKey := "AF_ARTIFACT_NAME"
artifactName := os.Getenv(artifactNameKey)
if artifactName == "" {
return errNoEnv(artifactNameKey)
}
downloadDirKey := "AF_DOWNLOAD_DIR"
downloadDir := os.Getenv(downloadDirKey)
if downloadDir == "" {
downloadDir = "."
}
if err := os.MkdirAll(downloadDir, os.ModePerm); err != nil {
return err
}
c := New(accessToken)
artifacts, err := c.GetArtifactsForBuild(appSlug, buildSlug)
if err != nil {
return err
}
artifactSlugMap := map[string]string{}
for _, artifact := range artifacts.Data {
artifactSlugMap[artifact.Title] = artifact.Slug
}
artifactSlug, exists := artifactSlugMap[artifactName]
if !exists {
keys, err := json.MarshalIndent(artifactSlugMap, "", " ")
if err != nil {
return err
}
return fmt.Errorf("unable to find artifact with name (%s), available artifacts:\n%s", artifactName, string(keys))
}
reader, err := c.DownloadArtifact(appSlug, buildSlug, artifactSlug)
if err != nil {
return err
}
file, err := os.Create(filepath.Join(downloadDir, artifactName))
if err != nil {
return err
}
n, err := io.Copy(file, reader)
if err != nil {
return err
}
fmt.Printf("done, [%d byte] downloaded\n", n)
return nil
}
func main() {
if err := mainE(); err != nil {
fmt.Printf("Error: %+v\n", err)
os.Exit(1)
}
}
To use this Go script in your workflow, copy the whole script to a Script stepโs content input, set Execute with / runner binary input to: go run
, and set Script file path to $HOME/main.go
Hope this helps
2 Likes