From 9d3514399130748283461a52e6cf43d6ae65fa34 Mon Sep 17 00:00:00 2001 From: Krisztian Dobmayer Date: Wed, 14 Mar 2018 16:57:40 +0100 Subject: [PATCH] Parse env vars into the step config --- Gopkg.lock | 26 +++ Gopkg.toml | 7 + bitrise.yml | 25 +-- config.go | 8 +- main.go | 41 ++-- message.go | 37 ++-- step.yml | 42 +---- vendor/github.com/bitrise-io/go-utils/LICENSE | 22 +++ .../go-utils/colorstring/colorstring.go | 110 +++++++++++ .../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 | 84 +++++++++ .../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/pointers/pointers.go | 98 ++++++++++ .../go-steputils/stepconf/stepconf.go | 176 ++++++++++++++++++ 18 files changed, 810 insertions(+), 104 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml 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/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/pointers/pointers.go create mode 100644 vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..fe36131 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,26 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/bitrise-io/go-utils" + packages = [ + "colorstring", + "log", + "parseutil", + "pointers" + ] + revision = "b33f6bcef9b50045d7e62364b354584afc3ee329" + +[[projects]] + branch = "master" + name = "github.com/bitrise-tools/go-steputils" + packages = ["stepconf"] + revision = "b0e1079aa921440413a5c56f301246fe8367c072" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "40aff52da76b55134e39ead41650739f168186167f8bf35b6e706a53aac687ce" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..63cd7d1 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,7 @@ +[[constraint]] + name = "github.com/bitrise-tools/go-steputils" + branch = "master" + +[prune] + go-tests = true + unused-packages = true diff --git a/bitrise.yml b/bitrise.yml index 10cf694..72f6d92 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -5,7 +5,6 @@ app: envs: # An example secret param, define it (A_SECRET_PARAM) in .bitrise.secrets.yml - STEP_VERSION: 1.0.1 - - INCOMING_WEBHOOK_URL : $INCOMING_WEBHOOK_URL # If you want to share this step into a StepLib - BITRISE_STEP_ID: microsoft-teams-integration - BITRISE_STEP_VERSION: "1.0.1" @@ -15,33 +14,11 @@ app: workflows: test: steps: - - script: - inputs: - - content: | - #!/bin/bash - echo "Just an example 'secrets' print." - echo "The value of 'A_SECRET_PARAM' is: $A_SECRET_PARAM" - - 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: Step Test run_if: true inputs: - - INCOMING_WEBHOOK_URL,required: https://outlook.office.com/webhook/5bdfe871-8b29-47d0-82dc-4fd42e02d0b9@dacd0a8f-30ed-4ee0-ad06-de7c7aa659ed/IncomingWebhook/6bcb8d7d07e04dc5880a0830c1e861a0/a05f31c9-bb92-46a8-bbdb-371c36e8902c - - script: - inputs: - - content: | - #!/bin/bash - echo "This output was generated by the Step (EXAMPLE_STEP_OUTPUT): $EXAMPLE_STEP_OUTPUT" + - webhook_url: https://outlook.office.com/webhook/5bdfe871-8b29-47d0-82dc-4fd42e02d0b9@dacd0a8f-30ed-4ee0-ad06-de7c7aa659ed/IncomingWebhook/6bcb8d7d07e04dc5880a0830c1e861a0/a05f31c9-bb92-46a8-bbdb-371c36e8902c # ---------------------------------------------------------------- diff --git a/config.go b/config.go index b3a6e71..635ed98 100644 --- a/config.go +++ b/config.go @@ -1,10 +1,8 @@ package main -// Config will be populated with the retrieved values from environment variables +// config will be populated with the retrieved values from environment variables // configured as step inputs. -type Config struct { - - // Message +type config struct { BuildNumber string `env:"BITRISE_BUILD_NUMBER"` AppTitle string `env:"BITRISE_APP_TITLE"` AppURL string `env:"BITRISE_APP_URL"` @@ -12,4 +10,6 @@ type Config struct { RepoURL string `env:"GIT_REPOSITORY_URL"` GitBranch string `env:"BITRISE_GIT_BRANCH"` AppImageURL string `env:"BITRISE_APP_SLUG"` + + WebhookURL string `env:"webhook_url,required"` } diff --git a/main.go b/main.go index ef5bc31..0832399 100644 --- a/main.go +++ b/main.go @@ -7,23 +7,25 @@ import ( "io/ioutil" "net/http" "os" + + "github.com/bitrise-tools/go-steputils/stepconf" ) -func newMessage(buildOk bool, conf Config) Message { +func newMessage(buildOk bool, cfg config) Message { message := Message{} message.Type = "MessageCard" message.Context = "http://schema.org/extensions" message.ThemeColor = "0076D7" if buildOk { - message.Summary = fmt.Sprintf("%s build number %s succeeded", conf.AppTitle, conf.BuildNumber) + message.Summary = fmt.Sprintf("%s build number %s succeeded", cfg.AppTitle, cfg.BuildNumber) } else { - message.Summary = fmt.Sprintf("%s build number %s failed", conf.AppTitle, conf.BuildNumber) + message.Summary = fmt.Sprintf("%s build number %s failed", cfg.AppTitle, cfg.BuildNumber) } message.Markdown = "true" section := Section{} - section.AppTitle = fmt.Sprintf("![AppImage](%s)%s", conf.AppImageURL, conf.AppTitle) - section.BuildNumber = conf.BuildNumber - section.AppImage = conf.AppImageURL + section.AppTitle = fmt.Sprintf("![AppImage](%s)%s", cfg.AppImageURL, cfg.AppTitle) + section.BuildNumber = cfg.BuildNumber + section.AppImage = cfg.AppImageURL buildStatusFact := Fact{} buildStatusFact.Name = "Build Status" @@ -35,11 +37,11 @@ func newMessage(buildOk bool, conf Config) Message { buildNumberFact := Fact{} buildNumberFact.Name = "Build Number" - buildNumberFact.Value = conf.BuildNumber + buildNumberFact.Value = cfg.BuildNumber buildBranchFact := Fact{} buildBranchFact.Name = "Git Branch" - buildBranchFact.Value = conf.GitBranch + buildBranchFact.Value = cfg.GitBranch section.Facts = []Fact{buildStatusFact, buildNumberFact, buildBranchFact} @@ -51,7 +53,7 @@ func newMessage(buildOk bool, conf Config) Message { goToRepoActionCardAction := Action{} goToRepoActionCardAction.Type = "HttpGet" goToRepoActionCardAction.Name = "Go To Repo" - goToRepoActionCardAction.Target = conf.RepoURL + goToRepoActionCardAction.Target = cfg.RepoURL goToBuildActionCard := ActionCard{} goToBuildActionCard.Type = "ActionCard" @@ -59,7 +61,7 @@ func newMessage(buildOk bool, conf Config) Message { goToBuildActionCardAction := Action{} goToBuildActionCardAction.Type = "HttpGet" goToBuildActionCardAction.Name = "Go To Build" - goToBuildActionCardAction.Target = conf.BuildURL + goToBuildActionCardAction.Target = cfg.BuildURL message.Actions = []ActionCard{goToRepoActionCard, goToBuildActionCard} @@ -96,19 +98,20 @@ func postMessage(webhookURL string, msg Message) error { } func main() { - var webhookURL = os.Getenv("INCOMING_WEBHOOK_URL,required") - // success is true if the build is successful, false otherwise. - fmt.Println(fmt.Sprintf("Webhook URL: %s", os.Getenv("INCOMING_WEBHOOK_URL,required"))) + var cfg config + if err := stepconf.Parse(&cfg); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + } + stepconf.Print(cfg) - var success = os.Getenv("BITRISE_BUILD_STATUS") == "0" - var conf Config - message := newMessage(success, conf) - if err := postMessage(webhookURL, message); err != nil { + // success is true if the build is successful, false otherwise. + success := os.Getenv("BITRISE_BUILD_STATUS") == "0" + message := newMessage(success, cfg) + if err := postMessage(cfg.WebhookURL, message); err != nil { fmt.Println(fmt.Sprintf("Error: %s", err)) os.Exit(1) } fmt.Println("Message successfully sent!") - - os.Exit(0) } diff --git a/message.go b/message.go index b4dd010..1723782 100644 --- a/message.go +++ b/message.go @@ -1,28 +1,17 @@ package main -// Message to post to a slack channel. -// See also: https://api.slack.com/methods/chat.postMessage +// Message to send to Microsoft Teams. type Message struct { - // Channel to send message to. - // - // Can be an encoded ID (eg. C024BE91L), or the channel's name (eg. #general). - Type string `json:"@type"` - - // Text of the message to send. Required, unless providing only attachments instead. - Context string `json:"@context"` - - ThemeColor string `json:"themeColor"` - - Summary string `json:"summary"` - - Markdown string `json:"markdown"` - - Sections []Section `json:"sections"` - - Actions []ActionCard `json:"potentialAction"` + Type string `json:"@type"` + Context string `json:"@context"` + ThemeColor string `json:"themeColor"` + Summary string `json:"summary"` + Markdown string `json:"markdown"` + Sections []Section `json:"sections"` + Actions []ActionCard `json:"potentialAction"` } -//Section to be shown in the message +// Section to be shown in the message type Section struct { AppTitle string `json:"activityTitle"` BuildNumber string `json:"activitySubtitle"` @@ -30,13 +19,13 @@ type Section struct { Facts []Fact `json:"facts"` } -//Fact realted to the message +// Fact realted to the message type Fact struct { Name string `json:"name"` Value string `json:"value"` } -//ActionCard to be added to the message +// ActionCard to be added to the message type ActionCard struct { Type string `json:"@type"` Name string `json:"name"` @@ -44,14 +33,14 @@ type ActionCard struct { Actions []Action `json:"actions"` } -//ActionInput for actions if any +// ActionInput for actions if any type ActionInput struct { Type string `json:"@type"` ID string `json:"id"` Title string `json:"title"` } -//Action to be taken by the action card +// Action to be taken by the action card type Action struct { Type string `json:"@type"` Name string `json:"name"` diff --git a/step.yml b/step.yml index cc7bdc6..547fb17 100644 --- a/step.yml +++ b/step.yml @@ -1,15 +1,6 @@ -# -# A couple of useful guides & docs: -# -# - Main Bitrise CLI docs: https://github.com/bitrise-io/bitrise/tree/master/_docs -# - Step Development Guideline: https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md -# - Bitrise.yml format spec: https://github.com/bitrise-io/bitrise/blob/master/_docs/bitrise-yml-format-spec.md -# - Bitrise docs: http://devcenter.bitrise.io/ -# - Bitrise CLI guides: http://devcenter.bitrise.io/bitrise-cli/ -title: |- - Microsoft Teams Integration -summary: | - Allows you to recieve build status updates on Microsoft Team using Incoming Webhooks +--- +title: Microsoft Teams Integration +summary: Allows you to recieve build status updates on Microsoft Team using Incoming Webhooks description: | Uses Incoming Webhooks feature in Microsft Teams to send updates on your builds website: https://github.com/amrfarid140/bitrise-step-microsoft-teams-integration @@ -18,43 +9,28 @@ support_url: https://github.com/amrfarid140/bitrise-step-microsoft-teams-integra host_os_tags: - osx-10.10 - ubuntu-16.04 - - -# Type tags are used for categorizing steps, for easier step discovery in Step Libraries. -# You can find more information about type tags in the Step Development Guideline: -# https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md type_tags: - notification - is_requires_admin_user: true -is_always_run: false +is_always_run: true is_skippable: true -run_if: "" - deps: brew: - - name: git - - name: wget + - name: go apt_get: - - name: git - - name: wget - - + - name: golang + bin_name: go toolkit: go: package_name: github.com/amrfarid140/bitrise-step-microsoft-teams-integration - - inputs: - - INCOMING_WEBHOOK_URL,required: + - webhook_url: opts: title: "Incoming Webhook URL" - summary: Url of the "Incoming Webhook URL" generated by Microsoft Teams + summary: "Url of the Incoming Webhook generated by Microsoft Teams" description: | * Go to Microsoft Teams * Click on the three dots next to channel name then **Add Connector** * Select **Incoming Webhook* and in the text input type **Bitrise** * Save and copy the link to the input for this step - is_expand: true is_required: true - value_options: [] 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/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..3b7c1fa --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/print.go @@ -0,0 +1,84 @@ +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...) +} + +// 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/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..cd757c6 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go @@ -0,0 +1,176 @@ +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:\n", 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 +} + +func contains(s, opt string) bool { + // TODO: improve readability. + for _, o := range strings.Split(opt[strings.Index(opt, "[")+1:len(opt)-1], ",") { + if o == s { + return true + } + } + return false +}