Skip to content

Instantly share code, notes, and snippets.

@suiluj
Last active December 3, 2022 00:07
Show Gist options
  • Save suiluj/835ef82780d6b05db43bc6a8a1c10f28 to your computer and use it in GitHub Desktop.
Save suiluj/835ef82780d6b05db43bc6a8a1c10f28 to your computer and use it in GitHub Desktop.
Golang Start Template #golang #start-template
// example .devcontainer/devcontainer.json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/go
{
"name": "Go",
"build": {
"dockerfile": "Dockerfile",
"args": {
// Update the VARIANT arg to pick a version of Go: 1, 1.18, 1.17
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"VARIANT": "1-bullseye",
// Options
"NODE_VERSION": "none"
}
},
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
// Set *default* container specific settings.json values on container create.
"settings": {
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true,
"go.gopath": "/go"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"golang.Go",
"eamodio.gitlens",
"donjayamanne.githistory",
"yzhang.markdown-all-in-one"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"features": {
"docker-from-docker": "latest"
}
}
# should get .gitignore in real project
SERVICE_CONFIG=/workspace/configurations/config1.yml
USER_AT_HOST=user@host
DOCKER_IMAGE=app-name:latest
# LOGGING_LEVEL=INFO
LOGGING_LEVEL=DEBUG
USER_AT_HOST=user@host
DOCKER_IMAGE=app-name:latest

Golang Start Template

Disclaimer! Will not work. copied without check and reduced code to important parts.

VS Code devcontainer

  • create repo
  • clone repo
  • open repo in vscode
  • ctrl + shift + p
    • devcontainer create configuration files
    • show all
    • select Go
    • select additional:
      • docker from docker

vscode extensions and other settings

  • see example devcontainer.json

Folder structure

Gists (and GistPad) do not really support multiple subfolders. So folder structure is displayed here:

  • root
    • cmd
      • binary-name-folder
        • main.go
    • internal
      • service
        • service.go

go mod init

go mod init myapp

go mod tidy

command line comands

# make ctl.sh executeable
chmod +x ctl.sh

# run command
./ctl.sh run:config1
package main
import (
"context"
"mod-name/internal/service"
"mod-name/internal/tracing"
"os"
"os/signal"
"strings"
"sync"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func initLogger(ctx context.Context, wg *sync.WaitGroup) (*zap.Logger, *zap.SugaredLogger, zap.AtomicLevel) {
wg.Add(1)
atom := zap.NewAtomicLevel()
// To keep the example deterministic, disable timestamps in the output.
encoderCfg := zap.NewDevelopmentEncoderConfig()
// encoderCfg.TimeKey = ""
encoderCfg.EncodeCaller = zapcore.ShortCallerEncoder
encoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
// // https://github.com/uber-go/zap/issues/661
// encoderCfg.EncodeTime = zapcore.TimeEncoder(func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
// enc.AppendString(t.Format(time.Stamp))
// // Aug 13 00:38:11
// })
// https://github.com/uber-go/zap/issues/661
encoderCfg.EncodeTime = zapcore.TimeEncoder(func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(strings.Fields(t.Format(time.StampMilli))[2])
// Aug 13 00:38:11
})
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderCfg),
zapcore.Lock(os.Stdout),
atom,
), zap.AddCaller())
go func() {
// logger.Info("Waiting for ending of programm...")
<-ctx.Done()
logger.Info("flush buffer of zap logger.")
logger.Sync() // flushes buffer, if any
wg.Done()
}()
sugar := logger.Sugar()
// sugar.Info("asdf")
// sugar.Debug("asdf")
// atom.SetLevel(zap.DebugLevel)
// sugar.Info("asdf")
// sugar.Debug("asdf")
// atom.SetLevel(zap.InfoLevel)
// sugar.Info("asdf")
return logger, sugar, atom
}
func printEnvs(sugar *zap.SugaredLogger) {
printEnv(sugar, "SERVICE_CONFIG")
printEnv(sugar, "env1")
printEnv(sugar, "env2")
printEnv(sugar, "LOGGING_LEVEL")
}
func printEnv(sugar *zap.SugaredLogger, envVarString string) {
envVar, existing := os.LookupEnv(envVarString)
if !existing {
sugar.Infof("env var '%v' is not set.", envVarString)
} else {
sugar.Infof("env var '%v' is set to '%v'", envVarString, envVar)
}
}
func main() {
// https://henvic.dev/posts/signal-notify-context/
// https://khanakia.medium.com/go-1-16-signal-notifycontext-fac21b3eaa1c
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
wg := new(sync.WaitGroup)
logger, sugar, atom := initLogger(ctx,wg)
// atom.SetLevel(zap.InfoLevel)
atom.SetLevel(zap.DebugLevel)
logger.Info("logger initialized.")
printEnvs(sugar)
loggingLevel, existing := os.LookupEnv("LOGGING_LEVEL")
if existing {
switch loggingLevel {
case "DEBUG":
sugar.Info("Set logging level to '%v'", loggingLevel)
atom.SetLevel(zap.DebugLevel)
case "INFO":
sugar.Infof("Set logging level to '%v'", loggingLevel)
atom.SetLevel(zap.InfoLevel)
default:
sugar.Fatalf("Don't know logging level '%v' at the moment", loggingLevel)
}
}
serviceConfigFilePath, existing := os.LookupEnv("SERVICE_CONFIG")
if !existing {
sugar.Fatalf("env var SERVICE_CONFIG is not set.")
}
s, err := service.NewService(ctx, stop, sugar, wg, serviceConfigFilePath)
if err != nil {
sugar.Fatal(err)
}
err = s.Start(ctx, stop, wg)
if err != nil {
sugar.Fatal(err)
}
// s.ProcessWorkload(wg, ctx)
wg.Wait()
sugar.Info("app was shutdown.")
}
#!/usr/bin/env bash
# get env vars from .env file: https://stackoverflow.com/a/30969768
# use check for .env file first
FILE=".env"
if [ -f "$FILE" ]; then
### Take action if $DIR exists ###
# echo "Found file: ${FILE}. Will add environment variables."
set -o allexport; source .env; set +o allexport
else
### Control will jump here if $FILE does NOT exists ###
echo -e "Error: ${FILE} not found. Can not continue.\ncreate some .env file with environment variables next to this bash file ctl.sh\nIn case you do not use any env just copy .env.sample or create a file .env with NOTUSED=1234"
exit 1
fi
set -eo pipefail
DC="${DC:-exec}"
# old env method not used because better method above
## set env vars from .env file (like SSH_HOST)
#export $(grep -v '^#' .env | xargs -d '\n')
# If we're running in CI we need to disable TTY allocation for docker-compose
# commands that enable it by default, such as exec and run.
TTY=""
if [[ ! -t 1 ]]; then
TTY="-T"
fi
# -----------------------------------------------------------------------------
# Helper functions start with _ and aren't listed in this script's help menu.
# -----------------------------------------------------------------------------
# function _dc {
# docker-compose "${DC}" ${TTY} "${@}"
# }
# function _build_run_down {
# docker-compose build
# docker-compose run ${TTY} "${@}"
# docker-compose down
# }
# -----------------------------------------------------------------------------
# function cmd {
# # Run any command you want in the web container
# _dc web "${@}"
# }
# function flask {
# # Run any Flask commands
# cmd flask "${@}"
# }
# function bash {
# # Start a Bash session in the web container
# cmd bash "${@}"
# }
function run:config2 {
export SERVICE_CONFIG=/workspace/configuration/config2.yml
cd cmd/app-name; go run .
}
function run:config3 {
export SERVICE_CONFIG=/workspace/configuration/config3.yml
cd cmd/app-name; go run .
}
function run {
# Install pip3 dependencies and write lock file? dont remember meaning of this comment (perhaps copied from python project)
cd cmd/app-name
go run .
}
function docker:image:build {
docker build . -t app-image-name:latest
}
function docker:image:transfer {
# docker save ${DOCKER_IMAGE} | bzip2 | pv | ssh ${USER_AT_HOST} sudo docker load
# docker save ${DOCKER_IMAGE} | bzip2 | ssh ${USER_AT_HOST} docker load
docker save ${DOCKER_IMAGE} | bzip2 | ssh ${USER_AT_HOST} sudo docker load
}
function ssh_connect {
echo "${USER_AT_HOST}"
echo "(will not work if keys not added for this computer or devcontainer)"
ssh ${USER_AT_HOST}
}
# function docker:upload:image {
# # transfer existing image to remote docker from host computer
# # first parameter is image name (without latest tag)
# docker save $1:latest | bzip2 | ssh root@${SSH_HOST} docker load
# }
function help {
printf "%s <task> [args]\n\nTasks:\n" "${0}"
compgen -A function | grep -v "^_" | cat -n
printf "\nExtended help:\n Each task has comments for general usage\n"
}
# This idea is heavily inspired by: https://github.com/adriancooney/Taskfile
TIMEFORMAT=$'\nTask completed in %3lR'
time "${@:-help}"
FROM golang as builder
ENV GO111MODULE=on \
CGO_ENABLED=0
WORKDIR /workspace
# https://www.docker.com/blog/containerize-your-go-developer-environment-part-2/
COPY go.* ./
# COPY ./proto ./proto
RUN go mod download
COPY . .
# for sqlite
# RUN --mount=type=cache,target=/root/.cache/go-build go build -a -ldflags '-linkmode external -extldflags "-static"' -o app .
RUN --mount=type=cache,target=/root/.cache/go-build go build -a -o app ./cmd/general-service
# Build the image to run
FROM alpine:latest
COPY --from=builder /workspace/app .
# COPY --from=builder /workspace/assets ./assets
EXPOSE 7777
# HEALTHCHECK --interval=5m --timeout=3s \
# CMD curl -f http://localhost:7777/health || exit 1
CMD ["./app"]
# entrypoint cannot be overridden
# ENTRYPOINT ["./app"]
package service
// usually in extra folder "service" but not possible in gistpad
import (
"context"
"fmt"
"sync"
"go.uber.org/zap"
)
type Service struct {
data map[string]interface{}
dataLock sync.RWMutex
sugar *zap.SugaredLogger
// put workload stuff here and export functions to change workload
LoadFactor float64
DeltaLoadFactor chan float64
Ctx context.Context
Stop context.CancelFunc
}
type serviceConfig struct {
Service struct {
Name string
}
// ...
// ...
}
func NewService(ctx context.Context, stop context.CancelFunc, sugar *zap.SugaredLogger, wg *sync.WaitGroup, filename string) *Service {
s := new(Service)
s.sugar = sugar
buf, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
c := &serviceConfig{}
err = yaml.Unmarshal(buf, c)
if err != nil {
return nil, fmt.Errorf("could not init from file %q: %v", filename, err)
}
s.ServiceConfig = c
s.DeltaLoadFactor = make(chan float64, 5)
s.Ctx = ctx
s.Stop = stop
// s.processWorkload(ctx, wg)
return s
}
func (s *Service) Set(key string, value interface{}) {
s.dataLock.Lock()
defer s.dataLock.Unlock()
s.data[key] = value
}
func (s *Service) Get(key string) (interface{}, error) {
s.dataLock.RLock()
defer s.dataLock.RUnlock()
if value, ok := s.data[key]; ok {
return value, nil
}
return nil, fmt.Errorf("key '%v' does not exist in state", key)
}
func (s *Service) Start(ctx context.Context, stop context.CancelFunc, wg *sync.WaitGroup) error {
// s.sugar.Infof("Start healthcheck API.")
s.ProcessWorkload(ctx, stop, wg) // start go routine to process workload that is passed from rest server later
s.startServers(ctx, wg)
s.startJobs(ctx, wg)
// s.startJobs(ctx, wg)
return nil
}
func (s *Service) ProcessWorkload(ctx context.Context, stop context.CancelFunc, wg *sync.WaitGroup) {
s.sugar.Info("Start processing workload.")
wg.Add(1)
go func() {
for {
select {
case dLoadFactor := <-s.DeltaLoadFactor:
s.sugar.Debugf("Received workload delta: %v", dLoadFactor)
s.LoadFactor += dLoadFactor
if s.LoadFactor >= 1 {
s.sugar.Errorf("LoadFactor reached %v, This is too high the service \"crashes\" and restarts (if configured this way).", s.LoadFactor)
// first easy implementation: os.exit with error
os.Exit(1)
// second idea: restart app simulation itself
stop()
wg.Done()
return
}
s.LoadFactor = math.Round(s.LoadFactor*100) / 100
s.sugar.Infof("Current service workload: %v", s.LoadFactor)
case <-ctx.Done():
s.sugar.Info("Stopped processing workloads.")
wg.Done()
return
}
}
}()
}
func (s *Service) SendDeltaLoadFactor(dLoadFactor float64) {
err := s.Ctx.Err()
if err != nil {
return
}
s.sugar.Debugf("Send workload delta: %v", dLoadFactor)
s.DeltaLoadFactor <- dLoadFactor
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment