From e4fb7509616ff2130a2f00154daf3552ab445b38 Mon Sep 17 00:00:00 2001 From: Bartosz Majsak Date: Tue, 17 Sep 2019 21:18:15 +0200 Subject: [PATCH] Initial commit --- .circleci/config.yml | 26 ++ .dependabot/config.yml | 9 + .editorconfig | 19 + .github/ISSUE_TEMPLATE/bug-report.md | 47 +++ .github/ISSUE_TEMPLATE/feature-request.md | 24 ++ .github/PULL_REQUEST_TEMPLATE.md | 23 ++ .gitignore | 128 +++++++ .golangci.yml | 65 ++++ Gopkg.lock | 345 ++++++++++++++++++ Gopkg.toml | 40 ++ LICENSE | 201 ++++++++++ Makefile | 112 ++++++ README.adoc | 20 + cmd/main.go | 83 +++++ init.sh | 24 ++ pkg/cmd/version/cmd.go | 32 ++ .../fixtures/latest_release_is_v.0.0.2.json | 210 +++++++++++ pkg/cmd/version/releases.go | 27 ++ pkg/cmd/version/releases_test.go | 60 +++ pkg/cmd/version/version_suite_test.go | 23 ++ pkg/config/config.go | 110 ++++++ pkg/format/help_format.go | 71 ++++ test/ginkgo_custom_reporter.go | 14 + version/version.go | 19 + 24 files changed, 1732 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .dependabot/config.yml create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.adoc create mode 100644 cmd/main.go create mode 100755 init.sh create mode 100644 pkg/cmd/version/cmd.go create mode 100644 pkg/cmd/version/fixtures/latest_release_is_v.0.0.2.json create mode 100644 pkg/cmd/version/releases.go create mode 100644 pkg/cmd/version/releases_test.go create mode 100644 pkg/cmd/version/version_suite_test.go create mode 100644 pkg/config/config.go create mode 100644 pkg/format/help_format.go create mode 100644 test/ginkgo_custom_reporter.go create mode 100644 version/version.go diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..fb6d4e2 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +version: 2.1 + +jobs: + ## Regular build running unit tests and linters + build: + working_directory: /go/src/github.com/bartoszmajsak/template-golang + docker: + - image: circleci/golang:1.12.9 + steps: + - checkout + - restore_cache: + keys: + - vendor-cache-{{ checksum "Gopkg.lock" }} + - run: + name: "Runs the build" + command: make build-ci + - save_cache: + key: vendor-cache-{{ checksum "Gopkg.lock" }} + paths: + - ./vendor + +workflows: + version: 2.1 + circleci_build: + jobs: + - build diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 0000000..d6ba456 --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,9 @@ +# Configures dependabot dependency updates +# For details, see: https://dependabot.com/docs/config-file/ +version: 1 +update_configs: + - package_manager: "go:dep" + directory: "/" + update_schedule: "weekly" + version_requirement_updates: "increase_versions" + default_reviewers: [ bartoszmajsak ] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cb130e1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +; http://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +max_line_length = off + +[*.{adoc, md}] +trim_trailing_whitespace = false + +[{*.go, Makefile}] +indent_style = tab +indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..2c587a3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,47 @@ +--- +name: Bug report +about: Use this issue type to report anything unusual observed while using this project. +title: '' +labels: bug +assignees: '' + +--- + + + +##### Issue Overview + +Tell us briefly what the problem is about. + +##### Expected Behaviour + +##### Current Behaviour + +##### Steps To Reproduce + +1. [step 1] +2. [step 2] + +##### Additional Information + +Anything relevant to help us resolve the problem. For example: + + * console log or any other relevant information from additional tools such as browser developer tools + * .... + +For long outputs such as stack traces please use HTML5 `
` + +``` +
+ $ uname -a +Darwin Bartoszs-MBP 17.7.0 +Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018 +root:xnu-4570.71.2~1/RELEASE_X86_64 x86_64 +
+``` diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..730db9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Use this issue type to share your ideas about improving user experience and functionalities. +title: '' +labels: enhancement +assignees: '' + +--- + + + +##### What can be improved + +Tell us briefly what the idea is about. + +##### Why + +Why do you find this important or useful? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e0292c8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ + + +#### Short description of what this resolves: + + +#### Changes proposed in this pull request: + +- +- +- + +Fixes # diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef576e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,128 @@ +# Test results +*ginkgo-test-results.xml + +# Temporary Files +bin/ +build/_output +build/_test +telepresence.log + +# Vendor +vendor/ +.vendor-new/ + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with 'go test -c' +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +### Vim ### +# swap +.sw[a-p] +.*.sw[a-p] +# session +Session.vim +# temporary +.netrwhist +# auto-generated tag files +tags + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile +projectile-bookmarks.eld + +# directory configuration +.dir-locals.el + +# saveplace +places + +# url cache +url/cache/ + +# cedet +ede-projects.el + +# smex +smex-items + +# company-statistics +company-statistics-cache.el + +# anaconda-mode +anaconda-mode/ + +### VisualStudioCode ### +.vscode +.history + +### JetBrains IDEs (Goland) ### + +.idea/ +*.iws +*.ipr +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + + diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..3c4ddd9 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,65 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 12 + dupl: + threshold: 128 + goconst: + min-len: 2 + min-occurrences: 2 + depguard: + list-type: blacklist + packages: + # logging is allowed only by "sigs.k8s.io/controller-runtime/pkg/runtime/log" + - github.com/sirupsen/logrus + misspell: + locale: US + ignore-words: + - istio + - k8s + lll: + line-length: 180 + goimports: + local-prefixes: github.com/bartoszmajsak/template-golang + gocritic: + enabled-tags: + - performance + - style + - experimental + disabled-checks: + - wrapperFunc + - commentFormatting # https://github.com/go-critic/go-critic/issues/755 + unused: + check-exported: true + +linters: + enable-all: true + disable: + - gochecknoinits # k8s/istio generated APIs are using init() + - gochecknoglobals # TODO discuss + - gofmt # We use goimports and when using them both leads to contradicting errors + +run: + deadline: 10m + skip-dirs: + - ./pkg/apis + - ./pkg/assets/generated + - ./pkg/client/clientset + +issues: + exclude-rules: + - path: pkg/openshift/ + linters: + - dupl + - path: pkg/k8s/ + linters: + - dupl + +service: + project-path: github.com/bartoszmajsak/template-golang + golangci-lint-version: 1.17.x # Locks the version to avoid newly introduces linters + prepare: + - make lint-prepare diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..a6048f1 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,345 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:1b91ae0dc69a41d4c2ed23ea5cffb721ea63f5037ca4b81e6d6771fbb8f45129" + name = "github.com/fsnotify/fsnotify" + packages = ["."] + pruneopts = "NT" + revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" + version = "v1.4.7" + +[[projects]] + digest = "1:6e9d2128f23114e87a0ac87ce6fd3f51556adfc1a6247c0632fbf44a4c34f846" + name = "github.com/google/go-github" + packages = ["github"] + pruneopts = "NT" + revision = "9686ff0746200cf521ce225525b421e13b4eac1a" + version = "v28.1.1" + +[[projects]] + digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690" + name = "github.com/google/go-querystring" + packages = ["query"] + pruneopts = "NT" + revision = "44c6ddd0a2342c386950e880b658017258da92fc" + version = "v1.0.0" + +[[projects]] + digest = "1:7313d6b9095eb86581402557bbf3871620cf82adf41853c5b9bee04b894290c7" + name = "github.com/h2non/parth" + packages = ["."] + pruneopts = "NT" + revision = "b4df798d65426f8c8ab5ca5f9987aec5575d26c9" + version = "v2.0.1" + +[[projects]] + digest = "1:f0d9d74edbd40fdeada436d5ac9cb5197407899af3fef85ff0137077ffe8ae19" + name = "github.com/hashicorp/errwrap" + packages = ["."] + pruneopts = "NT" + revision = "8a6fb523712970c966eefc6b39ed2c5e74880354" + version = "v1.0.0" + +[[projects]] + digest = "1:2ed138049ab373f696db2081ca48f15c5abdf20893803612a284f2bdce2bf443" + name = "github.com/hashicorp/go-multierror" + packages = ["."] + pruneopts = "NT" + revision = "886a7fbe3eb1c874d46f623bfa70af45f425b3d1" + version = "v1.0.0" + +[[projects]] + digest = "1:62ba88d64dabadbbad170cf6b0d3bfd1f50c36b1566583a28d99304eb08a9bd2" + name = "github.com/hashicorp/hcl" + packages = [ + ".", + "hcl/ast", + "hcl/parser", + "hcl/printer", + "hcl/scanner", + "hcl/strconv", + "hcl/token", + "json/parser", + "json/scanner", + "json/token", + ] + pruneopts = "NT" + revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241" + version = "v1.0.0" + +[[projects]] + digest = "1:76efa3b55850d9caa14f8c0b3a951797f9bc2ffc283526073dcad1b06b6e02d3" + name = "github.com/hpcloud/tail" + packages = [ + ".", + "ratelimiter", + "util", + "watch", + "winfile", + ] + pruneopts = "NT" + revision = "a30252cb686a21eb2d0b98132633053ec2f7f1e5" + version = "v1.0.0" + +[[projects]] + digest = "1:406338ad39ab2e37b7f4452906442a3dbf0eb3379dd1f06aafb5c07e769a5fbb" + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + pruneopts = "NT" + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + digest = "1:fecb18d8d2d2b9964ea3ac289f05e86615e2f90842063d4871e9d87049b61ffa" + name = "github.com/magiconair/properties" + packages = ["."] + pruneopts = "NT" + revision = "de8848e004dd33dc07a2947b3d76f618a7fc7ef1" + version = "v1.8.1" + +[[projects]] + digest = "1:a45ae66dea4c899d79fceb116accfa1892105c251f0dcd9a217ddc276b42ec68" + name = "github.com/mitchellh/mapstructure" + packages = ["."] + pruneopts = "NT" + revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe" + version = "v1.1.2" + +[[projects]] + digest = "1:b19a2d6df8585d12467dd8058e49d103b0f86ed92709ec26d33cd31932e9dd9f" + name = "github.com/onsi/ginkgo" + packages = [ + ".", + "config", + "internal/codelocation", + "internal/containernode", + "internal/failer", + "internal/leafnodes", + "internal/remote", + "internal/spec", + "internal/spec_iterator", + "internal/specrunner", + "internal/suite", + "internal/testingtproxy", + "internal/writer", + "reporters", + "reporters/stenographer", + "reporters/stenographer/support/go-colorable", + "reporters/stenographer/support/go-isatty", + "types", + ] + pruneopts = "NT" + revision = "974566c482abc93ffd3df6f4626e79076c7ed290" + version = "v1.10.1" + +[[projects]] + digest = "1:2ea4049c70f105105777583d180b3ff3f27a970d07230882c45590793919ed3a" + name = "github.com/onsi/gomega" + packages = [ + ".", + "format", + "internal/assertion", + "internal/asyncassertion", + "internal/oraclematcher", + "internal/testingtsupport", + "matchers", + "matchers/support/goraph/bipartitegraph", + "matchers/support/goraph/edge", + "matchers/support/goraph/node", + "matchers/support/goraph/util", + "types", + ] + pruneopts = "NT" + revision = "bdebf9e0ece900259084cfa4121b97ce1a540939" + version = "v1.7.0" + +[[projects]] + digest = "1:5e2ce9b90fcb9747d5ead0fefec4221c898ee7e2f6516ad7590bd999c808b834" + name = "github.com/pelletier/go-toml" + packages = ["."] + pruneopts = "NT" + revision = "728039f679cbcd4f6a54e080d2219a4c4928c546" + version = "v1.4.0" + +[[projects]] + digest = "1:49b6e0d199dc20969bf7c9d14647313e1dd1b102178fad4ca999d8a9584edfab" + name = "github.com/spf13/afero" + packages = [ + ".", + "mem", + ] + pruneopts = "NT" + revision = "588a75ec4f32903aa5e39a2619ba6a4631e28424" + version = "v1.2.2" + +[[projects]] + digest = "1:c5e6b121ef3d2043505edaf4c80e5a008cec2513dc8804795eb0479d1555bcf7" + name = "github.com/spf13/cast" + packages = ["."] + pruneopts = "NT" + revision = "8c9545af88b134710ab1cd196795e7f2388358d7" + version = "v1.3.0" + +[[projects]] + digest = "1:37a91e5093cb260946900c75198c293f3439187e43f48e1a3ae10b4aa55bd7ad" + name = "github.com/spf13/cobra" + packages = ["."] + pruneopts = "NT" + revision = "f2b07da1e2c38d5f12845a4f607e2e1018cbb1f5" + version = "v0.0.5" + +[[projects]] + digest = "1:3d72352adb74e79d6d5a43d6f51bfd2d0bd0c9b5f3c00cf5a4b1636d8d3b9d92" + name = "github.com/spf13/jwalterweatherman" + packages = ["."] + pruneopts = "NT" + revision = "94f6ae3ed3bceceafa716478c5fbf8d29ca601a1" + version = "v1.1.0" + +[[projects]] + digest = "1:9d8420bbf131d1618bde6530af37c3799340d3762cc47210c1d9532a4c3a2779" + name = "github.com/spf13/pflag" + packages = ["."] + pruneopts = "NT" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" + +[[projects]] + digest = "1:8a2c8828a6fc03841448c29c80eeb6c6f2e2b22c2ddcb93d125bf7dac56c136b" + name = "github.com/spf13/viper" + packages = ["."] + pruneopts = "NT" + revision = "b5bf975e5823809fb22c7644d008757f78a4259e" + version = "v1.4.0" + +[[projects]] + branch = "master" + digest = "1:47437e18d25c3559e119434fe8b67346b217397c4106d6e1660515f2d0745a79" + name = "go.uber.org/goleak" + packages = [ + ".", + "internal/stack", + ] + pruneopts = "NT" + revision = "227bd74c34825787d52452ecf0916929863b787c" + +[[projects]] + branch = "master" + digest = "1:0a93ab1b9f6515272587680f1743cc02f3b140b59f29b34816844856bc9c1e2b" + name = "golang.org/x/crypto" + packages = [ + "cast5", + "openpgp", + "openpgp/armor", + "openpgp/elgamal", + "openpgp/errors", + "openpgp/packet", + "openpgp/s2k", + ] + pruneopts = "NT" + revision = "227b76d455e791cb042b03e633e2f7fbcfdf74a5" + +[[projects]] + branch = "master" + digest = "1:d52ab007568fe1a0a2721dd27e7784eec0e16cd6f60b1d073d0d6b2bccef7bfd" + name = "golang.org/x/net" + packages = [ + "context", + "html", + "html/atom", + "html/charset", + ] + pruneopts = "NT" + revision = "c8589233b77dde5edd2205ba8a4fb5c9c2472556" + +[[projects]] + branch = "master" + digest = "1:6d271769f4cbc4a05e63dd169ff02b2171dbc7ba2b4ad3e24354a1bfa5aed336" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "NT" + revision = "c3b328c6e5a763a2bfc82a48b4bd855037210934" + +[[projects]] + digest = "1:348fa8283a7c60b5b71ce04d27b37f7c0fce552d4d0b463b5b3ebbd1840d3f1a" + name = "golang.org/x/text" + packages = [ + "encoding", + "encoding/charmap", + "encoding/htmlindex", + "encoding/internal", + "encoding/internal/identifier", + "encoding/japanese", + "encoding/korean", + "encoding/simplifiedchinese", + "encoding/traditionalchinese", + "encoding/unicode", + "internal/gen", + "internal/language", + "internal/language/compact", + "internal/tag", + "internal/triegen", + "internal/ucd", + "internal/utf8internal", + "language", + "runes", + "transform", + "unicode/cldr", + "unicode/norm", + ] + pruneopts = "NT" + revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475" + version = "v0.3.2" + +[[projects]] + digest = "1:1b91ae0dc69a41d4c2ed23ea5cffb721ea63f5037ca4b81e6d6771fbb8f45129" + name = "gopkg.in/fsnotify.v1" + packages = ["."] + pruneopts = "NT" + revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" + source = "https://github.com/fsnotify/fsnotify.git" + version = "v1.4.7" + +[[projects]] + digest = "1:721323966f12bfa5186e02ad807424f78a28b04606970105dbf6a5e9b62205e7" + name = "gopkg.in/h2non/gock.v1" + packages = ["."] + pruneopts = "NT" + revision = "3ffff9b1aa8200275a5eb219c5f9c62bd27acb31" + version = "v1.0.15" + +[[projects]] + branch = "v1" + digest = "1:8fb1ccb16a6cfecbfdfeb84d8ea1cc7afa8f9ef16526bc2326f72d993e32cef1" + name = "gopkg.in/tomb.v1" + packages = ["."] + pruneopts = "NT" + revision = "dd632973f1e7218eb1089048e0798ec9ae7dceb8" + +[[projects]] + digest = "1:18108594151654e9e696b27b181b953f9a90b16bf14d253dd1b397b025a1487f" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "NT" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/google/go-github/github", + "github.com/hashicorp/go-multierror", + "github.com/onsi/ginkgo", + "github.com/onsi/ginkgo/reporters", + "github.com/onsi/gomega", + "github.com/spf13/cobra", + "github.com/spf13/pflag", + "github.com/spf13/viper", + "go.uber.org/goleak", + "golang.org/x/net/context", + "gopkg.in/h2non/gock.v1", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..4ceb279 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,40 @@ +[prune] + go-tests = true + non-go = true + +[[constraint]] + name = "github.com/spf13/viper" + version = "=v1.4.0" + +[[constraint]] + name = "github.com/spf13/cobra" + version = "=v0.0.5" + +[[constraint]] + name = "github.com/google/go-github" + version = "=v28.1.1" + +# Workaround for https://github.com/golang/dep/issues/1799 +# Otherwise ginkgo/gomega won't be pulled in +[[override]] + name = "gopkg.in/fsnotify.v1" + source = "https://github.com/fsnotify/fsnotify.git" + +[[constraint]] + name = "github.com/onsi/gomega" + version = "=1.7.0" + +[[constraint]] + name = "github.com/onsi/ginkgo" + version = "=1.10.1" + +[[constraint]] + name = "go.uber.org/goleak" + branch = "master" + +[[constraint]] + name = "gopkg.in/h2non/gock.v1" + version = "=v1.0.15" + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..595ec6d --- /dev/null +++ b/Makefile @@ -0,0 +1,112 @@ +PROJECT_NAME:=template-golang +PACKAGE_NAME:=github.com/bartoszmajsak/$(PROJECT_NAME) + +PROJECT_DIR:=$(shell pwd) +BUILD_DIR:=$(PROJECT_DIR)/build +BINARY_DIR:=$(PROJECT_DIR)/dist +BINARY_NAME:=binary + +# Call this function with $(call header,"Your message") to see underscored green text +define header = +@echo -e "\n\e[92m\e[4m\e[1m$(1)\e[0m\n" +endef + +##@ Default target (all you need - just run "make") +.DEFAULT_GOAL:=all +.PHONY: all +all: deps format lint compile test ## Runs 'deps format lint test compile' targets + +##@ Build + +.PHONY: build-ci +build-ci: deps format compile test # Like 'all', but without linter which is executed as separated PR check + +.PHONY: compile +compile: $(BINARY_DIR)/$(BINARY_NAME) ## Compiles binaries + +.PHONY: test +test: ## Runs tests + $(call header,"Running tests") + ginkgo -r -v ${args} + +.PHONY: clean +clean: ## Removes build artifacts + rm -rf $(BINARY_DIR) $(PROJECT_DIR)/bin/ + +.PHONY: deps +deps: check-tools ## Fetches all dependencies + $(call header,"Fetching dependencies") + dep ensure -v + +.PHONY: format +format: ## Removes unneeded imports and formats source code + $(call header,"Formatting code") + goimports -l -w ./pkg/ ./cmd/ ./test/ + +.PHONY: lint-prepare +lint-prepare: deps + +.PHONY: lint +lint: lint-prepare ## Concurrently runs a whole bunch of static analysis tools + $(call header,"Running a whole bunch of static analysis tools") + golangci-lint run + +# ########################################################################## +# Build configuration +# ########################################################################## + +OS:=$(shell uname -s) +GOOS?=$(shell echo $(OS) | awk '{print tolower($$0)}') +GOARCH:=amd64 + +BUILD_TIME=$(shell date -u '+%Y-%m-%dT%H:%M:%SZ') +GITUNTRACKEDCHANGES:=$(shell git status --porcelain --untracked-files=no) +COMMIT:=$(shell git rev-parse --short HEAD) +ifneq ($(GITUNTRACKEDCHANGES),) + COMMIT:=$(COMMIT)-dirty +endif + +BINARY_VERSION?=$(shell git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") +GIT_TAG:=$(shell git describe --tags --abbrev=0 --exact-match > /dev/null 2>&1; echo $$?) +ifneq ($(GIT_TAG),0) + BINARY_VERSION:=$(BINARY_VERSION)-next-$(COMMIT) +else ifneq ($(GITUNTRACKEDCHANGES),) + BINARY_VERSION:=$(BINARY_VERSION)-dirty +endif + +GOBUILD:=GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 +RELEASE?=false +LDFLAGS="-w -X ${PACKAGE_NAME}/version.Release=${RELEASE} -X ${PACKAGE_NAME}/version.Version=${BINARY_VERSION} -X ${PACKAGE_NAME}/version.Commit=${COMMIT} -X ${PACKAGE_NAME}/version.BuildTime=${BUILD_TIME}" +SRCS=$(shell find ./pkg -name "*.go") $(shell find ./cmd -name "*.go") $(shell find ./version -name "*.go") + +$(BINARY_DIR): + [ -d $@ ] || mkdir -p $@ + +$(BINARY_DIR)/$(BINARY_NAME): $(BINARY_DIR) $(SRCS) + $(call header,"Compiling... carry on!") + ${GOBUILD} go build -ldflags ${LDFLAGS} -o $@ ./cmd + +##@ Setup + +.PHONY: tools +tools: ## Installs required go tools + $(call header,"Installing required tools") + go get -u github.com/golang/dep/cmd/dep + go get -u github.com/golangci/golangci-lint/cmd/golangci-lint + go get -u golang.org/x/tools/cmd/goimports + go get -u github.com/onsi/ginkgo/ginkgo + +EXECUTABLES:=dep golangci-lint goimports ginkgo +CHECK:=$(foreach exec,$(EXECUTABLES),\ + $(if $(shell which $(exec) 2>/dev/null),,"install")) +.PHONY: check-tools +check-tools: + $(call header,"Checking required tools") + @$(if $(strip $(CHECK)),$(MAKE) -f $(THIS_MAKEFILE) tools,echo "'$(EXECUTABLES)' are installed") + +##@ Helpers + +.PHONY: help +help: ## Displays this help \o/ + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-24s\033[0m\033[2m %s\033[0m\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + @cat $(MAKEFILE_LIST) | grep "[A-Z]?=" | sort | awk 'BEGIN {FS="?="; printf "\n\n\033[1mEnvironment variables\033[0m\n"} {printf " \033[36m%-25s\033[0m\033[2m %s\033[0m\n", $$1, $$2}' diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..d9a3b7f --- /dev/null +++ b/README.adoc @@ -0,0 +1,20 @@ += Template Repository - Golang + +Basic structure for golang projects. + +Code: + +- Folder structure +- Sample `dep` configurtion +- Basic `Makefile` +- `.editorconfig` +- `.gitignore` + +Services: + +- CircleCI +- GolangCI linter +- Dependabot + + +GitHub issues / pr templates (don't forget to customize the content!) diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..a35e7fe --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + "strings" + "time" + + "github.com/bartoszmajsak/template-golang/pkg/cmd/version" + "github.com/bartoszmajsak/template-golang/pkg/config" + "github.com/bartoszmajsak/template-golang/pkg/format" + v "github.com/bartoszmajsak/template-golang/version" + + "github.com/spf13/cobra" +) + +func main() { + rootCmd := newCmd() + + rootCmd.AddCommand(version.NewCmd()) + + if err := rootCmd.Execute(); err != nil { + panic(err) + } +} + +func newCmd() *cobra.Command { + var configFile string + releaseInfo := make(chan string, 1) + + rootCmd := &cobra.Command{ + Use: "cmd", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { //nolint[:unparam] + if v.Released() { + go func() { + latestRelease, _ := version.LatestRelease() + if !version.IsLatestRelease(latestRelease) { + releaseInfo <- fmt.Sprintf("WARN: you are using %s which is not the latest release (newest is %s).\n"+ + "Follow release notes for update info https://github.com/Maistra/istio-workspace/releases/latest", v.Version, latestRelease) + } else { + releaseInfo <- "" + } + }() + } + return config.SetupConfigSources(configFile, cmd.Flag("config").Changed) + }, + RunE: func(cmd *cobra.Command, args []string) error { //nolint[:unparam] + shouldPrintVersion, _ := cmd.Flags().GetBool("version") + if shouldPrintVersion { + version.PrintVersion() + } else { + fmt.Print(cmd.UsageString()) + } + return nil + }, + PersistentPostRunE: func(cmd *cobra.Command, args []string) error { + if v.Released() { + timer := time.NewTimer(2 * time.Second) + select { + case release := <-releaseInfo: + fmt.Println(release) + case <-timer.C: + // do nothing, just timeout + } + } + close(releaseInfo) + return nil + }, + } + + rootCmd.PersistentFlags(). + StringVarP(&configFile, "config", "c", ".ike.config.yaml", + fmt.Sprintf("config file (supported formats: %s)", strings.Join(config.SupportedExtensions(), ", "))) + rootCmd.Flags().Bool("version", false, "prints the version number of ike cli") + rootCmd.PersistentFlags().String("help-format", "standard", "prints help in asciidoc table") + if err := rootCmd.PersistentFlags().MarkHidden("help-format"); err != nil { + fmt.Printf("failed while trying to hide a flag: %s\n", err) + } + + format.EnhanceHelper(rootCmd) + format.RegisterTemplateFuncs() + + return rootCmd +} diff --git a/init.sh b/init.sh new file mode 100755 index 0000000..ec8ac63 --- /dev/null +++ b/init.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +die () { + echo >&2 "$@" + exit 1 +} + +defaultName=$(git remote -v | cut -d':' -f 2 | cut -d'.' -f 1 | uniq) +read -p "Repo name (${defaultName}): " name +test -z "$name" && name=${defaultName} + +find . -type f -not -path "./vendor/*" -not -path "./.git/*" -not -path "./init.sh" -exec sed -i "s|bartoszmajsak/template-golang|${name}|g" '{}' \; + +read -p "Project name: " project +test -z "$name" && { + die "You must specify project name" +} +sed -i "s|PROJECT_NAME:=template-golang|PROJECT_NAME:=${project}|g" Makefile + +read -p "Executable name: " binary +test -z "$name" && { + die "You must specify executable name" +} +sed -i "s|PROJECT_NAME:=template-golang|PROJECT_NAME:=${binary}|g" Makefile diff --git a/pkg/cmd/version/cmd.go b/pkg/cmd/version/cmd.go new file mode 100644 index 0000000..794c577 --- /dev/null +++ b/pkg/cmd/version/cmd.go @@ -0,0 +1,32 @@ +package version + +import ( + "fmt" + "runtime" + + "github.com/bartoszmajsak/template-golang/version" + + "github.com/spf13/cobra" +) + +// NewCmd creates version cmd which prints version and Build details of the executed binary +func NewCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Prints the version number of tool", + Long: "All software has versions. This is ours", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { //nolint[:unparam] + PrintVersion() + return nil + }, + } +} + +func PrintVersion() { + fmt.Printf("Binary Version: %s\n", version.Version) + fmt.Printf("Go Version: %s\n", runtime.Version()) + fmt.Printf("Go OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH) + fmt.Printf("Build Commit: %v\n", version.Commit) + fmt.Printf("Build Time: %v\n", version.BuildTime) +} diff --git a/pkg/cmd/version/fixtures/latest_release_is_v.0.0.2.json b/pkg/cmd/version/fixtures/latest_release_is_v.0.0.2.json new file mode 100644 index 0000000..a5c8d37 --- /dev/null +++ b/pkg/cmd/version/fixtures/latest_release_is_v.0.0.2.json @@ -0,0 +1,210 @@ +{ + "url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/18367686", + "assets_url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/18367686/assets", + "upload_url": "https://uploads.github.com/repos/bartoszmajsak/template-golang/releases/18367686/assets{?name,label}", + "html_url": "https://github.com/bartoszmajsak/template-golang/releases/tag/v0.0.2", + "id": 18367686, + "node_id": "MDc6UmVsZWFzZTE4MzY3Njg2", + "tag_name": "v0.0.2", + "target_commitish": "master", + "name": "v0.0.2", + "draft": false, + "author": { + "login": "bartoszmajsak", + "id": 719616, + "node_id": "MDQ6VXNlcjcxOTYxNg==", + "avatar_url": "https://avatars1.githubusercontent.com/u/719616?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bartoszmajsak", + "html_url": "https://github.com/bartoszmajsak", + "followers_url": "https://api.github.com/users/bartoszmajsak/followers", + "following_url": "https://api.github.com/users/bartoszmajsak/following{/other_user}", + "gists_url": "https://api.github.com/users/bartoszmajsak/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bartoszmajsak/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bartoszmajsak/subscriptions", + "organizations_url": "https://api.github.com/users/bartoszmajsak/orgs", + "repos_url": "https://api.github.com/users/bartoszmajsak/repos", + "events_url": "https://api.github.com/users/bartoszmajsak/events{/privacy}", + "received_events_url": "https://api.github.com/users/bartoszmajsak/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2019-07-02T18:03:17Z", + "published_at": "2019-07-02T18:31:57Z", + "assets": [ + { + "url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/assets/13516502", + "id": 13516502, + "node_id": "MDEyOlJlbGVhc2VBc3NldDEzNTE2NTAy", + "name": "checksums.txt", + "label": "", + "uploader": { + "login": "bartoszmajsak", + "id": 719616, + "node_id": "MDQ6VXNlcjcxOTYxNg==", + "avatar_url": "https://avatars1.githubusercontent.com/u/719616?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bartoszmajsak", + "html_url": "https://github.com/bartoszmajsak", + "followers_url": "https://api.github.com/users/bartoszmajsak/followers", + "following_url": "https://api.github.com/users/bartoszmajsak/following{/other_user}", + "gists_url": "https://api.github.com/users/bartoszmajsak/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bartoszmajsak/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bartoszmajsak/subscriptions", + "organizations_url": "https://api.github.com/users/bartoszmajsak/orgs", + "repos_url": "https://api.github.com/users/bartoszmajsak/repos", + "events_url": "https://api.github.com/users/bartoszmajsak/events{/privacy}", + "received_events_url": "https://api.github.com/users/bartoszmajsak/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "text/plain; charset=utf-8", + "state": "uploaded", + "size": 382, + "download_count": 0, + "created_at": "2019-07-02T18:31:57Z", + "updated_at": "2019-07-02T18:31:57Z", + "browser_download_url": "https://github.com/bartoszmajsak/template-golang/releases/download/v0.0.2/checksums.txt" + }, + { + "url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/assets/13516504", + "id": 13516504, + "node_id": "MDEyOlJlbGVhc2VBc3NldDEzNTE2NTA0", + "name": "ike_0.0.1_Darwin_i386.tar.gz", + "label": "", + "uploader": { + "login": "bartoszmajsak", + "id": 719616, + "node_id": "MDQ6VXNlcjcxOTYxNg==", + "avatar_url": "https://avatars1.githubusercontent.com/u/719616?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bartoszmajsak", + "html_url": "https://github.com/bartoszmajsak", + "followers_url": "https://api.github.com/users/bartoszmajsak/followers", + "following_url": "https://api.github.com/users/bartoszmajsak/following{/other_user}", + "gists_url": "https://api.github.com/users/bartoszmajsak/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bartoszmajsak/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bartoszmajsak/subscriptions", + "organizations_url": "https://api.github.com/users/bartoszmajsak/orgs", + "repos_url": "https://api.github.com/users/bartoszmajsak/repos", + "events_url": "https://api.github.com/users/bartoszmajsak/events{/privacy}", + "received_events_url": "https://api.github.com/users/bartoszmajsak/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 11306269, + "download_count": 0, + "created_at": "2019-07-02T18:31:57Z", + "updated_at": "2019-07-02T18:31:58Z", + "browser_download_url": "https://github.com/bartoszmajsak/template-golang/releases/download/v0.0.2/ike_0.0.1_Darwin_i386.tar.gz" + }, + { + "url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/assets/13516505", + "id": 13516505, + "node_id": "MDEyOlJlbGVhc2VBc3NldDEzNTE2NTA1", + "name": "ike_0.0.1_Darwin_x86_64.tar.gz", + "label": "", + "uploader": { + "login": "bartoszmajsak", + "id": 719616, + "node_id": "MDQ6VXNlcjcxOTYxNg==", + "avatar_url": "https://avatars1.githubusercontent.com/u/719616?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bartoszmajsak", + "html_url": "https://github.com/bartoszmajsak", + "followers_url": "https://api.github.com/users/bartoszmajsak/followers", + "following_url": "https://api.github.com/users/bartoszmajsak/following{/other_user}", + "gists_url": "https://api.github.com/users/bartoszmajsak/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bartoszmajsak/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bartoszmajsak/subscriptions", + "organizations_url": "https://api.github.com/users/bartoszmajsak/orgs", + "repos_url": "https://api.github.com/users/bartoszmajsak/repos", + "events_url": "https://api.github.com/users/bartoszmajsak/events{/privacy}", + "received_events_url": "https://api.github.com/users/bartoszmajsak/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 11888121, + "download_count": 0, + "created_at": "2019-07-02T18:31:57Z", + "updated_at": "2019-07-02T18:31:58Z", + "browser_download_url": "https://github.com/bartoszmajsak/template-golang/releases/download/v0.0.2/ike_0.0.1_Darwin_x86_64.tar.gz" + }, + { + "url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/assets/13516501", + "id": 13516501, + "node_id": "MDEyOlJlbGVhc2VBc3NldDEzNTE2NTAx", + "name": "ike_0.0.1_Linux_i386.tar.gz", + "label": "", + "uploader": { + "login": "bartoszmajsak", + "id": 719616, + "node_id": "MDQ6VXNlcjcxOTYxNg==", + "avatar_url": "https://avatars1.githubusercontent.com/u/719616?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bartoszmajsak", + "html_url": "https://github.com/bartoszmajsak", + "followers_url": "https://api.github.com/users/bartoszmajsak/followers", + "following_url": "https://api.github.com/users/bartoszmajsak/following{/other_user}", + "gists_url": "https://api.github.com/users/bartoszmajsak/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bartoszmajsak/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bartoszmajsak/subscriptions", + "organizations_url": "https://api.github.com/users/bartoszmajsak/orgs", + "repos_url": "https://api.github.com/users/bartoszmajsak/repos", + "events_url": "https://api.github.com/users/bartoszmajsak/events{/privacy}", + "received_events_url": "https://api.github.com/users/bartoszmajsak/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 10713509, + "download_count": 0, + "created_at": "2019-07-02T18:31:57Z", + "updated_at": "2019-07-02T18:31:58Z", + "browser_download_url": "https://github.com/bartoszmajsak/template-golang/releases/download/v0.0.2/ike_0.0.1_Linux_i386.tar.gz" + }, + { + "url": "https://api.github.com/repos/bartoszmajsak/template-golang/releases/assets/13516503", + "id": 13516503, + "node_id": "MDEyOlJlbGVhc2VBc3NldDEzNTE2NTAz", + "name": "ike_0.0.1_Linux_x86_64.tar.gz", + "label": "", + "uploader": { + "login": "bartoszmajsak", + "id": 719616, + "node_id": "MDQ6VXNlcjcxOTYxNg==", + "avatar_url": "https://avatars1.githubusercontent.com/u/719616?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bartoszmajsak", + "html_url": "https://github.com/bartoszmajsak", + "followers_url": "https://api.github.com/users/bartoszmajsak/followers", + "following_url": "https://api.github.com/users/bartoszmajsak/following{/other_user}", + "gists_url": "https://api.github.com/users/bartoszmajsak/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bartoszmajsak/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bartoszmajsak/subscriptions", + "organizations_url": "https://api.github.com/users/bartoszmajsak/orgs", + "repos_url": "https://api.github.com/users/bartoszmajsak/repos", + "events_url": "https://api.github.com/users/bartoszmajsak/events{/privacy}", + "received_events_url": "https://api.github.com/users/bartoszmajsak/received_events", + "type": "User", + "site_admin": false + }, + "content_type": "application/gzip", + "state": "uploaded", + "size": 11307955, + "download_count": 37, + "created_at": "2019-07-02T18:31:57Z", + "updated_at": "2019-07-02T18:31:58Z", + "browser_download_url": "https://github.com/bartoszmajsak/template-golang/releases/download/v0.0.2/ike_0.0.1_Linux_x86_64.tar.gz" + } + ], + "tarball_url": "https://api.github.com/repos/bartoszmajsak/template-golang/tarball/v0.0.2", + "zipball_url": "https://api.github.com/repos/bartoszmajsak/template-golang/zipball/v0.0.2", + "body": "# Highlights of v0.0.2 release\n\nInspired by @bobbytables talk from [OSCON 2018](https://youtu.be/yhvR02UofZE) we started hacking on open source tooling realizing the idea of safe development on production.\nNow, after long weeks in the making, we are extremely happy to announce the first release of `istio-workspace` project.\n\nWith this toolkit, we strive to enable developers to:\n\n> Develop their microservices locally, using the tools they love!\n>\n> Connect to other services in the cluster and test their changes!\n>\n> All of that without interfering with other developers and users!\n\nAll of it based on [CNCF](https://www.cncf.io/) open source projects such as [Istio](https://istio.io) and [Telepresence](https://telepresence.io).\n\n## How does it work?\n\n- `ike develop` wraps [Telepresence](https://telepresence.io) and let you start local development while being connected to the real services in the cluster\n\n - in addition, it will restart the process when any files have been changed (see `--watch` flag)\n\n- `istio-workspace` operator takes care of configuring routes in Istio so that only you can hit your service under development\n\nTo learn more head over to the [official docs](https://istio-workspace-docs.netlify.com/istio-workspace/v0.0.2/index.html).\n\n" +} diff --git a/pkg/cmd/version/releases.go b/pkg/cmd/version/releases.go new file mode 100644 index 0000000..02ba31a --- /dev/null +++ b/pkg/cmd/version/releases.go @@ -0,0 +1,27 @@ +package version + +import ( + "net/http" + + "github.com/bartoszmajsak/template-golang/version" + + "github.com/google/go-github/github" + "golang.org/x/net/context" +) + +func LatestRelease() (string, error) { + httpClient := http.Client{} + defer httpClient.CloseIdleConnections() + + client := github.NewClient(&httpClient) + latestRelease, _, err := client.Repositories. + GetLatestRelease(context.Background(), "bartoszmajsak", "template-golang") + if err != nil { + return "", err + } + return *latestRelease.Name, nil +} + +func IsLatestRelease(latestRelease string) bool { + return latestRelease == version.Version +} diff --git a/pkg/cmd/version/releases_test.go b/pkg/cmd/version/releases_test.go new file mode 100644 index 0000000..de7bd15 --- /dev/null +++ b/pkg/cmd/version/releases_test.go @@ -0,0 +1,60 @@ +package version_test + +import ( + "gopkg.in/h2non/gock.v1" + + "github.com/bartoszmajsak/template-golang/pkg/cmd/version" + v "github.com/bartoszmajsak/template-golang/version" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Fetching latest release", func() { + + BeforeEach(func() { + gock.New("https://api.github.com"). + Get("/repos/bartoszmajsak/template-golang/releases/latest"). + Reply(200). + File("fixtures/latest_release_is_v.0.0.2.json") + }) + + AfterEach(func() { + gock.Off() + }) + + It("should get latest release", func() { + // when + release, err := version.LatestRelease() + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(release).To(Equal("v0.0.2")) + }) + + It("should determine that v0.0.0 is not latest release", func() { + // given + latestRelease, err := version.LatestRelease() + Expect(err).ToNot(HaveOccurred()) + + // when + latest := version.IsLatestRelease(latestRelease) + + // then + Expect(latest).To(BeFalse()) + }) + + It("should determine that v0.0.2 is latest release", func() { + // given + v.Version = "v0.0.2" + latestRelease, err := version.LatestRelease() + Expect(err).ToNot(HaveOccurred()) + + // when + latest := version.IsLatestRelease(latestRelease) + + // then + Expect(latest).To(BeTrue()) + }) + +}) diff --git a/pkg/cmd/version/version_suite_test.go b/pkg/cmd/version/version_suite_test.go new file mode 100644 index 0000000..e15663d --- /dev/null +++ b/pkg/cmd/version/version_suite_test.go @@ -0,0 +1,23 @@ +package version_test + +import ( + "testing" + + "go.uber.org/goleak" + + . "github.com/bartoszmajsak/template-golang/test" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestVersion(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecWithJUnitReporter(t, "Version Suite") +} + +var _ = SynchronizedAfterSuite(func() {}, func() { + goleak.VerifyNone(GinkgoT(), + goleak.IgnoreTopFunction("github.com/bartoszmajsak/template-golang/vendor/github.com/onsi/ginkgo/internal/specrunner.(*SpecRunner).registerForInterrupts"), + ) +}) diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..7bc49ad --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,110 @@ +package config + +import ( + "fmt" + "path" + "strings" + + "github.com/hashicorp/go-multierror" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +// SetupConfigSources sets up Viper configuration. +// +// If specific file path is provided but fails when loading it will return an error. +// +// In case of default config location it will not fail if file does not exist, +// but will in any other case such as parse error. +// +// Config precedence (each item takes precedence over the item below it): +// . Flags +// . Env variables +// . Config file +// +// Environment variables are prefixed with `IKE` and have fully qualified names, for example +// in case of `develop` command and its `port` flag corresponding environment variable is +// `IKE_DEVELOP_PORT`. +func SetupConfigSources(configFile string, notDefault bool) error { + viper.Reset() + viper.SetEnvPrefix("BINARY_NAME") + viper.AutomaticEnv() + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.SetTypeByDefaultValue(true) + + ext := path.Ext(configFile) + viper.SetConfigName(strings.TrimSuffix(path.Base(configFile), ext)) + if !contains(SupportedExtensions(), strings.TrimPrefix(path.Ext(ext), ".")) { + return fmt.Errorf("'%s' extension is not supported. Use one of [%s]", ext, strings.Join(SupportedExtensions(), ", ")) + } + viper.SetConfigType(ext[1:]) + viper.AddConfigPath(path.Dir(configFile)) + + if err := viper.ReadInConfig(); err != nil { + if notDefault { + return err + } + + if _, fileDoesNotExist := err.(viper.ConfigFileNotFoundError); !fileDoesNotExist { + return err + } + } + return nil +} + +// SupportedExtensions returns a slice of all supported config format (as file extensions) +func SupportedExtensions() []string { + return viper.SupportedExts +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +// SyncFlag ensures that if configuration provides a value for a given cmd.flag it will be set back to the flag itself, +// but only if the flag was not set through CLI. +// +// This way we can make flags required but still have their values provided by the configuration source +func SyncFlag(cmd *cobra.Command, flagName string) error { //nolint[:unused] + value := viper.GetString(cmd.Name() + "." + flagName) + if value != "" && !cmd.Flag(flagName).Changed { + return cmd.Flags().Set(flagName, value) + } + return nil +} + +// SyncFlags ensures that if configuration provide a value for any of defined flags it will be set +// back to the flag itself. +// +// This function iterates over all flags defined for cobra.Command and accumulates errors if they occur while +// calling SyncFlag for every flag. +func SyncFlags(cmd *cobra.Command) error { //nolint[:unused] + var accErrors *multierror.Error + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + syncFlagErr := SyncFlag(cmd, flag.Name) + accErrors = multierror.Append(accErrors, syncFlagErr) + }) + return accErrors.ErrorOrNil() +} + +// BindFullyQualifiedFlag ensures that each flag used in commands is bound to a key using fully qualified name +// which has a following form: +// +// commandName.flagName +// +// This lets us keep structure of yaml file: +// +// commandName: +// flagName: value +func BindFullyQualifiedFlag(cmd *cobra.Command) func(flag *pflag.Flag) { //nolint[:unused] + return func(flag *pflag.Flag) { + _ = viper.BindPFlag(cmd.Name()+"."+flag.Name, flag) + } +} diff --git a/pkg/format/help_format.go b/pkg/format/help_format.go new file mode 100644 index 0000000..7584622 --- /dev/null +++ b/pkg/format/help_format.go @@ -0,0 +1,71 @@ +package format + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// EnhanceHelper wraps helper function with alternative formatting +// and templates (e.g. asciidoc) based on --helper-format flag +// Applies to all subcommands. +// This can be useful when automatically generating documentation for CLI +func EnhanceHelper(command *cobra.Command) { + originalHelpFunc := command.HelpFunc() + command.SetHelpFunc(func(cmd *cobra.Command, i []string) { + defer originalHelpFunc(cmd, i) // always delegate to original helper function + helpFormat, err := cmd.Flags().GetString("help-format") + if err != nil { + return + } + + if helpFormat == "standard" { + return + } + + cmd.SetHelpTemplate(OnlyUsageString) + if helpFormat == "adoc" { + cmd.SetUsageTemplate(ADocHelpTable) + } else { + fmt.Printf("unknown help format: [%s]. using standard one\n", helpFormat) + } + }) +} + +func RegisterTemplateFuncs() { + cobra.AddTemplateFunc("localFlagsSlice", func(set *pflag.FlagSet) []pflag.Flag { + flags := make([]pflag.Flag, 0) + set.VisitAll(func(flag *pflag.Flag) { + if flag.Name != "help" { + flags = append(flags, *flag) + } + }) + return flags + }) + cobra.AddTemplateFunc("type", func(flag *pflag.Flag) string { + flagType := flag.Value.Type() + if strings.Contains(flagType, "Slice") { + return "comma-separated list of " + strings.Replace(flagType, "Slice", "", 1) + "s" + } + return "`" + flagType + "`" + }) + +} + +const ( + OnlyUsageString = "{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}" + ADocHelpTable = `{{if .HasAvailableLocalFlags}}{{ $tick := "` + "`" + `" }} +[cols="2,4,2,1"] +|=== +|Option|Purpose|Format|Default +{{range localFlagsSlice .LocalFlags}}{{ if not .Hidden }} +|{{$tick}}--{{.Name}}{{$tick}} {{if .Shorthand}}({{$tick}}-{{.Shorthand}}{{$tick}}){{end}} +|{{.Usage}} +|{{type .}} +|{{if .DefValue}}{{$tick}}{{.DefValue | trimTrailingWhitespaces}}{{$tick}}{{end}}{{end}} +{{end}} +|=== +{{end}}` +) diff --git a/test/ginkgo_custom_reporter.go b/test/ginkgo_custom_reporter.go new file mode 100644 index 0000000..2953711 --- /dev/null +++ b/test/ginkgo_custom_reporter.go @@ -0,0 +1,14 @@ +package test + +import ( + "testing" + + "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/reporters" +) + +// RunSpecWithJUnitReporter calls custom ginkgo junit reporter +func RunSpecWithJUnitReporter(t *testing.T, description string) { + junitReporter := reporters.NewJUnitReporter("ginkgo-test-results.xml") + ginkgo.RunSpecsWithDefaultAndCustomReporters(t, description, []ginkgo.Reporter{junitReporter}) +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..8faf93b --- /dev/null +++ b/version/version.go @@ -0,0 +1,19 @@ +package version + +import "strconv" + +var ( + // Version hold a semantic version of the running binary + Version = "v0.0.0" + // Commit holds the commit hash against which the binary build was ran + Commit string + // BuildTime holds timestamp when the binary build was ran + BuildTime string + // Release indicates if built binary is an official release + Release = "false" +) + +func Released() bool { + released, _ := strconv.ParseBool(Release) + return released +}