Skip to content
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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG GO_VERSION=1.26.3
ARG ALPINE_VERSION=3.22
ARG ALPINE_VERSION=3.23

FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION}

Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.9'

services:

switcher-gitops:
switcher-client-go:
build:
context: .
dockerfile: Dockerfile
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: test cover cover-html lint lint-install
.PHONY: test fmt cover cover-html lint lint-install

GOLANGCI_LINT_VERSION=v2.12.2

Expand All @@ -8,6 +8,9 @@ test-clean:
test:
go test -p 1 -v ./...

fmt:
gofmt -s -w .

lint-install:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)

Expand Down
51 changes: 51 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,42 @@ func (c *Client) SnapshotVersion() int {
return c.snapshot.Domain.Version
}

func CheckSnapshot() (bool, error) {
return defaultClient().CheckSnapshot()
}

func (c *Client) CheckSnapshot() (bool, error) {
token, err := c.ensureToken()
if err != nil {
return false, err
}

if err := missingTokenError(token); err != nil {
return false, err
}

upToDate, err := c.checkSnapshotVersion(token, c.SnapshotVersion())
if err != nil {
return false, err
}

if upToDate {
return false, nil
}

snapshot, err := c.resolveSnapshot(token)
if err != nil {
return false, err
}

if err := saveSnapshotToFile(c.Context(), snapshot); err != nil {
return false, err
}

c.setSnapshot(snapshot)
return true, nil
}

func (c *Client) snapshotState() *Snapshot {
c.mu.RLock()
defer c.mu.RUnlock()
Expand All @@ -154,6 +190,21 @@ func (c *Client) stopBackgroundTasks() {
c.UnwatchSnapshot()
}

func (c *Client) shouldCheckSnapshot(fetchRemote bool) bool {
ctx := c.Context()
return c.SnapshotVersion() == 0 && (fetchRemote || !ctx.Options.Local)
}

func (c *Client) loadSnapshotFromCurrentFile() (*Snapshot, error) {
snapshot, err := loadSnapshotFromFile(c.Context())
if err != nil {
return nil, err
}

c.setSnapshot(snapshot)
return snapshot, nil
}

func defaultClient() *Client {
if client := globalClient.Load(); client != nil {
return client
Expand Down
187 changes: 0 additions & 187 deletions local_test.go
Original file line number Diff line number Diff line change
@@ -1,199 +1,12 @@
package client

import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestSnapshotLoading(t *testing.T) {
t.Run("should load snapshot version from a local file", func(t *testing.T) {
BuildContext(Context{
Domain: "My Domain",
Options: ContextOptions{
Local: true,
SnapshotLocation: snapshotFixtureDir(),
},
})

assert.Equal(t, 0, SnapshotVersion())

version, err := LoadSnapshot(nil)

assert.NoError(t, err)
assert.Equal(t, 1, version)
assert.Equal(t, 1, SnapshotVersion())
})

t.Run("should return an error when the snapshot file is malformed", func(t *testing.T) {
BuildContext(Context{
Domain: "My Domain",
Options: ContextOptions{
Local: true,
SnapshotLocation: snapshotFixtureDir(),
},
Environment: "default_malformed",
})

version, err := LoadSnapshot(nil)

assert.Error(t, err)
assert.Zero(t, version)
})

t.Run("should return an error when the snapshot file is not accessible", func(t *testing.T) {
snapshotLocation := filepath.Join(t.TempDir(), "snapshot-location-file")
writeErr := os.WriteFile(snapshotLocation, []byte("not-a-directory"), 0o644)
assert.NoError(t, writeErr)

BuildContext(Context{
Domain: "My Domain",
Options: ContextOptions{
Local: true,
SnapshotLocation: snapshotLocation,
},
Environment: "default",
})

version, err := LoadSnapshot(nil)

assert.Error(t, err)
assert.Zero(t, version)
})

t.Run("should return an error when the snapshot file is not accessible while saving remote updates", func(t *testing.T) {
snapshotDir := t.TempDir()
writeSnapshotFixture(t, snapshotDir, "default_load_1", "default_load_1")

server := newSnapshotTestServer(t, snapshotRemoteHandlers{
authStatus: http.StatusOK,
authBody: map[string]any{"token": "[token]", "exp": time.Now().Add(time.Hour).Unix()},
snapshotCheckStatus: http.StatusOK,
snapshotCheckBody: map[string]any{"status": false},
resolveStatus: http.StatusOK,
resolveDomain: loadSnapshotFixture(t, "default_load_2"),
})
defer server.Close()

BuildContext(Context{
Domain: "My Domain",
URL: server.URL,
APIKey: "[YOUR_API_KEY]",
Component: "MyApp",
Environment: "default_load_1",
Options: ContextOptions{
Local: true,
SnapshotLocation: snapshotDir,
},
})

version, loadErr := LoadSnapshot(nil)
assert.NoError(t, loadErr)
assert.Equal(t, 1588557288040, version)

removeErr := os.RemoveAll(snapshotDir)
assert.NoError(t, removeErr)
blockErr := os.WriteFile(snapshotDir, []byte("not-a-directory"), 0o644)
assert.NoError(t, blockErr)

updated, err := CheckSnapshot()

assert.Error(t, err)
assert.False(t, updated)
})

t.Run("should return an error when the snapshot file path cannot be created", func(t *testing.T) {
BuildContext(Context{
Domain: "My Domain",
Options: ContextOptions{
Local: true,
SnapshotLocation: t.TempDir(),
},
Environment: filepath.Join("nested", "missing"),
})

version, err := LoadSnapshot(nil)

assert.Error(t, err)
assert.Zero(t, version)
})

t.Run("should return an error when check snapshot fails during load snapshot", func(t *testing.T) {
server := newSnapshotTestServer(t, snapshotRemoteHandlers{
authStatus: http.StatusOK,
authBody: map[string]any{"token": "[token]", "exp": time.Now().Add(time.Hour).Unix()},
snapshotCheckStatus: http.StatusInternalServerError,
snapshotCheckBody: map[string]any{"status": false},
})
defer server.Close()

BuildContext(Context{
Domain: "My Domain",
URL: server.URL,
APIKey: "[YOUR_API_KEY]",
Component: "MyApp",
Options: ContextOptions{
Local: false,
},
})

version, err := LoadSnapshot(nil)

assert.Error(t, err)
assert.Zero(t, version)
assert.EqualError(t, err, "[check_snapshot_version] failed with status: 500")
})

t.Run("should return an error when watch snapshot fails during load snapshot", func(t *testing.T) {
BuildContext(Context{
Domain: "My Domain",
Options: ContextOptions{
Local: true,
},
})

version, err := LoadSnapshot(&LoadSnapshotOptions{
WatchSnapshot: true,
})

assert.Error(t, err)
assert.Zero(t, version)
assert.EqualError(t, err, "snapshot location is not defined in the context options")
})

t.Run("should create a clean snapshot when no file exists", func(t *testing.T) {
snapshotDir := t.TempDir()

BuildContext(Context{
Domain: "My Domain",
Environment: "generated-clean",
Options: ContextOptions{
Local: true,
SnapshotLocation: snapshotDir,
},
})

version, err := LoadSnapshot(nil)

assert.NoError(t, err)
assert.Equal(t, 0, version)
assert.Equal(t, 0, SnapshotVersion())

content, readErr := os.ReadFile(filepath.Join(snapshotDir, "generated-clean.json"))
assert.NoError(t, readErr)

var snapshot Snapshot
unmarshalErr := json.Unmarshal(content, &snapshot)
assert.NoError(t, unmarshalErr)
assert.Equal(t, 0, snapshot.Domain.Version)
})
}

func TestSwitcherLocalEvaluation(t *testing.T) {
t.Run("should use local snapshot to evaluate a switcher without strategies", func(t *testing.T) {
useLocalSnapshotFixture(t, "default")
Expand Down
Loading
Loading