diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..0136e2b --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,56 @@ +name: ci + +on: + push: + branches: + - master + - release-* + tags: + - "*" + pull_request: {} + workflow_dispatch: {} + +permissions: + contents: write + # packages: write + # issues: write + +env: + GO_VERSION: '1.21.6' + GOLANGCI_VERSION: 'v1.55.2' + GORELEASER_VERSION: 'v1.23.0' + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + distribution: goreleaser + version: ${{ env.GORELEASER_VERSION }} + args: build --clean --snapshot + - name: Publish artifacts to Github + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist + lint: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + - name: Lint + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3 + with: + version: ${{ env.GOLANGCI_VERSION }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..b02ea9f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,43 @@ +name: release + +on: + push: + tags: + - "*" + +permissions: + contents: write + +env: + GO_VERSION: '1.21.6' + GOLANGCI_VERSION: 'v1.55.2' + GORELEASER_VERSION: 'v1.23.0' + +jobs: + goreleaser: + needs: + - build + - lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + distribution: goreleaser + version: ${{ env.GORELEASER_VERSION }} + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish artifacts to Github + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist diff --git a/.gitignore b/.gitignore index 598ae79..180ea8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /.vscode +/dist diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..f7dfcfa --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,49 @@ +project_name: crossplanereleaser +builds: + - id: crossplanereleaser + env: + - CGO_ENABLED=0 + goos: + - linux + # - darwin + goarch: + - amd64 + # - arm64 + main: ./cmd/crossplanereleaser + +archives: + - id: crossplanereleaser + format: tar.gz + name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}' + builds: + - crossplanereleaser + wrap_in_directory: true + strip_parent_binary_folder: true + files: + - LICENSE.txt + - README.md + +checksum: + algorithm: sha256 + name_template: 'checksums.txt' + ids: + - crossplanereleaser + +release: + mode: keep-existing + github: + owner: mistermx + name: crossplanereleaser + +changelog: + use: git + groups: + - title: New Features + regexp: '^[\w\d]+\sfeat(\([\w-_\d]+\))?!?:.*$' + order: 0 + - title: Bug fixes + regexp: '^[\w\d]+\sfix(\([\w-_\d]+\))?!?:.*$' + order: 1 + - title: Others + regexp: '^[\w\d]+\s(build|chore|ci|docs|style|refactor|perf|test)(\([\w-_\d]+\))?!?:.*$' + order: 999 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index b765b3d..0ca21ce 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # XPReleaser -Like [Goreleaser](github.com/goreleaser/goreleaser) but for Crossplane packages. +Like [Goreleaser](https://github.com/goreleaser/goreleaser) but for Crossplane packages. diff --git a/cmd/crossplanereleaser/build.go b/cmd/crossplanereleaser/build.go new file mode 100644 index 0000000..13dc1c9 --- /dev/null +++ b/cmd/crossplanereleaser/build.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "path/filepath" + + "github.com/pkg/errors" + "github.com/spf13/afero" + + v1 "github.com/mistermx/crossplanereleaser/config/v1" + "github.com/mistermx/crossplanereleaser/internal/build" + "github.com/mistermx/crossplanereleaser/internal/git" +) + +type buildCmd struct { + git git.Client + builder build.BuilderBackend +} + +func (c *buildCmd) BeforeApply() error { + c.git = git.NewGitCLIBackend() + c.builder = build.NewCrankCLIBackend() + return nil +} + +func (c *buildCmd) Run(fsys afero.Fs) error { + ctx := context.Background() + + cfg, err := getConfig(fsys, c.git) + if err != nil { + return err + } + return c.buildPackages(ctx, fsys, cfg) +} + +func (c *buildCmd) buildPackages(ctx context.Context, fsys afero.Fs, cfg *v1.Config) error { + for _, pkgCfg := range cfg.XPackages { + buildCfg := &build.PackageBuildConfig{ + PackageDir: pkgCfg.Dir, + ExamplesDir: pkgCfg.Examples, + OutputPath: getPackageOutputPath(cfg, &pkgCfg), + } + err := c.builder.BuildPackage(ctx, buildCfg) + if err != nil { + return errors.Wrapf(err, "cannot build package %q", pkgCfg.ID) + } + } + return nil +} + +func getPackageOutputPath(cfg *v1.Config, pkgCfg *v1.XPackageConfig) string { + return filepath.Join(cfg.Dist, pkgCfg.ID, pkgCfg.NameTemplate) +} diff --git a/cmd/crossplanereleaser/config.go b/cmd/crossplanereleaser/config.go new file mode 100644 index 0000000..8d0bb78 --- /dev/null +++ b/cmd/crossplanereleaser/config.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/pkg/errors" + "github.com/spf13/afero" + + v1 "github.com/mistermx/crossplanereleaser/config/v1" + "github.com/mistermx/crossplanereleaser/internal/config" + "github.com/mistermx/crossplanereleaser/internal/git" +) + +func getConfig(fsys afero.Fs, g git.Client) (*v1.Config, error) { + cfgFileName, err := config.FindConfigFile(fsys) + if err != nil { + return nil, errors.Wrap(err, "cannot find config file") + } + cfg, err := config.Parse(fsys, cfgFileName) + if err != nil { + return nil, errors.Wrap(err, "cannot parse config file") + } + props, err := config.BuildProjectProperties(g, cfg) + if err != nil { + return nil, errors.Wrap(err, "cannot setup project properties") + } + + if err := config.RenderConfigTemplates(cfg, props); err != nil { + return nil, errors.Wrap(err, "cannot render config template fields") + } + return cfg, nil +} diff --git a/cmd/xpreleaser/main.go b/cmd/crossplanereleaser/main.go similarity index 68% rename from cmd/xpreleaser/main.go rename to cmd/crossplanereleaser/main.go index 5ccf388..36fed13 100644 --- a/cmd/xpreleaser/main.go +++ b/cmd/crossplanereleaser/main.go @@ -1,16 +1,13 @@ package main import ( - "os" - "github.com/alecthomas/kong" - "github.com/go-log/log" - fmtLog "github.com/go-log/log/fmt" "github.com/spf13/afero" ) var cli struct { - Build buildCmd `cmd:"build" help:"Build artifacts"` + Build buildCmd `cmd:"build" help:"Build artifacts"` + Release releaseCmd `cmd:"release" help:"Release and publish artifacts"` Version versionCmd `cmd:"version" help:"Print version information"` } @@ -19,13 +16,11 @@ var _ = kong.Must(&cli) func main() { fs := afero.NewOsFs() - logger := fmtLog.NewFromWriter(os.Stderr) ctx := kong.Parse(&cli, kong.Name("cnpctl"), kong.Description("CLI utility to deal with certain tasks around CNP@DBNetz."), kong.BindTo(fs, (*afero.Fs)(nil)), - kong.BindTo(logger, (*log.Logger)(nil)), ) err := ctx.Run() diff --git a/cmd/crossplanereleaser/release.go b/cmd/crossplanereleaser/release.go new file mode 100644 index 0000000..b158d0f --- /dev/null +++ b/cmd/crossplanereleaser/release.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/pkg/errors" + "github.com/spf13/afero" + + v1 "github.com/mistermx/crossplanereleaser/config/v1" + "github.com/mistermx/crossplanereleaser/internal/build" + "github.com/mistermx/crossplanereleaser/internal/git" + "github.com/mistermx/crossplanereleaser/internal/publish" +) + +type releaseCmd struct { + git git.Client + builder build.BuilderBackend + publisher publish.PackagePublisher + + Skip string `help:"Specify steps to skip"` +} + +func (c *releaseCmd) BeforeApply() error { + c.git = git.NewGitCLIBackend() + c.builder = build.NewCrankCLIBackend() + c.publisher = publish.NewCraneCLIPublisher() + return nil +} + +func (c *releaseCmd) Run(fsys afero.Fs) error { + ctx := context.Background() + cfg, err := getConfig(fsys, c.git) + if err != nil { + return err + } + + // NOTE: This implementation does not scale very well for many build + // pipeline steps. + // If this gets extended in the future we could think about + // implementing a build pipeline like Goreleaser: + // https://github.com/goreleaser/goreleaser/blob/bba4ee2be7fa0f16b682aceef3500f608f5bf18e/internal/pipeline/pipeline.go + + if c.Skip != "build" { + cmd := buildCmd{ + git: c.git, + builder: c.builder, + } + if err := cmd.Run(fsys); err != nil { + return errors.Wrap(err, "build failed") + } + } + return c.publishPackages(ctx, cfg) +} + +func (c *releaseCmd) publishPackages(ctx context.Context, cfg *v1.Config) error { + for _, pkgCfg := range cfg.XPackages { + filename := getPackageOutputPath(cfg, &pkgCfg) + ref, err := name.ParseReference(pkgCfg.NameTemplate) + if err != nil { + return errors.Wrap(err, "cannot parse image name") + } + if err := c.publisher.PublishPackage(ctx, filename, ref); err != nil { + return errors.Wrapf(err, "cannot publish package %q", pkgCfg.ID) + } + } + return nil +} diff --git a/cmd/xpreleaser/version.go b/cmd/crossplanereleaser/version.go similarity index 100% rename from cmd/xpreleaser/version.go rename to cmd/crossplanereleaser/version.go diff --git a/cmd/xpreleaser/build.go b/cmd/xpreleaser/build.go deleted file mode 100644 index 8fd1c2e..0000000 --- a/cmd/xpreleaser/build.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "context" - "path/filepath" - - "github.com/go-log/log" - "github.com/pkg/errors" - "github.com/spf13/afero" - - v1 "github.com/mistermx/xpreleaser/config/v1" - "github.com/mistermx/xpreleaser/internal/config" - "github.com/mistermx/xpreleaser/internal/xpkg/build" - "github.com/mistermx/xpreleaser/internal/xpkg/parse" -) - -type buildCmd struct { -} - -func (c *buildCmd) Run(fsys afero.Fs, logger log.Logger) error { - cfgFileName, err := config.FindConfigFile(fsys) - if err != nil { - return errors.Wrap(err, "cannot find config file") - } - cfg, err := config.Parse(fsys, cfgFileName) - if err != nil { - return errors.Wrap(err, "cannot parse config file") - } - return buildPackages(fsys, cfg) -} - -func buildPackages(fsys afero.Fs, cfg *v1.Config) error { - for _, pkgCfg := range cfg.XPackages { - err := buildPackage(fsys, cfg, &pkgCfg) - if err != nil { - return errors.Wrapf(err, "cannot build package %q", pkgCfg.ID) - } - } - return nil -} - -func buildPackage(fsys afero.Fs, cfg *v1.Config, pkgCfg *v1.XPackageConfig) error { - ctx := context.TODO() - parseBackend := parse.NewFSDirBackend(fsys, pkgCfg.Dir, pkgCfg.Examples) - pkg, err := parse.Parse(ctx, parseBackend) - if err != nil { - return errors.Wrap(err, "cannot parse package") - } - buildBackend, err := build.NewImageBackend() - if err != nil { - return errors.Wrap(err, "cannot setup builder backend") - } - if err := build.BuildImage(ctx, buildBackend, pkg); err != nil { - return errors.Wrap(err, "cannot build image") - } - outputPath := filepath.Join(cfg.Dist, pkgCfg.ID, "package.xpkg") - if err != nil { - return err - } - if err := fsys.MkdirAll(filepath.Dir(outputPath), 0755); err != nil { - return err - } - if err := buildBackend.WriteTarball(fsys, outputPath, nil); err != nil { - return errors.Wrap(err, "cannot write image tarball") - } - return err -} diff --git a/config/v1/config.go b/config/v1/config.go index e15ebe7..180e6b9 100644 --- a/config/v1/config.go +++ b/config/v1/config.go @@ -1,12 +1,20 @@ package v1 type Config struct { - XPackages []XPackageConfig `json:"xpackages"` - Dist string `json:"dist"` + ProjectName string `json:"project_name"` + XPackages []XPackageConfig `json:"xpackages"` + Dist string `json:"dist"` + Dockers []DockerConfig `json:"dockers"` } type XPackageConfig struct { - ID string `json:"id"` - Dir string `json:"dir"` - Examples string `json:"examples"` + ID string `json:"id"` + Dir string `json:"dir"` + Examples string `json:"examples"` + NameTemplate string `json:"name_template"` +} + +type DockerConfig struct { + IDs []string `json:"ids"` + ImageTemplates []string `json:"image_templates"` } diff --git a/go.mod b/go.mod index b4597aa..1086b93 100644 --- a/go.mod +++ b/go.mod @@ -1,38 +1,19 @@ -module github.com/mistermx/xpreleaser +module github.com/mistermx/crossplanereleaser go 1.21.6 require ( github.com/alecthomas/kong v0.8.1 - github.com/go-log/log v0.2.0 github.com/google/go-containerregistry v0.19.0 - github.com/mistermx/go-utils/k8s v0.0.0-20240130135253-12a66979ace7 github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.11.0 - k8s.io/apimachinery v0.29.1 sigs.k8s.io/yaml v1.3.0 ) require ( - github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect - github.com/go-logr/logr v1.3.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.5 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc3 // indirect - github.com/vbatts/tar-split v0.11.3 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect golang.org/x/text v0.14.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 529ac5d..58de99d 100644 --- a/go.sum +++ b/go.sum @@ -1,148 +1,36 @@ -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= -github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM= -github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.0+incompatible h1:z4bf8HvONXX9Tde5lGBMQ7yCJgNahmJumdrStZAbeY4= -github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= -github.com/go-log/log v0.2.0 h1:z8i91GBudxD5L3RmF0KVpetCbcGWAV7q1Tw1eRwQM9Q= -github.com/go-log/log v0.2.0/go.mod h1:xzCnwajcues/6w7lne3yK2QU7DBPW7kqbgPGG5AF65U= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -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-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mistermx/go-utils/k8s v0.0.0-20240130135253-12a66979ace7 h1:b2+vH5eQKkxoFOcFBVKif59A2kcvvFvkUoDyY3LUMX8= -github.com/mistermx/go-utils/k8s v0.0.0-20240130135253-12a66979ace7/go.mod h1:xAO6Yve9jityuhe1PonAAq4z/WWpvtApRUkQq1ykbjw= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= -github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= -github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= -github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/build/build.go b/internal/build/build.go new file mode 100644 index 0000000..b392ecc --- /dev/null +++ b/internal/build/build.go @@ -0,0 +1,15 @@ +package build + +import ( + "context" +) + +type PackageBuildConfig struct { + PackageDir string + ExamplesDir string + OutputPath string +} + +type BuilderBackend interface { + BuildPackage(ctx context.Context, cfg *PackageBuildConfig) error +} diff --git a/internal/build/build_crank.go b/internal/build/build_crank.go new file mode 100644 index 0000000..71cd511 --- /dev/null +++ b/internal/build/build_crank.go @@ -0,0 +1,39 @@ +package build + +import ( + "context" + "fmt" + "os" + "os/exec" + + "github.com/pkg/errors" +) + +type CrankCLIBackend struct { + cmd string +} + +func NewCrankCLIBackend() *CrankCLIBackend { + return &CrankCLIBackend{ + cmd: "crank", + } +} + +func (c CrankCLIBackend) exec(ctx context.Context, args ...string) error { + cmd := exec.CommandContext(ctx, c.cmd, args...) + cmd.Stderr = os.Stderr + err := cmd.Run() + return errors.Wrap(err, c.cmd) +} + +func (c *CrankCLIBackend) BuildPackage(ctx context.Context, cfg *PackageBuildConfig) error { + args := []string{ + fmt.Sprintf("--package-root=%s", cfg.PackageDir), + fmt.Sprintf("--output=%s", cfg.OutputPath), + } + if cfg.ExamplesDir != "" { + args = append(args, fmt.Sprintf("--examples-root=%s", cfg.ExamplesDir)) + } + // TODO: Support setting --controller-tar option + return c.exec(ctx, args...) +} diff --git a/internal/config/parse.go b/internal/config/parse.go index b5a63ca..c445b6d 100644 --- a/internal/config/parse.go +++ b/internal/config/parse.go @@ -7,11 +7,11 @@ import ( "github.com/spf13/afero" "sigs.k8s.io/yaml" - v1 "github.com/mistermx/xpreleaser/config/v1" + v1 "github.com/mistermx/crossplanereleaser/config/v1" ) const ( - defaultConfigFileName = ".xpreleaser.yaml" + defaultConfigFileName = ".crossplanereleaser.yaml" ) func FindConfigFile(_ afero.Fs) (string, error) { @@ -41,6 +41,7 @@ func Parse(fsys afero.Fs, filename string) (*v1.Config, error) { } func fillDefaults(filename string, cfg *v1.Config) error { + cfg.ProjectName = valueOrFallback(cfg.ProjectName, filepath.Base(filepath.Dir(filename))) cfg.Dist = valueOrFallback(cfg.Dist, "dist") for i := range cfg.XPackages { @@ -50,10 +51,11 @@ func fillDefaults(filename string, cfg *v1.Config) error { if len(cfg.XPackages) > 1 { return errors.New("package ID is required if there is more than one package") } - // If there is only one package use the base name of the directory - // as package ID. - cfg.XPackages[i].ID = filepath.Base(filepath.Dir(filename)) + // If there is only one package use the project name as ID + cfg.XPackages[i].ID = cfg.ProjectName } + + cfg.XPackages[i].NameTemplate = valueOrFallback(cfg.XPackages[i].NameTemplate, cfg.XPackages[i].ID) } return nil } diff --git a/internal/config/project_properties.go b/internal/config/project_properties.go new file mode 100644 index 0000000..0cbb3ed --- /dev/null +++ b/internal/config/project_properties.go @@ -0,0 +1,72 @@ +package config + +import ( + "os" + "strings" + + v1 "github.com/mistermx/crossplanereleaser/config/v1" + "github.com/mistermx/crossplanereleaser/internal/git" +) + +type ProjectProperties struct { + // Common + ProjectName string + Env map[string]string + + // Git + Branch string + Tag string + FullCommit string + ShortCommit string + IsGitDirty bool + IsGitClean bool + GitTreeState string +} + +func BuildProjectProperties(g git.Client, cfg *v1.Config) (*ProjectProperties, error) { + props := &ProjectProperties{ + ProjectName: cfg.ProjectName, + Env: getEnvMap(), + } + ref := git.RefHead + + var err error + props.Branch, err = g.GetCurrentBranch() + if err != nil && !git.IsErrNoBranch(err) { + return nil, err + } + + props.Tag, err = git.GetVersion(g, ref) + if err != nil { + return nil, err + } + + props.FullCommit, err = g.GetCommitSHA(ref) + if err != nil { + return nil, err + } + props.ShortCommit = props.FullCommit[:8] + + props.IsGitClean, err = g.IsGitTreeClean() + if err != nil { + return nil, err + } + props.IsGitDirty = !props.IsGitClean + if props.IsGitClean { + props.GitTreeState = "clean" + } else { + props.GitTreeState = "dirty" + } + + return props, nil +} + +func getEnvMap() map[string]string { + environ := os.Environ() + m := make(map[string]string, len(environ)) + for _, s := range os.Environ() { + split := strings.SplitN(s, "=", 1) + m[split[0]] = split[1] + } + return m +} diff --git a/internal/config/template.go b/internal/config/template.go new file mode 100644 index 0000000..7b09e4b --- /dev/null +++ b/internal/config/template.go @@ -0,0 +1,40 @@ +package config + +import ( + "bytes" + "text/template" + + v1 "github.com/mistermx/crossplanereleaser/config/v1" +) + +func RenderConfigTemplates(cfg *v1.Config, props *ProjectProperties) error { + for di, docker := range cfg.Dockers { + for ii, imgTmpl := range docker.ImageTemplates { + var err error + cfg.Dockers[di].ImageTemplates[ii], err = renderTemplate(imgTmpl, props) + if err != nil { + return err + } + } + } + for pi, pkgCfg := range cfg.XPackages { + var err error + cfg.XPackages[pi].NameTemplate, err = renderTemplate(pkgCfg.NameTemplate, props) + if err != nil { + return err + } + } + return nil +} + +func renderTemplate(tmplStr string, props *ProjectProperties) (string, error) { + tmpl, err := template.New("").Parse(tmplStr) + if err != nil { + return "", err + } + buf := &bytes.Buffer{} + if err := tmpl.Execute(buf, props); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/internal/git/git.go b/internal/git/git.go new file mode 100644 index 0000000..78f2e50 --- /dev/null +++ b/internal/git/git.go @@ -0,0 +1,55 @@ +package git + +import ( + "fmt" + + "github.com/pkg/errors" +) + +type Ref string + +const ( + RefHead Ref = "HEAD" +) + +type Client interface { + GetTag(ref Ref) (string, error) + GetCommitSHA(ref Ref) (string, error) + GetCommitCount(ref Ref) (int, error) + GetCurrentBranch() (string, error) + IsGitTreeClean() (bool, error) +} + +var ( + errNoTag = errors.New("errNoTag") + errNoBranch = errors.New("errNoBranch") +) + +func IsErrNoTag(err error) bool { + return err == errNoTag +} + +func IsErrNoBranch(err error) bool { + return err == errNoBranch +} + +func GetVersion(git Client, ref Ref) (string, error) { + tag, err := git.GetTag(ref) + if err == nil || !IsErrNoTag(err) { + return tag, err + } + return getUntaggedVersion(git, ref) +} + +func getUntaggedVersion(git Client, ref Ref) (string, error) { + base := "v0.0.0" + revCount, err := git.GetCommitCount(ref) + if err != nil { + return "", err + } + sha, err := git.GetCommitSHA(ref) + if err != nil { + return "", err + } + return fmt.Sprintf("%s-%d-g%s", base, revCount, sha[:8]), nil +} diff --git a/internal/git/git_cli.go b/internal/git/git_cli.go new file mode 100644 index 0000000..c1a272a --- /dev/null +++ b/internal/git/git_cli.go @@ -0,0 +1,88 @@ +package git + +import ( + "bytes" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +// GitCLIBackend uses the system's "git" command to implement [GitBackend]. +type GitCLIBackend struct{} + +func NewGitCLIBackend() *GitCLIBackend { + return &GitCLIBackend{} +} + +func (g *GitCLIBackend) run(args ...string) (string, error) { + stdout := bytes.Buffer{} + cmd := exec.Command("git", args...) + cmd.Stderr = os.Stderr + cmd.Stdout = &stdout + err := cmd.Run() + return stdout.String(), errors.Wrapf(err, "cmd %q", cmd.String()) +} + +func (g *GitCLIBackend) hasTags() (bool, error) { + out, err := g.run("tag") + return out != "", err +} + +func (g *GitCLIBackend) GetCommitCount(ref Ref) (int, error) { + out, err := g.run("rev-list", "--count", string(ref)) + if err != nil { + return 0, err + } + return strconv.Atoi(out) +} + +func (g *GitCLIBackend) GetTag(ref Ref) (string, error) { + hasTag, err := g.hasTags() + if err != nil { + return "", err + } + if !hasTag { + return "", errNoTag + } + tag, err := g.run("describe", "--tags", "--abbrev=8", string(ref)) + return strings.TrimSpace(tag), err +} + +func (g *GitCLIBackend) GetCommitSHA(ref Ref) (string, error) { + sha, err := g.run("rev-parse", string(ref)) + return strings.TrimSpace(sha), err +} + +func (g *GitCLIBackend) GetCurrentBranch() (string, error) { + out, err := g.run("rev-parse", "--abbrev-ref", string(RefHead)) + if err != nil { + return "", err + } + ref := strings.TrimSpace(out) + + // rev-parse returns HEAD if we are not on a branch. + if ref != string(RefHead) { + return ref, nil + } + + branches, err := g.run("branch", "-a", "--points-at", string(RefHead)) + if err != nil { + return "", err + } + branchesSplit := strings.Split(branches, "\n") + if len(branchesSplit) == 0 { + return "", errNoBranch + } + return branchesSplit[0], nil +} + +func (g *GitCLIBackend) IsGitTreeClean() (bool, error) { + out, err := g.run("status", "--porcelain") + if err != nil { + return false, err + } + return out == "", nil +} diff --git a/internal/publish/publish.go b/internal/publish/publish.go new file mode 100644 index 0000000..2e76cb3 --- /dev/null +++ b/internal/publish/publish.go @@ -0,0 +1,11 @@ +package publish + +import ( + "context" + + "github.com/google/go-containerregistry/pkg/name" +) + +type PackagePublisher interface { + PublishPackage(ctx context.Context, filename string, ref name.Reference) error +} diff --git a/internal/publish/publish_crane.go b/internal/publish/publish_crane.go new file mode 100644 index 0000000..abb0eb1 --- /dev/null +++ b/internal/publish/publish_crane.go @@ -0,0 +1,31 @@ +package publish + +import ( + "context" + "os" + "os/exec" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/pkg/errors" +) + +type CraneCLIPublisher struct { + cmd string +} + +func NewCraneCLIPublisher() *CraneCLIPublisher { + return &CraneCLIPublisher{ + cmd: "crane", + } +} + +func (c *CraneCLIPublisher) exec(ctx context.Context, args ...string) error { + cmd := exec.CommandContext(ctx, c.cmd, args...) + cmd.Stderr = os.Stderr + err := cmd.Run() + return errors.Wrap(err, c.cmd) +} + +func (c *CraneCLIPublisher) PublishPackage(ctx context.Context, filename string, ref name.Reference) error { + return c.exec(ctx, "push", filename, ref.String()) +} diff --git a/internal/xpkg/build/build.go b/internal/xpkg/build/build.go deleted file mode 100644 index a9a791e..0000000 --- a/internal/xpkg/build/build.go +++ /dev/null @@ -1,55 +0,0 @@ -package build - -import ( - "bytes" - "context" - "io" - - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/yaml" - - "github.com/mistermx/xpreleaser/internal/xpkg" -) - -type BuilderBackend interface { - WritePackage(in io.Reader) error - WriteExamples(in io.Reader) error -} - -func BuildImage(ctx context.Context, backend BuilderBackend, pkg *xpkg.Package) error { - packageBuf := &bytes.Buffer{} - examplesBuf := &bytes.Buffer{} - if err := writeObjectListYaml(pkg.MetaObjects, packageBuf); err != nil { - return errors.Wrap(err, "cannot write meta objects to YAML") - } - if err := writeObjectListYaml(pkg.PackageObjects, packageBuf); err != nil { - return errors.Wrap(err, "cannot write package objects to YAML") - } - if err := writeObjectListYaml(pkg.ExampleObjects, examplesBuf); err != nil { - return errors.Wrap(err, "cannot write package objects to YAML") - } - if err := backend.WritePackage(packageBuf); err != nil { - return errors.Wrap(err, "cannot write package") - } - // Write examples if there are any - if examplesBuf.Len() > 0 { - if err := backend.WriteExamples(examplesBuf); err != nil { - return errors.Wrap(err, "cannot write example") - } - } - return nil -} - -func writeObjectListYaml(list []*unstructured.Unstructured, out io.Writer) error { - for _, u := range list { - raw, err := yaml.Marshal(u) - if err != nil { - return err - } - io.WriteString(out, "---\n") - out.Write(raw) - io.WriteString(out, "\n") - } - return nil -} diff --git a/internal/xpkg/build/build_image.go b/internal/xpkg/build/build_image.go deleted file mode 100644 index 7c86841..0000000 --- a/internal/xpkg/build/build_image.go +++ /dev/null @@ -1,156 +0,0 @@ -package build - -import ( - "archive/tar" - "bytes" - "fmt" - "io" - "io/fs" - - "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/empty" - "github.com/google/go-containerregistry/pkg/v1/mutate" - "github.com/google/go-containerregistry/pkg/v1/tarball" - "github.com/spf13/afero" -) - -const ( - // MetaFile is the name of a Crossplane package metadata file. - MetaFile string = "crossplane.yaml" - - // StreamFile is the name of the file in a Crossplane package image that - // contains its YAML stream. - StreamFile string = "package.yaml" - - // StreamFileMode determines the permissions on the stream file. - StreamFileMode fs.FileMode = 0o644 - - // XpkgExtension is the extension for compiled Crossplane packages. - XpkgExtension string = ".xpkg" - - // XpkgMatchPattern is the match pattern for identifying compiled Crossplane packages. - XpkgMatchPattern string = "*" + XpkgExtension - - // XpkgExamplesFile is the name of the file in a Crossplane package image - // that contains the examples YAML stream. - XpkgExamplesFile string = ".up/examples.yaml" - - // AnnotationKey is the key value for xpkg annotations. - AnnotationKey string = "io.crossplane.xpkg" - - // PackageAnnotation is the annotation value used for the package.yaml - // layer. - PackageAnnotation string = "base" - - // ExamplesAnnotation is the annotation value used for the examples.yaml - // layer. - // TODO(lsviben) Consider changing this to "examples". - ExamplesAnnotation string = "upbound" - - // DefaultRegistry is the registry name that will be used when no registry - // is provided. - DefaultRegistry string = "xpkg.upbound.io" -) - -type ImageBackend struct { - image v1.Image - cfg *v1.Config - layers []v1.Layer -} - -func NewImageBackend() (*ImageBackend, error) { - base := empty.Image - cfgFile, err := base.ConfigFile() - if err != nil { - return nil, err - } - cfg := &cfgFile.Config - cfg.Labels = map[string]string{} - return &ImageBackend{ - image: base, - cfg: cfg, - }, nil -} - -// WriteTarball writes the image as a tarball to a file. -func (b *ImageBackend) WriteTarball(fsys afero.Fs, filename string, ref name.Reference) error { - img, err := mutate.AppendLayers(b.image, b.layers...) - if err != nil { - return err - } - img, err = mutate.Config(img, *b.cfg) - if err != nil { - return err - } - // _, err = img.Digest() - // if err != nil { - // return err - // } - file, err := fsys.Create(filename) - if err != nil { - return err - } - defer file.Close() - return tarball.Write(ref, img, file) -} - -func (b *ImageBackend) WritePackage(content io.Reader) error { - return b.addLayerSingleFile(PackageAnnotation, StreamFile, content) -} - -func (b *ImageBackend) WriteExamples(content io.Reader) error { - return b.addLayerSingleFile(ExamplesAnnotation, XpkgExamplesFile, content) -} - -func (b *ImageBackend) addLayerSingleFile(layerAnnotation string, filename string, content io.Reader) error { - layer, err := newLayerSingleFile(layerAnnotation, filename, content, StreamFileMode, b.cfg) - if err != nil { - return err - } - b.layers = append(b.layers, layer) - // b.image, err = mutate.AppendLayers(b.image, layer) - return err -} - -func newLayerSingleFile(layerAnnotation string, filename string, content io.Reader, mode fs.FileMode, cfg *v1.Config) (v1.Layer, error) { - tarBuf := &bytes.Buffer{} - tarw := tar.NewWriter(tarBuf) - contentBytes, err := io.ReadAll(content) - if err != nil { - return nil, err - } - h := tar.Header{ - Name: filename, - Mode: int64(mode), - Size: int64(len(contentBytes)), - } - if err := tarw.WriteHeader(&h); err != nil { - return nil, err - } - if _, err := tarw.Write(contentBytes); err != nil { - return nil, err - } - if err := tarw.Close(); err != nil { - return nil, err - } - layer, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) { - return io.NopCloser(bytes.NewReader(tarBuf.Bytes())), nil - }) - if err != nil { - return nil, err - } - d, err := layer.Digest() - if err != nil { - return nil, err - } - if layerAnnotation != "" { - cfg.Labels[xpkgLabel(d.String())] = layerAnnotation - } - return layer, nil -} - -// Label constructs a specially formated label using the annotationKey. -func xpkgLabel(annotation string) string { - return fmt.Sprintf("%s:%s", AnnotationKey, annotation) -} diff --git a/internal/xpkg/package_types.go b/internal/xpkg/package_types.go deleted file mode 100644 index e808e4d..0000000 --- a/internal/xpkg/package_types.go +++ /dev/null @@ -1,9 +0,0 @@ -package xpkg - -import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - -type Package struct { - PackageObjects []*unstructured.Unstructured - MetaObjects []*unstructured.Unstructured - ExampleObjects []*unstructured.Unstructured -} diff --git a/internal/xpkg/parse/parse.go b/internal/xpkg/parse/parse.go deleted file mode 100644 index f77697c..0000000 --- a/internal/xpkg/parse/parse.go +++ /dev/null @@ -1,94 +0,0 @@ -package parse - -import ( - "context" - "io" - - "github.com/mistermx/go-utils/k8s/yaml" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/mistermx/xpreleaser/internal/xpkg" -) - -type ParserBackend interface { - // GetPackageFiles returns the individual package YAML file streams from the - // parser backend. - // Every file can contain multiple documents that are parsed into individual - // objects. - // - // The function returns the filestreams as [io.ReadCloser]. - // It is in the receivers responsibility to [io.ReadCloser.Close] after - // reading. - GetPackageFiles(ctx context.Context) (chan io.ReadCloser, error) - - // GetExampleFiles returns the individual example YAML file streams from the - // parser backend. - // Every file can contain multiple documents that are parsed into individual - // objects. - // - // The function returns the filestreams as [io.ReadCloser]. - // It is in the receivers responsibility to [io.ReadCloser.Close] after - // reading. - GetExampleFiles(ctx context.Context) (chan io.ReadCloser, error) -} - -func Parse(ctx context.Context, backend ParserBackend) (*xpkg.Package, error) { - packageFiles, err := backend.GetPackageFiles(ctx) - if err != nil { - return nil, err - } - packageObjects := []*unstructured.Unstructured{} - metaObjects := []*unstructured.Unstructured{} - for f := range packageFiles { - objects, err := parseStream(f) - if err != nil { - return nil, err - } - for _, o := range objects { - switch { - case isMetaObject(o): - metaObjects = append(metaObjects, o) - case isPackageObject(o): - packageObjects = append(packageObjects, o) - default: - return nil, errors.Errorf("object %q with kind %q is not a package or meta object", o.GetName(), o.GroupVersionKind().String()) - } - } - } - if len(metaObjects) == 0 { - return nil, errors.New("package has no meta descriptor") - } - - exampleFiles, err := backend.GetExampleFiles(ctx) - if err != nil { - return nil, err - } - exampleObjects := []*unstructured.Unstructured{} - for f := range exampleFiles { - objects, err := parseStream(f) - if err != nil { - return nil, err - } - exampleObjects = append(exampleObjects, objects...) - } - - return &xpkg.Package{ - PackageObjects: packageObjects, - MetaObjects: metaObjects, - ExampleObjects: exampleObjects, - }, nil -} - -func isMetaObject(o *unstructured.Unstructured) bool { - return o.GetObjectKind().GroupVersionKind().Group == "meta.pkg.crossplane.io" -} - -func isPackageObject(o *unstructured.Unstructured) bool { - return o.GetObjectKind().GroupVersionKind().Group == "apiextensions.crossplane.io" -} - -func parseStream(in io.ReadCloser) ([]*unstructured.Unstructured, error) { - defer in.Close() - return yaml.UnmarshalObjectsReader[*unstructured.Unstructured](in) -} diff --git a/internal/xpkg/parse/parse_fs.go b/internal/xpkg/parse/parse_fs.go deleted file mode 100644 index 441dea8..0000000 --- a/internal/xpkg/parse/parse_fs.go +++ /dev/null @@ -1,90 +0,0 @@ -package parse - -import ( - "context" - "io" - "io/fs" - "strings" - - "github.com/spf13/afero" -) - -type FsDirBackend struct { - fsys afero.Fs - packageDir string - exampleDir string -} - -func NewFSDirBackend(fsys afero.Fs, packageDir, exampleDir string) *FsDirBackend { - return &FsDirBackend{ - fsys: fsys, - packageDir: packageDir, - exampleDir: exampleDir, - } -} - -func (b *FsDirBackend) GetPackageFiles(_ context.Context) (chan io.ReadCloser, error) { - files := make(chan io.ReadCloser, 8) - go func() { - afero.Walk(b.fsys, b.packageDir, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - // Ignore hidden files or directories - if info.Name()[0] == '.' { - return fs.SkipDir - } - if info.IsDir() { - return nil - } - if !strings.HasSuffix(path, ".yaml") && !strings.HasSuffix(path, ".yml") { - return nil - } - f, err := b.fsys.Open(path) - if err != nil { - return err - } - files <- f - return nil - }) - // TODO: handle errors in a way - close(files) - }() - return files, nil -} - -func (b *FsDirBackend) GetExampleFiles(_ context.Context) (chan io.ReadCloser, error) { - files := make(chan io.ReadCloser, 8) - if exists, _ := afero.DirExists(b.fsys, b.exampleDir); !exists { - close(files) - return files, nil - } - - go func() { - afero.Walk(b.fsys, b.exampleDir, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - // Ignore hidden files and directories - isHidden := info.Name()[0] == '.' - if info.IsDir() && info.Name()[0] == '.' { - return fs.SkipDir - } - if isHidden { - return nil - } - if !strings.HasSuffix(path, ".yaml") && !strings.HasSuffix(path, ".yml") { - return nil - } - f, err := b.fsys.Open(path) - if err != nil { - return err - } - files <- f - return nil - }) - // TODO: handle errors in a way - close(files) - }() - return files, nil -}