Skip to content
This repository was archived by the owner on Jan 30, 2026. It is now read-only.
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
13 changes: 8 additions & 5 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ linters:
- unparam
- whitespace
linters-settings:
stylecheck:
dot-import-whitelist:
- github.com/onsi/gomega
- github.com/onsi/ginkgo
- github.com/onsi/ginkgo/v2
revive:
rules:
- name: dot-imports
arguments:
- allowedPackages:
- github.com/onsi/gomega
- github.com/onsi/ginkgo
- github.com/onsi/ginkgo/v2
goimports:
local-prefixes: github.com/opdev/productctl
depguard:
Expand Down
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require (
dario.cat/mergo v1.0.2
github.com/Khan/genqlient v0.8.0
github.com/invopop/jsonschema v0.13.0
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.36.3
github.com/opdev/discover-workload v0.0.0-20250115205614-3233d42da6d9
github.com/spf13/cobra v1.9.1
sigs.k8s.io/yaml v1.4.0
Expand All @@ -14,11 +16,20 @@ require (
require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/vektah/gqlparser/v2 v2.5.22 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
35 changes: 32 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,38 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/opdev/discover-workload v0.0.0-20250115205614-3233d42da6d9 h1:sLy7Kdnp5w+0GVQ6rYjLTpGR+kUCarfFnPcolLqJTKo=
github.com/opdev/discover-workload v0.0.0-20250115205614-3233d42da6d9/go.mod h1:pYjtJEWZgLGKOM1/4aCxHVf297u00qJN/KMNN9uWMtM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
Expand All @@ -40,8 +56,21 @@ github.com/vektah/gqlparser/v2 v2.5.22 h1:yaaeJ0fu+nv1vUMW0Hl+aS1eiv1vMfapBNjpff
github.com/vektah/gqlparser/v2 v2.5.22/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
Expand Down
15 changes: 11 additions & 4 deletions internal/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"log/slog"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -42,6 +43,8 @@ func (w *LazyOverwriter) logger() *slog.Logger {
return w.OptionalLogger
}

// nameGenFn returns the in-instance BackupFilenameGenFn, or a default if
// none has been provided.
func (w *LazyOverwriter) nameGenFn() BackupNameGenerator {
if w.BackupFilenameGenFn == nil {
return prependWithSecondsSinceEpoch
Expand All @@ -50,6 +53,9 @@ func (w *LazyOverwriter) nameGenFn() BackupNameGenerator {
return w.BackupFilenameGenFn
}

// CreateBackup will create a backup file for the given w.Filename using
// w.BackupFilenameGenFn to produce the output file. Filenames can be full paths. This
// function will ensure that only basenames are passed to BackupFilenameGenFn.
func (w *LazyOverwriter) CreateBackup() error {
newNameFn := w.nameGenFn()

Expand All @@ -58,7 +64,8 @@ func (w *LazyOverwriter) CreateBackup() error {
return err
}

newName := newNameFn(w.Filename)
newBaseName := newNameFn(filepath.Base(w.Filename))
newName := filepath.Join(filepath.Dir(w.Filename), newBaseName)
w.logger().Debug("writing backup file before overwriting original", "backup", newName, "original", w.Filename)
newFile, err := os.OpenFile(newName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
Expand Down Expand Up @@ -100,6 +107,6 @@ func prependWithSecondsSinceEpoch(s string) string {
// Ensure this meets the function signature over time.
var _ BackupNameGenerator = prependWithSecondsSinceEpoch

// BackupNameGenerator describes the function to accept an originalName and
// produce a new Name.
type BackupNameGenerator = func(originalName string) (newName string)
// BackupNameGenerator describes the function to accept an originalBaseName and
// produce a newBaseName.
type BackupNameGenerator = func(originalBaseName string) (newBaseName string)
13 changes: 13 additions & 0 deletions internal/file/file_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package file_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestFile(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "File Suite")
}
98 changes: 98 additions & 0 deletions internal/file/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package file

import (
"fmt"
"os"
"path/filepath"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("File", func() {
When("using the LazyOverwriter", func() {
var (
workingDirectory string
targetFileName string
)

BeforeEach(func() {
var err error
workingDirectory, err = os.MkdirTemp("", "workingdir-file_test.go-*")
Expect(err).ToNot(HaveOccurred())

targetFile, err := os.CreateTemp(workingDirectory, "target-file-file_test.go-*")
Expect(err).ToNot(HaveOccurred())
targetFileName = targetFile.Name()
targetFile.Close()
})

AfterEach(func() {
os.RemoveAll(workingDirectory)
})

When("the file contains data", func() {
var originalFileContent string

BeforeEach(func() {
originalFileContent = "olddata"
f, err := os.Create(targetFileName)
Expect(err).ToNot(HaveOccurred())
defer f.Close()
written, err := f.Write([]byte(originalFileContent))
Expect(err).ToNot(HaveOccurred())
Expect(written).ToNot(BeZero())
})

It("should not truncate the data at instantiation of the LazyOverwriter", func() {
_ = &LazyOverwriter{Filename: targetFileName}

content, err := os.ReadFile(targetFileName)
Expect(err).ToNot(HaveOccurred())
Expect(len(content)).ToNot(BeZero())
Expect(string(content)).To(Equal(originalFileContent))
})

It("should truncate the data when Write is called", func() {
lw := &LazyOverwriter{Filename: targetFileName}

newFileContent := "newdata"
written, err := lw.Write([]byte(newFileContent))
Expect(err).ToNot(HaveOccurred())
Expect(written).ToNot(BeZero())

content, err := os.ReadFile(targetFileName)
Expect(err).ToNot(HaveOccurred())
Expect(len(content)).ToNot(BeZero())
Expect(string(content)).To(Equal(newFileContent))
})

When("the backup flag is enabled", func() {
When("writing new content", func() {
It("should produce a backup before overwriting", func() {
newNameFn := func(s string) string {
return fmt.Sprintf("GENBACKUP-%s", s)
}

lw := &LazyOverwriter{
Filename: targetFileName,
DoBackup: true,
BackupFilenameGenFn: newNameFn,
}

written, err := lw.Write([]byte("newdata"))
Expect(err).ToNot(HaveOccurred())
Expect(written).ToNot(BeZero())

expectedBackupFilePath := filepath.Join(workingDirectory, newNameFn(filepath.Base(targetFileName)))
Expect(expectedBackupFilePath).To(BeAnExistingFile())

content, err := os.ReadFile(expectedBackupFilePath)
Expect(err).ToNot(HaveOccurred())
Expect(string(content)).To(Equal(originalFileContent))
})
})
})
})
})
})