diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6123ba7..7c20cda 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -21,6 +21,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Ensures all tags are fetched - name: Set up Go uses: actions/setup-go@v4 @@ -28,14 +30,14 @@ jobs: go-version: "stable" - name: Build - run: go build -v ./cmd/... + run: make - name: vet run: go vet ./... - - uses: dominikh/staticcheck-action@v1.2.0 + - uses: dominikh/staticcheck-action@v1 with: - version: "2022.1.1" + version: "latest" - name: gofmt uses: Jerome1337/gofmt-action@v1.0.4 @@ -45,5 +47,3 @@ jobs: - name: Revive Action uses: morphy2k/revive-action@v2.5.1 - - name: Tests - run: go test -v ./... diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37cc87b --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# This file is Free Software under the Apache-2.0 License +# without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +# +# SPDX-License-Identifier: Apache-2.0 +# +# SPDX-FileCopyrightText: 2025 German Federal Office for Information Security (BSI) +# Software-Engineering: 2025 Intevation GmbH + +.PHONY: all fakedoc createtemplate test + +all: fakedoc createtemplate test + +# See comment here (2024-11-15) +# https://github.com/gocsaf/csaf/blob/3093f717817b9369d390e56d1012eaedcfa19e32/Makefile#L40-L49 +GITDESC := $(shell git describe --tags --always) +GITDESCPATCH := $(shell echo '$(GITDESC)' | sed -E 's/v?[0-9]+\.[0-9]+\.([0-9]+)[-+]?.*/\1/') +SEMVERPATCH := $(shell echo $$(( $(GITDESCPATCH) + 1 ))) +# Hint: The second regexp in the next line only matches +# if there is a hyphen (`-`) followed by a number, +# by which we assume that git describe has added a string after the tag +SEMVER := $(shell echo '$(GITDESC)' | sed -E -e 's/^v//' -e 's/([0-9]+\.[0-9]+\.)([0-9]+)(-[1-9].*)/\1$(SEMVERPATCH)\3/' ) +testsemver: + @echo from \'$(GITDESC)\' transformed to \'$(SEMVER)\' + +LDFLAGS=-ldflags "-X github.com/gocsaf/fakedoc/pkg/fakedoc.SemVersion=$(SEMVER)" +GO_FLAGS=$(LDFLAGS) + +# Build for coverage profile generation +ifeq ($(BUILD_COVER), true) +GO_FLAGS += "-cover" +endif + + +fakedoc: build_pkg + cd cmd/fakedoc && go build $(GO_FLAGS) + +createtemplate: build_pkg + cd cmd/createtemplate && go build $(GO_FLAGS) + +build_pkg: + cd pkg && go build $(GO_FLAGS) ./... + +test: + go test ./... + diff --git a/cmd/fakedoc/main.go b/cmd/fakedoc/main.go index 38fe470..3fa56e1 100644 --- a/cmd/fakedoc/main.go +++ b/cmd/fakedoc/main.go @@ -13,7 +13,6 @@ import ( "bytes" "encoding/json" "errors" - "flag" "fmt" "io" "log" @@ -24,47 +23,9 @@ import ( "strings" "text/template" - "github.com/gocsaf/fakedoc/pkg/fakedoc" -) - -const ( - seedDocumentation = ` -random number seed, format 'pcg:<1-16 hex digits>:<1-16 hex digits>'. -If omitted, the generator uses a random seed. -` - - outputDocumentation = ` -output filename. Setting this will also set the tracking ID in the -generated file so that it matches the filename. The filename must end -with '.json' -` - - numOutputDocumentation = ` -How many documents to generate . If greate than 1, the output filename -must be given. It is treated as a template for filenames in which {{$}} -will be replaced with the number of the file, starting with 0. -` - - formattedDocumentation = ` -Output JSON should be formatted. -` + "github.com/jessevdk/go-flags" - limitsDocumentation = ` -Guidance on the Size of CSAF Documents. -` - - sizeFactorDocumentation = ` -Factor by which to multiply the maxima given in the limits file. -` - - forceMaxSizeDocumentation = ` -Try to force size of arrays to their maxiumum as defined in the limits -file and modified by the size factor. -` - - requireDocumentation = ` -Specifies with a regular expression what fields to force as required. -` + "github.com/gocsaf/fakedoc/pkg/fakedoc" ) func check(err error) { @@ -73,46 +34,53 @@ func check(err error) { } } -func main() { - var ( - templatefile string - limitsfile string - sizeFactor float64 - forceMaxSize bool - seed string - outputfile string - numOutputs int - formatted bool - requireRegex string - ) +type Options struct { + Version bool `long:"version" description:"Display version of the binary"` + TemplateFile string `long:"template" short:"t" description:"Template file"` + LimitsFile string `long:"limit-file" short:"l" description:"Guidance on the Size of CSAF Documents."` + SizeFactor float64 `long:"size" default:"0.00001" description:"Factor by which to multiply the maxima given in the limits file."` + ForceMaxSize bool `long:"force-max-size" description:"Try to force size of arrays to their maxiumum as defined in the limits file and modified by the size factor."` + Seed string `long:"seed" description:"random number seed, format 'pcg:<1-16 hex digits>:<1-16 hex digits>'. If omitted, the generator uses a random seed."` + OutputFile string `long:"output-file" short:"o" description:"Output filename. Setting this will also set the tracking ID in the generated file so that it matches the filename. The filename must end with '.json'"` + NumOutputs int `long:"num-outputs" short:"n" default:"1" description:"How many documents to generate . If greater than 1, the output filename must be given. It is treated as a template for filenames in which {{$}} will be replaced with the number of the file, starting with 0."` + Formatted bool `long:"format" short:"f" description:"Output JSON should be formatted."` + RequireRegex string `long:"require" description:"Specifies with a regular expression what fields to force as required."` +} - flag.StringVar(&templatefile, "template", "", "template file") - flag.StringVar(&limitsfile, "l", "", limitsDocumentation) - flag.Float64Var(&sizeFactor, "size", 0.00001, sizeFactorDocumentation) - flag.BoolVar(&forceMaxSize, "force-max-size", false, forceMaxSizeDocumentation) - flag.StringVar(&seed, "seed", "", seedDocumentation) - flag.StringVar(&outputfile, "o", "", outputDocumentation) - flag.IntVar(&numOutputs, "n", 1, numOutputDocumentation) - flag.BoolVar(&formatted, "f", false, formattedDocumentation) - flag.StringVar(&requireRegex, "require", "", requireDocumentation) +func main() { + var opts Options + parser := flags.NewParser(&opts, flags.Default) // Only used when compiled with 'profile' tag. - pf := addProfileFlags() - flag.Parse() + pf, err := addProfileFlags(parser) + check(err) + + _, err = parser.Parse() + if err != nil { + if flags.WroteHelp(err) { + os.Exit(0) + } + log.Fatal(err) + } + + if opts.Version { + fmt.Println(fakedoc.SemVersion) + return + } - if numOutputs > 1 && outputfile == "" { + if opts.NumOutputs > 1 && opts.OutputFile == "" { log.Fatal("Multiple outputs require an explicit output file template") } - rng, err := fakedoc.ParseSeed(seed) + rng, err := fakedoc.ParseSeed(opts.Seed) check(err) check(pf.profile(func() error { return generate( - templatefile, rng, - outputfile, limitsfile, - sizeFactor, forceMaxSize, - numOutputs, formatted, - requireRegex) + opts.TemplateFile, rng, + opts.OutputFile, opts.LimitsFile, + opts.SizeFactor, opts.ForceMaxSize, + opts.NumOutputs, opts.Formatted, + opts.RequireRegex) })) } diff --git a/cmd/fakedoc/no_profile.go b/cmd/fakedoc/no_profile.go index 06cc366..071cefe 100644 --- a/cmd/fakedoc/no_profile.go +++ b/cmd/fakedoc/no_profile.go @@ -10,12 +10,14 @@ package main +import "github.com/jessevdk/go-flags" + // profileFlags is empty. type profileFlags struct{} // addProfileFlags does nothing and returns nil. -func addProfileFlags() *profileFlags { - return nil +func addProfileFlags(_ *flags.Parser) (*profileFlags, error) { + return nil, nil } // profile only calls fn and returns its return value. diff --git a/cmd/fakedoc/profile.go b/cmd/fakedoc/profile.go index 05c2b7b..a596239 100644 --- a/cmd/fakedoc/profile.go +++ b/cmd/fakedoc/profile.go @@ -12,40 +12,34 @@ package main import ( "errors" - "flag" "os" "runtime" "runtime/pprof" -) -const ( - cpuProfileDocumentation = ` -Name of the profile file. If empty (default) no profile file is written. -` - memProfileDocumentation = ` -Name of the memory profile file. If empty (default) no memory profile file is written. -` + "github.com/jessevdk/go-flags" ) type profileFlags struct { - // cpuProfile is the file name of the cpu profile. - cpuProfile string - // memProfile is the file name of the memory profile. - memProfile string + // CpuProfile is the file name of the cpu profile. + CpuProfile string `long:"cpuprofile" description:"Name of the profile file. If empty (default) no profile file is written."` + // MemProfile is the file name of the memory profile. + MemProfile string `long:"memprofile" description:"Name of the memory profile file. If empty (default) no memory profile file is written."` } // addProfileFlags adds flags for the profiler to the command line parser. -func addProfileFlags() *profileFlags { +func addProfileFlags(parser *flags.Parser) (*profileFlags, error) { pf := profileFlags{} - flag.StringVar(&pf.cpuProfile, "cpuprofile", "", cpuProfileDocumentation) - flag.StringVar(&pf.memProfile, "memprofile", "", memProfileDocumentation) - return &pf + _, err := parser.AddGroup("Profile flags", "Configuration for profile collection", &pf) + if err != nil { + return nil, err + } + return &pf, nil } // profile create cpu and/or mery profile files for the given function. func (pf *profileFlags) profile(fn func() error) error { - if pf.cpuProfile != "" { - f, err := os.Create(pf.cpuProfile) + if pf.CpuProfile != "" { + f, err := os.Create(pf.CpuProfile) if err != nil { return err } @@ -56,8 +50,8 @@ func (pf *profileFlags) profile(fn func() error) error { defer pprof.StopCPUProfile() } ret := fn() - if pf.memProfile != "" { - f, err := os.Create(pf.memProfile) + if pf.MemProfile != "" { + f, err := os.Create(pf.MemProfile) if err != nil { return errors.Join(ret, err) } diff --git a/go.mod b/go.mod index f8563e9..b21a5cd 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.23 require ( github.com/BurntSushi/toml v1.4.0 + github.com/go-loremipsum/loremipsum v1.1.3 + github.com/jessevdk/go-flags v1.6.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 ) -require github.com/go-loremipsum/loremipsum v1.1.3 +require golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 34311fb..7494db4 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,15 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-loremipsum/loremipsum v1.1.3 h1:ZRhA0ZmJ49lGe5HhWeMONr+iGftWDsHfrYBl5ktDXso= github.com/go-loremipsum/loremipsum v1.1.3/go.mod h1:OJQjXdvwlG9hsyhmMQoT4HOm4DG4l62CYywebw0XBoo= +github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= +github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= 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/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/fakedoc/version.go b/pkg/fakedoc/version.go new file mode 100644 index 0000000..ae37577 --- /dev/null +++ b/pkg/fakedoc/version.go @@ -0,0 +1,13 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2024 German Federal Office for Information Security (BSI) +// Software-Engineering: 2024 Intevation GmbH + +package fakedoc + +// SemVersion the version in semver.org format, MUST be overwritten during +// the linking stage of the build process +var SemVersion = "0.0.0"