├── .gitignore ├── README.md ├── demos ├── demo-1 │ └── notes.md ├── demo-2 │ ├── main.go │ ├── notes.md │ └── solution │ │ └── main.go ├── demo-3 │ ├── main.go │ ├── notes.md │ └── solution │ │ └── main.go ├── demo-4 │ ├── main.go │ ├── notes.md │ └── solution │ │ └── main.go ├── demo-5 │ ├── main.go │ └── notes.md ├── demo-6 │ └── notes.md ├── demo-7 │ ├── .goreleaser.yml │ ├── images │ │ └── artifactory-repo-browser.png │ └── notes.md ├── demo-8 │ ├── dep │ │ └── .travis.yml │ ├── mod │ │ └── .travis.yml │ └── notes.md └── demo-9 │ ├── dep │ └── Jenkinsfile │ ├── mod │ └── Jenkinsfile │ └── notes.md ├── exercises ├── exercise-1 │ ├── instructions.md │ └── solution │ │ ├── cmd │ │ └── install.go │ │ ├── solution.md │ │ ├── templ │ │ └── gen.go │ │ └── utils │ │ └── file.go ├── exercise-2 │ ├── instructions.md │ └── solution │ │ ├── go.mod │ │ ├── go.sum │ │ ├── solution.md │ │ └── utils │ │ └── error.go ├── exercise-3 │ ├── instructions.md │ └── solution │ │ ├── images │ │ └── coverage-report.png │ │ ├── solution.md │ │ └── utils │ │ └── file_standard_test.go ├── exercise-4 │ ├── instructions.md │ └── solution │ │ ├── go.mod │ │ ├── go.sum │ │ ├── solution.md │ │ └── utils │ │ ├── file_ginkgo_test.go │ │ └── file_testify_test.go ├── exercise-5 │ ├── instructions.md │ └── solution.md │ │ └── solution.md └── exercise-6 │ ├── instructions.md │ └── solution │ ├── .goreleaser.yml │ └── solution.md ├── prerequisites └── instructions.md └── slides.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go project automation 2 | 3 | Today, developers are under pressure to deliver software fast, reliable and frequently. The right tooling can help with boosting developer productivity dramatically. Join Benjamin Muschko to discuss what Go has to offer for package management, testing support, Continuous Integration, and IDE integration. You’ll leave with a good understanding on how to implement common automation tasks for Go projects to reduce your time to market. 4 | 5 | In this course, you’ll learn how to improve software quality by analyzing your source code with the right tooling support and how to ensure reproducibility of your project by using proper package management. Ben will walk you through writing and executing tests to detect bugs before delivering the software to production. At the end of the training, we’ll set up a CI/CD pipeline that orchestrates the tooling into a fully automated process. 6 | 7 | This class builds upon the knowledge gained in the training "Getting Started with Go." It’s also a great addition for any beginner or advanced Go programmer interested in getting to know the wider Go tooling ecosystem with the help of engaging hands-on exercises. 8 | 9 | ## Prerequisites 10 | 11 | All exercises are based on a sample Go project. Please make sure to follow the [instructions](./prerequisites/instructions.md) for setting up the sample project before joining the training. 12 | 13 | ## Exercises 14 | 15 | All exercises are numbered and live in dedicated directories starting with the name `exercise-`. You'll find instructions for each exercise in each folder. Solutions are available in the `solution` folder. Try to solve each exercise yourself before having a look at the solution. -------------------------------------------------------------------------------- /demos/demo-1/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 1 2 | 3 | Demonstrates the functionality and code base of the sample project "Let's gopher". You can find instructions in the [README.adoc file](https://github.com/bmuschko/lets-gopher-exercise/blob/master/README.adoc) of the project. -------------------------------------------------------------------------------- /demos/demo-2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | // [go] ./main.go:6:2: age declared and not used 7 | age := 14 8 | // [go-vet] ./main.go:7: Printf format %s reads arg #1, but call has 0 args 9 | fmt.Printf("Ben's age is %s") 10 | } 11 | -------------------------------------------------------------------------------- /demos/demo-2/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 2 2 | 3 | Demonstrates the purpose and usage of the tool [Go vet](https://golang.org/cmd/vet/). 4 | 5 | ## Features 6 | 7 | * Starts where the compiler ends 8 | * Examines Go source code and reports suspicious constructs 9 | * Mistakes could easily be missed during code reviews or unit testing 10 | * Typical example: Missing arguments for `Printf` calls 11 | 12 | ## Usage 13 | 14 | ``` 15 | $ go vet 16 | # _/Users/bmuschko/dev/projects/go-project-automation/demos/demo-2 [_/Users/bmuschko/dev/projects/go-project-automation/demos/demo-2.test] 17 | ./main.go:7:2: age declared and not used 18 | ``` -------------------------------------------------------------------------------- /demos/demo-2/solution/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | age := 14 7 | fmt.Printf("Ben's age is %d", age) 8 | } 9 | -------------------------------------------------------------------------------- /demos/demo-3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println(foo(1)) 7 | } 8 | 9 | // [golint] main.go:12:9: if block ends with a return statement, so drop this else and outdent its block 10 | func foo(bar int) int { 11 | if bar > 0 { 12 | return 123 13 | } else { 14 | return 456 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demos/demo-3/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 3 2 | 3 | Demonstrates the purpose and usage of the tool [Golint](https://github.com/golang/lint). 4 | 5 | ## Features 6 | 7 | * Check for proper code styling and not correctness 8 | * Makes suggestions on improving code 9 | * Possible to get false positives 10 | * Typical example: `if`/`else` conditionals where `else` is unnecessary 11 | 12 | ## Usage 13 | 14 | ``` 15 | $ golint 16 | main.go:13:9: if block ends with a return statement, so drop this else and outdent its block 17 | ``` -------------------------------------------------------------------------------- /demos/demo-3/solution/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println(foo(1)) 7 | } 8 | 9 | func foo(bar int) int { 10 | if bar > 0 { 11 | return 123 12 | } 13 | return 456 14 | } 15 | -------------------------------------------------------------------------------- /demos/demo-4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func main() { 8 | f, _ := os.Open("foo.txt") 9 | // [errcheck] main.go:9:9: f.Write([]byte("Hello, world.")) 10 | f.Write([]byte("Hello, world.")) 11 | // [errcheck] main.go:10:9: f.Close() 12 | f.Close() 13 | } 14 | -------------------------------------------------------------------------------- /demos/demo-4/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 4 2 | 3 | Demonstrates the purpose and usage of the tool [errcheck](https://github.com/kisielk/errcheck). 4 | 5 | ## Features 6 | 7 | * Checks that program handles errors 8 | * Typical example: file handling functions that return the file and/or and error 9 | 10 | ## Usage 11 | 12 | ``` 13 | $ errcheck 14 | main.go:10:9: f.Write([]byte("Hello, world.")) 15 | main.go:12:9: f.Close() 16 | ``` -------------------------------------------------------------------------------- /demos/demo-4/solution/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func main() { 8 | f, err := os.Open("foo.txt") 9 | if err != nil { 10 | panic(err) 11 | } 12 | _, err = f.Write([]byte("Hello, world.")) 13 | if err != nil { 14 | panic(err) 15 | } 16 | defer func() { 17 | if err := f.Close(); err != nil { 18 | panic(err) 19 | } 20 | }() 21 | } 22 | -------------------------------------------------------------------------------- /demos/demo-5/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // [gocyclo] 4 main main main.go:5:1 6 | func main() { 7 | x := 1 8 | y := 2 9 | z := 2 10 | 11 | if x == 1 { 12 | if x > y { 13 | y = x 14 | } else { 15 | if z > y { 16 | z = x 17 | } 18 | } 19 | } else { 20 | y = z 21 | } 22 | 23 | fmt.Printf("x: %d, y: %d, z: %d", x, y, z) 24 | } 25 | -------------------------------------------------------------------------------- /demos/demo-5/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 5 2 | 3 | Demonstrates the purpose and usage of the tool [gocyclo](https://github.com/fzipp/gocyclo). 4 | 5 | ## Features 6 | 7 | * Calculates cyclomatic complexities of functions 8 | * 1 is the base complexity of a function 9 | * +1 for each 'if', 'for', 'case', '&&' or '||' 10 | * Can provide set acceptable boundaries 11 | * Typical example: `if`/`else if`/`else` branches 12 | 13 | ## Usage 14 | 15 | ``` 16 | $ gocyclo . 17 | 4 main main main.go:6:1 18 | ``` -------------------------------------------------------------------------------- /demos/demo-6/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 6 2 | 3 | Demonstrates the purpose and usage of [Go report card](https://goreportcard.com/). 4 | 5 | ## Usage of web application 6 | 7 | * Open Go report card 8 | * Enter `github.com/bmuschko/lets-gopher-exercise` into input field 9 | * Click "Generate Report" button 10 | 11 | ## Usage of CLI application 12 | 13 | ``` 14 | $ go get -u github.com/gojp/goreportcard/cmd/goreportcard-cli 15 | $ cd $GOPATH/src/github.com/bmuschko/lets-gopher-exercise 16 | $ goreportcard-cli 17 | Grade: A+ (92.4%) 18 | Files: 681 19 | Issues: 81 20 | gofmt: 99% 21 | go_vet: 99% 22 | golint: 98% 23 | gocyclo: 89% 24 | ineffassign: 0% 25 | license: 100% 26 | misspell: 0% 27 | ``` -------------------------------------------------------------------------------- /demos/demo-7/.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | goos: 5 | - linux 6 | - darwin 7 | - windows 8 | - netbsd 9 | - openbsd 10 | - freebsd 11 | goarch: 12 | - 386 13 | - amd64 14 | - arm 15 | - arm64 16 | ldflags: -s -w -X main.version={{.Version}} 17 | archive: 18 | name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm}}v{{ .Arm }}{{ end }}' 19 | format_overrides: 20 | - goos: windows 21 | format: zip 22 | files: 23 | - LICENSE 24 | checksum: 25 | name_template: '{{ .ProjectName }}_checksums.txt' 26 | snapshot: 27 | name_template: "{{ .Tag }}-next" 28 | changelog: 29 | sort: asc 30 | filters: 31 | exclude: 32 | - '^docs:' 33 | - '^test:' 34 | release: 35 | disable: true 36 | artifactories: 37 | - name: production 38 | target: http://localhost:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/ 39 | username: admin -------------------------------------------------------------------------------- /demos/demo-7/images/artifactory-repo-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmuschko/go-project-automation/5384c29d7547bb6381c9714545a749237255898a/demos/demo-7/images/artifactory-repo-browser.png -------------------------------------------------------------------------------- /demos/demo-7/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 7 2 | 3 | Demonstrates building and publishing binaries to Artifactory with GoReleaser. 4 | 5 | 1. Download open source version of Artifactory at https://jfrog.com/open-source/. 6 | 2. Start Artifactory with via `/artifactory-oss-6.6.1/bin/artifactory.sh`. 7 | 3. (Optional) Set environment variable `ARTIFACTORY_PRODUCTION_USERNAME`: `export ARTIFACTORY_PRODUCTION_USERNAME=admin` 8 | 4. Set environment variable `ARTIFACTORY_PRODUCTION_SECRET`: `export ARTIFACTORY_PRODUCTION_SECRET=admin` 9 | 5. Add the [GoReleaser configuration file](./.goreleaser.yml) to the root directory of the example project. 10 | 6. Tag the current release (e.g. with version 0.4) by running the command `git tag -a v0.4 -m "Version 0.4"`. 11 | 7. Publish the binary files with the command `goreleaser release`. You may have to delete existing binaries if the `dist` directory already exists. Use the command line option `--skip-validate` if your local repository checkout still has uncommitted changes. 12 | 13 | ``` 14 | $ goreleaser release --skip-validate 15 | 16 | • releasing using goreleaser 0.95.0... 17 | • loading config file file=.goreleaser.yml 18 | • RUNNING BEFORE HOOKS 19 | • GETTING AND VALIDATING GIT STATE 20 | • releasing v0.4, commit 5a4f318aecd575ea184903c6386c7d47bd4c6c4e 21 | • skipped reason=validation is disabled 22 | • SETTING DEFAULTS 23 | • SNAPSHOTING 24 | • skipped reason=not a snapshot 25 | • CHECKING ./DIST 26 | • WRITING EFFECTIVE CONFIG FILE 27 | • writing config=dist/config.yaml 28 | • GENERATING CHANGELOG 29 | • writing changelog=dist/CHANGELOG.md 30 | • LOADING ENVIRONMENT VARIABLES 31 | • skipped reason=release pipe is disabled 32 | • BUILDING BINARIES 33 | • building binary=dist/linux_386/lets-gopher-exercise 34 | • building binary=dist/linux_amd64/lets-gopher-exercise 35 | • building binary=dist/windows_amd64/lets-gopher-exercise.exe 36 | • building binary=dist/freebsd_arm_6/lets-gopher-exercise 37 | • building binary=dist/netbsd_386/lets-gopher-exercise 38 | • building binary=dist/openbsd_386/lets-gopher-exercise 39 | • building binary=dist/netbsd_amd64/lets-gopher-exercise 40 | • building binary=dist/netbsd_arm_6/lets-gopher-exercise 41 | • building binary=dist/openbsd_amd64/lets-gopher-exercise 42 | • building binary=dist/freebsd_386/lets-gopher-exercise 43 | • building binary=dist/darwin_386/lets-gopher-exercise 44 | • building binary=dist/linux_arm_6/lets-gopher-exercise 45 | • building binary=dist/linux_arm64/lets-gopher-exercise 46 | • building binary=dist/freebsd_amd64/lets-gopher-exercise 47 | • building binary=dist/darwin_amd64/lets-gopher-exercise 48 | • building binary=dist/windows_386/lets-gopher-exercise.exe 49 | • building binary=dist/openbsd_arm_6/lets-gopher-exercise 50 | • ARCHIVES 51 | • creating archive=dist/lets-gopher-exercise-0.4-openbsd-386.tar.gz 52 | • creating archive=dist/lets-gopher-exercise-0.4-linux-arm64.tar.gz 53 | • creating archive=dist/lets-gopher-exercise-0.4-darwin-amd64.tar.gz 54 | • creating archive=dist/lets-gopher-exercise-0.4-netbsd-armv6.tar.gz 55 | • creating archive=dist/lets-gopher-exercise-0.4-freebsd-amd64.tar.gz 56 | • creating archive=dist/lets-gopher-exercise-0.4-freebsd-armv6.tar.gz 57 | • creating archive=dist/lets-gopher-exercise-0.4-windows-amd64.zip 58 | • creating archive=dist/lets-gopher-exercise-0.4-netbsd-386.tar.gz 59 | • creating archive=dist/lets-gopher-exercise-0.4-windows-386.zip 60 | • creating archive=dist/lets-gopher-exercise-0.4-netbsd-amd64.tar.gz 61 | • creating archive=dist/lets-gopher-exercise-0.4-openbsd-amd64.tar.gz 62 | • creating archive=dist/lets-gopher-exercise-0.4-linux-armv6.tar.gz 63 | • creating archive=dist/lets-gopher-exercise-0.4-linux-386.tar.gz 64 | • creating archive=dist/lets-gopher-exercise-0.4-freebsd-386.tar.gz 65 | • creating archive=dist/lets-gopher-exercise-0.4-openbsd-armv6.tar.gz 66 | • creating archive=dist/lets-gopher-exercise-0.4-linux-amd64.tar.gz 67 | • creating archive=dist/lets-gopher-exercise-0.4-darwin-386.tar.gz 68 | • LINUX PACKAGES WITH NFPM 69 | • skipped reason=no output formats configured 70 | • SNAPCRAFT PACKAGES 71 | • skipped reason=no summary nor description were provided 72 | • CALCULATING CHECKSUMS 73 | • checksumming file=lets-gopher-exercise-0.4-windows-amd64.zip 74 | • checksumming file=lets-gopher-exercise-0.4-windows-386.zip 75 | • checksumming file=lets-gopher-exercise-0.4-linux-386.tar.gz 76 | • checksumming file=lets-gopher-exercise-0.4-darwin-amd64.tar.gz 77 | • checksumming file=lets-gopher-exercise-0.4-netbsd-armv6.tar.gz 78 | • checksumming file=lets-gopher-exercise-0.4-openbsd-armv6.tar.gz 79 | • checksumming file=lets-gopher-exercise-0.4-netbsd-amd64.tar.gz 80 | • checksumming file=lets-gopher-exercise-0.4-openbsd-386.tar.gz 81 | • checksumming file=lets-gopher-exercise-0.4-freebsd-armv6.tar.gz 82 | • checksumming file=lets-gopher-exercise-0.4-openbsd-amd64.tar.gz 83 | • checksumming file=lets-gopher-exercise-0.4-netbsd-386.tar.gz 84 | • checksumming file=lets-gopher-exercise-0.4-linux-amd64.tar.gz 85 | • checksumming file=lets-gopher-exercise-0.4-linux-armv6.tar.gz 86 | • checksumming file=lets-gopher-exercise-0.4-linux-arm64.tar.gz 87 | • checksumming file=lets-gopher-exercise-0.4-freebsd-386.tar.gz 88 | • checksumming file=lets-gopher-exercise-0.4-freebsd-amd64.tar.gz 89 | • checksumming file=lets-gopher-exercise-0.4-darwin-386.tar.gz 90 | • SIGNING ARTIFACTS 91 | • skipped reason=artifact signing is disabled 92 | • DOCKER IMAGES 93 | • skipped reason=docker section is not configured 94 | • PUBLISHING 95 | • S3 96 | • skipped reason=s3 section is not configured 97 | • releasing with HTTP PUT 98 | • skipped reason=put section is not configured 99 | • Artifactory 100 | • skipped reason=artifactory section 'production' is not configured properly (missing ARTIFACTORY_PRODUCTION_SECRET environment variable) 101 | • Docker images 102 | • Snapcraft Packages 103 | • GitHub Releases 104 | • skipped reason=release pipe is disabled 105 | • homebrew tap formula 106 | • skipped reason=brew section is not configured 107 | • scoop manifest 108 | • skipped reason=scoop section is not configured 109 | • release succeeded after 11.20s 110 | ``` 111 | 112 | 8. Verify the release on Artifactory by opening a browser with the URL `http://localhost:8081/artifactory/webapp/#/artifacts/browse/tree/General/example-repo-local/lets-gopher-exercise/0.4`. Open the folder to see all binary files. 113 | 114 | ![Artifactory repository browser](./images/artifactory-repo-browser.png) -------------------------------------------------------------------------------- /demos/demo-8/dep/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | 6 | install: true 7 | 8 | cache: 9 | directories: 10 | - $GOPATH/pkg/dep 11 | 12 | jobs: 13 | include: 14 | - stage: "Compile" 15 | name: "Compile Packages and Dependencies" 16 | before_script: 17 | - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 18 | - dep ensure 19 | script: go build 20 | - stage: "Tests" 21 | name: "Unit Tests" 22 | script: go test ./... -coverprofile=coverage.txt -covermode=count 23 | after_success: 24 | - bash <(curl -s https://codecov.io/bash) 25 | - stage: "Code Quality" 26 | name: "Code Quality Analysis" 27 | script: golangci-lint run 28 | - stage: "Release" 29 | name: "Release Binaries" 30 | script: skip 31 | deploy: 32 | - provider: script 33 | skip_cleanup: true 34 | before_script: 35 | - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.21.0 36 | script: curl -sL https://git.io/goreleaser | bash 37 | on: 38 | tags: true 39 | condition: $TRAVIS_OS_NAME = linux -------------------------------------------------------------------------------- /demos/demo-8/mod/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | 6 | env: 7 | - GO111MODULE=on 8 | 9 | install: true 10 | 11 | cache: 12 | directories: 13 | - $GOPATH/pkg/mod 14 | 15 | jobs: 16 | include: 17 | - stage: "Compile" 18 | name: "Compile Packages and Dependencies" 19 | script: go build 20 | - stage: "Tests" 21 | name: "Unit Tests" 22 | script: go test ./... -coverprofile=coverage.txt -covermode=count 23 | after_success: 24 | - bash <(curl -s https://codecov.io/bash) 25 | - stage: "Code Quality" 26 | name: "Code Quality Analysis" 27 | before_script: 28 | - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.21.0 29 | script: golangci-lint run 30 | - stage: "Release" 31 | name: "Release Binaries" 32 | script: skip 33 | deploy: 34 | - provider: script 35 | skip_cleanup: true 36 | script: curl -sL https://git.io/goreleaser | bash 37 | on: 38 | tags: true 39 | condition: $TRAVIS_OS_NAME = linux -------------------------------------------------------------------------------- /demos/demo-8/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 8 2 | 3 | Demonstrates how to build a pipeline with Travis CI. 4 | 5 | The provided `.travis.yml` files contain an example Travis CI pipeline definition. They use `golangci-lint` for static code analysis and `GoReleaser` for publishing the binary artifacts to GitHub releases. 6 | 7 | ## Samples 8 | 9 | You can find pipeline definitions for `dep` and Go modules: 10 | 11 | * Using [dep](./dep/.travis.yml) 12 | * Using [Go modules](./mod/.travis.yml) 13 | 14 | ## Pipeline steps 15 | 16 | Each pipeline is comprised of multiple build steps: 17 | 18 | 1. Compile packages and dependencies. 19 | 2. Run unit tests and publish code coverage result to Codecov. 20 | 3. Run code quality analysis using `golangci-lint`. 21 | 4. Build and release the binaries to GitHub if the current commit points to Git tag. -------------------------------------------------------------------------------- /demos/demo-9/dep/Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | def goHome = tool('go-1.13') 3 | 4 | ws("${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_ID}/src/github.com/bmuschko/lets-gopher-exercise") { 5 | withEnv(["GOROOT=${goHome}", "GOPATH=${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_ID}/", "PATH+GO=${goHome}/bin"]) { 6 | stage('Checkout') { 7 | git 'https://github.com/bmuschko/lets-gopher-exercise.git' 8 | } 9 | stage('Prepare') { 10 | sh 'go version' 11 | sh "mkdir ${GOPATH}/bin" 12 | sh 'curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh' 13 | sh 'dep ensure' 14 | } 15 | stage('Compile') { 16 | sh 'go build' 17 | } 18 | stage('Test') { 19 | sh 'go test ./... -coverprofile=coverage.txt -covermode=count' 20 | withCredentials([string(credentialsId: 'codecov_token', variable: 'CODECOV_TOKEN')]) { 21 | sh "curl -s https://codecov.io/bash | bash -s - -t $CODECOV_TOKEN" 22 | } 23 | } 24 | stage('Code Analysis') { 25 | sh 'curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.21.0' 26 | sh 'golangci-lint run' 27 | } 28 | def tag = sh(returnStdout: true, script: "git tag --contains | head -1").trim() 29 | if (tag) { 30 | stage('Release') { 31 | withCredentials([string(credentialsId: 'github_token', variable: 'GITHUB_TOKEN')]) { 32 | sh 'curl -sL https://git.io/goreleaser | bash' 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /demos/demo-9/mod/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | tools { 4 | go 'go-1.13' 5 | } 6 | environment { 7 | GO111MODULE = 'on' 8 | } 9 | stages { 10 | stage('Compile') { 11 | steps { 12 | sh 'go build' 13 | } 14 | } 15 | stage('Test') { 16 | environment { 17 | CODECOV_TOKEN = credentials('codecov_token') 18 | } 19 | steps { 20 | sh 'go test ./... -coverprofile=coverage.txt' 21 | sh "curl -s https://codecov.io/bash | bash -s -" 22 | } 23 | } 24 | stage('Code Analysis') { 25 | steps { 26 | sh 'curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.21.0' 27 | sh 'golangci-lint run' 28 | } 29 | } 30 | stage('Release') { 31 | when { 32 | buildingTag() 33 | } 34 | environment { 35 | GITHUB_TOKEN = credentials('github_token') 36 | } 37 | steps { 38 | sh 'curl -sL https://git.io/goreleaser | bash' 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /demos/demo-9/notes.md: -------------------------------------------------------------------------------- 1 | # Demo 9 2 | 3 | Demonstrates how to build a pipeline with Jenkins. 4 | 5 | Jenkins can automatically bootstrap the Go runtime with the help pf the [Go plugin](https://wiki.jenkins.io/display/JENKINS/Go+Plugin). It automatically installs a pre-defined version of Go and exports the `GOROOT` and `GOPATH` environment variable. 6 | 7 | ## Samples 8 | 9 | You can find pipeline definitions for `dep` and Go modules: 10 | 11 | * Using [dep](./dep/Jenkinsfile): At the moment only [scripted pipelines](https://jenkins.io/doc/book/pipeline/syntax/#scripted-pipeline) are supported. For more information see [JENKINS-55630](https://issues.jenkins-ci.org/browse/JENKINS-55630). 12 | * Using [Go modules](./mod/Jenkinsfile): This scenario does not require the checked out project to live in `$GOPATH/src`. For that reason a [declarative pipeline](https://jenkins.io/doc/book/pipeline/syntax/#declarative-pipeline) definition can be used which makes the setup much easier. 13 | 14 | ## Pipeline steps 15 | 16 | The pipeline is comprised of multiple build steps: 17 | 18 | 1. Compile packages and dependencies. 19 | 2. Run unit tests and publish code coverage result to Codecov. 20 | 3. Run code quality analysis using `golangci-lint`. 21 | 4. Build and release the binaries to GitHub if the current commit points to Git tag. 22 | 23 | ## Managing the environment 24 | 25 | The pipeline references environment variables, credentials and tools that need to be set up manually. 26 | 27 | 1. The Go version needs to be configured under _Manage Jenkins > Global Tool Configuration > Go_. 28 | 1. The environment variable `CODECOV_TOKEN` needs to provide the Codecov token. Environment variables can be configured under _Manage Jenkins > Configure System > Environment variables_. 29 | 2. The credential `GITHUB_TOKEN` needs to provide a valid GitHub token. Credentials can be configured under _Manage Jenkins > Configure Credentials_. -------------------------------------------------------------------------------- /exercises/exercise-1/instructions.md: -------------------------------------------------------------------------------- 1 | # Exercise 1 2 | 3 | In this exercise, you will learn how to use code analysis tools to improve the code and find potential errors. 4 | 5 | ## Using an individual code analysis tool 6 | 7 | 1. Install the tool [`errcheck`](github.com/kisielk/errcheck 8 | ) using the command `go get -u github.com/kisielk/errcheck`. 9 | 2. Run the tool on the project. The tool should report multiple errors. Fix the code based on the indicated file and line number. Tip: You can use `utils.checkIfError(error)` for handling errors. 10 | 3. Ensure that the `errcheck` stops reporting the issues by re-running the command. 11 | 12 | ## Using a linter 13 | 14 | 1. Install the tool `golangci-lint`. You can find the installation instructions on the [web page](https://github.com/golangci/golangci-lint#install). Ensure that the tool is installed properly by running `golangci-lint help`. 15 | 2. Run the tool on the project with the command `golangci-lint run`. The tool should report multiple errors. Fix the code based on the suggestions. 16 | 3. Ensure that the tool stops reporting the issues after re-running it. 17 | 4. (Optional) Try out integrating and running the tool from VSCode or GoLand if you have one of them installed on your machine. -------------------------------------------------------------------------------- /exercises/exercise-1/solution/cmd/install.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/bmuschko/lets-gopher-exercise/remote" 8 | "github.com/bmuschko/lets-gopher-exercise/templ" 9 | "github.com/bmuschko/lets-gopher-exercise/utils" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var repoUrl string 14 | 15 | var installCmd = &cobra.Command{ 16 | Use: "install", 17 | Short: "installs a template from a URL", 18 | Run: installTemplate, 19 | } 20 | 21 | func init() { 22 | rootCmd.AddCommand(installCmd) 23 | installCmd.PersistentFlags().StringVar(&repoUrl, "url", "", "template URL") 24 | err := installCmd.MarkFlagRequired("url") 25 | utils.CheckIfError(err) 26 | } 27 | 28 | func installTemplate(cmd *cobra.Command, args []string) { 29 | err := utils.CreateDir(templ.TemplateDir) 30 | utils.CheckIfError(err) 31 | install(repoUrl) 32 | } 33 | 34 | func install(repoUrl string) { 35 | var repo remote.Repository 36 | 37 | if strings.HasSuffix(repoUrl, ".git") { 38 | repo = &remote.GitRepo{RepoUrl: repoUrl, TargetPath: templ.TemplateDir} 39 | } else { 40 | err := errors.New("Currently templates can only be installed from a Git repository") 41 | utils.CheckIfError(err) 42 | } 43 | 44 | repo.Install() 45 | } 46 | -------------------------------------------------------------------------------- /exercises/exercise-1/solution/solution.md: -------------------------------------------------------------------------------- 1 | # Solution 2 | 3 | ## Using an individual code analysis tool 4 | 5 | To check all packages beneath the current directory, run `errcheck ./...`. You should see the following error warnings: 6 | 7 | ``` 8 | $ errcheck ./... 9 | cmd/install.go:24:29: installCmd.MarkFlagRequired("url") 10 | cmd/install.go:39:13: errors.New("Currently templates can only be installed from a Git repository") 11 | templ/gen.go:22:15: utils.CopyDir(templatePath, targetPath) 12 | utils/file.go:30:19: defer srcfd.Close() 13 | utils/file.go:35:19: defer dstfd.Close() 14 | ``` 15 | 16 | You can fix the issues by properly handling the errors. 17 | 18 | ## Using a linter 19 | 20 | To check all packages beneath the current directory, run `golangci-lint run`. You should see the following error warnings: 21 | 22 | ``` 23 | $ golangci-lint run 24 | cmd/install.go:38:11: `remote.GitRepo` composite literal uses unkeyed fields (govet) 25 | repo = &remote.GitRepo{repoUrl, templ.TemplateDir} 26 | ``` 27 | 28 | You should get a similar result if you just run with the `go vet` tool. 29 | 30 | ``` 31 | $ go vet ./... 32 | # github.com/bmuschko/lets-gopher-exercise/cmd 33 | cmd/install.go:37:11: github.com/bmuschko/lets-gopher-exercise/remote.GitRepo composite literal uses unkeyed fields 34 | cmd/install.go:39:13: result of errors.New call not used 35 | ``` 36 | 37 | You can fix the issue by assigning the variables to the fields in `remote.GitRepo`. 38 | 39 | ```go 40 | repo = &remote.GitRepo{RepoUrl: repoUrl, TargetPath: templ.TemplateDir} 41 | ``` -------------------------------------------------------------------------------- /exercises/exercise-1/solution/templ/gen.go: -------------------------------------------------------------------------------- 1 | package templ 2 | 3 | import ( 4 | "fmt" 5 | "go/build" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/bmuschko/lets-gopher-exercise/utils" 10 | ) 11 | 12 | func GenerateProject(templatePath string, goHomeBasePath string) { 13 | srcGoPath := filepath.Join(build.Default.GOPATH, "src") 14 | targetPath := filepath.Join(srcGoPath, goHomeBasePath) 15 | 16 | if _, err := os.Stat(targetPath); !os.IsNotExist(err) { 17 | fmt.Printf("The target directory %q already exists\n", targetPath) 18 | os.Exit(1) 19 | } 20 | 21 | fmt.Printf("Generating project in %q\n", targetPath) 22 | err := utils.CopyDir(templatePath, targetPath) 23 | utils.CheckIfError(err) 24 | } 25 | -------------------------------------------------------------------------------- /exercises/exercise-1/solution/utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | ) 10 | 11 | func CreateDir(dir string) error { 12 | if _, err := os.Stat(dir); os.IsNotExist(err) { 13 | if os.MkdirAll(dir, 0755) != nil { 14 | return err 15 | } 16 | } 17 | 18 | return nil 19 | } 20 | 21 | func CopyFile(src, dst string) error { 22 | var err error 23 | var srcfd *os.File 24 | var dstfd *os.File 25 | var srcinfo os.FileInfo 26 | 27 | if srcfd, err = os.Open(src); err != nil { 28 | return err 29 | } 30 | defer func() { 31 | err := srcfd.Close() 32 | CheckIfError(err) 33 | }() 34 | 35 | if dstfd, err = os.Create(dst); err != nil { 36 | return err 37 | } 38 | defer func() { 39 | err := dstfd.Close() 40 | CheckIfError(err) 41 | }() 42 | 43 | if _, err = io.Copy(dstfd, srcfd); err != nil { 44 | return err 45 | } 46 | if srcinfo, err = os.Stat(src); err != nil { 47 | return err 48 | } 49 | 50 | return os.Chmod(dst, srcinfo.Mode()) 51 | } 52 | 53 | func CopyDir(src string, dst string) error { 54 | var err error 55 | var fds []os.FileInfo 56 | var srcinfo os.FileInfo 57 | 58 | if srcinfo, err = os.Stat(src); err != nil { 59 | return err 60 | } 61 | 62 | if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil { 63 | return err 64 | } 65 | 66 | if fds, err = ioutil.ReadDir(src); err != nil { 67 | return err 68 | } 69 | for _, fd := range fds { 70 | srcfp := path.Join(src, fd.Name()) 71 | dstfp := path.Join(dst, fd.Name()) 72 | 73 | if fd.IsDir() { 74 | if err = CopyDir(srcfp, dstfp); err != nil { 75 | fmt.Println(err) 76 | } 77 | } else { 78 | if err = CopyFile(srcfp, dstfp); err != nil { 79 | fmt.Println(err) 80 | } 81 | } 82 | } 83 | 84 | return nil 85 | } -------------------------------------------------------------------------------- /exercises/exercise-2/instructions.md: -------------------------------------------------------------------------------- 1 | # Exercise 2 2 | 3 | In this exercise, you will declare and resolve a dependency using Go Modules. 4 | 5 | 1. Make sure the environment variable `GO111MODULES=on` has been set. You can find additional information in Go Modules in the [Wiki](https://github.com/golang/go/wiki/Modules). 6 | 2. Add a dependency on the package [github.com/pkg/errors](https://github.com/pkg/errors) using the `go get` command. Inspect the `go.mod` file and find the dependency. 7 | 3. Improve `utils/error.go` by replacing the code with functions from the external `errors` package. 8 | 4. Run the `go mod tidy` command and inspect the `go.mod` file. Do you notice a difference? 9 | 5. Run the `go mod graph` command and find the `errors` package in the rendered list of dependencies. Check the resolved version value in the console output. 10 | 6. Commit the changed files to SCM and push them to the remote repository. -------------------------------------------------------------------------------- /exercises/exercise-2/solution/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bmuschko/lets-gopher-exercise 2 | 3 | require ( 4 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect 5 | github.com/emirpasic/gods v1.12.0 // indirect 6 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect 7 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 9 | github.com/mattn/go-colorable v0.0.9 // indirect 10 | github.com/mattn/go-isatty v0.0.4 // indirect 11 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 12 | github.com/mitchellh/go-homedir v1.0.0 13 | github.com/pkg/errors v0.8.1 14 | github.com/spf13/cobra v0.0.3 15 | github.com/spf13/pflag v1.0.3 // indirect 16 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect 17 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect 18 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339 // indirect 19 | gopkg.in/AlecAivazis/survey.v1 v1.7.1 20 | gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect 21 | gopkg.in/src-d/go-git.v4 v4.8.1 22 | gopkg.in/yaml.v2 v2.2.2 23 | ) 24 | 25 | go 1.13 -------------------------------------------------------------------------------- /exercises/exercise-2/solution/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b h1:sSQK05nvxs4UkgCJaxihteu+r+6ela3dNMm7NVmsS3c= 2 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= 3 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= 4 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 5 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 6 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 10 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 11 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 12 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= 13 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 14 | github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= 15 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 16 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 17 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 18 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c h1:kp3AxgXgDOmIJFR7bIwqFhwJ2qWar8tEQSE5XXhCfVk= 19 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= 20 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 21 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 22 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 23 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 24 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 25 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 26 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 27 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= 28 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 29 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 30 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 31 | github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= 32 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 33 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 34 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 35 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 36 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 37 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 38 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 39 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= 40 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 41 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 42 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 43 | github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= 44 | github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= 45 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 46 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 47 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 48 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 49 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 52 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 53 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 54 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 55 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 56 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 57 | github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= 58 | github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 59 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 60 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 61 | github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= 62 | github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= 63 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 64 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M= 65 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 66 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 67 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM= 68 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 69 | golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 70 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0= 71 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 72 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 73 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 74 | gopkg.in/AlecAivazis/survey.v1 v1.7.1 h1:mzQIVyOPSXJaQWi1m6AFCjrCEPIwQBSOn48Ri8ZpzAg= 75 | gopkg.in/AlecAivazis/survey.v1 v1.7.1/go.mod h1:2Ehl7OqkBl3Xb8VmC4oFW2bItAhnUfzIjrOzwRxCrOU= 76 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 77 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 78 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 79 | gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= 80 | gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= 81 | gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= 82 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= 83 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= 84 | gopkg.in/src-d/go-git.v4 v4.8.1 h1:aAyBmkdE1QUUEHcP4YFCGKmsMQRAuRmUcPEQR7lOAa0= 85 | gopkg.in/src-d/go-git.v4 v4.8.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= 86 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 87 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 88 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 89 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -------------------------------------------------------------------------------- /exercises/exercise-2/solution/solution.md: -------------------------------------------------------------------------------- 1 | # Solution 2 | 3 | 1. You can check if the environment variable is set by using the `echo` command e.g. `echo $GO111MODULES` (Linux, MacOS) or `echo %GO111MODULES%` (Windows). 4 | 2. Run the command `go get github.com/pkg/errors` to resolve the package. 5 | 6 | ``` 7 | $ go get github.com/pkg/errors 8 | go: finding github.com/pkg/errors v0.8.1 9 | go: downloading github.com/pkg/errors v0.8.1 10 | go: extracting github.com/pkg/errors v0.8.1 11 | ``` 12 | 13 | The contents of `go.mod` look as follows. You can see that the `errors` package has been marked as indirect. The indirect comment indicates a dependency is not used directly by this module, only indirectly by other module dependencies. 14 | 15 | ``` 16 | module github.com/bmuschko/lets-gopher-exercise 17 | 18 | require ( 19 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect 20 | github.com/emirpasic/gods v1.12.0 // indirect 21 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect 22 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 23 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 24 | github.com/mattn/go-colorable v0.0.9 // indirect 25 | github.com/mattn/go-isatty v0.0.4 // indirect 26 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 27 | github.com/mitchellh/go-homedir v1.0.0 28 | github.com/pkg/errors v0.8.1 // indirect <------ 29 | github.com/spf13/cobra v0.0.3 30 | github.com/spf13/pflag v1.0.3 // indirect 31 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect 32 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect 33 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339 // indirect 34 | gopkg.in/AlecAivazis/survey.v1 v1.7.1 35 | gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect 36 | gopkg.in/src-d/go-git.v4 v4.8.1 37 | gopkg.in/yaml.v2 v2.2.2 38 | ) 39 | 40 | go 1.13 41 | ``` 42 | 43 | 3. On line 15 of the file `utils/error.go` you can use the instruction `errors.WithMessage(err, "error")` from the package `github.com/pkg/errors` instead of `fmt.Sprintf("error: %s", err)`. Make sure to import the package. 44 | 4. Now that we are actually using the `error` package in our code, running `go mod tidy` will fix the dependency by removing the `// indirect` comment in `go.mod`. 45 | 46 | ``` 47 | module github.com/bmuschko/lets-gopher-exercise 48 | 49 | require ( 50 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect 51 | github.com/emirpasic/gods v1.12.0 // indirect 52 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect 53 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 54 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 55 | github.com/mattn/go-colorable v0.0.9 // indirect 56 | github.com/mattn/go-isatty v0.0.4 // indirect 57 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 58 | github.com/mitchellh/go-homedir v1.0.0 59 | github.com/pkg/errors v0.8.1 <------ 60 | github.com/spf13/cobra v0.0.3 61 | github.com/spf13/pflag v1.0.3 // indirect 62 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect 63 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect 64 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339 // indirect 65 | gopkg.in/AlecAivazis/survey.v1 v1.7.1 66 | gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect 67 | gopkg.in/src-d/go-git.v4 v4.8.1 68 | gopkg.in/yaml.v2 v2.2.2 69 | ) 70 | 71 | go 1.13 72 | ``` 73 | 74 | 5. Running the command `go mod graph` shows that the version `v0.8.1` was selected by our package. We can also see that an earlier version of package was requested as transitive dependency by an external package. 75 | 76 | ``` 77 | $ go mod graph 78 | github.com/bmuschko/lets-gopher-exercise github.com/Netflix/go-expect@v0.0.0-20180928190340-9d1f4485533b 79 | github.com/bmuschko/lets-gopher-exercise github.com/emirpasic/gods@v1.12.0 80 | github.com/bmuschko/lets-gopher-exercise github.com/hinshun/vt10x@v0.0.0-20180809195222-d55458df857c 81 | github.com/bmuschko/lets-gopher-exercise github.com/inconshreveable/mousetrap@v1.0.0 82 | github.com/bmuschko/lets-gopher-exercise github.com/kballard/go-shellquote@v0.0.0-20180428030007-95032a82bc51 83 | github.com/bmuschko/lets-gopher-exercise github.com/mattn/go-colorable@v0.0.9 84 | github.com/bmuschko/lets-gopher-exercise github.com/mattn/go-isatty@v0.0.4 85 | github.com/bmuschko/lets-gopher-exercise github.com/mgutz/ansi@v0.0.0-20170206155736-9520e82c474b 86 | github.com/bmuschko/lets-gopher-exercise github.com/mitchellh/go-homedir@v1.0.0 87 | github.com/bmuschko/lets-gopher-exercise github.com/pkg/errors@v0.8.1 <------ 88 | github.com/bmuschko/lets-gopher-exercise github.com/spf13/cobra@v0.0.3 89 | github.com/bmuschko/lets-gopher-exercise github.com/spf13/pflag@v1.0.3 90 | github.com/bmuschko/lets-gopher-exercise golang.org/x/crypto@v0.0.0-20190103213133-ff983b9c42bc 91 | github.com/bmuschko/lets-gopher-exercise golang.org/x/net@v0.0.0-20190110200230-915654e7eabc 92 | github.com/bmuschko/lets-gopher-exercise golang.org/x/sys@v0.0.0-20190116161447-11f53e031339 93 | github.com/bmuschko/lets-gopher-exercise gopkg.in/AlecAivazis/survey.v1@v1.7.1 94 | github.com/bmuschko/lets-gopher-exercise gopkg.in/src-d/go-billy.v4@v4.3.0 95 | github.com/bmuschko/lets-gopher-exercise gopkg.in/src-d/go-git.v4@v4.8.1 96 | github.com/bmuschko/lets-gopher-exercise gopkg.in/yaml.v2@v2.2.2 97 | gopkg.in/src-d/go-billy.v4@v4.3.0 github.com/kr/pretty@v0.1.0 98 | gopkg.in/src-d/go-billy.v4@v4.3.0 golang.org/x/sys@v0.0.0-20180903190138-2b024373dcd9 99 | gopkg.in/src-d/go-billy.v4@v4.3.0 gopkg.in/check.v1@v1.0.0-20180628173108-788fd7840127 100 | gopkg.in/yaml.v2@v2.2.2 gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405 101 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/alcortesm/tgz@v0.0.0-20161220082320-9c5fe88206d7 102 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/anmitsu/go-shlex@v0.0.0-20161002113705-648efa622239 103 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/davecgh/go-spew@v1.1.1 104 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/emirpasic/gods@v1.9.0 105 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/flynn/go-shlex@v0.0.0-20150515145356-3f9db97f8568 106 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/gliderlabs/ssh@v0.1.1 107 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/google/go-cmp@v0.2.0 108 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/jbenet/go-context@v0.0.0-20150711004518-d14ea06fba99 109 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/jessevdk/go-flags@v1.4.0 110 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/kevinburke/ssh_config@v0.0.0-20180830205328-81db2a75821e 111 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/mitchellh/go-homedir@v1.0.0 112 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/pelletier/go-buffruneio@v0.2.0 113 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/pkg/errors@v0.8.0 <------ 114 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/pmezard/go-difflib@v1.0.0 115 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/sergi/go-diff@v1.0.0 116 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/src-d/gcfg@v1.4.0 117 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/stretchr/testify@v1.2.2 118 | gopkg.in/src-d/go-git.v4@v4.8.1 github.com/xanzy/ssh-agent@v0.2.0 119 | gopkg.in/src-d/go-git.v4@v4.8.1 golang.org/x/crypto@v0.0.0-20180904163835-0709b304e793 120 | gopkg.in/src-d/go-git.v4@v4.8.1 golang.org/x/net@v0.0.0-20180906233101-161cd47e91fd 121 | gopkg.in/src-d/go-git.v4@v4.8.1 golang.org/x/text@v0.3.0 122 | gopkg.in/src-d/go-git.v4@v4.8.1 gopkg.in/check.v1@v1.0.0-20180628173108-788fd7840127 123 | gopkg.in/src-d/go-git.v4@v4.8.1 gopkg.in/src-d/go-billy.v4@v4.2.1 124 | gopkg.in/src-d/go-git.v4@v4.8.1 gopkg.in/src-d/go-git-fixtures.v3@v3.1.1 125 | gopkg.in/src-d/go-git.v4@v4.8.1 gopkg.in/warnings.v0@v0.1.2 126 | gopkg.in/src-d/go-billy.v4@v4.2.1 github.com/kr/pretty@v0.1.0 127 | gopkg.in/src-d/go-billy.v4@v4.2.1 golang.org/x/sys@v0.0.0-20180903190138-2b024373dcd9 128 | gopkg.in/src-d/go-billy.v4@v4.2.1 gopkg.in/check.v1@v1.0.0-20180628173108-788fd7840127 129 | github.com/kr/pretty@v0.1.0 github.com/kr/text@v0.1.0 130 | github.com/kr/text@v0.1.0 github.com/kr/pty@v1.1.1 131 | ``` -------------------------------------------------------------------------------- /exercises/exercise-2/solution/utils/error.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func CheckIfError(err error) { 11 | if err == nil { 12 | return 13 | } 14 | 15 | fmt.Printf("\x1b[31;1m%s\x1b[0m\n", errors.WithMessage(err, "error")) 16 | os.Exit(1) 17 | } 18 | -------------------------------------------------------------------------------- /exercises/exercise-3/instructions.md: -------------------------------------------------------------------------------- 1 | # Exercise 3 2 | 3 | In this exercise, you will use Go's `testing` package to implement a simple test. Furthermore, you will generate code coverage metric for the whole project. 4 | 5 | ## Writing and executing tests 6 | 7 | 1. Create a new Go file named `file_test.go` in the package `utils`. 8 | 2. Write two different test cases for the function `CreateDir`. The first test case should verify that the directory is created if it didn't exist before. The second test case should verify that not recreate the directory if it already exists. Create the new directory in a temporary directory which is deleted after the test cases finish. 9 | 3. Execute the tests from the command line. 10 | 11 | ## Generating code coverage metrics 12 | 13 | 1. Run the tests and generate code coverage metric for the whole project. 14 | 2. Create the HTML report for the coverage metrics. Open the report in the browser. -------------------------------------------------------------------------------- /exercises/exercise-3/solution/images/coverage-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmuschko/go-project-automation/5384c29d7547bb6381c9714545a749237255898a/exercises/exercise-3/solution/images/coverage-report.png -------------------------------------------------------------------------------- /exercises/exercise-3/solution/solution.md: -------------------------------------------------------------------------------- 1 | # Solution 2 | 3 | ## Writing and executing tests 4 | 5 | 1. Create a new file `file_test.go` in the `utils` package. 6 | 2. Copy/paste the code from [file_standard_test.go](./utils/file_standard_test.go) into the file. 7 | 3. Run the command `go test ./...`. 8 | 9 | ``` 10 | $ go test ./... -v 11 | === RUN TestCreateDirForNonExistentDirectoryWithTestingPackage 12 | --- PASS: TestCreateDirForNonExistentDirectoryWithTestingPackage (0.00s) 13 | === RUN TestCreateDirForExistentDirectoryWithTestingPackage 14 | --- PASS: TestCreateDirForExistentDirectoryWithTestingPackage (0.00s) 15 | PASS 16 | ok github.com/bmuschko/lets-gopher-exercise/utils 0.007s 17 | ``` 18 | 19 | ## Generating code coverage metrics 20 | 21 | 1. Run the command `go test ./... -coverprofile=coverage.txt -covermode=count`. 22 | 23 | ``` 24 | $ go test ./... -coverprofile=coverage.txt -covermode=count 25 | ok github.com/bmuschko/lets-gopher-exercise/utils 0.006s coverage: 7.3% of statements 26 | ``` 27 | 28 | 2. Run the command `go tool cover -html=coverage.txt`. A browser window will open automatically and render the HTML report. 29 | 30 | ![HTML coverage report](./images/coverage-report.png) -------------------------------------------------------------------------------- /exercises/exercise-3/solution/utils/file_standard_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | . "github.com/bmuschko/lets-gopher-exercise/utils" 10 | ) 11 | 12 | func TestCreateDirForNonExistentDirectoryWithTestingPackage(t *testing.T) { 13 | dir, err := ioutil.TempDir("", "test") 14 | if err != nil { 15 | t.Errorf("Failed to create temporary directory %s", dir) 16 | } 17 | defer os.RemoveAll(dir) 18 | 19 | newDir := filepath.Join(dir, "new") 20 | err = CreateDir(newDir) 21 | if err != nil { 22 | t.Errorf("Failed to create new directory %s", newDir) 23 | } 24 | 25 | fileInfo, err := os.Stat(newDir) 26 | if err != nil { 27 | t.Errorf("Expected directory to %s to be created", newDir) 28 | } 29 | if fileInfo.Mode().Perm() != os.FileMode(0755) { 30 | t.Errorf("Expected directory permissions 0755 but received %s", fileInfo.Mode().Perm()) 31 | } 32 | } 33 | 34 | func TestCreateDirForExistentDirectoryWithTestingPackage(t *testing.T) { 35 | dir, err := ioutil.TempDir("", "test") 36 | if err != nil { 37 | t.Errorf("Failed to create temporary directory %s", dir) 38 | } 39 | defer os.RemoveAll(dir) 40 | 41 | newDir := filepath.Join(dir, "new") 42 | err = os.Mkdir(newDir, 0755) 43 | if err != nil { 44 | t.Errorf("Failed to create new directory %s", newDir) 45 | } 46 | err = CreateDir(newDir) 47 | if err != nil { 48 | t.Errorf("Failed to create new directory %s", newDir) 49 | } 50 | 51 | fileInfo, err := os.Stat(newDir) 52 | if err != nil { 53 | t.Errorf("Expected directory to %s to be created", newDir) 54 | } 55 | if fileInfo.Mode().Perm() != os.FileMode(0755) { 56 | t.Errorf("Expected directory permissions 0755 but received %s", fileInfo.Mode().Perm()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /exercises/exercise-4/instructions.md: -------------------------------------------------------------------------------- 1 | # Exercise 4 2 | 3 | In this exercise, you will use enhance the existing test code from exercise 4. Use the package `testify` to implement the assertions. 4 | 5 | 1. Add and resolve the `github.com/stretchr/testify` package for your project. Use the latest version of `testify`. 6 | 2. Change the test file `file_test.go` in a way that uses `testify`'s assertion functions. 7 | 3. Execute the tests from the command line. 8 | 4. (Discuss) Do you find the code easier to understand and read after switching to `testify`? -------------------------------------------------------------------------------- /exercises/exercise-4/solution/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bmuschko/lets-gopher-exercise 2 | 3 | require ( 4 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b // indirect 5 | github.com/emirpasic/gods v1.12.0 // indirect 6 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect 7 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 9 | github.com/mattn/go-colorable v0.0.9 // indirect 10 | github.com/mattn/go-isatty v0.0.4 // indirect 11 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 12 | github.com/mitchellh/go-homedir v1.0.0 13 | github.com/pkg/errors v0.8.1 // indirect 14 | github.com/spf13/cobra v0.0.3 15 | github.com/spf13/pflag v1.0.3 // indirect 16 | github.com/stretchr/testify v1.4.0 17 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect 18 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect 19 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339 // indirect 20 | gopkg.in/AlecAivazis/survey.v1 v1.7.1 21 | gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect 22 | gopkg.in/src-d/go-git.v4 v4.8.1 23 | gopkg.in/yaml.v2 v2.2.2 24 | ) 25 | 26 | go 1.13 -------------------------------------------------------------------------------- /exercises/exercise-4/solution/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b h1:sSQK05nvxs4UkgCJaxihteu+r+6ela3dNMm7NVmsS3c= 2 | github.com/Netflix/go-expect v0.0.0-20180928190340-9d1f4485533b/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= 3 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= 4 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 5 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 6 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 11 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 12 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 13 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= 14 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 15 | github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= 16 | github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 17 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 18 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 19 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c h1:kp3AxgXgDOmIJFR7bIwqFhwJ2qWar8tEQSE5XXhCfVk= 20 | github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= 21 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 22 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 23 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 24 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 25 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 26 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 27 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 28 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= 29 | github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 30 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 31 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 32 | github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= 33 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 34 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 37 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 38 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 39 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 40 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= 41 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 42 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 43 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 44 | github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= 45 | github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= 46 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 47 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 48 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 49 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 53 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 54 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 55 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 56 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 57 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 58 | github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= 59 | github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= 60 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 61 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 62 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 63 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 64 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 65 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 66 | github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= 67 | github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= 68 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 69 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M= 70 | golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 71 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 72 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM= 73 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 74 | golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 75 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0= 76 | golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 77 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 78 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 79 | gopkg.in/AlecAivazis/survey.v1 v1.7.1 h1:mzQIVyOPSXJaQWi1m6AFCjrCEPIwQBSOn48Ri8ZpzAg= 80 | gopkg.in/AlecAivazis/survey.v1 v1.7.1/go.mod h1:2Ehl7OqkBl3Xb8VmC4oFW2bItAhnUfzIjrOzwRxCrOU= 81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 82 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 83 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 84 | gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= 85 | gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= 86 | gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= 87 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= 88 | gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= 89 | gopkg.in/src-d/go-git.v4 v4.8.1 h1:aAyBmkdE1QUUEHcP4YFCGKmsMQRAuRmUcPEQR7lOAa0= 90 | gopkg.in/src-d/go-git.v4 v4.8.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= 91 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 92 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 93 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 94 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -------------------------------------------------------------------------------- /exercises/exercise-4/solution/solution.md: -------------------------------------------------------------------------------- 1 | # Solution 2 | 3 | 1. On the command line, execute `go get github.com/stretchr/testify`. 4 | 5 | ``` 6 | $ go get github.com/stretchr/testify 7 | go: finding github.com/stretchr/testify v1.4.0 8 | go: downloading github.com/stretchr/testify v1.4.0 9 | go: extracting github.com/stretchr/testify v1.4.0 10 | go: downloading github.com/stretchr/objx v0.1.0 11 | go: extracting github.com/stretchr/objx v0.1.0 12 | go: finding github.com/stretchr/objx v0.1.0 13 | ``` 14 | 15 | 2. Copy/paste the code from [file_testify_test.go](./utils/file_testify_test.go) and overwrite the existing code in `file_test.go`. 16 | 3. Run the command `go test ./...`. 17 | 18 | ``` 19 | $ go test ./... -v 20 | === RUN TestCreateDirForNonExistentDirectoryWithTestingPackage 21 | --- PASS: TestCreateDirForNonExistentDirectoryWithTestingPackage (0.00s) 22 | === RUN TestCreateDirForExistentDirectoryWithTestingPackage 23 | --- PASS: TestCreateDirForExistentDirectoryWithTestingPackage (0.00s) 24 | PASS 25 | ok github.com/bmuschko/lets-gopher-exercise/utils 0.007s 26 | ``` 27 | 28 | _Note:_ The file [file_ginkgo_test.go](./utils/file_ginkgo_test.go) contains test code that uses the Ginkgo package. -------------------------------------------------------------------------------- /exercises/exercise-4/solution/utils/file_ginkgo_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | . "github.com/bmuschko/lets-gopher-exercise/utils" 5 | . "github.com/onsi/ginkgo" 6 | . "github.com/onsi/gomega" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | ) 12 | 13 | func TestFileUtils(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "File Utils Suite") 16 | } 17 | 18 | var _ = Describe("File Utilities", func() { 19 | var newDir string 20 | 21 | BeforeEach(func() { 22 | dir, err := ioutil.TempDir("", "test") 23 | Expect(err).To(BeNil(), "Failed to create temporary directory %s", dir) 24 | defer os.RemoveAll(dir) 25 | 26 | newDir = filepath.Join(dir, "new") 27 | }) 28 | 29 | Describe("Create Directory", func() { 30 | Context("that doesn't exist", func() { 31 | It("should be created", func() { 32 | err := CreateDir(newDir) 33 | Expect(err).To(BeNil(), "Failed to create new directory %s", newDir) 34 | 35 | fileInfo, err := os.Stat(newDir) 36 | Expect(err).To(BeNil(), "Expected directory to %s to be created", newDir) 37 | Expect(fileInfo.Mode().Perm()).To(Equal(os.FileMode(0755)), "Expected directory permissions 0755 but received %s", fileInfo.Mode().Perm()) 38 | }) 39 | }) 40 | 41 | Context("that does already exist", func() { 42 | It("should not be recreated", func() { 43 | err := os.MkdirAll(newDir, 0755) 44 | Expect(err).To(BeNil(), "Failed to create new directory %s", newDir) 45 | err = CreateDir(newDir) 46 | Expect(err).To(BeNil(), "Failed to create new directory %s", newDir) 47 | 48 | fileInfo, err := os.Stat(newDir) 49 | Expect(err).To(BeNil(), "Expected directory to %s to be created", newDir) 50 | Expect(fileInfo.Mode().Perm()).To(Equal(os.FileMode(0755)), "Expected directory permissions 0755 but received %s", fileInfo.Mode().Perm()) 51 | }) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /exercises/exercise-4/solution/utils/file_testify_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | . "github.com/bmuschko/lets-gopher-exercise/utils" 5 | . "github.com/stretchr/testify/assert" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "testing" 10 | ) 11 | 12 | func TestCreateDirForNonExistentDirectoryWithTestify(t *testing.T) { 13 | dir, err := ioutil.TempDir("", "test") 14 | Nil(t, err, "Failed to create temporary directory %s", dir) 15 | defer os.RemoveAll(dir) 16 | 17 | newDir := filepath.Join(dir, "new") 18 | err = CreateDir(newDir) 19 | Nil(t, err, "Failed to create new directory %s", newDir) 20 | 21 | fileInfo, err := os.Stat(newDir) 22 | Nil(t, err, "Expected directory to %s to be created", newDir) 23 | Equal(t, os.FileMode(0755), fileInfo.Mode().Perm(), "Expected directory permissions 0755 but received %s", fileInfo.Mode().Perm()) 24 | } 25 | 26 | func TestCreateDirForExistentDirectoryWithTestify(t *testing.T) { 27 | dir, err := ioutil.TempDir("", "test") 28 | Nil(t, err, "Failed to create temporary directory %s", dir) 29 | defer os.RemoveAll(dir) 30 | 31 | newDir := filepath.Join(dir, "new") 32 | err = os.Mkdir(newDir, 0755) 33 | Nil(t, err, "Failed to create new directory %s", newDir) 34 | err = CreateDir(newDir) 35 | Nil(t, err, "Failed to create new directory %s", newDir) 36 | 37 | fileInfo, err := os.Stat(newDir) 38 | Nil(t, err, "Expected directory to %s to be created", newDir) 39 | Equal(t, os.FileMode(0755), fileInfo.Mode().Perm(), "Expected directory permissions 0755 but received %s", fileInfo.Mode().Perm()) 40 | } 41 | -------------------------------------------------------------------------------- /exercises/exercise-5/instructions.md: -------------------------------------------------------------------------------- 1 | # Exercise 5 2 | 3 | In this exercise, you will learn how to build binaries for different target platforms. 4 | 5 | 1. Install Gox with the command `go get github.com/mitchellh/gox` or download the latest, pre-built binary from the [list of releases](github.com/mitchellh/gox). 6 | 2. Verify that Gox was installed properly by running `gox -h`. Gox should present you with all available CLI options. 7 | 3. Navigate to the root directory of the sample project and run the `gox` command. You should see multiple binary files. 8 | 4. Provide relevant CLI options to `gox` to only produce the binary for the appropriate platform on your machine e.g. `windows/amd64`. 9 | 5. Executing the `gox` command without any additional options does not provide a version in the file name nor does it pass the version to the linker for consumption by the program. Provide the appropriate CLI options to create binaries with the version `1.0`. Running the produced binary with the `version` option should render the appropriate version in the console output. Try to think of a way to pass the version to the linker. Make sure that the binaries are created in the subdirectory `dist`. 10 | 6. (Discuss) What if you wanted to provide the current Git commit hash as part of the version. Can you come up with a way to inject the value? 11 | 7. (Discuss) What challenges could arise for a team using Gox? Can you think of any improvements to the user experience? -------------------------------------------------------------------------------- /exercises/exercise-5/solution.md/solution.md: -------------------------------------------------------------------------------- 1 | # Solution 2 | 3 | 3. It takes quite a while to produce all binary files. The output should look similar to this: 4 | 5 | ``` 6 | $ gox 7 | Number of parallel builds: 7 8 | 9 | --> linux/mipsle: github.com/bmuschko/lets-gopher-exercise 10 | --> linux/amd64: github.com/bmuschko/lets-gopher-exercise 11 | --> windows/amd64: github.com/bmuschko/lets-gopher-exercise 12 | --> netbsd/arm: github.com/bmuschko/lets-gopher-exercise 13 | --> darwin/386: github.com/bmuschko/lets-gopher-exercise 14 | --> freebsd/386: github.com/bmuschko/lets-gopher-exercise 15 | --> darwin/amd64: github.com/bmuschko/lets-gopher-exercise 16 | --> linux/mips64: github.com/bmuschko/lets-gopher-exercise 17 | --> linux/s390x: github.com/bmuschko/lets-gopher-exercise 18 | --> linux/arm: github.com/bmuschko/lets-gopher-exercise 19 | --> linux/386: github.com/bmuschko/lets-gopher-exercise 20 | --> netbsd/386: github.com/bmuschko/lets-gopher-exercise 21 | --> freebsd/arm: github.com/bmuschko/lets-gopher-exercise 22 | --> linux/mips64le: github.com/bmuschko/lets-gopher-exercise 23 | --> netbsd/amd64: github.com/bmuschko/lets-gopher-exercise 24 | --> linux/mips: github.com/bmuschko/lets-gopher-exercise 25 | --> openbsd/386: github.com/bmuschko/lets-gopher-exercise 26 | --> freebsd/amd64: github.com/bmuschko/lets-gopher-exercise 27 | --> openbsd/amd64: github.com/bmuschko/lets-gopher-exercise 28 | --> windows/386: github.com/bmuschko/lets-gopher-exercise 29 | ``` 30 | 31 | 4. Gox provides two different options to generate a particular binary for a target platform. You can use invidual CLI options `-os` and `-arch`: `gox -os="windows" -arch="amd64"`. Alternatively, you can provide a combination of OS and architecture with the `-osarch` CLI option: `gox -osarch="windows/amd64"`. For a list of available OS and architecure values run the command `gox -osarch-list`. The output should look similar to this: 32 | 33 | ``` 34 | $ gox -osarch="windows/amd64" 35 | Number of parallel builds: 7 36 | 37 | --> windows/amd64: github.com/bmuschko/lets-gopher-exercise 38 | ``` 39 | 40 | 5. By default, Gox uses the pattern `{{.Dir}}_{{.OS}}_{{.Arch}}` for building the file name of a binary. You can set a custom output file pattern with the CLI option `-output`. To set a value for the `version` variable in `main.go`, you will have to provide a linker option: `-ldflags="-X main.version=1.0"`. The full command could look as such: `gox -osarch="darwin/amd64" -output "dist/{{.Dir}}-1.0-{{.OS}}-{{.Arch}}" -ldflags="-X main.version=1.0"`. The output should look similar to this: 41 | 42 | ``` 43 | $ gox -osarch="darwin/amd64" -output "dist/{{.Dir}}-1.0-{{.OS}}-{{.Arch}}" -ldflags="-X main.version=1.0" 44 | Number of parallel builds: 7 45 | 46 | --> darwin/amd64: github.com/bmuschko/lets-gopher-exercise 47 | ``` 48 | 49 | 6. You can inject the output of a Git command line execution as expression: `-ldflags="-X main.version=$(git rev-parse HEAD)"`. The output should look similar to this: 50 | 51 | ``` 52 | gox -osarch="darwin/amd64" -output "dist/{{.Dir}}-1.0-{{.OS}}-{{.Arch}}" -ldflags="-X main.version=$(git rev-parse HEAD)" 53 | Number of parallel builds: 7 54 | 55 | --> darwin/amd64: github.com/bmuschko/lets-gopher-exercise 56 | ``` 57 | 58 | 7. Different team members need to know and understand available CLI options. Often times you will want to use the same set of options but with different values. It would be great of Gox would support setting values in a configuration file instead of having to provide them on the command line. -------------------------------------------------------------------------------- /exercises/exercise-6/instructions.md: -------------------------------------------------------------------------------- 1 | # Exercise 6 2 | 3 | In the exercise, you will practice how to use [GoReleaser](https://goreleaser.com/) for generating binaries and publishing them to GitHub releases. 4 | 5 | 1. Install GoReleaser on your machine. You can find the installation instructions on the [web page](https://goreleaser.com/install/). Alternatively, download and install the latest binary from the [release page](https://github.com/goreleaser/goreleaser/releases) if you do not use any of the proposed package managers. Run `goreleaser -h` to verify the proper installation. 6 | 2. [Auto-generate](https://goreleaser.com/quick-start/) a GoReleaser configuration file for your sample project with the command `goreleaser init`. Remove the "before" hooks from the generated `.goreleaser.yml` file. Those lines are only needed if you use Go modules. 7 | 3. Modify the `.goreleaser.yml` file so that the produced archive files include the `LICENSE` file of the project. Archives targeting the Windows OS, the archive file format should be `.zip` instead of `.tar.gz`. 8 | 4. Modify the `.goreleaser.yml` file so that the Git tag is passed to the version variable in `main.go`. 9 | 5. Generate the binaries _without_ publishing them to GitHub. Please consult the user guide for more information. 10 | 6. Create a local Git tag and publish the produced archives to GitHub. You should see the release on the [GitHub releases tab](github.com//lets-gopher/releases). -------------------------------------------------------------------------------- /exercises/exercise-6/solution/.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # you may remove this if you don't use vgo 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - darwin 15 | - windows 16 | - netbsd 17 | - openbsd 18 | - freebsd 19 | goarch: 20 | - 386 21 | - amd64 22 | - arm 23 | - arm64 24 | ldflags: -s -w -X main.version={{.Version}} 25 | archives: 26 | - replacements: 27 | darwin: Darwin 28 | linux: Linux 29 | windows: Windows 30 | 386: i386 31 | amd64: x86_64 32 | archive: 33 | name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm}}v{{ .Arm }}{{ end }}' 34 | format_overrides: 35 | - goos: windows 36 | format: zip 37 | files: 38 | - LICENSE 39 | checksum: 40 | name_template: 'checksums.txt' 41 | snapshot: 42 | name_template: "{{ .Tag }}-next" 43 | changelog: 44 | sort: asc 45 | filters: 46 | exclude: 47 | - '^docs:' 48 | - '^test:' 49 | -------------------------------------------------------------------------------- /exercises/exercise-6/solution/solution.md: -------------------------------------------------------------------------------- 1 | # Solution 2 | 3 | 1. A properly installed binary should render help options similar to the ones below: 4 | 5 | ``` 6 | $ goreleaser -h 7 | 8 | Deliver Go binaries as fast and easily as possible 9 | 10 | USAGE: 11 | $ goreleaser [] [ ...] 12 | 13 | FLAGS: 14 | -h, --help Show context-sensitive help (also try --help-long and --help-man). 15 | --debug Enable debug mode 16 | -f, --config=.goreleaser.yml Load configuration from file 17 | -v, --version Show application version. 18 | 19 | COMMANDS: 20 | help [...] 21 | Show help. 22 | 23 | init 24 | Generates a .goreleaser.yml file 25 | 26 | check 27 | Checks if configuration is valid 28 | 29 | release [] (default) 30 | Releases the current project 31 | ``` 32 | 33 | 2. After running the command you should see the file `.goreleaser.yml` in the directory. The output of the command looks as such: 34 | 35 | ``` 36 | $ goreleaser init 37 | 38 | • Generating .goreleaser.yml file 39 | • config created; please edit accordingly to your needs file=.goreleaser.yml 40 | ``` 41 | 42 | 3. The section `archive` should contain the following code: 43 | 44 | ```yaml 45 | archive: 46 | name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm}}v{{ .Arm }}{{ end }}' 47 | format_overrides: 48 | - goos: windows 49 | format: zip 50 | files: 51 | - LICENSE 52 | ``` 53 | 54 | 4. The section `builds` should contain the following code: 55 | 56 | ```yaml 57 | builds: 58 | - env: 59 | ... 60 | ldflags: -s -w -X main.version={{.Version}} 61 | ``` 62 | 63 | 5. First tag the release with the the `git` command e.g. `git tag -a v0.4 -m "Version 0.4"`. The command `goreleaser release --help` renders all options needed to perform the task. To skip validation and publishing, run the command `goreleaser release --skip-validate --skip-publish`. You will want to delete the existing `dist` directory beforehand. The output of the command looks as such: 64 | 65 | ``` 66 | $ goreleaser release --skip-validate --skip-publish 67 | 68 | • releasing using goreleaser 0.119.0... 69 | • loading config file file=.goreleaser.yml 70 | • RUNNING BEFORE HOOKS 71 | • running go mod tidy 72 | • running go generate ./... 73 | • LOADING ENVIRONMENT VARIABLES 74 | • pipe skipped error=publishing is disabled 75 | • GETTING AND VALIDATING GIT STATE 76 | • releasing v0.4, commit 8e611b1791c11a4a52369e645a4027363ccb266c 77 | • pipe skipped error=validation is disabled 78 | • PARSING TAG 79 | • SETTING DEFAULTS 80 | • LOADING ENVIRONMENT VARIABLES 81 | • SNAPSHOTING 82 | • GITHUB/GITLAB/GITEA RELEASES 83 | • PROJECT NAME 84 | • BUILDING BINARIES 85 | • ARCHIVES 86 | • LINUX PACKAGES WITH NFPM 87 | • SNAPCRAFT PACKAGES 88 | • CALCULATING CHECKSUMS 89 | • SIGNING ARTIFACTS 90 | • DOCKER IMAGES 91 | • ARTIFACTORY 92 | • S3 93 | • BLOB 94 | • HOMEBREW TAP FORMULA 95 | • optimistically guessing `brew[0].installs`, double check 96 | • SCOOP MANIFEST 97 | • SNAPSHOTING 98 | • pipe skipped error=not a snapshot 99 | • CHECKING ./DIST 100 | • WRITING EFFECTIVE CONFIG FILE 101 | • writing config=dist/config.yaml 102 | • GENERATING CHANGELOG 103 | • writing changelog=dist/CHANGELOG.md 104 | • BUILDING BINARIES 105 | • building binary=dist/lets-gopher-exercise_freebsd_arm_6/lets-gopher-exercise 106 | • building binary=dist/lets-gopher-exercise_linux_arm64/lets-gopher-exercise 107 | • building binary=dist/lets-gopher-exercise_linux_386/lets-gopher-exercise 108 | • building binary=dist/lets-gopher-exercise_linux_amd64/lets-gopher-exercise 109 | • building binary=dist/lets-gopher-exercise_netbsd_amd64/lets-gopher-exercise 110 | • building binary=dist/lets-gopher-exercise_windows_amd64/lets-gopher-exercise.exe 111 | • building binary=dist/lets-gopher-exercise_netbsd_arm_6/lets-gopher-exercise 112 | • building binary=dist/lets-gopher-exercise_netbsd_386/lets-gopher-exercise 113 | • building binary=dist/lets-gopher-exercise_darwin_amd64/lets-gopher-exercise 114 | • building binary=dist/lets-gopher-exercise_openbsd_arm_6/lets-gopher-exercise 115 | • building binary=dist/lets-gopher-exercise_openbsd_386/lets-gopher-exercise 116 | • building binary=dist/lets-gopher-exercise_darwin_386/lets-gopher-exercise 117 | • building binary=dist/lets-gopher-exercise_openbsd_amd64/lets-gopher-exercise 118 | • building binary=dist/lets-gopher-exercise_freebsd_386/lets-gopher-exercise 119 | • building binary=dist/lets-gopher-exercise_freebsd_amd64/lets-gopher-exercise 120 | • building binary=dist/lets-gopher-exercise_linux_arm_6/lets-gopher-exercise 121 | • building binary=dist/lets-gopher-exercise_windows_386/lets-gopher-exercise.exe 122 | • ARCHIVES 123 | • creating archive=dist/lets-gopher-exercise_0.4_netbsd_x86_64.tar.gz 124 | • creating archive=dist/lets-gopher-exercise_0.4_netbsd_i386.tar.gz 125 | • creating archive=dist/lets-gopher-exercise_0.4_freebsd_armv6.tar.gz 126 | • creating archive=dist/lets-gopher-exercise_0.4_Linux_arm64.tar.gz 127 | • creating archive=dist/lets-gopher-exercise_0.4_openbsd_x86_64.tar.gz 128 | • creating archive=dist/lets-gopher-exercise_0.4_openbsd_i386.tar.gz 129 | • creating archive=dist/lets-gopher-exercise_0.4_Darwin_i386.tar.gz 130 | • creating archive=dist/lets-gopher-exercise_0.4_Linux_i386.tar.gz 131 | • creating archive=dist/lets-gopher-exercise_0.4_freebsd_x86_64.tar.gz 132 | • creating archive=dist/lets-gopher-exercise_0.4_openbsd_armv6.tar.gz 133 | • creating archive=dist/lets-gopher-exercise_0.4_netbsd_armv6.tar.gz 134 | • creating archive=dist/lets-gopher-exercise_0.4_Linux_x86_64.tar.gz 135 | • creating archive=dist/lets-gopher-exercise_0.4_Windows_x86_64.tar.gz 136 | • creating archive=dist/lets-gopher-exercise_0.4_Darwin_x86_64.tar.gz 137 | • creating archive=dist/lets-gopher-exercise_0.4_Linux_armv6.tar.gz 138 | • creating archive=dist/lets-gopher-exercise_0.4_Windows_i386.tar.gz 139 | • creating archive=dist/lets-gopher-exercise_0.4_freebsd_i386.tar.gz 140 | • LINUX PACKAGES WITH NFPM 141 | • pipe skipped error=no output formats configured 142 | • SNAPCRAFT PACKAGES 143 | • pipe skipped error=no summary nor description were provided 144 | • CALCULATING CHECKSUMS 145 | • checksumming file=lets-gopher-exercise_0.4_netbsd_i386.tar.gz 146 | • checksumming file=lets-gopher-exercise_0.4_openbsd_armv6.tar.gz 147 | • checksumming file=lets-gopher-exercise_0.4_freebsd_i386.tar.gz 148 | • checksumming file=lets-gopher-exercise_0.4_Linux_armv6.tar.gz 149 | • checksumming file=lets-gopher-exercise_0.4_freebsd_armv6.tar.gz 150 | • checksumming file=lets-gopher-exercise_0.4_Linux_arm64.tar.gz 151 | • checksumming file=lets-gopher-exercise_0.4_netbsd_x86_64.tar.gz 152 | • checksumming file=lets-gopher-exercise_0.4_openbsd_i386.tar.gz 153 | • checksumming file=lets-gopher-exercise_0.4_Darwin_i386.tar.gz 154 | • checksumming file=lets-gopher-exercise_0.4_Linux_i386.tar.gz 155 | • checksumming file=lets-gopher-exercise_0.4_Windows_x86_64.tar.gz 156 | • checksumming file=lets-gopher-exercise_0.4_openbsd_x86_64.tar.gz 157 | • checksumming file=lets-gopher-exercise_0.4_Windows_i386.tar.gz 158 | • checksumming file=lets-gopher-exercise_0.4_Linux_x86_64.tar.gz 159 | • checksumming file=lets-gopher-exercise_0.4_freebsd_x86_64.tar.gz 160 | • checksumming file=lets-gopher-exercise_0.4_netbsd_armv6.tar.gz 161 | • checksumming file=lets-gopher-exercise_0.4_Darwin_x86_64.tar.gz 162 | • SIGNING ARTIFACTS 163 | • pipe skipped error=artifact signing is disabled 164 | • DOCKER IMAGES 165 | • pipe skipped error=docker section is not configured 166 | • PUBLISHING 167 | • pipe skipped error=publishing is disabled 168 | • release succeeded after 13.50s 169 | ``` 170 | 171 | 6. First, export the `GITHUB_TOKEN` environment variable. The value can be retrieved from your GitHub account. For more information, see the page ["Creating a personal access token for the command line"](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line). Run the command `goreleaser release`. -------------------------------------------------------------------------------- /prerequisites/instructions.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | All exercises build upon a sample project. You will need to fork the repository containing the source code and clone it on your machine to follow along. 4 | 5 | ## Forking the repository 6 | 7 | 1. Log into [GitHub](https://github.com/). Create a new account if you do not have an account yet. 8 | 2. In a browser of your choice, open the URL [https://github.com/bmuschko/lets-gopher-exercise](https://github.com/bmuschko/lets-gopher-exercise). 9 | 3. Click the _Fork_ button in the top right corner of the screen. 10 | 4. After a couple of seconds, you should see a the forked repository in your account. 11 | 12 | ## Cloning the repository 13 | 14 | 1. Open your shell. 15 | 2. Navigate to any target directory you want to use to host the source code. 16 | 3. Clone the forked repository with the command `git clone `. To retrieve the URL, click the _Clone or Download_ button for your forked repository on GitHub. 17 | 4. You should end up with the repository cloned in `lets-gopher-exercise`. 18 | 5. In your shell, set the environment variable `GO111MODULES=on` to enable support for Go Modules. 19 | -------------------------------------------------------------------------------- /slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmuschko/go-project-automation/5384c29d7547bb6381c9714545a749237255898a/slides.pdf --------------------------------------------------------------------------------