diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3e8c2cd..0823625 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,31 +4,32 @@ on: push: branches: - '**' - # - $default-branch pull_request: branches: - '**' - # - $default-branch jobs: build: + runs-on: ubuntu-latest env: COVER: true - runs-on: ubuntu-20.04 + steps: - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Set up Go 1.17 - uses: actions/setup-go@v2 + - name: Set up Go + uses: actions/setup-go@v5 with: - go-version: 1.17.x + go-version-file: reference-gen/go.mod id: go - name: Get dependencies + env: + GOLANGCI_LINT_VERSION: v1.63.4 run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.36.0 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter chmod +x ./cc-test-reporter diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f3aa8cf..e85cf83 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,32 +15,22 @@ jobs: strategy: fail-fast: false - # CodeQL runs on ubuntu-latest and windows-latest runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: go # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -54,4 +44,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9a21b54..5df173b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.' diff --git a/reference-gen/go.mod b/reference-gen/go.mod index f935716..3d85415 100644 --- a/reference-gen/go.mod +++ b/reference-gen/go.mod @@ -1,11 +1,12 @@ module github.com/oauth2-proxy/tools/reference-gen -go 1.17 +go 1.22 require ( github.com/go-git/go-git/v5 v5.4.2 github.com/onsi/ginkgo v1.14.1 github.com/onsi/gomega v1.10.2 + github.com/sergi/go-diff v1.1.0 github.com/spf13/pflag v1.0.5 k8s.io/gengo v0.0.0-20201113003025-83324d819ded k8s.io/klog/v2 v2.4.0 @@ -15,7 +16,6 @@ require ( github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-logr/logr v0.2.0 // indirect github.com/nxadm/tail v1.4.4 // indirect - github.com/sergi/go-diff v1.1.0 // indirect golang.org/x/net v0.0.0-20210326060303-6b1517762897 // indirect golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 // indirect golang.org/x/text v0.3.3 // indirect diff --git a/reference-gen/pkg/generator/generator.go b/reference-gen/pkg/generator/generator.go index 151bfb5..31d5fe8 100644 --- a/reference-gen/pkg/generator/generator.go +++ b/reference-gen/pkg/generator/generator.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" "os" "path/filepath" "text/template" @@ -68,7 +67,7 @@ func loadHeaderText(fileName string) ([]byte, error) { return []byte{}, nil } - headerText, err := ioutil.ReadFile(fileName) + headerText, err := os.ReadFile(fileName) if err != nil { return nil, fmt.Errorf("error reading file: %v", err) } @@ -184,7 +183,7 @@ func (g *generator) writeOutput(content []byte) error { return nil } - if err := ioutil.WriteFile(g.outputFileName, content, 0600); err != nil { + if err := os.WriteFile(g.outputFileName, content, 0600); err != nil { return fmt.Errorf("could not write file %q: %v", g.outputFileName, err) } klog.Infof("Rendered output written to %q", g.outputFileName) diff --git a/reference-gen/pkg/generator/generator_test.go b/reference-gen/pkg/generator/generator_test.go index 554d469..e3b89f2 100644 --- a/reference-gen/pkg/generator/generator_test.go +++ b/reference-gen/pkg/generator/generator_test.go @@ -4,7 +4,6 @@ import ( "bytes" "embed" "fmt" - "io/ioutil" "os" "strings" @@ -16,7 +15,7 @@ import ( ) const ( - testDataPackage = "github.com/oauth2-proxy/tools/reference-gen/pkg/generator/testdata" + testDataPackage = "github.com/oauth2-proxy/tools/reference-gen/pkg/generator/testdata/" ) //go:embed testdata/*.md @@ -29,35 +28,37 @@ var _ = Describe("Generator", func() { expectedOutputFileName string } - DescribeTable("should generate the expected output", func(in generatorTableInput) { - By("Creating an output file") - outputFile, err := ioutil.TempFile("", "oauth2-proxy-reference-generator-suite-") - Expect(err).ToNot(HaveOccurred()) - - outputFileName := outputFile.Name() - Expect(outputFile.Close()).To(Succeed()) - - By("Constructing the generator") - gen, err := NewGenerator(testDataPackage, in.requestedTypes, in.headerFileName, outputFileName, "") - Expect(err).ToNot(HaveOccurred()) - - By("Running the generator") - Expect(gen.Run()).To(Succeed()) - - By("Loading the output") - output, err := os.ReadFile(outputFileName) - Expect(err).ToNot(HaveOccurred()) - - By("Loading the expected output") - expectedOutput, err := testOutputs.ReadFile(in.expectedOutputFileName) - Expect(err).ToNot(HaveOccurred()) - - By("Comparing the outputs") - diffs := diff.Do(string(expectedOutput), string(output)) - if len(diffs) > 1 { - // A single diff means the two files are equal, only fail if there is more than one diff. - fmt.Printf("\nUnexpected diff:\n\n%s\n", prettyPrintDiff(diffs)) - Fail("Unexpected diff in generated output") + DescribeTable("should generate the expected output for json & yaml tags", func(in generatorTableInput) { + for _, pkg := range []string{"json", "yaml"} { + By(pkg + ": Creating an output file") + outputFile, err := os.CreateTemp("", pkg+"-oauth2-proxy-reference-generator-suite-") + Expect(err).ToNot(HaveOccurred()) + + outputFileName := outputFile.Name() + Expect(outputFile.Close()).To(Succeed()) + + By(pkg + ": Constructing the generator") + gen, err := NewGenerator(testDataPackage+pkg, in.requestedTypes, in.headerFileName, outputFileName, "") + Expect(err).ToNot(HaveOccurred()) + + By(pkg + ": Running the generator") + Expect(gen.Run()).To(Succeed()) + + By(pkg + ": Loading the output") + output, err := os.ReadFile(outputFileName) + Expect(err).ToNot(HaveOccurred()) + + By(pkg + ": Loading the expected output") + expectedOutput, err := testOutputs.ReadFile(in.expectedOutputFileName) + Expect(err).ToNot(HaveOccurred()) + + By(pkg + ": Comparing the outputs") + diffs := diff.Do(string(expectedOutput), string(output)) + if len(diffs) > 1 { + // A single diff means the two files are equal, only fail if there is more than one diff. + fmt.Printf("\n%s: Unexpected diff:\n\n%s\n", pkg, prettyPrintDiff(diffs)) + Fail(pkg + ": Unexpected diff in generated output") + } } }, Entry("With the full test structure, pulls in references for all substructs", generatorTableInput{ diff --git a/reference-gen/pkg/generator/testdata/fullMyTestStruct.md b/reference-gen/pkg/generator/testdata/fullMyTestStruct.md index bf00914..801df77 100644 --- a/reference-gen/pkg/generator/testdata/fullMyTestStruct.md +++ b/reference-gen/pkg/generator/testdata/fullMyTestStruct.md @@ -9,10 +9,10 @@ members table as the origin struct. | Field | Type | Description | | ----- | ---- | ----------- | -| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a json tag, so the name will be capitalised. | +| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a tag, so the name will be capitalised. | ### AliasedExternalMap -#### (`map[string]interface{}` alias) +#### (`map[string]any` alias) (**Appears on:** [MyTestStruct](#myteststruct)) @@ -77,4 +77,4 @@ SomeSubStruct is a struct to go within another struct. | Field | Type | Description | | ----- | ---- | ----------- | -| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a json tag, so the name will be capitalised. | +| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a tag, so the name will be capitalised. | diff --git a/reference-gen/pkg/generator/testdata/types.go b/reference-gen/pkg/generator/testdata/json/types.go similarity index 97% rename from reference-gen/pkg/generator/testdata/types.go rename to reference-gen/pkg/generator/testdata/json/types.go index cafbb07..e40464b 100644 --- a/reference-gen/pkg/generator/testdata/types.go +++ b/reference-gen/pkg/generator/testdata/json/types.go @@ -1,4 +1,4 @@ -package testdata +package json import ( "text/template" @@ -63,7 +63,7 @@ type MyTestStruct struct { // SomeSubStruct is a struct to go within another struct. type SomeSubStruct struct { - // NonTaggedField doesn't have a json tag, so the name will be capitalised. + // NonTaggedField doesn't have a tag, so the name will be capitalised. NonTaggedField bool // privateStruct should not be included in the docs. diff --git a/reference-gen/pkg/generator/testdata/someSubStructOnly.md b/reference-gen/pkg/generator/testdata/someSubStructOnly.md index 753d788..ea39da4 100644 --- a/reference-gen/pkg/generator/testdata/someSubStructOnly.md +++ b/reference-gen/pkg/generator/testdata/someSubStructOnly.md @@ -6,4 +6,4 @@ SomeSubStruct is a struct to go within another struct. | Field | Type | Description | | ----- | ---- | ----------- | -| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a json tag, so the name will be capitalised. | +| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a tag, so the name will be capitalised. | diff --git a/reference-gen/pkg/generator/testdata/someSubStructWithHeader.md b/reference-gen/pkg/generator/testdata/someSubStructWithHeader.md index 27997d4..e031575 100644 --- a/reference-gen/pkg/generator/testdata/someSubStructWithHeader.md +++ b/reference-gen/pkg/generator/testdata/someSubStructWithHeader.md @@ -10,4 +10,4 @@ SomeSubStruct is a struct to go within another struct. | Field | Type | Description | | ----- | ---- | ----------- | -| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a json tag, so the name will be capitalised. | +| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a tag, so the name will be capitalised. | diff --git a/reference-gen/pkg/generator/testdata/unrelatedStructs.md b/reference-gen/pkg/generator/testdata/unrelatedStructs.md index 51a5c6c..e71fe1f 100644 --- a/reference-gen/pkg/generator/testdata/unrelatedStructs.md +++ b/reference-gen/pkg/generator/testdata/unrelatedStructs.md @@ -18,4 +18,4 @@ SomeSubStruct is a struct to go within another struct. | Field | Type | Description | | ----- | ---- | ----------- | -| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a json tag, so the name will be capitalised. | +| `NonTaggedField` | _bool_ | NonTaggedField doesn't have a tag, so the name will be capitalised. | diff --git a/reference-gen/pkg/generator/testdata/yaml/types.go b/reference-gen/pkg/generator/testdata/yaml/types.go new file mode 100644 index 0000000..ceb1789 --- /dev/null +++ b/reference-gen/pkg/generator/testdata/yaml/types.go @@ -0,0 +1,98 @@ +package types + +import ( + "text/template" + "time" +) + +// MyTestStruct contains a collection of fields all attempting to test various +// aspects of the code generation. +type MyTestStruct struct { + // Name is the name of the MyTestStruct. + Name string `yaml:"name"` + + // privateField is a private field and so should not be documented + // in the generated docs. + privateField string + + // LongMessageInt has a very long message, very very very very very very + // very very very very very very very very very very very very very very + // very very very very very very very very very very very very very very + // very very very very very very very very very very very very very very + // very very very very very very very very very very very very very very + // long message attached to the top of it. + // This should prove how the generator handles long doc strings. + LongMessageInt int `yaml:"longMessageInt"` + + // SubStruct is a struct referenced from within the parent struct. + // This should get its own section in the referenced docs. + SubStruct SomeSubStruct `yaml:"subStruct"` + + // SubStructMap is a map of a known struct type. + SubStructMap map[string]SomeSubStruct `yaml:"subStructMap"` + + // AnEmbeddedStruct is embedded here. + AnEmbeddedStruct + + // AliasedDuration is a type alias to a duration. + AliasedDuration MyDuration `yaml:"aliasedDuration"` + + // AliasDurationString is a type alias to a duration that should be documented + // as a string type. + AliasedDurationString MyDurationString `yaml:"aliasedDurationString"` + + // PointerString shows that the docs gen strips the pointer (*) from the beginning + // of the type when documented. + PointerString *string `yaml:"pointerString"` + + // Private should be included as a new struct, but without any documented members. + Private PrivateMembers `yaml:"private"` + + // AliasedStruct is a type aliased struct + AliasedStruct AliasSubStruct `yaml:"aliasedStruct"` + + // ExternalMap references and external map type outisde of the package. + ExternalMap template.FuncMap `yaml:"externalMap"` + + // AliasExternalMap references an external map type outside of the package via an alias. + AliasExternalMap AliasedExternalMap `yaml:"aliasExternalMap"` + + // Bytes is a slice of raw byte data. + Bytes []byte `yaml:"bytes"` +} + +// SomeSubStruct is a struct to go within another struct. +type SomeSubStruct struct { + // NonTaggedField doesn't have a tag, so the name will be capitalised. + NonTaggedField bool + + // privateStruct should not be included in the docs. + privateStruct PrivateMembers +} + +// AliasSubStruct is an aliased struct, it will be added to the documentation with an identical +// members table as the origin struct. +type AliasSubStruct SomeSubStruct + +// AnEmbeddedStruct gets embedded within other structures. +type AnEmbeddedStruct struct { + // EmbeddedDuration is a duration within an embedded struct. + EmbeddedDuration time.Duration `yaml:"embeddedDuration"` +} + +// PrivateMembers only has private members so when documented, should not have a members table printed. +type PrivateMembers struct { + privateInt int64 + privateBool bool + privateBytes []byte +} + +// MyDuration is an alias to a duration. +type MyDuration time.Duration + +// MyDuration is an alias to a duration with the type overridden as a string +// +reference-gen:alias-name=string +type MyDurationString time.Duration + +// AliasedExternalMap is an alias type for a map type outside of the package. +type AliasedExternalMap template.FuncMap diff --git a/reference-gen/pkg/generator/utils.go b/reference-gen/pkg/generator/utils.go index ec59f54..898e9c6 100644 --- a/reference-gen/pkg/generator/utils.go +++ b/reference-gen/pkg/generator/utils.go @@ -145,11 +145,19 @@ func fieldEmbedded(m types.Member) bool { // fieldName extracts the field name from the json tag func fieldName(m types.Member) string { - v := reflect.StructTag(m.Tags).Get("json") - v = strings.Split(v, ",")[0] - if v != "" { - return v + vj := reflect.StructTag(m.Tags).Get("json") + vj = strings.Split(vj, ",")[0] + + if vj != "" { + return vj + } + + vy := reflect.StructTag(m.Tags).Get("yaml") + vy = strings.Split(vy, ",")[0] + if vy != "" { + return vy } + return m.Name }