Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ci #9

Merged
merged 8 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
bin
docs
examples
.gitignore
.git/
**/*.md
Dockerfile
Makefile
10 changes: 10 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
87 changes: 87 additions & 0 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Check

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_call:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Format
run: go fmt ./... && git diff --exit-code
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Vet
run: go vet ./...
- name: Lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.58
module:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Mod tidy
run: go mod tidy && git diff --exit-code
- name: Mod download
run: go mod download
- name: Mod verify
run: go mod verify
build:
needs: [format, lint, module]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Build
run: go build -o /dev/null
test:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Test
run: go test -v -race -shuffle=on -coverprofile=coverage.txt ./...
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.txt
66 changes: 66 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Release

on:
push:
tags:
- "v*.*.*"

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
check:
uses: ./.github/workflows/check.yaml
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the container registry
uses: docker/login-action@v5
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
bin
dist/
46 changes: 46 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com

# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

version: 2

before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...

builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin

archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM golang:1.22 AS build-stage

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -o /zettelkasten-exporter

FROM gcr.io/distroless/base-debian12 AS release-stage

WORKDIR /

COPY --from=build-stage /zettelkasten-exporter /zettelkasten-exporter

USER nonroot:nonroot

ENTRYPOINT ["/zettelkasten-exporter"]
23 changes: 19 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
BINARY_NAME=zettelkasten-exporter

.PHONY: all clean format test build run
.PHONY: all clean format vet lint test build run

all: format vet build
all: format vet build test

clean:
go clean
Expand All @@ -14,11 +14,26 @@ format:
vet:
go vet ./...

lint:
golangci-lint run

test:
go test ./...

build:
go build -o bin/$(BINARY_NAME) ./cmd/zettelkasten-exporter/main.go
go build -o bin/$(BINARY_NAME)

run: build
ZETTELKASTEN_DIRECTORY=./sample ./bin/$(BINARY_NAME)
LOG_LEVEL=INFO \
ZETTELKASTEN_GIT_URL=<GIT_URL> \
ZETTELKASTEN_GIT_BRANCH=master \
ZETTELKASTEN_GIT_TOKEN=<GIT_TOKEN> \
COLLECTION_INTERVAL=10s \
INFLUXDB_TOKEN=<INFLUXDB_TOKEN> \
INFLUXDB_URL=http://localhost:8086 \
INFLUXDB_ORG=default \
INFLUXDB_BUCKET=zettelkasten \
./bin/$(BINARY_NAME)

docker:
docker build . -t zettelkasten-exporter:latest
5 changes: 5 additions & 0 deletions internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ func (c *Collector) collectMetrics(root fs.FS) (metrics.Metrics, error) {
notes := make(map[string]metrics.NoteMetrics)

err := fs.WalkDir(root, ".", func(path string, dir fs.DirEntry, err error) error {
if err != nil {
slog.Error("Error on path. Will not enter it", slog.Any("error", err), slog.String("path", path))
return nil
}

// Skip ignored files or directories
if slices.Contains(c.config.IgnorePatterns, filepath.Base(path)) {
if dir.IsDir() {
Expand Down
5 changes: 4 additions & 1 deletion internal/collector/note.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func collectLinks(content []byte) map[string]int {
reader := text.NewReader(content)
root := md.Parser().Parse(reader)
links := make(map[string]int)
ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
err := ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering && slices.Contains(linkKinds, n.Kind()) {
var target string
switch v := n.(type) {
Expand All @@ -55,6 +55,9 @@ func collectLinks(content []byte) map[string]int {
}
return ast.WalkContinue, nil
})
if err != nil {
slog.Error("Error walking note AST", slog.Any("error", err))
}
slog.Debug("Collected links", slog.Any("links", links))
return links
}
17 changes: 13 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

type Config struct {
ZettelkastenDirectory string `koanf:"zettelkasten_directory" validate:"requiredWithout:ZettelkastenGitURL"`
ZettelkastenGitURL string `koanf:"zettelkasten_git_url" validate:"requiredWithout:ZettelkastenDirectory" validate:"url/isURL"`
ZettelkastenGitURL string `koanf:"zettelkasten_git_url" validate:"requiredWithout:ZettelkastenDirectory|url"`
ZettelkastenGitBranch string `koanf:"zettelkasten_git_branch"`
ZettelkastenGitToken string `koanf:"zettelkasten_git_token"`
LogLevel slog.Level `koanf:"log_level"`
Expand All @@ -32,16 +32,19 @@ func LoadConfig() (Config, error) {
k := koanf.New(".")

// Set default values
k.Load(structs.Provider(Config{
err := k.Load(structs.Provider(Config{
LogLevel: slog.LevelInfo,
IgnoreFiles: []string{".git", ".obsidian", ".trash", "README.md"},
ZettelkastenGitBranch: "main",
CollectionInterval: time.Minute * 5,
CollectHistoricalMetrics: true,
}, "koanf"), nil)
if err != nil {
return Config{}, fmt.Errorf("error loading default config values: %w", err)
}

// Load env variables
k.Load(env.ProviderWithValue("", ".", func(key, value string) (string, interface{}) {
err = k.Load(env.ProviderWithValue("", ".", func(key, value string) (string, interface{}) {
key = strings.ToLower(key)
if key == "collection_interval" {
parsedValue, err := parseCollectionInterval(value)
Expand All @@ -53,10 +56,16 @@ func LoadConfig() (Config, error) {
}
return key, value
}), nil)
if err != nil {
return Config{}, fmt.Errorf("error loading env variables: %w", err)
}

// Unmarshal into config struct
var cfg Config
k.Unmarshal("", &cfg)
err = k.Unmarshal("", &cfg)
if err != nil {
return Config{}, fmt.Errorf("error unmarshalling config: %w", err)
}

// Validate config
v := validate.Struct(cfg)
Expand Down
2 changes: 1 addition & 1 deletion internal/zettelkasten/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (g GitZettelkasten) WalkHistory(walkFunc WalkFunc) error {
return nil
})
err = w.Reset(&git.ResetOptions{
Commit: *&originalHash,
Commit: originalHash,
Mode: git.HardReset,
})
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion cmd/zettelkasten-exporter/main.go → main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import (
func main() {
// Setup
cfg, err := config.LoadConfig()
slog.SetLogLoggerLevel(cfg.LogLevel)
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: cfg.LogLevel}))
slog.SetDefault(logger)
if err != nil {
slog.Error("Error loading config", slog.Any("error", err))
os.Exit(1)
Expand Down