From acd1d4e54bb86ec2b177a934452d815945158079 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 15 Dec 2023 16:11:11 -0500 Subject: [PATCH] feat: initial merge for compgen (#220) * Iintial commit for compgen * Updating compgen scraper templates * Compgen's receiver scraper is now functional * fix: remove extra package from scrapper template * Update build launch.json * Add unmarshal() to compgen's dummy receiver * Simplify sample config data in the dummy receiver * Correcting a whoopsie * Fixing configKey in compgen's dummy receiver * Renaming PostProcessing() to CompleteModule() * update: config.go and config_test.go template * remove: MyAwesomeReceiver2 generated code * Updated compgen documentation * add: test for config.go file * More test cases for compgen receiver template * update: template files and testdata * Compgen makefile cleanup * Cleanup compgen launch.json files * fix: factory template * Compgen documentation changes from PR feedback * Restore build/.vscode/launch.json * Simplified common.go in compgen * update: add more comments to config_test.go template * Refactored compgen * Remove Gen() from compgen * Added compgen test for common.Tidy() * Add tests for common.Render() in compgen * Add test cases for receiver.go in compgen * Remove pkg references from compgen * Upgrade compgen and templates to go 1.21 and otel 0.91.0 * Appeasing linter * Whitespace fix * Cleanup unnecessary comment in compgen * Removing receiver.Init() from compgen. It isn't needed * Simplify go.mod.tmpl in compgen templates * Use temp directory for debugging compgen * Updated comments for compgen. Changed "templates" accessibility * First pass at complete copmgen documentation * remove: stoof ambiguous name and changed to empty and dummy * Refactored away the use of panic * Copmgen template cleanup * Add contributing instructions to compgen regarding Makefile.Common * Moving .debug reference to top-level .gitignore file --------- Co-authored-by: densellp --- .gitignore | 4 + CONTRIBUTING.md | 20 ++- Makefile | 30 ++-- build/.vscode/launch.json | 2 +- cmd/compgen/.vscode/launch.json | 29 ++++ cmd/compgen/.vscode/tasks.json | 25 +++ cmd/compgen/Makefile | 2 + cmd/compgen/READEME.md | 90 +++++++++++ cmd/compgen/cmd/common/common.go | 77 +++++++++ cmd/compgen/cmd/common/common_test.go | 72 +++++++++ cmd/compgen/cmd/receiver/receiver.go | 73 +++++++++ cmd/compgen/cmd/receiver/receiver_test.go | 36 +++++ .../cmd/receiver/templates/Makefile.tmpl | 1 + .../cmd/receiver/templates/README.md.tmpl | 27 ++++ .../cmd/receiver/templates/config.go.tmpl | 76 +++++++++ .../receiver/templates/config_test.go.tmpl | 148 ++++++++++++++++++ .../cmd/receiver/templates/doc.go.tmpl | 6 + .../cmd/receiver/templates/factory.go.tmpl | 80 ++++++++++ .../receiver/templates/factory_test.go.tmpl | 48 ++++++ .../cmd/receiver/templates/go.mod.tmpl | 1 + .../templates/internal/scraper.go.tmpl | 54 +++++++ .../templates/internal/scraper_test.go.tmpl | 29 ++++ .../cmd/receiver/templates/metadata.yaml.tmpl | 13 ++ .../testdata/config-manual.yaml.tmpl | 16 ++ .../templates/testdata/empty.yaml.tmpl | 1 + .../templates/testdata/golden.yaml.tmpl | 4 + .../testdata/invalid-data-type.yaml.tmpl | 4 + .../templates/testdata/missing-data.yaml.tmpl | 4 + .../templates/testdata/partial.yaml.tmpl | 3 + cmd/compgen/cmd/root.go | 36 +++++ cmd/compgen/go.mod | 16 ++ cmd/compgen/go.sum | 18 +++ cmd/compgen/main.go | 9 ++ 33 files changed, 1039 insertions(+), 15 deletions(-) create mode 100644 cmd/compgen/.vscode/launch.json create mode 100644 cmd/compgen/.vscode/tasks.json create mode 100644 cmd/compgen/Makefile create mode 100644 cmd/compgen/READEME.md create mode 100644 cmd/compgen/cmd/common/common.go create mode 100644 cmd/compgen/cmd/common/common_test.go create mode 100644 cmd/compgen/cmd/receiver/receiver.go create mode 100644 cmd/compgen/cmd/receiver/receiver_test.go create mode 100644 cmd/compgen/cmd/receiver/templates/Makefile.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/README.md.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/config.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/config_test.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/doc.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/factory.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/factory_test.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/go.mod.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/internal/scraper.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/internal/scraper_test.go.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/metadata.yaml.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/testdata/config-manual.yaml.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/testdata/empty.yaml.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/testdata/golden.yaml.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/testdata/invalid-data-type.yaml.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/testdata/missing-data.yaml.tmpl create mode 100644 cmd/compgen/cmd/receiver/templates/testdata/partial.yaml.tmpl create mode 100644 cmd/compgen/cmd/root.go create mode 100644 cmd/compgen/go.mod create mode 100644 cmd/compgen/go.sum create mode 100644 cmd/compgen/main.go diff --git a/.gitignore b/.gitignore index b2a0dabb..78c7375c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ *.dll *.so *.dylib +__debug_bin* + +# Temp directory for debugging compgen +.debug # Test binary, built with `go test -c` *.test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0f104c8..8c9c559f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ We love your input! We want to make contributing to this project as easy and tra We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. -## We Use [Coding Conventions, e.g., PEP8], So Pull Requests Need To Pass This. +## We Use [Coding Conventions, e.g., PEP8], So Pull Requests Need To Pass This TODO: Include information about the coding style guide or conventions that you use. If there are any tools that they should use or any checks that their code needs to pass, detail them here. @@ -50,6 +50,22 @@ Great Bug Reports tend to have: TODO: Detail the style guide and coding conventions further, if needed. +## Running Checks + +After making a code change, run the following commands and validate that each runs successfully: + +```sh +make generate +make tidy-all +make lint-all +make test-all +make fmt-all +``` + +## Adding New Components + +New compponents can be created by running compgen. See compgen's [README](./cmd/compgen/READEME.md) for instructions and guidance. + ## License By contributing, you agree that your contributions will be licensed under its [License Name]. @@ -58,4 +74,4 @@ By contributing, you agree that your contributions will be licensed under its [L Include any references or resources that might be helpful for the contributor. -Thank you for considering contributing to liatrio-otel-collector! \ No newline at end of file +Thank you for considering contributing to liatrio-otel-collector! diff --git a/Makefile b/Makefile index 9c32ac0a..48df3ec1 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,7 @@ GORELEASER_VERSION = 1.20.0 GOLANGCI_LINT_VERSION ?= v1.53.2 # Arguments for getting directories & executing commands against them -# PKG_RECEIVER_DIRS = $(shell find ./receiver/* -type f -name "go.mod" -print -exec dirname {} \; | sort | uniq) -PKG_RECEIVER_DIRS = $(shell find ./receiver/* -type f -name '*go.mod*' | sed -r 's|/[^/]+$$||' |sort | uniq ) +PKG_DIRS = $(shell find ./* -not -path "./build/*" -not -path "./tmp/*" -type f -name "go.mod" -exec dirname {} \; | sort | grep -E '^./') CHECKS = prep lint-all genqlient-all metagen-all test-all tidy-all fmt-all # set ARCH var based on output @@ -65,20 +64,26 @@ install-tools: go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) go install github.com/Khan/genqlient@latest -.PHONY: lint-all $(PKG_RECEIVER_DIRS) -lint-all: $(PKG_RECEIVER_DIRS) - -$(PKG_RECEIVER_DIRS): - $(MAKE) -j 4 -C $@ lint +.PHONY: for-all +for-all: + @set -e; for dir in $(DIRS); do \ + (cd "$${dir}" && \ + echo "running $${CMD} in $${dir}" && \ + $${CMD} ); \ + done + +.PHONY: lint-all +lint-all: + $(MAKE) for-all DIRS="$(PKG_DIRS)" CMD="$(MAKE) lint" .PHONY: generate generate: check-prep install-tools - cd tmp/opentelemetry-collector-contrib/cmd/mdatagen && go install . - $(MAKE) -j 4 -C $(PKG_RECEIVER_DIRS) gen + cd $(OCB_PATH)/opentelemetry-collector-contrib/cmd/mdatagen && go install . + $(MAKE) for-all DIRS="$(PKG_DIRS)" CMD="$(MAKE) gen" .PHONY: test-all test-all: - $(MAKE) -j 4 -C $(PKG_RECEIVER_DIRS) test + $(MAKE) for-all DIRS="$(PKG_DIRS)" CMD="$(MAKE) test" .PHONY: cibuild cibuild: check-prep @@ -91,11 +96,11 @@ dockerbuild: .PHONY: tidy-all tidy-all: - $(MAKE) -j 4 -C $(PKG_RECEIVER_DIRS) tidy + $(MAKE) for-all DIRS="$(PKG_DIRS)" CMD="$(MAKE) tidy" .PHONY: fmt-all fmt-all: - $(MAKE) -j 4 -C $(PKG_RECEIVER_DIRS) fmt + $(MAKE) for-all DIRS="$(PKG_DIRS)" CMD="$(MAKE) fmt" .PHONY: checks checks: @@ -106,3 +111,4 @@ checks: else \ echo "completed successfully."; \ fi + fi diff --git a/build/.vscode/launch.json b/build/.vscode/launch.json index ab33a054..106d534e 100644 --- a/build/.vscode/launch.json +++ b/build/.vscode/launch.json @@ -14,4 +14,4 @@ "envFile": "${workspaceFolder}/../.debug.env" } ] -} +} \ No newline at end of file diff --git a/cmd/compgen/.vscode/launch.json b/cmd/compgen/.vscode/launch.json new file mode 100644 index 00000000..9669bb8e --- /dev/null +++ b/cmd/compgen/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Run Compgen", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}", + "args": [ + "receiver", + "github.com/myreceiver", + "${workspaceFolder}/.debug/" + ], + "preLaunchTask": "createDebugDir", + "postDebugTask": "deleteDebugDir" + }, + { + "name": "Debug Current Test File", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${workspaceFolder}/${relativeFileDirname}", + } + ] +} \ No newline at end of file diff --git a/cmd/compgen/.vscode/tasks.json b/cmd/compgen/.vscode/tasks.json new file mode 100644 index 00000000..4c10bac7 --- /dev/null +++ b/cmd/compgen/.vscode/tasks.json @@ -0,0 +1,25 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "createDebugDir", + "type": "shell", + "command": "mkdir", + "args": [ + "-p", + "${workspaceFolder}/.debug" + ] + }, + { + "label": "deleteDebugDir", + "type": "shell", + "command": "rm", + "args": [ + "-r", + "${workspaceFolder}/.debug" + ] + } + ] +} \ No newline at end of file diff --git a/cmd/compgen/Makefile b/cmd/compgen/Makefile new file mode 100644 index 00000000..84677bc7 --- /dev/null +++ b/cmd/compgen/Makefile @@ -0,0 +1,2 @@ +include ../../Makefile.Common + diff --git a/cmd/compgen/READEME.md b/cmd/compgen/READEME.md new file mode 100644 index 00000000..7212779d --- /dev/null +++ b/cmd/compgen/READEME.md @@ -0,0 +1,90 @@ +# Compgen + +Compgen is a tool for building new otel components. The following component types are currently supported: + +- [Receivers](https://opentelemetry.io/docs/collector/configuration/#receivers) + - Pull (Scraper) + - [ ] Logs + - [x] Metrics + - [ ] Traces + - Push + - [ ] Logs + - [ ] Metrics + - [ ] Traces +- [ ] [Processors](https://opentelemetry.io/docs/collector/configuration/#processors) +- [ ] [Exporters](https://opentelemetry.io/docs/collector/configuration/#exporters) + +## Usage + +```sh +Compgen is a tool for building new receivers, processors, and exporters for Open Telemetry. + +Usage: + compgen [command] + +Available Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + receiver Build a new Open Telemetry receiver component + +Flags: + -h, --help help for compgen + +Use "compgen [command] --help" for more information about a command. +``` + +## Naming Conventions for Open Telemetry Components + +New component names are passed to compgen via command line arguments. These are expected to be a full [module paths](https://go.dev/ref/mod#glos-module-path) from which compgen will extract a short name to use in code generation. + +For example, if you wish to build a new receiver with a short name of `myreceiver`, then supply this string to the receiver subcommand: `github.com/liatrio/liatrio-otel-collector/receiver/myreceiver` + +## After Running Compgen + +### Makefile + +Components are expected to pass a series of tests defined by Makefile.Common. Your component's Makefile must import Makefile.common. An example is provided as a commment. + +### Metadata.yaml + +Compgen's includes templates for metadata.yaml. This file is used by [mdatagen](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/cmd/mdatagen) to generate aditional code for the component. See the [README](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/cmd/mdatagen/README.md) for details on how to use mdatagen. + +You can run mdatagen by running `make gen`. + +### Build Component Logic + +Compgen's component template are very limited by design. They are intended to supply the minimum amount of code required to compile, start, and run the component in a collector. Component developers are required to add all functionality to fulfill the component's purpose. See [OpenTelemetry's Building Custom Components](https://opentelemetry.io/docs/collector/building/) page for detailed information and examples of how to build new components. + +### Building Into The OTEL Binary + +When ready, you can compile your new component into Open Telemetry using OCB. Update `config/manifest.yaml` with configurations appropriate for your component. For example, a new receiver named `myreceiver` may require this added yaml: + +```yaml +receivers: + - gomod: github.com/liatrio/liatrio-otel-collector/receiver/myreceiver v0.1.0 +``` + +This will instruct OCB to include `myreceiver` when compiling Open Telemetry. Continuing the example, include the following configuration to instruct OCB to use the local code: + +```yaml +replaces: + - github.com/liatrio/liatrio-otel-collector/receiver/myreceiver => ../receiver/myreceiver/ +``` + +See [OpenTelemetry's Building a Custom Collector](https://opentelemetry.io/docs/collector/custom-collector/) documentation for additional guidance. + +## Contributing to Compgen + +### Adding Commands to Compgen + +Compgen is built on the [Cobra](https://github.com/spf13/cobra) library. While new commands can be added manually, it may be easier to use the [cobra-cli](https://github.com/spf13/cobra-cli/blob/main/README.md) instead. New commands can be added to compgen by running the following shell commands: + +```sh +cd cmd/compgen +cobra-cli add [command-name] +cobra-cli add [command-name] -p [parent-command-name] +``` + +### Adding Templates to Compgen + +New compgen commands are expected to be paired with new templates for Open Telemetry compnents. These templates should include the minimum functionality required to compile, start, and run the component in a collector. Conversely, these templates should include the maximum supporting code expected for the component, such as README and Makefile templates. diff --git a/cmd/compgen/cmd/common/common.go b/cmd/compgen/cmd/common/common.go new file mode 100644 index 00000000..7b3bb5b2 --- /dev/null +++ b/cmd/compgen/cmd/common/common.go @@ -0,0 +1,77 @@ +package cmd + +import ( + "errors" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" +) + +type TemplateData struct { + Name string + PackageName string +} + +func Tidy(path string) error { + cmd := exec.Command("go", "mod", "tidy", "-e") + cmd.Dir = path + output, err := cmd.CombinedOutput() + if err != nil { + return errors.New(string(output)) + } + return nil +} + +func Render(source fs.FS, destination string, data TemplateData) error { + // Iterate over all files/folders in 'source' and execute func() for entry found + return fs.WalkDir(source, "templates", func(path string, entry fs.DirEntry, err error) error { + // There was an error reading the fs. So we return the error to stop execution + if err != nil { + return err + } + + // WalkDir starts with the root directory passed. So we skip the first iteration + if path == "templates" { + return nil + } + + // We remove the top-level 'templates' directory + // E.g. "templates/main.go.tmpl" becomes "main.go.tmpl" + newFilename, err := filepath.Rel("templates", path) + if err != nil { + return err + } + + newFilename = filepath.Join(destination, strings.TrimSuffix(newFilename, ".tmpl")) + + // Copy directories + if entry.IsDir() { + return os.MkdirAll(newFilename, os.ModePerm) + } + + // Note that 'path' is not unmodified + if strings.HasSuffix(path, ".tmpl") { + content, err := fs.ReadFile(source, path) + if err != nil { + return err + } + + file, err := os.Create(newFilename) + if err != nil { + return err + } + defer file.Close() + + // The template is finally rendered and immediately written to file + tmpl := template.Must(template.New(newFilename).Parse(string(content))) + return tmpl.Execute(file, data) + } + + // The entry is not a directory and its name does not match *.tmpl + // Do nothing with these entries and continue iterating. + return nil + }) +} diff --git a/cmd/compgen/cmd/common/common_test.go b/cmd/compgen/cmd/common/common_test.go new file mode 100644 index 00000000..b5a7314b --- /dev/null +++ b/cmd/compgen/cmd/common/common_test.go @@ -0,0 +1,72 @@ +package cmd + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTidy(t *testing.T) { + dir := t.TempDir() + assert.Error(t, Tidy(dir)) + + cmd := exec.Command("go", "mod", "init", "dummy") + cmd.Dir = dir + _, err := cmd.Output() + assert.NoError(t, err) + + assert.NoError(t, Tidy(dir)) + assert.FileExists(t, filepath.Join(dir, "go.mod")) +} + +func TestRender(t *testing.T) { + source := t.TempDir() + destination := t.TempDir() + data := TemplateData{Name: "dummy", PackageName: "github.com/dummy"} + + // Source does not contain a "templates" directory + err := Render(os.DirFS(source), destination, data) + assert.Error(t, err) + + // Nothing to do + err = os.Mkdir(filepath.Join(source, "templates"), os.ModePerm) + assert.NoError(t, err) + err = Render(os.DirFS(source), destination, data) + assert.NoError(t, err) + + // Ignore non-template files + _, err = os.Create(filepath.Join(source, "templates", "main.go")) + assert.NoError(t, err) + err = Render(os.DirFS(source), destination, data) + assert.NoError(t, err) + assert.NoFileExists(t, filepath.Join(destination, "main.go")) + + // Empty tmpl file + file, err := os.Create(filepath.Join(source, "templates", "main.go.tmpl")) + assert.NoError(t, err) + err = Render(os.DirFS(source), destination, data) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(destination, "main.go")) + + // Normal tmpl file + _, err = file.WriteString("pkg {{ .PackageName }}") + assert.NoError(t, err) + err = Render(os.DirFS(source), destination, data) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(destination, "main.go")) + content, err := os.ReadFile(filepath.Join(destination, "main.go")) + assert.NoError(t, err) + assert.Equal(t, "pkg github.com/dummy", string(content)) + + // Empty tmpl file + err = os.Mkdir(filepath.Join(source, "templates", "more"), os.ModePerm) + assert.NoError(t, err) + _, err = os.Create(filepath.Join(source, "templates", "more", "test.go.tmpl")) + assert.NoError(t, err) + err = Render(os.DirFS(source), destination, data) + assert.NoError(t, err) + assert.FileExists(t, filepath.Join(destination, "more", "test.go")) +} diff --git a/cmd/compgen/cmd/receiver/receiver.go b/cmd/compgen/cmd/receiver/receiver.go new file mode 100644 index 00000000..6632a743 --- /dev/null +++ b/cmd/compgen/cmd/receiver/receiver.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "embed" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + common "github.com/liatrio/compgen/cmd/common" + "github.com/spf13/cobra" +) + +var shortDescription = "Build a new Open Telemetry receiver component" +var longDescription = ` +receiverName: A full module path (https://go.dev/ref/mod#glos-module-path) + E.g. 'github.com/liatrio/liatrio-otel-collector/receiver/myreceiver' + +outputDir: A full or relative path to a directory that contains receivers + E.g. receiver/` + +// ReceiverCmd represents the receiver command +var ReceiverCmd = &cobra.Command{ + Use: "receiver [flags] receiverName outputDir", + Short: shortDescription, + Long: fmt.Sprint(shortDescription, "\n", longDescription), + Args: cobra.MinimumNArgs(2), + RunE: run, +} + +//go:embed templates/* +var templates embed.FS + +// func init() { +// // Here you will define your flags and configuration settings. + +// // Cobra supports Persistent Flags which will work for this command +// // and all subcommands, e.g.: +// // receiverCmd.PersistentFlags().String("foo", "", "A help for foo") + +// // Cobra supports local flags which will only run when this command +// // is called directly, e.g.: +// // receiverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +// } + +func run(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("not enough arguments") + } + + packageName := args[0] + name := packageName[strings.LastIndex(packageName, "/")+1:] + modulePath := filepath.Join(args[1], name) + + err := os.MkdirAll(modulePath, os.ModePerm) + if err != nil { + return err + } + + data := common.TemplateData{Name: name, PackageName: packageName} + err = common.Render(templates, modulePath, data) + if err != nil { + return err + } + + err = common.Tidy(modulePath) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/compgen/cmd/receiver/receiver_test.go b/cmd/compgen/cmd/receiver/receiver_test.go new file mode 100644 index 00000000..09c3faee --- /dev/null +++ b/cmd/compgen/cmd/receiver/receiver_test.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRun(t *testing.T) { + // Missing args + // Note that this should be impossible at runtime. We use `cobra.MinimumNArgs()` + // when building `ReceiverCmd` to enforce that the correct number of arguments + // are passed in at runtime. This is enforced outside of run() so we still want + // to test the scenario. + args := []string{} + assert.Error(t, run(ReceiverCmd, args)) + + // Missing outputDir argument + args = []string{"github.com/dummy"} + assert.Error(t, run(ReceiverCmd, args)) + + // Healthy run + dir := t.TempDir() + args = []string{"github.com/dummy", dir} + assert.NoError(t, run(ReceiverCmd, args)) + + // Validate file count + // Note that the +1 is the result of generating go.sum without a go.sum.tmpl file + entries, err := os.ReadDir(filepath.Join(dir, "dummy")) + assert.NoError(t, err) + sources, err := templates.ReadDir("templates") + assert.NoError(t, err) + assert.Equal(t, len(sources)+1, len(entries)) +} diff --git a/cmd/compgen/cmd/receiver/templates/Makefile.tmpl b/cmd/compgen/cmd/receiver/templates/Makefile.tmpl new file mode 100644 index 00000000..0352ac97 --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/Makefile.tmpl @@ -0,0 +1 @@ +# include ../../../Makefile.Common diff --git a/cmd/compgen/cmd/receiver/templates/README.md.tmpl b/cmd/compgen/cmd/receiver/templates/README.md.tmpl new file mode 100644 index 00000000..c1ee938a --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/README.md.tmpl @@ -0,0 +1,27 @@ +# {{ .PackageName }} + +What does your receiver do? + +## Features + +- [x] Write a feature description here + +## Getting Started + +```yaml +sample: data +``` + +Here is a more complete example: + +```yaml +receivers: + {{ .Name }}: + sample: data +service: + pipelines: + metrics: + receivers: [..., {{ .Name }}] + processors: [] + exporters: [...] +``` diff --git a/cmd/compgen/cmd/receiver/templates/config.go.tmpl b/cmd/compgen/cmd/receiver/templates/config.go.tmpl new file mode 100644 index 00000000..f860037f --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/config.go.tmpl @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package {{ .Name }} // import "github.com/liatrio/liatrio-otel-collector/receiver/{{ .Name }}" + +import ( + "errors" + "strings" + + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +const configKey = "sample" + +var ( + ErrMustString = errors.New("sample.configuration must be a string") + ErrMustNotNil = errors.New("sample interface must not be nil") + ErrSampleConfig = errors.New("sample config data is required") + ErrMustLowercase = errors.New("sample config data must be lowercase") +) + +// Config that is exposed to this receiver through the OTEL config.yaml +type Config struct { + scraperhelper.ScraperControllerSettings `mapstructure:",squash"` + sample string +} + +// Unmarshal a config.Parser into the config struct. +func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error { + if componentParser == nil { + return nil + } + + // load the non-dynamic config normally + err := componentParser.Unmarshal(cfg) + if err != nil { + return err + } + + // dynamically load the individual collector configs based on the key name + if componentParser.IsSet(configKey) { + // use the value provided in the otel config.yaml + value, ok := componentParser.Get(configKey).(string) + if !ok { + if componentParser.Get(configKey) == nil { + return ErrMustNotNil + } else { + return ErrMustString + } + } + cfg.sample = value + } else { + // default value + cfg.sample = "data" + } + + return nil +} + +// Validate the configuration passed through the OTEL config.yaml +func (cfg *Config) Validate() error { + var err error = nil + + if cfg.sample == "" { + // err = multierr.Append(err, errors.New("sample config data is required")) + err = ErrSampleConfig + } else { + if cfg.sample != strings.ToLower(cfg.sample) { + // err = multierr.Append(err, errors.New("sample config data must be lowercase")) + err = ErrMustLowercase + } + } + + return err +} \ No newline at end of file diff --git a/cmd/compgen/cmd/receiver/templates/config_test.go.tmpl b/cmd/compgen/cmd/receiver/templates/config_test.go.tmpl new file mode 100644 index 00000000..212dabe6 --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/config_test.go.tmpl @@ -0,0 +1,148 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package {{ .Name }} + +import ( + "errors" + "path/filepath" + "testing" + + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/receiver/scraperhelper" +) + +////////////////// Testing Begins ////////////////// + +/* +This test file is an example of what test will look like in a typical custom +receiver config.go file. This will run some test to confirm the the data you +were expecting from the Custom Config is read correctly. The Go extension in +VSCode uses this default testing method where test are read in as structs and +then a loop goes through each struct and runs a test case. Go officially adopted +this approach, while this may not be the best way, it is a method that Go recommends. +Below you will see some test cases ran against a custom sample field added to a +custom config for our custom receiver. +*/ + +func TestConfig_Unmarshal(t *testing.T) { + type fields struct { // Custom struct for the Config passed into Unmarshal() by reference + ScraperControllerSettings scraperhelper.ScraperControllerSettings + sample string + } + tests := []struct { + name string // Name of the test + confMapKey string // confMapKey for filename + fields fields // config to be passed into Unmarshal() + wantErr error // error expected from function + wantConfig Config // check config output from function with custom config + wantErrCM error // errorCM expected from reading file + }{ // Test Cases for Unmarshal() Function + { // Test to see if the config file reads properly + name: "default healthy config", + confMapKey: "golden.yaml", + fields: fields{}, + wantErr: nil, + wantConfig: Config{ + sample: "data", + }, + wantErrCM: nil, + }, + { // Test to see if there is no value in the sample field of config + name: "no sample value", + confMapKey: "missing-data.yaml", + fields: fields{}, + wantErr: ErrMustNotNil, + wantConfig: Config{}, + wantErrCM: nil, + }, + { // Test to see if the sample field doesn't exist in the config + name: "no sample field", + confMapKey: "partial.yaml", + fields: fields{}, + wantErr: nil, + wantConfig: Config{ + sample: "data", + }, + wantErrCM: nil, + }, + { // Test to see if the sample field contains a string + name: "not a string", + confMapKey: "invalid-data-type.yaml", + fields: fields{}, + wantErr: ErrMustString, + wantConfig: Config{}, + wantErrCM: nil, + }, + { // Test to see if a config file exist + name: "no data in file", + confMapKey: "empty.yaml", + fields: fields{}, + wantErr: nil, + wantConfig: Config{ + sample: "data", + }, + wantErrCM: nil, + }, + } + for _, tt := range tests { // loop through test struct objects + t.Run(tt.name, func(t *testing.T) { + // This Config uses a default configuration for the ScraperControllerSettings and whatever is read from a config.yaml file for the sample data + cfg := &Config{ // config to pass into Unmarshal() function + ScraperControllerSettings: tt.fields.ScraperControllerSettings, + sample: tt.fields.sample, + } + confMap, errCM := confmaptest.LoadConf(filepath.Join("testdata", tt.confMapKey)) + if !errors.Is(errCM, tt.wantErrCM) { // Check if file is supposed to exist or not + t.Errorf("Could not read config.yaml file: %v", tt.confMapKey) + } + err := cfg.Unmarshal(confMap) // confMap is dynamic instead of static to ensure generic testing + if !errors.Is(err, tt.wantErr) { // check if the error message returned was correct + t.Errorf("Config.Unmarshal() error: %v, wantErr is: %v", err, tt.wantErr) + } + // check config to see if that was updated properly (cfg) using custom cfg in test + if tt.wantConfig.sample != cfg.sample { + t.Errorf("cfg.sample value is: %v, wantConfig is: %v", cfg.sample, tt.wantConfig.sample) + } + }) + } +} + +func TestConfig_Validate(t *testing.T) { + tests := []struct { + name string // Name of the test + wantErr error // error expected from function + config Config // Config to validate if correct + }{ // Test Cases for Validate() Function + { // healthy test to see if the function returns nil error + name: "healthy", + wantErr: nil, + config: Config{ + sample: "dummy", + }, + }, + { // no sample config, so the function should return error + name: "no sample config", + wantErr: ErrSampleConfig, + config: Config{ + sample: "", + }, + }, + { // no sample data, so function should return error + name: "sample data must be lowercase", + wantErr: ErrMustLowercase, + config: Config{ + sample: "DuMmy", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := tt.config // Set config to variable provided in test cases + err := cfg.Validate() + if !errors.Is(err, tt.wantErr) { // check if the error message returned was correct + t.Errorf("Config.Validate() error: %v, wantErr is: %v", err, tt.wantErr) + } + }) + } +} \ No newline at end of file diff --git a/cmd/compgen/cmd/receiver/templates/doc.go.tmpl b/cmd/compgen/cmd/receiver/templates/doc.go.tmpl new file mode 100644 index 00000000..cdb20924 --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/doc.go.tmpl @@ -0,0 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:generate mdatagen metadata.yaml + +package {{ .Name }} // import "{{ .PackageName }}" diff --git a/cmd/compgen/cmd/receiver/templates/factory.go.tmpl b/cmd/compgen/cmd/receiver/templates/factory.go.tmpl new file mode 100644 index 00000000..e43c05ec --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/factory.go.tmpl @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package {{ .Name }} // import "{{ .PackageName }}" + +import ( + "context" + "errors" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/scraperhelper" + + "{{ .PackageName }}/internal" + "{{ .PackageName }}/internal/metadata" +) + +var ( + ErrConfigNotValid = errors.New("configuration is not valid") +) + +// NewFactory creates a factory for the new receiver +func NewFactory() receiver.Factory { + return receiver.NewFactory( + metadata.Type, + createDefaultConfig, + receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability), + ) +} + +// Create the default config based on the const(s) defined above. +func createDefaultConfig() component.Config { + return &Config{ScraperControllerSettings: scraperhelper.NewDefaultScraperControllerSettings(metadata.Type)} +} + +// Create the metrics receiver according to the OTEL conventions taking in the +// context, receiver params, configuration from the component, and consumer (process or exporter) +func createMetricsReceiver( + ctx context.Context, + params receiver.CreateSettings, + cfg component.Config, + consumer consumer.Metrics, +) (receiver.Metrics, error) { + + // check that the configuration is valid + conf, ok := cfg.(*Config) + if !ok { + return nil, ErrConfigNotValid + } + + scraperConfig := internal.ScraperConfig{ScraperControllerSettings: conf.ScraperControllerSettings} + + addScraperOpts, err := createAddScraperOpts(ctx, params, scraperConfig) + if err != nil { + return nil, err + } + + return scraperhelper.NewScraperControllerReceiver( + &conf.ScraperControllerSettings, + params, + consumer, + addScraperOpts, + ) +} + +func createAddScraperOpts( + ctx context.Context, + params receiver.CreateSettings, + cfg internal.ScraperConfig, +) (scraperhelper.ScraperControllerOption, error) { + + factory := internal.ScraperFactory{} + scraper, err := factory.CreateMetricsScraper(ctx, params, cfg) + if err != nil { + return nil, err + } + + return scraperhelper.AddScraper(scraper), nil +} diff --git a/cmd/compgen/cmd/receiver/templates/factory_test.go.tmpl b/cmd/compgen/cmd/receiver/templates/factory_test.go.tmpl new file mode 100644 index 00000000..0ecd8b9b --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/factory_test.go.tmpl @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package {{ .Name }} + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +var creationSet = receivertest.NewNopCreateSettings() + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, componenttest.CheckConfigStruct(cfg)) +} + +func TestCreateReceiver(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + tReceiver, err := factory.CreateTracesReceiver(context.Background(), creationSet, cfg, consumertest.NewNop()) + assert.Equal(t, err, component.ErrDataTypeIsNotSupported) + assert.Nil(t, tReceiver) + + mReceiver, err := factory.CreateMetricsReceiver(context.Background(), creationSet, cfg, consumertest.NewNop()) + assert.NoError(t, err) + assert.NotNil(t, mReceiver) + + tLogs, err := factory.CreateLogsReceiver(context.Background(), creationSet, cfg, consumertest.NewNop()) + assert.Equal(t, err, component.ErrDataTypeIsNotSupported) + assert.Nil(t, tLogs) +} + +func TestCreateReceiver_ScraperConfigError(t *testing.T) { + factory := NewFactory() + + _, err := factory.CreateMetricsReceiver(context.Background(), creationSet, "dummy", consumertest.NewNop()) + assert.EqualError(t, err, ErrConfigNotValid.Error()) +} diff --git a/cmd/compgen/cmd/receiver/templates/go.mod.tmpl b/cmd/compgen/cmd/receiver/templates/go.mod.tmpl new file mode 100644 index 00000000..c567ef3c --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/go.mod.tmpl @@ -0,0 +1 @@ +module {{ .PackageName }} diff --git a/cmd/compgen/cmd/receiver/templates/internal/scraper.go.tmpl b/cmd/compgen/cmd/receiver/templates/internal/scraper.go.tmpl new file mode 100644 index 00000000..bb1f4634 --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/internal/scraper.go.tmpl @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "{{ .PackageName }}/internal" + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/scraperhelper" + "go.uber.org/zap" +) + +type ScraperFactory struct{} + +func (f *ScraperFactory) CreateMetricsScraper( + ctx context.Context, + params receiver.CreateSettings, + cfg ScraperConfig, +) (scraperhelper.Scraper, error) { + s := &scraper{ + cfg: &cfg, + logger: params.Logger, + settings: params.TelemetrySettings, + } + + return scraperhelper.NewScraper( + "{{ .Name }}Scraper", + s.scrape, + scraperhelper.WithStart(s.start), + ) +} + +type ScraperConfig struct { + scraperhelper.ScraperControllerSettings `mapstructure:",squash"` +} + +type scraper struct { + cfg *ScraperConfig + logger *zap.Logger + settings component.TelemetrySettings +} + +func (s *scraper) start(_ context.Context, host component.Host) error { + s.logger.Sugar().Info("starting the {{ .Name }} scraper") + return nil +} + +func (s *scraper) scrape(_ context.Context) (pmetric.Metrics, error) { + s.logger.Sugar().Info("running the {{ .Name }} scrape function") + return pmetric.NewMetrics(), nil +} diff --git a/cmd/compgen/cmd/receiver/templates/internal/scraper_test.go.tmpl b/cmd/compgen/cmd/receiver/templates/internal/scraper_test.go.tmpl new file mode 100644 index 00000000..29eee27b --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/internal/scraper_test.go.tmpl @@ -0,0 +1,29 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal + +import ( + "context" + "testing" + + "github.com/alecthomas/assert/v2" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +var creationSet = receivertest.NewNopCreateSettings() + +func TestScraperFactory_CreateMetricsScraper(t *testing.T) { + factory := ScraperFactory{} + ctx := context.Background() + cfg := &ScraperConfig{} + + scraper, err := factory.CreateMetricsScraper(ctx, creationSet, *cfg) + assert.NoError(t, err) + + assert.NoError(t, scraper.Start(ctx, componenttest.NewNopHost())) + + _, err = scraper.Scrape(ctx) + assert.NoError(t, err) +} diff --git a/cmd/compgen/cmd/receiver/templates/metadata.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/metadata.yaml.tmpl new file mode 100644 index 00000000..a0a4fb65 --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/metadata.yaml.tmpl @@ -0,0 +1,13 @@ +--- +type: {{ .Name }} + +sem_conv_version: 1.18.0 + +status: + class: receiver + stability: + development: [metrics] + distributions: [liatrio] + +# See the [mdatagen documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/cmd/mdatagen) for building this file +# Also see [metadata-sample.yaml](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/cmd/mdatagen/metadata-sample.yaml) diff --git a/cmd/compgen/cmd/receiver/templates/testdata/config-manual.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/testdata/config-manual.yaml.tmpl new file mode 100644 index 00000000..be6721bb --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/testdata/config-manual.yaml.tmpl @@ -0,0 +1,16 @@ +--- +exporters: + logging: + +receivers: + {{ .Name }}: + collection_interval: 10s + initial_delay: 1s + sample: data + +service: + pipelines: + metrics: + receivers: [{{ .Name }}] + processors: [] + exporters: [logging] diff --git a/cmd/compgen/cmd/receiver/templates/testdata/empty.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/testdata/empty.yaml.tmpl new file mode 100644 index 00000000..73b314ff --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/testdata/empty.yaml.tmpl @@ -0,0 +1 @@ +--- \ No newline at end of file diff --git a/cmd/compgen/cmd/receiver/templates/testdata/golden.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/testdata/golden.yaml.tmpl new file mode 100644 index 00000000..1d0ed9ad --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/testdata/golden.yaml.tmpl @@ -0,0 +1,4 @@ +--- +collection_interval: 10s +initial_delay: 1s +sample: data \ No newline at end of file diff --git a/cmd/compgen/cmd/receiver/templates/testdata/invalid-data-type.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/testdata/invalid-data-type.yaml.tmpl new file mode 100644 index 00000000..9f42a4dd --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/testdata/invalid-data-type.yaml.tmpl @@ -0,0 +1,4 @@ +--- +collection_interval: 10s +initial_delay: 1s +sample: 32 \ No newline at end of file diff --git a/cmd/compgen/cmd/receiver/templates/testdata/missing-data.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/testdata/missing-data.yaml.tmpl new file mode 100644 index 00000000..048e6e48 --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/testdata/missing-data.yaml.tmpl @@ -0,0 +1,4 @@ +--- +collection_interval: 10s +initial_delay: 1s +sample: \ No newline at end of file diff --git a/cmd/compgen/cmd/receiver/templates/testdata/partial.yaml.tmpl b/cmd/compgen/cmd/receiver/templates/testdata/partial.yaml.tmpl new file mode 100644 index 00000000..ce19b4ae --- /dev/null +++ b/cmd/compgen/cmd/receiver/templates/testdata/partial.yaml.tmpl @@ -0,0 +1,3 @@ +--- +collection_interval: 10s +initial_delay: 1s \ No newline at end of file diff --git a/cmd/compgen/cmd/root.go b/cmd/compgen/cmd/root.go new file mode 100644 index 00000000..a090f0b0 --- /dev/null +++ b/cmd/compgen/cmd/root.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "log" + "os" + + receiver "github.com/liatrio/compgen/cmd/receiver" + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "compgen", + Short: "A tool for building new otel components.", + Long: `Compgen is a tool for building new receivers, processors, and exporters for Open Telemetry.`, + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + log.Default().SetFlags(log.Lshortfile) + + rootCmd.AddCommand(receiver.ReceiverCmd) +} diff --git a/cmd/compgen/go.mod b/cmd/compgen/go.mod new file mode 100644 index 00000000..a62ca6c8 --- /dev/null +++ b/cmd/compgen/go.mod @@ -0,0 +1,16 @@ +module github.com/liatrio/compgen + +go 1.21 + +require ( + github.com/spf13/cobra v1.8.0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cmd/compgen/go.sum b/cmd/compgen/go.sum new file mode 100644 index 00000000..8236a263 --- /dev/null +++ b/cmd/compgen/go.sum @@ -0,0 +1,18 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= diff --git a/cmd/compgen/main.go b/cmd/compgen/main.go new file mode 100644 index 00000000..7dae4875 --- /dev/null +++ b/cmd/compgen/main.go @@ -0,0 +1,9 @@ +package main + +import ( + cmd "github.com/liatrio/compgen/cmd" +) + +func main() { + cmd.Execute() +}