Skip to content

Commit

Permalink
Merge pull request #17 from Genymobile/dev/use-gmsaas-json-output
Browse files Browse the repository at this point in the history
Integrate API Token and Improve gmsaas commands with json format
  • Loading branch information
thomascarpentier committed Jun 21, 2024
2 parents 8d3fd51 + b6e1e5f commit 975a2f7
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 64 deletions.
72 changes: 69 additions & 3 deletions bitrise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ app:
# Define these in your .bitrise.secrets.yml
- GMCLOUD_SAAS_EMAIL: $GMCLOUD_SAAS_EMAIL
- GMCLOUD_SAAS_PASSWORD: $GMCLOUD_SAAS_PASSWORD
- GMCLOUD_SAAS_API_TOKEN: $GMCLOUD_SAAS_API_TOKEN

workflows:
ci:
Expand All @@ -20,9 +21,74 @@ workflows:
- errcheck:
- go-test:

test:
before_run:
- audit-this-step

test-credentials:
steps:
- change-workdir:
title: Switch working dir to test / _tmp dir
description: |-
To prevent step testing issues, like referencing relative
files with just './some-file' in the step's code, which would
work for testing the step from this directory directly
but would break if the step is included in another `bitrise.yml`.
run_if: true
inputs:
- path: ./_tmp
- is_create_path: true
- path::./:
title: Genymotion Cloud SaaS Start
run_if: "true"
inputs:
- email: $GMCLOUD_SAAS_EMAIL
- password: $GMCLOUD_SAAS_PASSWORD
- recipe_uuid:
- script:
inputs:
- content: |
#!/bin/bash
echo "The value of 'GMCLOUD_SAAS_INSTANCE_UUID' is: $GMCLOUD_SAAS_INSTANCE_UUID
echo "The value of 'GMCLOUD_SAAS_INSTANCE_ADB_SERIAL_PORT' is: $GMCLOUD_SAAS_INSTANCE_ADB_SERIAL_PORT
- git::https://github.com/Genymobile/bitrise-step-genymotion-cloud-saas-stop.git:
title: "Genymotion Cloud SaaS Stop"
description: |-
Stop Genymotion Cloud SaaS Android Devices.
inputs:
- instance_uuid: $GMCLOUD_SAAS_INSTANCE_UUID

test-api-token:
steps:
- change-workdir:
title: Switch working dir to test / _tmp dir
description: |-
To prevent step testing issues, like referencing relative
files with just './some-file' in the step's code, which would
work for testing the step from this directory directly
but would break if the step is included in another `bitrise.yml`.
run_if: true
inputs:
- path: ./_tmp
- is_create_path: true
- path::./:
title: Genymotion Cloud SaaS Start
run_if: "true"
inputs:
- api_token: $GMCLOUD_SAAS_API_TOKEN
- recipe_uuid:
- adb_serial_port:
- script:
inputs:
- content: |
#!/bin/bash
echo "The value of 'GMCLOUD_SAAS_INSTANCE_UUID' is: $GMCLOUD_SAAS_INSTANCE_UUID
echo "The value of 'GMCLOUD_SAAS_INSTANCE_ADB_SERIAL_PORT' is: $GMCLOUD_SAAS_INSTANCE_ADB_SERIAL_PORT
- git::https://github.com/Genymobile/bitrise-step-genymotion-cloud-saas-stop.git:
title: "Genymotion Cloud SaaS Stop"
description: |-
Stop Genymotion Cloud SaaS Android Devices.
inputs:
- instance_uuid: $GMCLOUD_SAAS_INSTANCE_UUID

test-adb-serial-port:
steps:
- change-workdir:
title: Switch working dir to test / _tmp dir
Expand Down
141 changes: 86 additions & 55 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"os"
"os/exec"
Expand All @@ -25,12 +25,24 @@ var isError bool = false

// Config ...
type Config struct {
GMCloudSaaSEmail string `env:"email,required"`
GMCloudSaaSPassword stepconf.Secret `env:"password,required"`
GMCloudSaaSEmail string `env:"email"`
GMCloudSaaSPassword stepconf.Secret `env:"password"`
GMCloudSaaSAPIToken stepconf.Secret `env:"api_token"`

GMCloudSaaSRecipeUUID string `env:"recipe_uuid,required"`
GMCloudSaaSAdbSerialPort string `env:"adb_serial_port"`
GMCloudSaaSGmsaasVersion string `env:"gmsaas_version"`
GMCloudSaaSGmsaasVersion string `env:"gmsaas_version"`
}

type Instance struct {
UUID string `json:"uuid"`
ADB_SERIAL string `json:"adb_serial"`
NAME string `json:"name"`
}

type Output struct {
Instance Instance `json:"instance"`
Instances []Instance `json:"instances"`
}

// install gmsaas if not installed.
Expand All @@ -41,15 +53,18 @@ func ensureGMSAASisInstalled(version string) error {

var installCmd *exec.Cmd
if version != "" {
installCmd = exec.Command("pip3", "install", "gmsaas=="+version)
installCmd = exec.Command("pip3", "install", "gmsaas=="+version, "--break-system-packages")
} else {
installCmd = exec.Command("pip3", "install", "gmsaas")
installCmd = exec.Command("pip3", "install", "gmsaas", "--break-system-packages")
}

if out, err := installCmd.CombinedOutput(); err != nil {
return fmt.Errorf("%s failed, error: %s | output: %s", installCmd.Args, err, out)
}

// Execute asdf reshim to update PATH
exec.Command("asdf", "reshim", "python").CombinedOutput()

if version != "" {
log.Infof("gmsaas %s has been installed.", version)
} else {
Expand Down Expand Up @@ -82,42 +97,32 @@ func setOperationFailed(format string, args ...interface{}) {
isError = true
}

func getInstanceDetails(name string) (string, string) {
for index, line := range getInstancesList() {
if index >= 2 {
s := strings.Fields(line)
if strings.Compare(s[1], name) == 0 {
uuid := s[0]
serial := s[2]
return uuid, serial
}
}
func getADBSerialFromJSON(jsonData string) string {
var output Output
if err := json.Unmarshal([]byte(jsonData), &output); err != nil {
setOperationFailed("Issue with JSON parsing : %w", err)
}
return "", ""
return output.Instance.ADB_SERIAL
}

func getInstancesList() []string {
result := []string{}

adminList := exec.Command("gmsaas", "instances", "list")
out, err := adminList.StdoutPipe()
func getInstanceDetails(name string) (string, string) {
cmd := command.New("gmsaas", "--format", "json", "instances", "list")
out, err := cmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
setOperationFailed("Issue with gmsaas command line: %s", err)
return result
}
if err := adminList.Start(); err != nil {
setOperationFailed("Issue with gmsaas command line: %s", err)
return result
}
// Create new Scanner.
scanner := bufio.NewScanner(out)
// Use Scan.
for scanner.Scan() {
line := scanner.Text()
// Append line to result.
result = append(result, line)
}
return result
setOperationFailed("Failed to get instances list, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, out)
return "", ""
}
var output Output
if err := json.Unmarshal([]byte(out), &output); err != nil {
setOperationFailed("Issue with JSON parsing : %w", err)
}

for _, instance := range output.Instances {
if instance.NAME == name {
return instance.UUID, instance.ADB_SERIAL
}
}
return "", ""
}

func configureAndroidSDKPath() {
Expand All @@ -138,44 +143,66 @@ func configureAndroidSDKPath() {
}
}

func login(username, password string) {
func login(api_token, username, password string) {
log.Infof("Login Genymotion Account")
cmd := command.New("gmsaas", "auth", "login", username, password)
out, err := cmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
abortf("Failed to log with gmsaas, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, out)

var cmd *exec.Cmd
if api_token != "" {
cmd = exec.Command("gmsaas", "auth", "token", api_token)
} else if username != "" && password != "" {
cmd = exec.Command("gmsaas", "auth", "login", username, password)
} else {
abortf("Invalid arguments. Must provide either a token or both email and password.")
return
}

if out, err := cmd.CombinedOutput(); err != nil {
abortf("Failed to login with gmsaas, error: error: %s | output: %s", cmd.Args, err, out)
return
}

log.Infof("Logged to Genymotion Cloud SaaS platform")
}

func startInstanceAndConnect(wg *sync.WaitGroup, recipeUUID, instanceName, adbSerialPort string) {
var output Output
defer wg.Done()

cmd := command.New("gmsaas", "instances", "start", recipeUUID, instanceName)
instanceUUID, err := cmd.RunAndReturnTrimmedCombinedOutput()
cmd := command.New("gmsaas", "--format", "json", "instances", "start", recipeUUID, instanceName)
jsonData, err := cmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
setOperationFailed("Failed to start a device, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, instanceUUID)
setOperationFailed("Failed to start a device, error: %s | output: %s\n", err, jsonData)
return
}

log.Infof("Device started %s", instanceUUID)
if err := json.Unmarshal([]byte(jsonData), &output); err != nil {
setOperationFailed("Issue with JSON parsing : %s", err)
}

// Connect to adb with adb-serial-port
if adbSerialPort != "" {
cmd := command.New("gmsaas", "instances", "adbconnect", instanceUUID, "--adb-serial-port", adbSerialPort)
out, err := cmd.RunAndReturnTrimmedCombinedOutput()
cmd := command.New("gmsaas", "--format", "json", "instances", "adbconnect", output.Instance.UUID, "--adb-serial-port", adbSerialPort)
ADBjsonData, err := cmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
setOperationFailed("Failed to connect a device, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, out)
setOperationFailed("Failed to connect a device, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, ADBjsonData)
return
}
if err := json.Unmarshal([]byte(ADBjsonData), &output); err != nil {
setOperationFailed("Issue with JSON parsing : %s", err)
}
} else {
cmd := command.New("gmsaas", "instances", "adbconnect", instanceUUID)
out, err := cmd.RunAndReturnTrimmedCombinedOutput()
cmd := command.New("gmsaas", "--format", "json", "instances", "adbconnect", output.Instance.UUID)
ADBjsonData, err := cmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
setOperationFailed("Failed to connect a device, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, out)
setOperationFailed("Failed to connect a device, error: error: %s | output: %s", cmd.PrintableCommandArgs(), err, ADBjsonData)
return
}
if err := json.Unmarshal([]byte(ADBjsonData), &output); err != nil {
setOperationFailed("Issue with JSON parsing : %s", err)
}
}

log.Infof("Genymotion instance UUID : %s has been started and connected with ADB Serial Port : %s", output.Instance.UUID, output.Instance.ADB_SERIAL)

}

func main() {
Expand All @@ -195,7 +222,11 @@ func main() {
printError("Failed to export %s, error: %v", "GMSAAS_USER_AGENT_EXTRA_DATA", err)
}

login(c.GMCloudSaaSEmail, string(c.GMCloudSaaSPassword))
if c.GMCloudSaaSAPIToken != "" {
login(string(c.GMCloudSaaSAPIToken), "", "")
} else {
login("", c.GMCloudSaaSEmail, string(c.GMCloudSaaSPassword))
}

instancesList := []string{}
adbSerialList := []string{}
Expand Down
17 changes: 11 additions & 6 deletions step.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,26 @@ toolkit:


inputs:
- api_token: ""
opts:
title: Genymotion Cloud SaaS API Token
summary: ""
description: |-
API Token to authenticate to your Genymotion Cloud SaaS account. If you don't have an account please register on [https://cloud.geny.io](https://cloud.geny.io/?&utm_source=web-referral&utm_medium=docs&utm_campaign=bitrise&utm_content=signup) and create an [API Token](https://cloud.geny.io/api)
- email: ""
opts:
title: Genymotion Cloud SaaS email
summary: ""
description: |-
Email of your Genymotion Cloud SaaS account, if you don't have an account please create it first on [https://cloud.geny.io](https://cloud.geny.io/?&utm_source=web-referral&utm_medium=docs&utm_campaign=bitrise&utm_content=signup)
is_required: true
DEPRECATED : Email of your Genymotion Cloud SaaS account, if you don't have an account please create it first on [https://cloud.geny.io](https://cloud.geny.io/?&utm_source=web-referral&utm_medium=docs&utm_campaign=bitrise&utm_content=signup)
- password: ""
opts:
title: Genymotion Cloud SaaS password
summary: ""
description: |-
Password of your Genymotion Cloud SaaS account.
is_required: true
DEPRECATED: Password of your Genymotion Cloud SaaS account.
is_sensitive: true

- recipe_uuid: ""
Expand Down Expand Up @@ -125,12 +130,12 @@ inputs:
For example:
`4321,4322,4323`
- gmsaas_version: "1.9.0"
- gmsaas_version: "1.11.0"
opts:
title: gmsaas version
summary: ""
description: |-
Install a specific version of gmsaas, per default it will install the latest gmsaas compatible : 1.9.0
Install a specific version of gmsaas, per default it will install the latest compatible gmsaas version : 1.11.0
Expand Down

0 comments on commit 975a2f7

Please sign in to comment.