diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..8eeb64f --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,52 @@ +project_name: "cinful" + +builds: +- binary: "cinful" + main: cmd/cinful/main.go + + ldflags: + - -s -w + env: + - CGO_ENABLED=0 + + goos: + - darwin + - linux + - windows + goarch: + - amd64 + - arm64 + +snapshot: + name_template: "{{ .Tag }}-SNAPSHOT-{{.ShortCommit}}" + +archives: +- format: binary + replacements: + darwin: Darwin + linux: Linux + windows: Windows + amd64: x86_64 + # Needed hack for binary only uploads + # For more information, check #602 + files: + - "thisfiledoesnotexist*" + + +checksum: + name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt' + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + + +release: + disable: false + draft: false + github: + owner: hofstadter-io + name: cinful diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..de776f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2022 Hofstadter, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Hofstadter Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6ef4dc --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# cinful + +Golang library and CLI to detect if a program +is running in a CI system. + +Pronounced "sinful" by mashing together CI, info, and the 'ful' suffix. + +Originally ported from [watson/ci-info](https://github.com/watson/ci-info) +and [other links](https://adamj.eu/tech/2020/03/09/detect-if-your-tests-are-running-on-ci/) + + +### as a CLI + +prints details if in CI, otherwise nothing + +```sh +cinful + +cinful list # show all +``` + +Binaries are available on the releases page. + + +### as a pkg + +```go +package main + +import ( + "fmt" + + "github.com/hofstadter-io/cinful" +) + +func main() { + vendor := cinful.Info() + if vendor != nil { + fmt.Println(vendor) + } +} +``` + +where + +```go +type Vendor struct { + Name string `json:"name,omitempty"` + Constant string `json:"constant,omitempty"` + Env any `json:"env,omitempty"` + PR any `json:"pr,omitempty"` + Val string `json:"val,omitempty"` +} +``` + +Env will be a string after checking, +but due to vendor differences, is a +string, list, or map prior. + +### releasing + +git must be clean first + +release: `goreleaser --rm-dist -p 1` + +snapshot (test): `goreleaser --rm-dist -p 1 --snapshot` diff --git a/cinful.go b/cinful.go new file mode 100644 index 0000000..e38b410 --- /dev/null +++ b/cinful.go @@ -0,0 +1,97 @@ +package cinful + +import ( + _ "embed" + "encoding/json" + "fmt" + "os" +) + +//go:embed vendors.json +var b []byte + +func init() { + err := json.Unmarshal(b, &vendors) + if err != nil { + panic(err) + } +} + +var vendors []Vendor + +type Vendor struct { + Name string `json:"name,omitempty"` + Constant string `json:"constant,omitempty"` + Env any `json:"env,omitempty"` + PR any `json:"pr,omitempty"` + Val string `json:"val,omitempty"` +} + +func (v *Vendor) String() string { + vfmt := "Name: %s\nConst: %s\nEnv: %v\nVal: %v" + return fmt.Sprintf(vfmt, v.Name, v.Constant, v.Env, v.Val) +} + +func PrintVendors() { + for _, v := range vendors { + fmt.Printf("%#v\n", v) + } +} + +func Info() *Vendor { + // check vendor first, for more details + for _, v := range vendors { + switch vt := v.Env.(type) { + // normally just a string + case string: + val := os.Getenv(vt) + if val != "" { + v.Val = val + return &v + } + // a list of strings + case []interface {}: + for _, ev := range vt { + val := os.Getenv(ev.(string)) + if val != "" { + v.Env = ev.(string) + v.Val = val + return &v + } + } + + // an ENV var with a specific value + case map[string]interface {}: + for ek, ev := range vt { + val := os.Getenv(ek) + if val == ev.(string) { + v.Env = ek + v.Val, _ = ev.(string) + return &v + } + } + + } + } + + // check some common ENV vars + for _, ce := range commonEnv { + val := os.Getenv(ce) + if val != "" { + return &Vendor{ + Name: "Common Var", + Constant: "COMMON", + Env: ce, + Val: val, + } + } + } + return nil +} + +var commonEnv = []string{ + "CI", + "CONTINUOUS_INTEGRATION", + "BUILD_NUMBER", + "RUN_ID", +} diff --git a/cmd/cinful/main.go b/cmd/cinful/main.go new file mode 100644 index 0000000..76b197f --- /dev/null +++ b/cmd/cinful/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "os" + + "github.com/hofstadter-io/cinful" +) + +func main() { + if len(os.Args) == 2 && os.Args[1] == "list" { + cinful.PrintVendors() + return + } + + vendor := cinful.Info() + if vendor != nil { + fmt.Println(vendor) + } +} + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4100f8b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/hofstadter-io/cinful + +go 1.18 diff --git a/vendors.json b/vendors.json new file mode 100644 index 0000000..1c9e2ee --- /dev/null +++ b/vendors.json @@ -0,0 +1,210 @@ +[ + { + "name": "AppVeyor", + "constant": "APPVEYOR", + "env": "APPVEYOR", + "pr": "APPVEYOR_PULL_REQUEST_NUMBER" + }, + { + "name": "Azure Pipelines", + "constant": "AZURE_PIPELINES", + "env": "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", + "pr": "SYSTEM_PULLREQUEST_PULLREQUESTID" + }, + { + "name": "Appcircle", + "constant": "APPCIRCLE", + "env": "AC_APPCIRCLE" + }, + { + "name": "Bamboo", + "constant": "BAMBOO", + "env": "bamboo_planKey" + }, + { + "name": "Bitbucket Pipelines", + "constant": "BITBUCKET", + "env": "BITBUCKET_COMMIT", + "pr": "BITBUCKET_PR_ID" + }, + { + "name": "Bitrise", + "constant": "BITRISE", + "env": "BITRISE_IO", + "pr": "BITRISE_PULL_REQUEST" + }, + { + "name": "Buddy", + "constant": "BUDDY", + "env": "BUDDY_WORKSPACE_ID", + "pr": "BUDDY_EXECUTION_PULL_REQUEST_ID" + }, + { + "name": "Buildkite", + "constant": "BUILDKITE", + "env": "BUILDKITE", + "pr": { "env": "BUILDKITE_PULL_REQUEST", "ne": "false" } + }, + { + "name": "CircleCI", + "constant": "CIRCLE", + "env": "CIRCLECI", + "pr": "CIRCLE_PULL_REQUEST" + }, + { + "name": "Cirrus CI", + "constant": "CIRRUS", + "env": "CIRRUS_CI", + "pr": "CIRRUS_PR" + }, + { + "name": "AWS CodeBuild", + "constant": "CODEBUILD", + "env": "CODEBUILD_BUILD_ARN" + }, + { + "name": "Codefresh", + "constant": "CODEFRESH", + "env": "CF_BUILD_ID", + "pr": { "any": ["CF_PULL_REQUEST_NUMBER", "CF_PULL_REQUEST_ID"] } + }, + { + "name": "Codeship", + "constant": "CODESHIP", + "env": { "CI_NAME": "codeship" } + }, + { + "name": "Drone", + "constant": "DRONE", + "env": "DRONE", + "pr": { "DRONE_BUILD_EVENT": "pull_request" } + }, + { + "name": "dsari", + "constant": "DSARI", + "env": "DSARI" + }, + { + "name": "Expo Application Services", + "constant": "EAS", + "env": "EAS_BUILD" + }, + { + "name": "GitHub Actions", + "constant": "GITHUB_ACTIONS", + "env": "GITHUB_ACTIONS", + "pr": { "GITHUB_EVENT_NAME": "pull_request" } + }, + { + "name": "GitLab CI", + "constant": "GITLAB", + "env": "GITLAB_CI", + "pr": "CI_MERGE_REQUEST_ID" + }, + { + "name": "GoCD", + "constant": "GOCD", + "env": "GO_PIPELINE_LABEL" + }, + { + "name": "LayerCI", + "constant": "LAYERCI", + "env": "LAYERCI", + "pr": "LAYERCI_PULL_REQUEST" + }, + { + "name": "Hudson", + "constant": "HUDSON", + "env": "HUDSON_URL" + }, + { + "name": "Jenkins", + "constant": "JENKINS", + "env": ["JENKINS_URL", "BUILD_ID"], + "pr": { "any": ["ghprbPullId", "CHANGE_ID"] } + }, + { + "name": "Magnum CI", + "constant": "MAGNUM", + "env": "MAGNUM" + }, + { + "name": "Netlify CI", + "constant": "NETLIFY", + "env": "NETLIFY", + "pr": { "env": "PULL_REQUEST", "ne": "false" } + }, + { + "name": "Nevercode", + "constant": "NEVERCODE", + "env": "NEVERCODE", + "pr": { "env": "NEVERCODE_PULL_REQUEST", "ne": "false" } + }, + { + "name": "Render", + "constant": "RENDER", + "env": "RENDER", + "pr": { "IS_PULL_REQUEST": "true" } + }, + { + "name": "Sail CI", + "constant": "SAIL", + "env": "SAILCI", + "pr": "SAIL_PULL_REQUEST_NUMBER" + }, + { + "name": "Semaphore", + "constant": "SEMAPHORE", + "env": "SEMAPHORE", + "pr": "PULL_REQUEST_NUMBER" + }, + { + "name": "Screwdriver", + "constant": "SCREWDRIVER", + "env": "SCREWDRIVER", + "pr": { "env": "SD_PULL_REQUEST", "ne": "false" } + }, + { + "name": "Shippable", + "constant": "SHIPPABLE", + "env": "SHIPPABLE", + "pr": { "IS_PULL_REQUEST": "true" } + }, + { + "name": "Solano CI", + "constant": "SOLANO", + "env": "TDDIUM", + "pr": "TDDIUM_PR_ID" + }, + { + "name": "Strider CD", + "constant": "STRIDER", + "env": "STRIDER" + }, + { + "name": "TaskCluster", + "constant": "TASKCLUSTER", + "env": ["TASK_ID", "RUN_ID"] + }, + { + "name": "TeamCity", + "constant": "TEAMCITY", + "env": "TEAMCITY_VERSION" + }, + { + "name": "Travis CI", + "constant": "TRAVIS", + "env": "TRAVIS", + "pr": { "env": "TRAVIS_PULL_REQUEST", "ne": "false" } + }, + { + "name": "Vercel", + "constant": "VERCEL", + "env": "NOW_BUILDER" + }, + { + "name": "Visual Studio App Center", + "constant": "APPCENTER", + "env": "APPCENTER_BUILD_ID" + } +]