From 377048f6edb5b3f8edbe8c72d8553637845371d9 Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Sat, 20 Apr 2024 15:06:19 +0200 Subject: [PATCH] feat: rebuild task file and drop version generator --- .gitignore | 1 - .goreleaser.yml | 39 ++-- Taskfile.yml | 404 +++++++++++++++++----------------- Taskfile_windows.yml | 9 - cli/cmd/root.go | 11 +- cli/cmd/version.go | 5 +- util/config.go | 2 +- util/version.go | 19 ++ util/version_gen/generator.go | 47 ---- 9 files changed, 247 insertions(+), 290 deletions(-) delete mode 100644 Taskfile_windows.yml create mode 100644 util/version.go delete mode 100644 util/version_gen/generator.go diff --git a/.gitignore b/.gitignore index e8b0b055..65adc2be 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,6 @@ node_modules/ /semaphore.iml /bin/ -util/version.go /vendor/ /coverage.out /public/package-lock.json diff --git a/.goreleaser.yml b/.goreleaser.yml index 8146d2fa..d7e70a8c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,77 +1,64 @@ -# Goreleaser configuration -# for building binaries and packages for distributions and releasing on github dist: bin -before: - hooks: - - task compile +# before: +# hooks: +# - task build:fe builds: - binary: semaphore env: - CGO_ENABLED=0 main: ./cli/main.go + ldflags: -s -w -X github.com/ansible-semaphore/semaphore/util.Ver={{ .Version }} -X github.com/ansible-semaphore/semaphore/util.Commit={{ .ShortCommit }} -X github.com/ansible-semaphore/semaphore/util.Date={{ .Timestamp }} + tags: + - netgo goos: - windows - darwin - linux - freebsd - #- openbsd - #- netbsd goarch: - amd64 - #- 386 - arm - arm64 ignore: - goos: darwin goarch: 386 -# hooks: -# pre: task compile archives: - - + - files: + - LICENSE format_overrides: - goos: windows format: zip - files: - - LICENSE signs: - - - artifacts: checksum + - artifacts: checksum args: ["-u", "58A7 CC3D 8A9C A2E5 BB5C 141D 4064 23EA F814 63CA", "--pinentry-mode", "loopback", "--yes", "--batch", "--output", "${signature}", "--detach-sign", "${artifact}"] -# Start the snapshot name with a numerical value -# so it does not need to be force installed snapshot: name_template: "{{ .Timestamp }}-{{ .ShortCommit }}-SNAPSHOT" nfpms: - - - file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" - - vendor: Castaway Consulting LLC - homepage: https://github.com/ansible-semaphore/semaphore - maintainer: Castaway Consulting LLC + - file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" description: Open Source alternative to Ansible Tower + homepage: https://github.com/semaphoreui/semaphore + vendor: Semaphore UI + maintainer: Semaphore UI license: MIT formats: - deb - rpm - # Packages your package depends on. dependencies: - git suggests: - ansible - # install binary in /usr/bin bindir: /usr/bin release: - # Do not auto publish release draft: true name_template: "{{.Tag}}" diff --git a/Taskfile.yml b/Taskfile.yml index 2bff19b8..2d19645a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,77 +1,61 @@ -# Semaphore Tasks -# These tasks should be used to build and develop Semaphore -# -# Tasks without a `desc:` field are intended mainly to be called -# internally by other tasks and therefore are not listed when running `task` or `task -l` -version: '3' +version: "3" vars: - docker_namespace: semaphoreui - docker_image: semaphore - - + DOCKER_ORG: semaphoreui + DOCKER_SERVER: semaphore + DOCKER_RUNNER: runner + DOCKER_CMD: docker tasks: all: - desc: Install, Compile, Test and Build Semaphore for local architecture + desc: Install, test and build Semaphore for local architecture cmds: - task: deps - - task: compile - task: test - - task: build:local + - task: build vars: - GOOS: '' - GOARCH: '' + GOOS: "" + GOARCH: "" deps: - desc: Install all dependencies (except dredd requirements) + desc: Install all build dependencies cmds: - task: deps:tools - task: deps:be - - task: deps:fe2 + - task: deps:fe + + deps:tools: + desc: Installs required tools to build and publish + vars: + GOODMAN_VERSION: latest + SWAGGER_VERSION: v0.30.5 + GORELEASER_VERSION: v1.25.1 + GOLINTER_VERSION: v1.57.2 + cmds: + - go install github.com/snikch/goodman/cmd/goodman@{{ .GOODMAN_VERSION }} + - go install github.com/go-swagger/go-swagger/cmd/swagger@{{ .SWAGGER_VERSION }} + - go install github.com/goreleaser/goreleaser@{{ .GORELEASER_VERSION }} + - go install github.com/golangci/golangci-lint/cmd/golangci-lint@{{ .GOLINTER_VERSION }} deps:be: desc: Vendor application dependencies cmds: - go mod vendor - deps:fe2: - desc: Installs npm requirements for front end from package.json + deps:fe: + desc: Installs nodejs requirements dir: web cmds: - npm install - # - npm audit fix - deps:integration: - desc: Installs requirements for integration testing with dredd - dir: web + build: + desc: Build a full set of release binaries and packages cmds: - - npm install dredd@13.1.2 - # - npm audit fix + - task: build:fe + - task: build:be - deps:tools: - desc: Installs tools needed - dir: web - vars: - GORELEASER_VERSION: "0.183.0" - GOLINTER_VERSION: "1.46.2" - cmds: - - go install github.com/snikch/goodman/cmd/goodman@latest - - go install github.com/go-swagger/go-swagger/cmd/swagger@v0.29.0 - - '{{ if ne OS "windows" }} sh -c "curl -L https://github.com/goreleaser/goreleaser/releases/download/v{{ .GORELEASER_VERSION }}/goreleaser_$(uname -s)_$(uname -m).tar.gz | tar -xz -C $(go env GOPATH)/bin goreleaser"{{ else }} {{ end }}' - - '{{ if ne OS "windows" }} chmod +x $(go env GOPATH)/bin/goreleaser{{ else }} {{ end }}' - - '{{ if eq OS "windows" }} echo "NOTICE: You must download goreleaser manually to build this application https://github.com/goreleaser/goreleaser/releases "{{ else }}:{{ end }}' -# - '{{ if ne OS "windows" }} sh -c "curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v{{ .GOLINTER_VERSION }}"{{ else }}{{ end }}' - - '{{ if eq OS "windows" }} echo "NOTICE: You need to install golangci-lint manually to build this application https://github.com/golangci/golangci-lint#install"{{ else }}{{ end }}' - - compile: - desc: Generates compiled frontend and backend resources (must be in this order) - cmds: - - task: compile:fe2 - - task: compile:be - - compile:fe2: - desc: Build vue.js project + build:fe: + desc: Build VueJS project dir: web sources: - src/*.* @@ -90,199 +74,221 @@ tasks: cmds: - npm run build - compile:be: - desc: Generate the version + build:be: + desc: Build server binary cmds: - - go run util/version_gen/generator.go {{ if .TAG }}{{ .TAG }}{{ else }}{{ if .SEMAPHORE_VERSION }}{{ .SEMAPHORE_VERSION }}{{ else }}{{ .BRANCH }}-{{ .SHA }}-{{ .TIMESTAMP }}{{ if .DIRTY }}-dirty{{ end }}{{ end }}{{end}} + - env CGO_ENABLED=0 GOOS={{ .GOOS }} GOARCH={{ .GOARCH }} go build -o bin/semaphore{{ if eq OS "windows" }}.exe{{ end }} -tags "netgo" -ldflags "-s -w -X github.com/ansible-semaphore/semaphore/util.Ver={{ if eq .GITHUB_REF_TYPE "tag" }}{{ .GITHUB_REF_NAME }}{{ else }}{{ if .TAG }}{{ .TAG }}{{ else }}{{ .BRANCH }}{{ end }}{{ end }} -X github.com/ansible-semaphore/semaphore/util.Commit={{ .SHA }} -X github.com/ansible-semaphore/semaphore/util.Date={{ .DATE }}" ./cli vars: TAG: - sh: git name-rev --name-only --tags --no-undefined HEAD 2>/dev/null | sed -n 's/^\([^^~]\{1,\}\)\(\^0\)\{0,1\}$/\1/p' + sh: git name-rev --name-only --tags --no-undefined HEAD BRANCH: sh: git rev-parse --abbrev-ref HEAD - DIRTY: - # We must exclude the package-lock file as npm install can change it! - sh: git diff --exit-code --stat -- . ':(exclude)web/package-lock.json' ':(exclude)web/package.json' || true SHA: sh: git log --pretty=format:'%h' -n 1 - TIMESTAMP: - sh: date +%s - - compile:api:hooks: - dir: ./.dredd/hooks - cmds: - - go build -o ../compiled_hooks{{ if eq OS "windows" }}.exe{{ end }} - - build: - desc: Build a full set of release binaries and packages - cmds: - - task: release - - build:local: - desc: Build a binary for the current architecture - dir: cli - cmds: - - env CGO_ENABLED=0 GOOS={{ .GOOS }} GOARCH={{ .GOARCH }} go build -o ../bin/semaphore{{ if eq OS "windows" }}.exe{{ end }} - - release: - desc: creates a release without performing validations or publishing artifacts - cmds: - - goreleaser --snapshot --rm-dist - - release:prod: - cmds: - - goreleaser + DATE: "{{ now | unixEpoch }}" lint: cmds: + - task: lint:fe - task: lint:be + lint:fe: + dir: web + cmds: + - npm run lint + lint:be: - # --errors cmds: - golangci-lint run --disable goconst --timeout 240s ./... + - go vet ./... + - swagger validate ./api-docs.yml test: cmds: + - task: test:fe - task: test:be + test:fe: + dir: web + cmds: + - npm run test:unit + test:be: desc: Run go code tests cmds: - - go vet ./... - - swagger validate ./api-docs.yml - go test -v -coverprofile=coverage.out ./... - test:api: - desc: test the api with dredd + e2e:deps: + desc: Installs dredd dep for integration testing + dir: web + cmds: + - npm install dredd@13.1.2 + + e2e:hooks: + desc: Compile required dredd hooks built + dir: ./.dredd/hooks + cmds: + - go build -o ../compiled_hooks{{ if eq OS "windows" }}.exe{{ end }} + + e2e:test: + desc: Run end to end test for API with dredd cmds: - ./web/node_modules/.bin/dredd --config .dredd/dredd.yml - ci:artifacts: + release:prod: + desc: Create and publish a release cmds: - - rsync -a bin/ $CIRCLE_ARTIFACTS/ + - goreleaser - # docker(-compose) commands - dc:dev: - desc: build and start a development stack using docker-compose + release:test: + desc: Create a local test release cmds: - - task: docker - vars: - context: dev - args: build semaphore_dev - compose: true - - task: dc:up - vars: - context: dev - - # convenience function to build and start a production like stack - dc:prod: - desc: build and start a production like stack using docker-compose - cmds: - - task: docker - vars: - context: prod - args: build semaphore - compose: true - - task: dc:up - vars: - context: prod - - dc:up: - desc: start a docker-compose instance, requires context - cmds: - - task: docker - vars: - compose: true - args: up --abort-on-container-exit - prefix: "{{ .prefix }}" - context: "{{ .context }}" - - dc:build: - desc: build a set of docker-compose containers, requires context - cmds: - - task: docker - vars: - compose: true - args: build - context: "{{ .context }}" - - dc:down: - desc: down a docker-compose instance, requires context - cmds: - - task: docker - vars: - compose: true - args: down - context: "{{ .context }}" - - dc:stop: - desc: stop a docker-compose instance, requires context - cmds: - - task: docker - vars: - compose: true - args: stop - context: "{{ .context }}" - - docker:build: - desc: Build an image for Semaphore, requires context - vars: - tag: "{{ if .tag }}{{ .tag }}{{ else }}latest{{ end }}" - cmds: - - task: docker - vars: - context: "{{ .context }}" - action: build - tag: "{{ .tag }}" - args: -t "{{ .docker_namespace }}/{{ .docker_image }}:{{ .tag }}" . - - deps:docker: - desc: Install docker testing dependencies. These must be installed explicitly and are not included in the general deps task. - status: - - test -f /usr/local/bin/goss - - test -f /usr/local/bin/dgoss - - test -f /usr/local/bin/hadolint - cmds: - - sudo curl -L https://github.com/aelsabbahy/goss/releases/download/v0.3.5/goss-linux-amd64 -o /usr/local/bin/goss - - sudo chmod +rx /usr/local/bin/goss - - sudo curl -L https://raw.githubusercontent.com/aelsabbahy/goss/v0.3.5/extras/dgoss/dgoss -o /usr/local/bin/dgoss - - sudo chmod +rx /usr/local/bin/dgoss - - sudo curl -L https://github.com/hadolint/hadolint/releases/download/v2.10.0/hadolint-Linux-x86_64 -o /usr/local/bin/hadolint - - sudo chmod +rx /usr/local/bin/hadolint + - goreleaser --auto-snapshot --clean --skip=sign docker:test: - desc: Test docker containers by building them, running tests and deleting - deps: ['deps:docker'] + desc: Test containers by building, running, testing and deleting them + deps: + - task: docker:deps cmds: - - task: docker:lint - vars: - context: "{{ .context }}" - task: docker:build vars: - tag: "{{ .context }}-test" - - task: docker:goss - - docker rmi "{{ .docker_namespace }}/{{ .docker_image }}:{{ .context }}-test" + tag: test - docker:goss: - dir: "deployment/docker/{{ .context}}" - deps: ['deps:docker'] - cmds: - - GOSS_FILES_STRATEGY='cp' dgoss run -it "{{ .docker_namespace }}/{{ .docker_image }}:{{ .context }}-test" + - task: docker:goss + - task: docker:lint + + - "{{ .DOCKER_CMD }} rmi {{ .DOCKER_ORG }}/{{ .DOCKER_SERVER }}:test" + - "{{ .DOCKER_CMD }} rmi {{ .DOCKER_ORG }}/{{ .DOCKER_RUNNER }}:test" docker:lint: - desc: hadolint a dockerfile. Ignores version pinning warning - dir: "deployment/docker/{{ .context}}" + desc: Lint all dockerfiles based on Hadolint + deps: + - task: docker:deps + cmds: + - task: docker:lint:server + - task: docker:lint:runner + + docker:lint:server: + desc: Lint server dockerfile based on Hadolint + dir: deployment/docker/server cmds: - hadolint Dockerfile --ignore DL3018 - docker:push: - desc: push a docker image to a repo. Defaults to the official docker hub + docker:lint:runner: + desc: Lint runner dockerfile based on Hadolint + dir: deployment/docker/runner cmds: - - docker push {{ .docker_namespace }}/{{ .docker_image }}:{{ .tag }} + - hadolint Dockerfile --ignore DL3018 - # templated command to reduce code duplication - docker: - vars: - docker_root: deployment/docker/ + docker:goss: + desc: Check if container contains defined files + deps: + - task: docker:deps cmds: - - docker{{ if .compose }}-compose{{ end }} {{ if .action }}{{ .action }}{{ end }} -f {{ .docker_root }}{{ .context }}/{{ if .compose }}docker-compose{{ if .prefix }}{{ .prefix }}{{ end }}.yml{{ else }}Dockerfile{{ if .prefix }}{{ .prefix }}{{ end }}{{ end }} {{if .args }}{{ .args }}{{ end }} + - task: docker:goss:server + - task: docker:goss:runner + + docker:goss:server: + desc: Check if server contains defined files + dir: deployment/docker/server + env: + GOSS_FILES_STRATEGY: cp + cmds: + - dgoss run -it "{{ .DOCKER_ORG }}/{{ .DOCKER_SERVER }}:test" + + docker:goss:runner: + desc: Check if runner contains defined files + dir: deployment/docker/runner + env: + GOSS_FILES_STRATEGY: cp + cmds: + - dgoss run -it "{{ .DOCKER_ORG }}/{{ .DOCKER_RUNNER }}:test" + + docker:build: + desc: Build all defined images for Semaphore + vars: + tag: "{{ if .tag }}{{ .tag }}{{ else }}latest{{ end }}" + cmds: + - task: docker:build:server + vars: + tag: "{{ .tag }}" + - task: docker:build:runner + vars: + tag: "{{ .tag }}" + + docker:build:server: + desc: Build an image for Semaphore server + vars: + tag: "{{ if .tag }}{{ .tag }}{{ else }}latest{{ end }}" + cmds: + - "{{ .DOCKER_CMD }} build -f deployment/docker/server/Dockerfile -t {{ .DOCKER_ORG }}/{{ .DOCKER_SERVER }}:{{ .tag }} ." + + docker:build:runner: + desc: Build an image for Semaphore runner + vars: + tag: "{{ if .tag }}{{ .tag }}{{ else }}latest{{ end }}" + cmds: + - "{{ .DOCKER_CMD }} build -f deployment/docker/runner/Dockerfile -t {{ .DOCKER_ORG }}/{{ .DOCKER_RUNNER }}:{{ .tag }} ." + + docker:push: + desc: Push the images to registry + cmds: + - docker push {{ .DOCKER_ORG }}/{{ .DOCKER_SERVER }}:{{ .tag }} + - docker push {{ .DOCKER_ORG }}/{{ .DOCKER_RUNNER }}:{{ .tag }} + + docker:deps: + desc: Install docker testing dependencies + vars: + INSTALL_PATH: '{{ .INSTALL_PATH | default "/usr/local/bin" }}' + REQUIRE_SUDO: '{{ .REQUIRE_SUDO | default "true" }}' + cmds: + - task: docker:deps:hadolint + vars: + INSTALL_PATH: "{{ .INSTALL_PATH }}" + REQUIRE_SUDO: "{{ .REQUIRE_SUDO }}" + - task: docker:deps:goss + vars: + INSTALL_PATH: "{{ .INSTALL_PATH }}" + REQUIRE_SUDO: "{{ .REQUIRE_SUDO }}" + - task: docker:deps:dgoss + vars: + INSTALL_PATH: "{{ .INSTALL_PATH }}" + REQUIRE_SUDO: "{{ .REQUIRE_SUDO }}" + + docker:deps:hadolint: + platforms: + - linux/amd64 + - linux/arm64 + - darwin/amd64 + - darwin/arm64 + vars: + HADOLINT_VERSION: v2.10.0 + status: + - test -f "{{ .INSTALL_PATH }}/hadolint" + cmds: + - '{{ if eq .REQUIRE_SUDO "true" }}sudo {{ end }}curl -sSL https://github.com/hadolint/hadolint/releases/download/{{ .HADOLINT_VERSION }}/hadolint-{{ if eq OS "linux" }}Linux{{ end }}{{ if eq OS "darwin" }}Darwin{{ end }}-{{ if eq ARCH "amd64" }}x86_64{{ else }}{{ ARCH }}{{ end }} -o {{ .INSTALL_PATH }}/hadolint' + - '{{ if eq .REQUIRE_SUDO "true" }}sudo {{ end }}chmod +x {{ .INSTALL_PATH }}/hadolint' + + docker:deps:goss: + platforms: + - linux + - darwin + vars: + GOSS_VERSION: v0.3.5 + status: + - test -f "{{ .INSTALL_PATH }}/goss" + cmds: + - '{{ if eq .REQUIRE_SUDO "true" }}sudo {{ end }}curl -sSL https://github.com/aelsabbahy/goss/releases/download/{{ .GOSS_VERSION }}/goss-{{ OS }}-{{ ARCH }} -o {{ .INSTALL_PATH }}/goss' + - '{{ if eq .REQUIRE_SUDO "true" }}sudo {{ end }}chmod +x {{ .INSTALL_PATH }}/goss' + + docker:deps:dgoss: + platforms: + - linux + - darwin + vars: + GOSS_VERSION: v0.3.5 + status: + - test -f "{{ .INSTALL_PATH }}/dgoss" + cmds: + - '{{ if eq .REQUIRE_SUDO "true" }}sudo {{ end }}curl -sSL https://raw.githubusercontent.com/aelsabbahy/goss/{{ .GOSS_VERSION }}/extras/dgoss/dgoss -o {{ .INSTALL_PATH }}/dgoss' + - '{{ if eq .REQUIRE_SUDO "true" }}sudo {{ end }}chmod +x {{ .INSTALL_PATH }}/dgoss' diff --git a/Taskfile_windows.yml b/Taskfile_windows.yml deleted file mode 100644 index 7315f6be..00000000 --- a/Taskfile_windows.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '2' -tasks: - compile:be: - cmds: - - go run util/version_gen/generator.go 1 - build:local: - dir: cli - cmds: - - go build -o ../bin/semaphore{{ if eq OS "windows" }}.exe{{ end }} diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 78feee8b..ca7b448b 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -2,7 +2,10 @@ package cmd import ( "fmt" - log "github.com/sirupsen/logrus" + "net/http" + "os" + "strings" + "github.com/ansible-semaphore/semaphore/api" "github.com/ansible-semaphore/semaphore/api/sockets" "github.com/ansible-semaphore/semaphore/db" @@ -12,10 +15,8 @@ import ( "github.com/ansible-semaphore/semaphore/util" "github.com/gorilla/context" "github.com/gorilla/handlers" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "net/http" - "os" - "strings" ) var configPath string @@ -56,7 +57,7 @@ func runService() { } fmt.Printf("Tmp Path (projects home) %v\n", util.Config.TmpPath) - fmt.Printf("Semaphore %v\n", util.Version) + fmt.Printf("Semaphore %v\n", util.Version()) fmt.Printf("Interface %v\n", util.Config.Interface) fmt.Printf("Port %v\n", util.Config.Port) diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 4a22fb3e..5057af46 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ansible-semaphore/semaphore/util" "github.com/spf13/cobra" ) @@ -14,6 +15,6 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version of Semaphore", Run: func(cmd *cobra.Command, args []string) { - fmt.Println(util.Version) + fmt.Println(util.Version()) }, -} \ No newline at end of file +} diff --git a/util/config.go b/util/config.go index f5c51f7a..2801f84a 100644 --- a/util/config.go +++ b/util/config.go @@ -598,7 +598,7 @@ func CheckUpdate() (updateAvailable *github.RepositoryRelease, err error) { } updateAvailable = nil - if (*releases[0].TagName)[1:] != Version { + if (*releases[0].TagName)[1:] != Version() { updateAvailable = releases[0] } diff --git a/util/version.go b/util/version.go new file mode 100644 index 00000000..f837f1cb --- /dev/null +++ b/util/version.go @@ -0,0 +1,19 @@ +package util + +import ( + "strings" +) + +var ( + Ver = "undefined" + Commit = "00000000" + Date = "" +) + +func Version() string { + return strings.Join([]string{ + Ver, + Commit, + Date, + }, "-") +} diff --git a/util/version_gen/generator.go b/util/version_gen/generator.go deleted file mode 100644 index bd8fc180..00000000 --- a/util/version_gen/generator.go +++ /dev/null @@ -1,47 +0,0 @@ -// +build ignore - -package main - -import ( - "log" - "os" - "text/template" -) - -var versionTmpl = `package util - -//Version is the Semaphore build version as a string -var Version = "{{ .VERSION }}" -` - -func main(){ - - if len(os.Args) <= 1 { - log.Fatalln("Must pass in version number") - } - - data := make(map[string]string) - data["VERSION"] = os.Args[1] - - tmpl := template.New("version") - var err error - if tmpl, err = tmpl.Parse(versionTmpl); err != nil { - log.Fatalln(err) - } - - f, err := os.Create("util/version.go") - if err != nil { - log.Fatalln(err) - } - defer func(r *os.File) { - err = r.Close() - if err != nil { - log.Fatalln(err) - } - }(f) - - err = tmpl.Execute(f, data) - if err != nil { - log.Println(err) - } -} \ No newline at end of file