9 Commits

11 changed files with 332 additions and 68 deletions

View File

@ -12,8 +12,8 @@ jobs:
name: Main Process name: Main Process
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
GO_VERSION: 1.17 GO_VERSION: 1.19
GOLANGCI_LINT_VERSION: v1.44.0 GOLANGCI_LINT_VERSION: v1.49.0
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:

View File

@ -1,5 +1,9 @@
name: Go Matrix name: Go Matrix
on: [push, pull_request] on:
push:
branches:
- master
pull_request:
jobs: jobs:
@ -11,7 +15,7 @@ jobs:
strategy: strategy:
matrix: matrix:
go-version: [ 1.17, 1.x ] go-version: [ 1.19, 1.x ]
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
steps: steps:

View File

@ -9,7 +9,7 @@ jobs:
name: Release Process name: Release Process
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
GO_VERSION: 1.17 GO_VERSION: 1.19
SEIHON_VERSION: v0.9.0 SEIHON_VERSION: v0.9.0
CGO_ENABLED: 0 CGO_ENABLED: 0
@ -27,12 +27,6 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
# Install Docker image multi-arch builder
- name: Install seihon ${{ env.SEIHON_VERSION }}
run: |
curl -sSfL https://raw.githubusercontent.com/ldez/seihon/master/godownloader.sh | sh -s -- -b $(go env GOPATH)/bin ${SEIHON_VERSION}
seihon --version
- name: Docker Login - name: Docker Login
env: env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
@ -40,5 +34,17 @@ jobs:
run: | run: |
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
- name: Deploy Docker Images (seihon) - name: Set up QEMU
run: make publish-images uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# https://goreleaser.com/ci/actions/
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }}

View File

@ -1,47 +1,93 @@
run: run:
deadline: 2m timeout: 2m
skip-files: [] skip-files: []
linters-settings: linters-settings:
govet: govet:
check-shadowing: true enable-all: true
disable:
- fieldalignment
gocyclo: gocyclo:
min-complexity: 12 min-complexity: 16
goconst: goconst:
min-len: 3 min-len: 3
min-occurrences: 3 min-occurrences: 3
misspell: misspell:
locale: US locale: US
funlen:
lines: -1
statements: 40
gofumpt:
extra-rules: true
depguard:
list-type: blacklist
include-go-root: false
packages:
- github.com/pkg/errors
godox:
keywords:
- FIXME
gocritic:
enabled-tags:
- diagnostic
- style
- performance
disabled-checks:
- sloppyReassign
- rangeValCopy
- octalLiteral
- paramTypeCombine # already handle by gofumpt.extra-rules
settings:
hugeParam:
sizeThreshold: 100
forbidigo:
forbid:
- '^print(ln)?$'
- '^panic$'
- '^spew\.Print(f|ln)?$'
- '^spew\.Dump$'
linters: linters:
enable-all: true enable-all: true
disable: disable:
- deadcode # deprecated
- exhaustivestruct # deprecated
- golint # deprecated - golint # deprecated
- scopelint # deprecated - ifshort # deprecated
- interfacer # deprecated - interfacer # deprecated
- maligned # deprecated - maligned # deprecated
- lll - nosnakecase # deprecated
- gosec - scopelint # deprecated
- scopelint # deprecated
- structcheck # deprecated
- varcheck # deprecated
- execinquery # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
- sqlclosecheck # not relevant (SQL)
- cyclop # duplicate of gocyclo
- dupl - dupl
- prealloc - exhaustive
- exhaustruct
- forbidigo
- gochecknoglobals - gochecknoglobals
- gochecknoinits - gochecknoinits
- gomnd
- wsl
- nlreturn
- testpackage
- paralleltest
- tparallel
- goerr113 - goerr113
- wrapcheck - gomnd
- exhaustive - gosec
- exhaustivestruct - lll
- forbidigo
- varnamelen
- nilnil - nilnil
- ifshort - nlreturn
- paralleltest
- prealloc
- testpackage
- tparallel
- varnamelen
- wrapcheck
- wsl
issues: issues:
exclude-use-default: false exclude-use-default: false
max-per-linter: 0 max-per-linter: 0
max-same-issues: 0 max-same-issues: 0
exclude: [] exclude:
- 'package-comments: should have a package comment'

86
.goreleaser.yml Normal file
View File

@ -0,0 +1,86 @@
project_name: whoami
builds:
- binary: whoami
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm
- arm64
goarm:
- '7'
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^doc:'
- '^chore:'
- '^chore(deps):'
- '^test:'
- '^tests:'
archives:
- id: whoami
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm}}v{{ .Arm }}{{ end }}'
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- LICENSE
docker_manifests:
- name_template: 'traefik/whoami:{{ .Tag }}'
image_templates:
- 'traefik/whoami:{{ .Tag }}-amd64'
- 'traefik/whoami:{{ .Tag }}-arm64'
- name_template: 'traefik/whoami:latest'
image_templates:
- 'traefik/whoami:{{ .Tag }}-amd64'
- 'traefik/whoami:{{ .Tag }}-arm64'
- name_template: 'traefik/whoami:v{{ .Major }}.{{ .Minor }}'
image_templates:
- 'traefik/whoami:v{{ .Major }}.{{ .Minor }}-amd64'
- 'traefik/whoami:v{{ .Major }}.{{ .Minor }}-arm64'
dockers:
- use: buildx
goos: linux
goarch: amd64
dockerfile: buildx.Dockerfile
image_templates:
- 'traefik/whoami:latest-amd64'
- 'traefik/whoami:{{ .Tag }}-amd64'
- 'traefik/whoami:v{{ .Major }}.{{ .Minor }}-amd64'
build_flag_templates:
- '--pull'
- '--label=org.opencontainers.image.description=Tiny Go webserver that prints OS information and HTTP request to output'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--platform=linux/amd64'
- use: buildx
goos: linux
goarch: arm64
dockerfile: buildx.Dockerfile
image_templates:
- 'traefik/whoami:latest-arm64'
- 'traefik/whoami:{{ .Tag }}-arm64'
- 'traefik/whoami:v{{ .Major }}.{{ .Minor }}-arm64'
build_flag_templates:
- '--pull'
- '--label=org.opencontainers.image.description=Tiny Go webserver that prints OS information and HTTP request to output'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--platform=linux/arm64'

View File

@ -1,4 +1,4 @@
.PHONY: default build image check publish-images .PHONY: default check test build image publish-images
TAG_NAME := $(shell git tag -l --contains HEAD) TAG_NAME := $(shell git tag -l --contains HEAD)

24
app.go
View File

@ -1,14 +1,12 @@
package main package main
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -86,7 +84,7 @@ func main() {
} }
func setupMutualTLS(ca string) *tls.Config { func setupMutualTLS(ca string) *tls.Config {
clientCACert, err := ioutil.ReadFile(ca) clientCACert, err := os.ReadFile(ca)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -181,7 +179,7 @@ func dataHandler(w http.ResponseWriter, r *http.Request) {
attachment = false attachment = false
} }
content := fillContent(size) content := &contentReader{size: size}
if attachment { if attachment {
w.Header().Set("Content-Disposition", "Attachment") w.Header().Set("Content-Disposition", "Attachment")
@ -311,25 +309,9 @@ func healthHandler(w http.ResponseWriter, req *http.Request) {
} }
} }
func fillContent(length int64) io.ReadSeeker {
charset := "-ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
for i := range b {
b[i] = charset[i%len(charset)]
}
if length > 0 {
b[0] = '|'
b[length-1] = '|'
}
return bytes.NewReader(b)
}
func getEnv(key, fallback string) string { func getEnv(key, fallback string) string {
value := os.Getenv(key) value := os.Getenv(key)
if len(value) == 0 { if value == "" {
return fallback return fallback
} }
return value return value

View File

@ -1,25 +1,17 @@
# syntax=docker/dockerfile:1.2
FROM golang:1-alpine as builder FROM golang:1-alpine as builder
RUN apk --no-cache --no-progress add git ca-certificates tzdata make \ RUN apk --no-cache --no-progress add git ca-certificates tzdata make \
&& update-ca-certificates \ && update-ca-certificates \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
WORKDIR /go/whoami # syntax=docker/dockerfile:1.2
# Create a minimal container to run a Golang static binary
# Download go modules
COPY go.mod .
COPY go.sum .
RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download
COPY . .
RUN GOARCH={{ .GoARCH }} GOARM={{ .GoARM }} make build
FROM scratch FROM scratch
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/whoami/whoami . COPY whoami /
ENTRYPOINT ["/whoami"] ENTRYPOINT ["/whoami"]
EXPOSE 80 EXPOSE 80

66
content.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"errors"
"io"
)
const contentCharset = "-ABCDEFGHIJKLMNOPQRSTUVWXYZ"
type contentReader struct {
size int64
cur int64
}
// Read implements the io.Read interface.
func (r *contentReader) Read(p []byte) (int, error) {
length := r.size - 1
if r.cur >= length {
return 0, io.EOF
}
if len(p) == 0 {
return 0, nil
}
var n int
if r.cur == 0 {
p[n] = '|'
r.cur++
n++
}
for n < len(p) && r.cur <= length {
p[n] = contentCharset[int(r.cur)%len(contentCharset)]
r.cur++
n++
}
if r.cur >= length {
p[n-1] = '|'
}
return n, nil
}
// Seek implements the io.Seek interface.
func (r *contentReader) Seek(offset int64, whence int) (int64, error) {
switch whence {
default:
return 0, errors.New("seek: invalid whence")
case io.SeekStart:
offset += 0
case io.SeekCurrent:
offset += r.cur
case io.SeekEnd:
offset += r.size - 1
}
if offset < 0 {
return 0, errors.New("seek: invalid offset")
}
r.cur = offset
return offset, nil
}

82
content_test.go Normal file
View File

@ -0,0 +1,82 @@
package main
import (
"io"
"testing"
)
func Test_contentReader_Read(t *testing.T) {
tests := []struct {
name string
size int64
content string
}{
{
name: "simple",
size: 40,
content: "|ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJK|",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
d := &contentReader{size: test.size}
b, err := io.ReadAll(d)
if err != nil {
t.Errorf("contentReader.Read() error = %v", err)
return
}
if string(b) != test.content {
t.Errorf("return content does not match expected value: got %s want %s", b, test.content)
}
})
}
}
func Test_contentReader_ReadSeek(t *testing.T) {
tests := []struct {
name string
offset int64
seekWhence int
size int64
content string
}{
{
name: "simple",
offset: 10,
seekWhence: io.SeekCurrent,
size: 40,
content: "JKLMNOPQRSTUVWXYZ-ABCDEFGHIJK|",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
d := &contentReader{size: test.size}
_, err := d.Seek(test.offset, test.seekWhence)
if err != nil {
t.Errorf("contentReader.Seek() error = %v", err)
return
}
b, err := io.ReadAll(d)
if err != nil {
t.Errorf("contentReader.Read() error = %v", err)
return
}
if string(b) != test.content {
t.Errorf("return content does not match expected value: got %s want %s", b, test.content)
}
})
}
}

2
go.mod
View File

@ -1,5 +1,5 @@
module github.com/traefik/whoami module github.com/traefik/whoami
go 1.17 go 1.19
require github.com/gorilla/websocket v1.4.2 require github.com/gorilla/websocket v1.4.2