From a8d1eb1f7b0e327c72b45b4410628a0dd4b8a109 Mon Sep 17 00:00:00 2001 From: BirmacherAkos <37296013+BirmacherAkos@users.noreply.github.com> Date: Mon, 24 Sep 2018 11:33:31 +0200 Subject: [PATCH] Step Inititialization (#2) ## Basics This step uses the [expo-cli](https://docs.expo.io/versions/latest/introduction/installation#local-development-tool-expo-cli) to create Xcode and Android Studio projects for your app. ## Eject Method - If you use any Expo SDK you must to provide username and password for your Expo account. Because in this case, the expo-cli will use the expoKit eject method which needs an account. - If your project does not import any Expo SDK then you should not use the username/password option. Because in this case, the expo-cli will use the plain eject method. --- .gitignore | 2 + Gopkg.lock | 37 +++ Gopkg.toml | 11 + README.md | 98 +++++++ bitrise.yml | 142 ++++++++++ main.go | 200 +++++++++++++ step.yml | 63 +++++ vendor/github.com/bitrise-io/go-utils/LICENSE | 22 ++ .../go-utils/colorstring/colorstring.go | 110 ++++++++ .../bitrise-io/go-utils/command/command.go | 263 ++++++++++++++++++ .../bitrise-io/go-utils/command/file.go | 57 ++++ .../bitrise-io/go-utils/command/zip.go | 115 ++++++++ .../go-utils/errorutil/errorutil.go | 36 +++ .../bitrise-io/go-utils/log/defaultlogger.go | 57 ++++ .../bitrise-io/go-utils/log/dummylogger.go | 30 ++ .../bitrise-io/go-utils/log/json_logger.go | 31 +++ .../github.com/bitrise-io/go-utils/log/log.go | 34 +++ .../bitrise-io/go-utils/log/logger.go | 12 + .../bitrise-io/go-utils/log/print.go | 89 ++++++ .../bitrise-io/go-utils/log/raw_logger.go | 31 +++ .../bitrise-io/go-utils/log/severity.go | 35 +++ .../go-utils/parseutil/parseutil.go | 95 +++++++ .../bitrise-io/go-utils/pathutil/pathutil.go | 181 ++++++++++++ .../bitrise-io/go-utils/pointers/pointers.go | 98 +++++++ .../go-steputils/stepconf/stepconf.go | 199 +++++++++++++ 25 files changed, 2048 insertions(+) create mode 100644 .gitignore create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 README.md create mode 100644 bitrise.yml create mode 100644 main.go create mode 100644 step.yml create mode 100644 vendor/github.com/bitrise-io/go-utils/LICENSE create mode 100644 vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go create mode 100644 vendor/github.com/bitrise-io/go-utils/command/command.go create mode 100644 vendor/github.com/bitrise-io/go-utils/command/file.go create mode 100644 vendor/github.com/bitrise-io/go-utils/command/zip.go create mode 100644 vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/defaultlogger.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/dummylogger.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/json_logger.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/log.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/logger.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/print.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/raw_logger.go create mode 100644 vendor/github.com/bitrise-io/go-utils/log/severity.go create mode 100644 vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go create mode 100644 vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go create mode 100644 vendor/github.com/bitrise-io/go-utils/pointers/pointers.go create mode 100644 vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f235c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.bitrise* +_tmp diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..cad27c7 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,37 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:afe05c8adf1e427aba44ff9a3fba6d0eb766244cfc367dd4332583bf925533b1" + name = "github.com/bitrise-io/go-utils" + packages = [ + "colorstring", + "command", + "errorutil", + "log", + "parseutil", + "pathutil", + "pointers", + ] + pruneopts = "UT" + revision = "d1de0dbcb811588e61362aff67bb700b93c13c17" + +[[projects]] + branch = "master" + digest = "1:d9f47b0642c42512eefed4aad27c4daf41e9f3380087d302cd0a9f5612143856" + name = "github.com/bitrise-tools/go-steputils" + packages = ["stepconf"] + pruneopts = "UT" + revision = "d4d9e08cc4347e8784bb18419fcdceb932e17019" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/bitrise-io/go-utils/command", + "github.com/bitrise-io/go-utils/log", + "github.com/bitrise-tools/go-steputils/stepconf", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..54d02ae --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + branch = "master" + name = "github.com/bitrise-io/go-utils" + +[[constraint]] + branch = "master" + name = "github.com/bitrise-tools/go-steputils" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..344c37a --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# [Beta] Expo Detach + +Using the The Expo Development CLI to Creates Xcode and Android Studio projects for your app. + + +## How to use this Step + +Can be run directly with the [bitrise CLI](https://github.com/bitrise-io/bitrise), +just `git clone` this repository, `cd` into it's folder in your Terminal/Command Line +and call `bitrise run test`. + +*Check the `bitrise.yml` file for required inputs which have to be +added to your `.bitrise.secrets.yml` file!* + +Step by step: + +1. Open up your Terminal / Command Line +2. `git clone` the repository +3. `cd` into the directory of the step (the one you just `git clone`d) +5. Create a `.bitrise.secrets.yml` file in the same directory of `bitrise.yml` + (the `.bitrise.secrets.yml` is a git ignored file, you can store your secrets in it) +6. Check the `bitrise.yml` file for any secret you should set in `.bitrise.secrets.yml` + * Best practice is to mark these options with something like `# define these in your .bitrise.secrets.yml`, in the `app:envs` section. +7. Once you have all the required secret parameters in your `.bitrise.secrets.yml` you can just run this step with the [bitrise CLI](https://github.com/bitrise-io/bitrise): `bitrise run test` + +An example `.bitrise.secrets.yml` file: + +``` +envs: +- A_SECRET_PARAM_ONE: the value for secret one +- A_SECRET_PARAM_TWO: the value for secret two +``` + +## How to create your own step + +1. Create a new git repository for your step (**don't fork** the *step template*, create a *new* repository) +2. Copy the [step template](https://github.com/bitrise-steplib/step-template) files into your repository +3. Fill the `step.sh` with your functionality +4. Wire out your inputs to `step.yml` (`inputs` section) +5. Fill out the other parts of the `step.yml` too +6. Provide test values for the inputs in the `bitrise.yml` +7. Run your step with `bitrise run test` - if it works, you're ready + +__For Step development guidelines & best practices__ check this documentation: [https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md](https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md). + +**NOTE:** + +If you want to use your step in your project's `bitrise.yml`: + +1. git push the step into it's repository +2. reference it in your `bitrise.yml` with the `git::PUBLIC-GIT-CLONE-URL@BRANCH` step reference style: + +``` +- git::https://github.com/user/my-step.git@branch: + title: My step + inputs: + - my_input_1: "my value 1" + - my_input_2: "my value 2" +``` + +You can find more examples of step reference styles +in the [bitrise CLI repository](https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml#L65). + +## How to contribute to this Step + +1. Fork this repository +2. `git clone` it +3. Create a branch you'll work on +4. To use/test the step just follow the **How to use this Step** section +5. Do the changes you want to +6. Run/test the step before sending your contribution + * You can also test the step in your `bitrise` project, either on your Mac or on [bitrise.io](https://www.bitrise.io) + * You just have to replace the step ID in your project's `bitrise.yml` with either a relative path, or with a git URL format + * (relative) path format: instead of `- original-step-id:` use `- path::./relative/path/of/script/on/your/Mac:` + * direct git URL format: instead of `- original-step-id:` use `- git::https://github.com/user/step.git@branch:` + * You can find more example of alternative step referencing at: https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml +7. Once you're done just commit your changes & create a Pull Request + + +## Share your own Step + +You can share your Step or step version with the [bitrise CLI](https://github.com/bitrise-io/bitrise). If you use the `bitrise.yml` included in this repository, all you have to do is: + +1. In your Terminal / Command Line `cd` into this directory (where the `bitrise.yml` of the step is located) +1. Run: `bitrise run test` to test the step +1. Run: `bitrise run audit-this-step` to audit the `step.yml` +1. Check the `share-this-step` workflow in the `bitrise.yml`, and fill out the + `envs` if you haven't done so already (don't forget to bump the version number if this is an update + of your step!) +1. Then run: `bitrise run share-this-step` to share the step (version) you specified in the `envs` +1. Send the Pull Request, as described in the logs of `bitrise run share-this-step` + +That's all ;) + +## Trigger a new release + +- __merge every code changes__ to the `master` branch +- __push the new version tag__ to the `master` branch diff --git a/bitrise.yml b/bitrise.yml new file mode 100644 index 0000000..72de4dd --- /dev/null +++ b/bitrise.yml @@ -0,0 +1,142 @@ +format_version: 5 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +app: + envs: + - BITRISE_STEP_GIT_CLONE_URL: https://github.com/bitrise-steplib/steps-expo-detach.git + + # Define it in .bitrise.secrets.yml + - USER_NAME: $USER_NAME + - PASSWORD: $PASSWORD + +workflows: + test: + envs: + - ORIGIN_SOURCE_DIR: $BITRISE_SOURCE_DIR + - SAMPLE_APP_URL: https://github.com/bitrise-samples/react-native-expo.git + before_run: + - audit-this-step + steps: + - go-list: + - golint: + - errcheck: + - go-test: + after_run: + - plain + - expo_kit + - plain_2_0_0 + - expo_kit_2_0_0 + + plain: + before_run: + - _clear_workdir + steps: + - script: + title: Clone sample app + inputs: + - content: git clone $SAMPLE_APP_URL . && cd + - npm@0.9.1: + title: Install node modules for the expo project + inputs: + - command: install + - path::./: + title: Step Test + run_if: true + inputs: + - project_path: $BITRISE_SOURCE_DIR + - expo_cli_verson: "latest" + + plain_2_0_0: + before_run: + - _clear_workdir + steps: + - script: + title: Clone sample app + inputs: + - content: git clone $SAMPLE_APP_URL . && cd + - npm@0.9.1: + title: Install node modules for the expo project + inputs: + - command: install + - path::./: + title: Step Test + run_if: true + inputs: + - project_path: $BITRISE_SOURCE_DIR + - expo_cli_verson: "2.0.0" + + expo_kit: + before_run: + - _clear_workdir + steps: + - script: + title: Clone sample app + inputs: + - content: git clone -b expo_sdk $SAMPLE_APP_URL . && cd + - npm@0.9.1: + title: Install node modules for the expo project + inputs: + - command: install + - path::./: + title: Step Test + run_if: true + inputs: + - project_path: $BITRISE_SOURCE_DIR + - expo_cli_verson: "latest" + - user_name: $USER_NAME + - password: $PASSWORD + + expo_kit_2_0_0: + before_run: + - _clear_workdir + steps: + - script: + title: Clone sample app + inputs: + - content: git clone -b expo_sdk $SAMPLE_APP_URL . && cd + - npm@0.9.1: + title: Install node modules for the expo project + inputs: + - command: install + - path::./: + title: Step Test + run_if: true + inputs: + - project_path: $BITRISE_SOURCE_DIR + - expo_cli_verson: "2.0.0" + - user_name: $USER_NAME + - password: $PASSWORD + + _clear_workdir: + envs: + steps: + - script: + inputs: + - content: |- + #!/bin/bash + set -ex + cd ${ORIGIN_SOURCE_DIR} + rm -rf "./_tmp" + mkdir "_tmp" + - 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: ${ORIGIN_SOURCE_DIR}/_tmp + - is_create_path: true + + # ---------------------------------------------------------------- + # --- workflows to Share this step into a Step Library + audit-this-step: + steps: + - script: + inputs: + - content: |- + #!/bin/bash + set -ex + stepman audit --step-yml ./step.yml diff --git a/main.go b/main.go new file mode 100644 index 0000000..b890f88 --- /dev/null +++ b/main.go @@ -0,0 +1,200 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-tools/go-steputils/stepconf" +) + +// Config ... +type Config struct { + ProjectPath string `env:"project_path,dir"` + ExpoCLIVersion string `env:"expo_cli_verson,required"` + UserName string `env:"user_name"` + Password stepconf.Secret `env:"password"` +} + +// EjectMethod if the project is using Expo SDK and you choose the "plain" --eject-method those imports will stop working. +type EjectMethod string + +// const ... +const ( + Plain EjectMethod = "plain" + ExpoKit EjectMethod = "expoKit" +) + +func (m EjectMethod) String() string { + return string(m) +} + +// Expo CLI +type Expo struct { + Version string + Method EjectMethod +} + +// installExpoCLI runs the install npm command to install the expo-cli +func (e Expo) installExpoCLI() error { + args := []string{"install", "-g"} + if e.Version != "latest" { + args = append(args, "expo-cli@"+e.Version) + } else { + args = append(args, "expo-cli") + } + + cmd := command.New("npm", args...) + cmd.SetStdout(os.Stdout) + cmd.SetStderr(os.Stderr) + + log.Printf("$ " + cmd.PrintableCommandArgs()) + return cmd.Run() +} + +// Login with your Expo account +func (e Expo) login(userName string, password stepconf.Secret) error { + args := []string{"login", "--non-interactive", "-u", userName, "-p", string(password)} + + cmd := command.New("expo", args...) + cmd.SetStdout(os.Stdout) + cmd.SetStderr(os.Stderr) + + nonFilteredArgs := ("$ " + cmd.PrintableCommandArgs()) + fileredArgs := strings.Replace(nonFilteredArgs, string(password), "[REDACTED]", -1) + log.Printf(fileredArgs) + + return cmd.Run() +} + +// Logout from your Expo account +func (e Expo) logout() error { + cmd := command.New("expo", "logout", "--non-interactive") + cmd.SetStdout(os.Stdout) + cmd.SetStderr(os.Stderr) + + log.Printf("$ " + cmd.PrintableCommandArgs()) + return cmd.Run() +} + +// Eject command creates Xcode and Android Studio projects for your app. +func (e Expo) eject() error { + args := []string{"eject", "--non-interactive", "--eject-method", e.Method.String()} + + cmd := command.New("expo", args...) + cmd.SetStdout(os.Stdout) + cmd.SetStderr(os.Stderr) + + log.Printf("$ " + cmd.PrintableCommandArgs()) + return cmd.Run() +} + +func failf(format string, v ...interface{}) { + log.Errorf(format, v...) + log.Warnf("For more details you can enable the debug logs by turning on the verbose step input.") + os.Exit(1) +} + +func validateUserNameAndpassword(userName string, password stepconf.Secret) error { + if userName != "" && string(password) == "" { + return fmt.Errorf("user name is specified but password is not provided") + } + + if userName == "" && string(password) != "" { + return fmt.Errorf("password is specified but is not provided user name") + } + return nil +} + +func main() { + var cfg Config + if err := stepconf.Parse(&cfg); err != nil { + failf("Issue with input: %s", err) + } + + fmt.Println() + stepconf.Print(cfg) + + if err := validateUserNameAndpassword(cfg.UserName, cfg.Password); err != nil { + failf("Input validation error: %s", err) + } + + // + // Select the --eject-method + ejectMethod := Plain + fmt.Println() + log.Infof("Define --eject-method") + { + if cfg.UserName != "" { + ejectMethod = ExpoKit + log.Printf("Expo account credentials have provided => Set the --eject-method to %s", ejectMethod) + } else { + log.Printf("Expo account credentials have not provided => Set the --eject-method to %s", ejectMethod) + } + } + + e := Expo{ + Version: cfg.ExpoCLIVersion, + Method: ejectMethod, + } + + // + // Install expo-cli + fmt.Println() + log.Infof("Install Expo CLI version: %s", cfg.ExpoCLIVersion) + { + if err := e.installExpoCLI(); err != nil { + failf("Failed to install the selected (%s) version for Expo CLI, error: %s", cfg.ExpoCLIVersion, err) + } + } + + // + // Logging in the user to the Expo account + fmt.Println() + log.Infof("Login to Expo") + { + switch ejectMethod { + case ExpoKit: + if err := e.login(cfg.UserName, cfg.Password); err != nil { + failf("Failed to log in to your provided Expo account, error: %s", err) + } + case Plain: + log.Printf("--eject-method has been set to plain => Skip...") + } + } + + // + // Logging out the user from the Expo account at the end of the step (even if it fails) + defer func() { + fmt.Println() + log.Infof("Logging out from Expo") + { + if e.Method == ExpoKit { + if err := e.logout(); err != nil { + log.Warnf("Failed to log out from your Expo account, error: %s", err) + } + } else if e.Method == ExpoKit { + log.Printf("Logout input was set to false => Skip...") + } else { + log.Printf("You were not logged in => Skip...") + } + } + }() + + // + // Eject project via the Expo CLI + fmt.Println() + log.Infof("Eject project") + { + if err := e.eject(); err != nil { + failf("Failed to eject project (%s), error: %s", filepath.Base(cfg.ProjectPath), err) + } + + } + + fmt.Println() + log.Donef("Successfully ejected your project") +} diff --git a/step.yml b/step.yml new file mode 100644 index 0000000..3a8f258 --- /dev/null +++ b/step.yml @@ -0,0 +1,63 @@ +title: '[Beta] Expo Detach' +summary: Creates Xcode and Android Studio projects for your app. Use this if you need to add custom native functionality. +description: |- + Using the The Expo Development CLI to create Xcode and Android Studio projects for your app. + **EXPO DEVELOPMENT CLI** https://docs.expo.io/versions/latest/introduction/installation#local-development-tool-expo-cli +website: https://github.com/bitrise-steplib/steps-expo-detach +source_code_url: https://github.com/bitrise-steplib/steps-expo-detach +support_url: https://github.com/bitrise-steplib/steps-expo-detach/issues + +type_tags: + - utility +deps: + brew: + - name: go + apt_get: + - name: golang + bin_name: go +toolkit: + go: + package_name: github.com/bitrise-steplib/steps-expo-detach + +inputs: + - project_path: $BITRISE_SOURCE_DIR + opts: + title: Project path + summary: Project path + description: |- + The path of your project directory + is_required: true + - expo_cli_verson: "latest" + opts: + title: Expo CLI version + summary: Specify the Expo CLI verion to install. + description: |- + Specify the Expo CLI verion to install. + The Expo CLI detach your project and creates Xcode and Android Studio projects for your app. + + + [https://docs.expo.io/versions/latest/introduction/installation#local-development-tool-expo-cli](https://docs.expo.io/versions/latest/introduction/installation#local-development-tool-expo-cli) + + + A couple of examples: + + + * "2.0.0" + * latest + is_required: "true" + - user_name: "" + opts: + title: Username for Expo + summary: Username for Expo + description: |- + Your account's username for `https://expo.io/` . + + + **NOTE** You need to use your username and not your e-mail adress. + - password: "" + opts: + title: Password for your Expo account + summary: Password for your Expo account + description: |- + Your password for `https://expo.io/` . + is_sensitive: true \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/go-utils/LICENSE b/vendor/github.com/bitrise-io/go-utils/LICENSE new file mode 100644 index 0000000..a6a5c39 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go b/vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go new file mode 100644 index 0000000..5f31fa9 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go @@ -0,0 +1,110 @@ +package colorstring + +import ( + "fmt" +) + +// Color ... +// ANSI color escape sequences +type Color string + +const ( + blackColor Color = "\x1b[30;1m" + redColor Color = "\x1b[31;1m" + greenColor Color = "\x1b[32;1m" + yellowColor Color = "\x1b[33;1m" + blueColor Color = "\x1b[34;1m" + magentaColor Color = "\x1b[35;1m" + cyanColor Color = "\x1b[36;1m" + resetColor Color = "\x1b[0m" +) + +// ColorFunc ... +type ColorFunc func(a ...interface{}) string + +func addColor(color Color, msg string) string { + return string(color) + msg + string(resetColor) +} + +// NoColor ... +func NoColor(a ...interface{}) string { + return fmt.Sprint(a...) +} + +// Black ... +func Black(a ...interface{}) string { + return addColor(blackColor, fmt.Sprint(a...)) +} + +// Red ... +func Red(a ...interface{}) string { + return addColor(redColor, fmt.Sprint(a...)) +} + +// Green ... +func Green(a ...interface{}) string { + return addColor(greenColor, fmt.Sprint(a...)) +} + +// Yellow ... +func Yellow(a ...interface{}) string { + return addColor(yellowColor, fmt.Sprint(a...)) +} + +// Blue ... +func Blue(a ...interface{}) string { + return addColor(blueColor, fmt.Sprint(a...)) +} + +// Magenta ... +func Magenta(a ...interface{}) string { + return addColor(magentaColor, fmt.Sprint(a...)) +} + +// Cyan ... +func Cyan(a ...interface{}) string { + return addColor(cyanColor, fmt.Sprint(a...)) +} + +// ColorfFunc ... +type ColorfFunc func(format string, a ...interface{}) string + +// NoColorf ... +func NoColorf(format string, a ...interface{}) string { + return NoColor(fmt.Sprintf(format, a...)) +} + +// Blackf ... +func Blackf(format string, a ...interface{}) string { + return Black(fmt.Sprintf(format, a...)) +} + +// Redf ... +func Redf(format string, a ...interface{}) string { + return Red(fmt.Sprintf(format, a...)) +} + +// Greenf ... +func Greenf(format string, a ...interface{}) string { + return Green(fmt.Sprintf(format, a...)) +} + +// Yellowf ... +func Yellowf(format string, a ...interface{}) string { + return Yellow(fmt.Sprintf(format, a...)) +} + +// Bluef ... +func Bluef(format string, a ...interface{}) string { + return Blue(fmt.Sprintf(format, a...)) +} + +// Magentaf ... +func Magentaf(format string, a ...interface{}) string { + return Magenta(fmt.Sprintf(format, a...)) +} + +// Cyanf ... +func Cyanf(format string, a ...interface{}) string { + return Cyan(fmt.Sprintf(format, a...)) +} diff --git a/vendor/github.com/bitrise-io/go-utils/command/command.go b/vendor/github.com/bitrise-io/go-utils/command/command.go new file mode 100644 index 0000000..4cd005a --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/command.go @@ -0,0 +1,263 @@ +package command + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/errorutil" +) + +// ---------- + +// Model ... +type Model struct { + cmd *exec.Cmd +} + +// New ... +func New(name string, args ...string) *Model { + return &Model{ + cmd: exec.Command(name, args...), + } +} + +// NewWithStandardOuts - same as NewCommand, but sets the command's +// stdout and stderr to the standard (OS) out (os.Stdout) and err (os.Stderr) +func NewWithStandardOuts(name string, args ...string) *Model { + return New(name, args...).SetStdout(os.Stdout).SetStderr(os.Stderr) +} + +// NewWithParams ... +func NewWithParams(params ...string) (*Model, error) { + if len(params) == 0 { + return nil, errors.New("no command provided") + } else if len(params) == 1 { + return New(params[0]), nil + } + + return New(params[0], params[1:]...), nil +} + +// NewFromSlice ... +func NewFromSlice(slice []string) (*Model, error) { + return NewWithParams(slice...) +} + +// NewWithCmd ... +func NewWithCmd(cmd *exec.Cmd) *Model { + return &Model{ + cmd: cmd, + } +} + +// GetCmd ... +func (m *Model) GetCmd() *exec.Cmd { + return m.cmd +} + +// SetDir ... +func (m *Model) SetDir(dir string) *Model { + m.cmd.Dir = dir + return m +} + +// SetEnvs ... +func (m *Model) SetEnvs(envs ...string) *Model { + m.cmd.Env = envs + return m +} + +// AppendEnvs - appends the envs to the current os.Environ() +// Calling this multiple times will NOT appens the envs one by one, +// only the last "envs" set will be appended to os.Environ()! +func (m *Model) AppendEnvs(envs ...string) *Model { + return m.SetEnvs(append(os.Environ(), envs...)...) +} + +// SetStdin ... +func (m *Model) SetStdin(in io.Reader) *Model { + m.cmd.Stdin = in + return m +} + +// SetStdout ... +func (m *Model) SetStdout(out io.Writer) *Model { + m.cmd.Stdout = out + return m +} + +// SetStderr ... +func (m *Model) SetStderr(err io.Writer) *Model { + m.cmd.Stderr = err + return m +} + +// Run ... +func (m Model) Run() error { + return m.cmd.Run() +} + +// RunAndReturnExitCode ... +func (m Model) RunAndReturnExitCode() (int, error) { + return RunCmdAndReturnExitCode(m.cmd) +} + +// RunAndReturnTrimmedOutput ... +func (m Model) RunAndReturnTrimmedOutput() (string, error) { + return RunCmdAndReturnTrimmedOutput(m.cmd) +} + +// RunAndReturnTrimmedCombinedOutput ... +func (m Model) RunAndReturnTrimmedCombinedOutput() (string, error) { + return RunCmdAndReturnTrimmedCombinedOutput(m.cmd) +} + +// PrintableCommandArgs ... +func (m Model) PrintableCommandArgs() string { + return PrintableCommandArgs(false, m.cmd.Args) +} + +// ---------- + +// PrintableCommandArgs ... +func PrintableCommandArgs(isQuoteFirst bool, fullCommandArgs []string) string { + cmdArgsDecorated := []string{} + for idx, anArg := range fullCommandArgs { + quotedArg := strconv.Quote(anArg) + if idx == 0 && !isQuoteFirst { + quotedArg = anArg + } + cmdArgsDecorated = append(cmdArgsDecorated, quotedArg) + } + + return strings.Join(cmdArgsDecorated, " ") +} + +// RunCmdAndReturnExitCode ... +func RunCmdAndReturnExitCode(cmd *exec.Cmd) (int, error) { + err := cmd.Run() + if err != nil { + exitCode, castErr := errorutil.CmdExitCodeFromError(err) + if castErr != nil { + return 1, fmt.Errorf("failed get exit code from error: %s, error: %s", err, castErr) + } + + return exitCode, err + } + + return 0, nil +} + +// RunCmdAndReturnTrimmedOutput ... +func RunCmdAndReturnTrimmedOutput(cmd *exec.Cmd) (string, error) { + outBytes, err := cmd.Output() + outStr := string(outBytes) + return strings.TrimSpace(outStr), err +} + +// RunCmdAndReturnTrimmedCombinedOutput ... +func RunCmdAndReturnTrimmedCombinedOutput(cmd *exec.Cmd) (string, error) { + outBytes, err := cmd.CombinedOutput() + outStr := string(outBytes) + return strings.TrimSpace(outStr), err +} + +// RunCommandWithReaderAndWriters ... +func RunCommandWithReaderAndWriters(inReader io.Reader, outWriter, errWriter io.Writer, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = inReader + cmd.Stdout = outWriter + cmd.Stderr = errWriter + return cmd.Run() +} + +// RunCommandWithWriters ... +func RunCommandWithWriters(outWriter, errWriter io.Writer, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdout = outWriter + cmd.Stderr = errWriter + return cmd.Run() +} + +// RunCommandInDirWithEnvsAndReturnExitCode ... +func RunCommandInDirWithEnvsAndReturnExitCode(envs []string, dir, name string, args ...string) (int, error) { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if dir != "" { + cmd.Dir = dir + } + if len(envs) > 0 { + cmd.Env = envs + } + + return RunCmdAndReturnExitCode(cmd) +} + +// RunCommandInDirAndReturnExitCode ... +func RunCommandInDirAndReturnExitCode(dir, name string, args ...string) (int, error) { + return RunCommandInDirWithEnvsAndReturnExitCode([]string{}, dir, name, args...) +} + +// RunCommandWithEnvsAndReturnExitCode ... +func RunCommandWithEnvsAndReturnExitCode(envs []string, name string, args ...string) (int, error) { + return RunCommandInDirWithEnvsAndReturnExitCode(envs, "", name, args...) +} + +// RunCommandInDir ... +func RunCommandInDir(dir, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if dir != "" { + cmd.Dir = dir + } + return cmd.Run() +} + +// RunCommand ... +func RunCommand(name string, args ...string) error { + return RunCommandInDir("", name, args...) +} + +// RunCommandAndReturnStdout .. +func RunCommandAndReturnStdout(name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + return RunCmdAndReturnTrimmedOutput(cmd) +} + +// RunCommandInDirAndReturnCombinedStdoutAndStderr ... +func RunCommandInDirAndReturnCombinedStdoutAndStderr(dir, name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + if dir != "" { + cmd.Dir = dir + } + return RunCmdAndReturnTrimmedCombinedOutput(cmd) +} + +// RunCommandAndReturnCombinedStdoutAndStderr .. +func RunCommandAndReturnCombinedStdoutAndStderr(name string, args ...string) (string, error) { + return RunCommandInDirAndReturnCombinedStdoutAndStderr("", name, args...) +} + +// RunBashCommand ... +func RunBashCommand(cmdStr string) error { + return RunCommand("bash", "-c", cmdStr) +} + +// RunBashCommandLines ... +func RunBashCommandLines(cmdLines []string) error { + for _, aLine := range cmdLines { + if err := RunCommand("bash", "-c", aLine); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/command/file.go b/vendor/github.com/bitrise-io/go-utils/command/file.go new file mode 100644 index 0000000..6887e3f --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/file.go @@ -0,0 +1,57 @@ +package command + +import ( + "errors" + "os" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// CopyFile ... +func CopyFile(src, dst string) error { + // replace with a pure Go implementation? + // Golang proposal was: https://go-review.googlesource.com/#/c/1591/5/src/io/ioutil/ioutil.go + isDir, err := pathutil.IsDirExists(src) + if err != nil { + return err + } + if isDir { + return errors.New("Source is a directory: " + src) + } + args := []string{src, dst} + return RunCommand("rsync", args...) +} + +// CopyDir ... +func CopyDir(src, dst string, isOnlyContent bool) error { + if isOnlyContent && !strings.HasSuffix(src, "/") { + src = src + "/" + } + args := []string{"-ar", src, dst} + return RunCommand("rsync", args...) +} + +// RemoveDir ... +func RemoveDir(dirPth string) error { + if exist, err := pathutil.IsPathExists(dirPth); err != nil { + return err + } else if exist { + if err := os.RemoveAll(dirPth); err != nil { + return err + } + } + return nil +} + +// RemoveFile ... +func RemoveFile(pth string) error { + if exist, err := pathutil.IsPathExists(pth); err != nil { + return err + } else if exist { + if err := os.Remove(pth); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/command/zip.go b/vendor/github.com/bitrise-io/go-utils/command/zip.go new file mode 100644 index 0000000..b3e899c --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/zip.go @@ -0,0 +1,115 @@ +package command + +import ( + "archive/zip" + "errors" + "io" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// UnZIP ... +func UnZIP(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer func() { + if err := r.Close(); err != nil { + log.Fatal(err) + } + }() + + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } + + // Closure to address file descriptors issue with all the deferred .Close() methods + extractAndWriteFile := func(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + defer func() { + if err := rc.Close(); err != nil { + log.Fatal(err) + } + }() + + path := filepath.Join(dest, f.Name) + + if f.FileInfo().IsDir() { + if err := os.MkdirAll(path, f.Mode()); err != nil { + return err + } + } else { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Fatal(err) + } + }() + + if _, err = io.Copy(f, rc); err != nil { + return err + } + } + return nil + } + + for _, f := range r.File { + if err := extractAndWriteFile(f); err != nil { + return err + } + } + return nil +} + +// DownloadAndUnZIP ... +func DownloadAndUnZIP(url, pth string) error { + tmpDir, err := pathutil.NormalizedOSTempDirPath("") + if err != nil { + return err + } + srcFilePath := tmpDir + "/target.zip" + srcFile, err := os.Create(srcFilePath) + if err != nil { + return err + } + defer func() { + if err := srcFile.Close(); err != nil { + log.Fatal("Failed to close srcFile:", err) + } + if err := os.Remove(srcFilePath); err != nil { + log.Fatal("Failed to remove srcFile:", err) + } + }() + + response, err := http.Get(url) + if err != nil { + return err + } + defer func() { + if err := response.Body.Close(); err != nil { + log.Fatal("Failed to close response body:", err) + } + }() + + if response.StatusCode != http.StatusOK { + errorMsg := "Failed to download target from: " + url + return errors.New(errorMsg) + } + + if _, err := io.Copy(srcFile, response.Body); err != nil { + return err + } + + return UnZIP(srcFilePath, pth) +} diff --git a/vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go b/vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go new file mode 100644 index 0000000..1128416 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go @@ -0,0 +1,36 @@ +package errorutil + +import ( + "errors" + "os/exec" + "regexp" + "syscall" +) + +// IsExitStatusError ... +func IsExitStatusError(err error) bool { + return IsExitStatusErrorStr(err.Error()) +} + +// IsExitStatusErrorStr ... +func IsExitStatusErrorStr(errString string) bool { + // example exit status error string: exit status 1 + var rex = regexp.MustCompile(`^exit status [0-9]{1,3}$`) + return rex.MatchString(errString) +} + +// CmdExitCodeFromError ... +func CmdExitCodeFromError(err error) (int, error) { + cmdExitCode := 0 + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + waitStatus, ok := exitError.Sys().(syscall.WaitStatus) + if !ok { + return 1, errors.New("Failed to cast exit status") + } + cmdExitCode = waitStatus.ExitStatus() + } + return cmdExitCode, nil + } + return 0, nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/defaultlogger.go b/vendor/github.com/bitrise-io/go-utils/log/defaultlogger.go new file mode 100644 index 0000000..0d2a307 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/defaultlogger.go @@ -0,0 +1,57 @@ +package log + +// DefaultLogger ... +type DefaultLogger struct { + ts bool +} + +// NewDefaultLogger ... +func NewDefaultLogger(withTimestamp bool) DefaultLogger { + return DefaultLogger{withTimestamp} +} + +// Donef ... +func (dl DefaultLogger) Donef(format string, v ...interface{}) { + fSelect(dl.ts, TDonef, Donef)(format, v...) +} + +// Successf ... +func (dl DefaultLogger) Successf(format string, v ...interface{}) { + fSelect(dl.ts, TSuccessf, Successf)(format, v...) +} + +// Infof ... +func (dl DefaultLogger) Infof(format string, v ...interface{}) { + fSelect(dl.ts, TInfof, Infof)(format, v...) +} + +// Printf ... +func (dl DefaultLogger) Printf(format string, v ...interface{}) { + fSelect(dl.ts, TPrintf, Printf)(format, v...) +} + +// Warnf ... +func (dl DefaultLogger) Warnf(format string, v ...interface{}) { + fSelect(dl.ts, TWarnf, Warnf)(format, v...) +} + +// Errorf ... +func (dl DefaultLogger) Errorf(format string, v ...interface{}) { + fSelect(dl.ts, TErrorf, Errorf)(format, v...) +} + +// Debugf ... +func (dl DefaultLogger) Debugf(format string, v ...interface{}) { + if enableDebugLog { + fSelect(dl.ts, TDebugf, Debugf)(format, v...) + } +} + +type logfunc func(string, ...interface{}) + +func fSelect(t bool, tf logfunc, f logfunc) logfunc { + if t { + return tf + } + return f +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/dummylogger.go b/vendor/github.com/bitrise-io/go-utils/log/dummylogger.go new file mode 100644 index 0000000..54b0bb9 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/dummylogger.go @@ -0,0 +1,30 @@ +package log + +// DummyLogger ... +type DummyLogger struct{} + +// NewDummyLogger ... +func NewDummyLogger() DummyLogger { + return DummyLogger{} +} + +// Donef ... +func (dl DummyLogger) Donef(format string, v ...interface{}) {} + +// Successf ... +func (dl DummyLogger) Successf(format string, v ...interface{}) {} + +// Infof ... +func (dl DummyLogger) Infof(format string, v ...interface{}) {} + +// Printf ... +func (dl DummyLogger) Printf(format string, v ...interface{}) {} + +// Debugf ... +func (dl DummyLogger) Debugf(format string, v ...interface{}) {} + +// Warnf ... +func (dl DummyLogger) Warnf(format string, v ...interface{}) {} + +// Errorf ... +func (dl DummyLogger) Errorf(format string, v ...interface{}) {} diff --git a/vendor/github.com/bitrise-io/go-utils/log/json_logger.go b/vendor/github.com/bitrise-io/go-utils/log/json_logger.go new file mode 100644 index 0000000..43b8bfb --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/json_logger.go @@ -0,0 +1,31 @@ +package log + +import ( + "fmt" + "io" + "os" +) + +// JSONLoger ... +type JSONLoger struct { + writer io.Writer +} + +// NewJSONLoger ... +func NewJSONLoger(writer io.Writer) *JSONLoger { + return &JSONLoger{ + writer: writer, + } +} + +// NewDefaultJSONLoger ... +func NewDefaultJSONLoger() JSONLoger { + return JSONLoger{ + writer: os.Stdout, + } +} + +// Print ... +func (l JSONLoger) Print(f Formatable) { + fmt.Fprint(l.writer, f.JSON()) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/log.go b/vendor/github.com/bitrise-io/go-utils/log/log.go new file mode 100644 index 0000000..1b69028 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/log.go @@ -0,0 +1,34 @@ +package log + +import ( + "fmt" + "io" + "os" + "time" +) + +var outWriter io.Writer = os.Stdout + +// SetOutWriter ... +func SetOutWriter(writer io.Writer) { + outWriter = writer +} + +var enableDebugLog = false + +// SetEnableDebugLog ... +func SetEnableDebugLog(enable bool) { + enableDebugLog = enable +} + +var timestampLayout = "15:04:05" + +// SetTimestampLayout ... +func SetTimestampLayout(layout string) { + timestampLayout = layout +} + +func timestampField() string { + currentTime := time.Now() + return fmt.Sprintf("[%s]", currentTime.Format(timestampLayout)) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/logger.go b/vendor/github.com/bitrise-io/go-utils/log/logger.go new file mode 100644 index 0000000..4691122 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/logger.go @@ -0,0 +1,12 @@ +package log + +// Logger ... +type Logger interface { + Print(f Formatable) +} + +// Formatable ... +type Formatable interface { + String() string + JSON() string +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/print.go b/vendor/github.com/bitrise-io/go-utils/log/print.go new file mode 100644 index 0000000..9dc48db --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/print.go @@ -0,0 +1,89 @@ +package log + +import ( + "fmt" +) + +func printf(severity Severity, withTime bool, format string, v ...interface{}) { + colorFunc := severityColorFuncMap[severity] + message := colorFunc(format, v...) + if withTime { + message = fmt.Sprintf("%s %s", timestampField(), message) + } + + fmt.Fprintln(outWriter, message) +} + +// Successf ... +func Successf(format string, v ...interface{}) { + printf(successSeverity, false, format, v...) +} + +// Donef ... +func Donef(format string, v ...interface{}) { + Successf(format, v...) +} + +// Infof ... +func Infof(format string, v ...interface{}) { + printf(infoSeverity, false, format, v...) +} + +// Printf ... +func Printf(format string, v ...interface{}) { + printf(normalSeverity, false, format, v...) +} + +// Debugf ... +func Debugf(format string, v ...interface{}) { + if enableDebugLog { + printf(debugSeverity, false, format, v...) + } +} + +// Warnf ... +func Warnf(format string, v ...interface{}) { + printf(warnSeverity, false, format, v...) +} + +// Errorf ... +func Errorf(format string, v ...interface{}) { + printf(errorSeverity, false, format, v...) +} + +// TSuccessf ... +func TSuccessf(format string, v ...interface{}) { + printf(successSeverity, true, format, v...) +} + +// TDonef ... +func TDonef(format string, v ...interface{}) { + TSuccessf(format, v...) +} + +// TInfof ... +func TInfof(format string, v ...interface{}) { + printf(infoSeverity, true, format, v...) +} + +// TPrintf ... +func TPrintf(format string, v ...interface{}) { + printf(normalSeverity, true, format, v...) +} + +// TDebugf ... +func TDebugf(format string, v ...interface{}) { + if enableDebugLog { + printf(debugSeverity, true, format, v...) + } +} + +// TWarnf ... +func TWarnf(format string, v ...interface{}) { + printf(warnSeverity, true, format, v...) +} + +// TErrorf ... +func TErrorf(format string, v ...interface{}) { + printf(errorSeverity, true, format, v...) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/raw_logger.go b/vendor/github.com/bitrise-io/go-utils/log/raw_logger.go new file mode 100644 index 0000000..82dc54e --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/raw_logger.go @@ -0,0 +1,31 @@ +package log + +import ( + "fmt" + "io" + "os" +) + +// RawLogger ... +type RawLogger struct { + writer io.Writer +} + +// NewRawLogger ... +func NewRawLogger(writer io.Writer) *RawLogger { + return &RawLogger{ + writer: writer, + } +} + +// NewDefaultRawLogger ... +func NewDefaultRawLogger() RawLogger { + return RawLogger{ + writer: os.Stdout, + } +} + +// Print ... +func (l RawLogger) Print(f Formatable) { + fmt.Fprintln(l.writer, f.String()) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/severity.go b/vendor/github.com/bitrise-io/go-utils/log/severity.go new file mode 100644 index 0000000..a1c4631 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/severity.go @@ -0,0 +1,35 @@ +package log + +import "github.com/bitrise-io/go-utils/colorstring" + +// Severity ... +type Severity uint8 + +const ( + errorSeverity Severity = iota + warnSeverity + normalSeverity + infoSeverity + successSeverity + debugSeverity +) + +type severityColorFunc colorstring.ColorfFunc + +var ( + successSeverityColorFunc severityColorFunc = colorstring.Greenf + infoSeverityColorFunc severityColorFunc = colorstring.Bluef + normalSeverityColorFunc severityColorFunc = colorstring.NoColorf + debugSeverityColorFunc severityColorFunc = colorstring.NoColorf + warnSeverityColorFunc severityColorFunc = colorstring.Yellowf + errorSeverityColorFunc severityColorFunc = colorstring.Redf +) + +var severityColorFuncMap = map[Severity]severityColorFunc{ + successSeverity: successSeverityColorFunc, + infoSeverity: infoSeverityColorFunc, + normalSeverity: normalSeverityColorFunc, + debugSeverity: debugSeverityColorFunc, + warnSeverity: warnSeverityColorFunc, + errorSeverity: errorSeverityColorFunc, +} diff --git a/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go b/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go new file mode 100644 index 0000000..08cec36 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go @@ -0,0 +1,95 @@ +package parseutil + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/pointers" +) + +// ParseBool ... +func ParseBool(userInputStr string) (bool, error) { + if userInputStr == "" { + return false, errors.New("No string to parse") + } + userInputStr = strings.TrimSpace(userInputStr) + + lowercased := strings.ToLower(userInputStr) + if lowercased == "yes" || lowercased == "y" { + return true, nil + } + if lowercased == "no" || lowercased == "n" { + return false, nil + } + return strconv.ParseBool(lowercased) +} + +// CastToString ... +func CastToString(value interface{}) string { + casted, ok := value.(string) + + if !ok { + castedStr := fmt.Sprintf("%v", value) + casted = castedStr + } + + return casted +} + +// CastToStringPtr ... +func CastToStringPtr(value interface{}) *string { + castedValue := CastToString(value) + return pointers.NewStringPtr(castedValue) +} + +// CastToBool ... +func CastToBool(value interface{}) (bool, bool) { + casted, ok := value.(bool) + + if !ok { + castedStr := CastToString(value) + + castedBool, err := ParseBool(castedStr) + if err != nil { + return false, false + } + + casted = castedBool + } + + return casted, true +} + +// CastToBoolPtr ... +func CastToBoolPtr(value interface{}) (*bool, bool) { + castedValue, ok := CastToBool(value) + if !ok { + return nil, false + } + return pointers.NewBoolPtr(castedValue), true +} + +// CastToMapStringInterface ... +func CastToMapStringInterface(value interface{}) (map[string]interface{}, bool) { + castedValue, ok := value.(map[interface{}]interface{}) + desiredMap := map[string]interface{}{} + for key, value := range castedValue { + keyStr, ok := key.(string) + if !ok { + return map[string]interface{}{}, false + } + desiredMap[keyStr] = value + } + return desiredMap, ok +} + +// CastToMapStringInterfacePtr ... +func CastToMapStringInterfacePtr(value interface{}) (*map[string]interface{}, bool) { + casted, ok := CastToMapStringInterface(value) + if !ok { + return nil, false + } + return pointers.NewMapStringInterfacePtr(casted), true +} diff --git a/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go b/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go new file mode 100644 index 0000000..1ef74f9 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go @@ -0,0 +1,181 @@ +package pathutil + +import ( + "errors" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" +) + +// RevokableChangeDir ... +func RevokableChangeDir(dir string) (func() error, error) { + origDir, err := CurrentWorkingDirectoryAbsolutePath() + if err != nil { + return nil, err + } + + revokeFn := func() error { + return os.Chdir(origDir) + } + + return revokeFn, os.Chdir(dir) +} + +// ChangeDirForFunction ... +func ChangeDirForFunction(dir string, fn func()) error { + revokeFn, err := RevokableChangeDir(dir) + if err != nil { + return err + } + + fn() + + return revokeFn() +} + +// IsRelativePath ... +func IsRelativePath(pth string) bool { + if strings.HasPrefix(pth, "./") { + return true + } + + if strings.HasPrefix(pth, "/") { + return false + } + + if strings.HasPrefix(pth, "$") { + return false + } + + return true +} + +// EnsureDirExist ... +func EnsureDirExist(dir string) error { + exist, err := IsDirExists(dir) + if !exist || err != nil { + return os.MkdirAll(dir, 0777) + } + return nil +} + +func genericIsPathExists(pth string) (os.FileInfo, bool, error) { + if pth == "" { + return nil, false, errors.New("No path provided") + } + fileInf, err := os.Lstat(pth) + if err == nil { + return fileInf, true, nil + } + if os.IsNotExist(err) { + return nil, false, nil + } + return fileInf, false, err +} + +// IsPathExists ... +func IsPathExists(pth string) (bool, error) { + _, isExists, err := genericIsPathExists(pth) + return isExists, err +} + +// PathCheckAndInfos ... +// Returns: +// 1. file info or nil +// 2. bool, indicating whether the path exists +// 3. error, if any error happens during the check +func PathCheckAndInfos(pth string) (os.FileInfo, bool, error) { + return genericIsPathExists(pth) +} + +// IsDirExists ... +func IsDirExists(pth string) (bool, error) { + fileInf, isExists, err := genericIsPathExists(pth) + if err != nil { + return false, err + } + if !isExists { + return false, nil + } + if fileInf == nil { + return false, errors.New("No file info available") + } + return fileInf.IsDir(), nil +} + +// AbsPath expands ENV vars and the ~ character +// then call Go's Abs +func AbsPath(pth string) (string, error) { + if pth == "" { + return "", errors.New("No Path provided") + } + + pth, err := ExpandTilde(pth) + if err != nil { + return "", err + } + + return filepath.Abs(os.ExpandEnv(pth)) +} + +// ExpandTilde ... +func ExpandTilde(pth string) (string, error) { + if pth == "" { + return "", errors.New("No Path provided") + } + + if strings.HasPrefix(pth, "~") { + pth = strings.TrimPrefix(pth, "~") + + if len(pth) == 0 || strings.HasPrefix(pth, "/") { + return os.ExpandEnv("$HOME" + pth), nil + } + + splitPth := strings.Split(pth, "/") + username := splitPth[0] + + usr, err := user.Lookup(username) + if err != nil { + return "", err + } + + pathInUsrHome := strings.Join(splitPth[1:], "/") + + return filepath.Join(usr.HomeDir, pathInUsrHome), nil + } + + return pth, nil +} + +// CurrentWorkingDirectoryAbsolutePath ... +func CurrentWorkingDirectoryAbsolutePath() (string, error) { + return filepath.Abs("./") +} + +// UserHomeDir ... +func UserHomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} + +// NormalizedOSTempDirPath ... +// Creates a temp dir, and returns its path. +// If tmpDirNamePrefix is provided it'll be used +// as the tmp dir's name prefix. +// Normalized: it's guaranteed that the path won't end with '/'. +func NormalizedOSTempDirPath(tmpDirNamePrefix string) (retPth string, err error) { + retPth, err = ioutil.TempDir("", tmpDirNamePrefix) + if strings.HasSuffix(retPth, "/") { + retPth = retPth[:len(retPth)-1] + } + return +} diff --git a/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go b/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go new file mode 100644 index 0000000..e26647d --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go @@ -0,0 +1,98 @@ +package pointers + +import "time" + +// NewBoolPtr ... +func NewBoolPtr(val bool) *bool { + ptrValue := new(bool) + *ptrValue = val + return ptrValue +} + +// NewStringPtr ... +func NewStringPtr(val string) *string { + ptrValue := new(string) + *ptrValue = val + return ptrValue +} + +// NewTimePtr ... +func NewTimePtr(val time.Time) *time.Time { + ptrValue := new(time.Time) + *ptrValue = val + return ptrValue +} + +// NewIntPtr ... +func NewIntPtr(val int) *int { + ptrValue := new(int) + *ptrValue = val + return ptrValue +} + +// NewInt64Ptr ... +func NewInt64Ptr(val int64) *int64 { + ptrValue := new(int64) + *ptrValue = val + return ptrValue +} + +// NewMapStringInterfacePtr ... +func NewMapStringInterfacePtr(val map[string]interface{}) *map[string]interface{} { + ptrValue := new(map[string]interface{}) + *ptrValue = map[string]interface{}{} + for key, value := range val { + (*ptrValue)[key] = value + } + return ptrValue +} + +// ------------------------------------------------------ +// --- Safe Getters + +// Bool ... +func Bool(val *bool) bool { + return BoolWithDefault(val, false) +} + +// BoolWithDefault ... +func BoolWithDefault(val *bool, defaultValue bool) bool { + if val == nil { + return defaultValue + } + return *val +} + +// String ... +func String(val *string) string { + return StringWithDefault(val, "") +} + +// StringWithDefault ... +func StringWithDefault(val *string, defaultValue string) string { + if val == nil { + return defaultValue + } + return *val +} + +// TimeWithDefault ... +func TimeWithDefault(val *time.Time, defaultValue time.Time) time.Time { + if val == nil { + return defaultValue + } + return *val +} + +// Int ... +func Int(val *int) int { + return IntWithDefault(val, 0) +} + +// IntWithDefault ... +func IntWithDefault(val *int, defaultValue int) int { + if val == nil { + return defaultValue + } + return *val +} diff --git a/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go b/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go new file mode 100644 index 0000000..169035f --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go @@ -0,0 +1,199 @@ +package stepconf + +import ( + "errors" + "fmt" + "os" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/parseutil" +) + +// ErrNotStructPtr indicates a type is not a pointer to a struct. +var ErrNotStructPtr = errors.New("must be a pointer to a struct") + +// ParseError occurs when a struct field cannot be set. +type ParseError struct { + Field string + Value string + Err error +} + +// Error implements builtin errors.Error. +func (e *ParseError) Error() string { + segments := []string{e.Field} + if e.Value != "" { + segments = append(segments, e.Value) + } + segments = append(segments, e.Err.Error()) + return strings.Join(segments, ": ") +} + +// Secret variables are not shown in the printed output. +type Secret string + +const secret = "*****" + +// String implements fmt.Stringer.String. +// When a Secret is printed, it's masking the underlying string with asterisks. +func (s Secret) String() string { + if s == "" { + return "" + } + return secret +} + +// Print the name of the struct in blue color followed by a newline, +// then print all fields formatted as '- field name: field value` separated by newline. +func Print(config interface{}) { + v := reflect.ValueOf(config) + t := reflect.TypeOf(config) + + log.Infof("%s:", t.Name()) + for i := 0; i < t.NumField(); i++ { + fmt.Printf("- %s: %v\n", t.Field(i).Name, v.Field(i).Interface()) + } +} + +// parseTag splits a struct field's env tag into its name and option. +func parseTag(tag string) (string, string) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tag[idx+1:] + } + return tag, "" +} + +// Parse populates a struct with the retrieved values from environment variables +// described by struct tags and applies the defined validations. +func Parse(conf interface{}) error { + c := reflect.ValueOf(conf) + if c.Kind() != reflect.Ptr { + return ErrNotStructPtr + } + c = c.Elem() + if c.Kind() != reflect.Struct { + return ErrNotStructPtr + } + t := c.Type() + + var errs []*ParseError + for i := 0; i < c.NumField(); i++ { + tag, ok := t.Field(i).Tag.Lookup("env") + if !ok { + continue + } + key, constraint := parseTag(tag) + value := os.Getenv(key) + + if err := setField(c.Field(i), value, constraint); err != nil { + errs = append(errs, &ParseError{t.Field(i).Name, value, err}) + } + } + if len(errs) > 0 { + errorString := "failed to parse config:" + for _, err := range errs { + errorString += fmt.Sprintf("\n- %s", err) + } + return errors.New(errorString) + } + + return nil +} + +func setField(field reflect.Value, value, constraint string) error { + switch constraint { + case "": + break + case "required": + if value == "" { + return errors.New("required variable is not present") + } + case "file", "dir": + if err := checkPath(value, constraint == "dir"); err != nil { + return err + } + // TODO: use FindStringSubmatch to distinguish no match and match for empty string. + case regexp.MustCompile(`^opt\[.*\]$`).FindString(constraint): + if !contains(value, constraint) { + // TODO: print only the value options, not the whole string. + return fmt.Errorf("value is not in value options (%s)", constraint) + } + default: + return fmt.Errorf("invalid constraint (%s)", constraint) + } + + if value == "" { + return nil + } + + switch field.Kind() { + case reflect.String: + field.SetString(value) + case reflect.Bool: + b, err := parseutil.ParseBool(value) + if err != nil { + return errors.New("can't convert to bool") + } + field.SetBool(b) + case reflect.Int: + n, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return errors.New("can't convert to int") + } + field.SetInt(n) + case reflect.Slice: + field.Set(reflect.ValueOf(strings.Split(value, "|"))) + default: + return fmt.Errorf("type is not supported (%s)", field.Kind()) + } + return nil +} + +func checkPath(path string, dir bool) error { + file, err := os.Stat(path) + if err != nil { + // TODO: check case when file exist but os.Stat fails. + return os.ErrNotExist + } + if dir && !file.IsDir() { + return errors.New("not a directory") + } + return nil +} + +// contains reports whether s is within the value options, where value options +// are parsed from opt, which format's is opt[item1,item2,item3]. If an option +// contains commas, it should be single quoted (eg. opt[item1,'item2,item3']). +func contains(s, opt string) bool { + opt = strings.TrimSuffix(strings.TrimPrefix(opt, "opt["), "]") + var valueOpts []string + if strings.Contains(opt, "'") { + // The single quotes separate the options with comma and without comma + // Eg. "a,b,'c,d',e" will results "a,b," "c,d" and ",e" strings. + for _, s := range strings.Split(opt, "'") { + switch { + case s == "," || s == "": + case !strings.HasPrefix(s, ",") && !strings.HasSuffix(s, ","): + // If a string doesn't starts nor ends with a comma it means it's an option which + // contains comma, so we just append it to valueOpts as it is. Eg. "c,d" from above. + valueOpts = append(valueOpts, s) + default: + // If a string starts or ends with comma it means that it contains options without comma. + // So we split the string at commas to get the options. Eg. "a,b," and ",e" from above. + valueOpts = append(valueOpts, strings.Split(strings.Trim(s, ","), ",")...) + } + } + } else { + valueOpts = strings.Split(opt, ",") + } + for _, valOpt := range valueOpts { + if valOpt == s { + return true + } + } + return false +}