From ee3782f71914c67419b90c89f3bd13392836964b Mon Sep 17 00:00:00 2001 From: James Hong Date: Tue, 2 Jul 2024 20:14:57 +1000 Subject: [PATCH] feature: Restrict Kubechecks to a Single Namespace for App Watcher (#235) * Restrict Kubechecks to a Single Namespace for App Watcher * fix github webhook check * update ai check to use gpt-4o model * unit test for github_client * change dump_crd --- .github/actions/build-image/action.yaml | 2 +- .github/workflows/on_pull-request_docs.yaml | 2 +- .github/workflows/on_pull-request_helm.yaml | 2 +- .github/workflows/on_pull_request_go.yaml | 2 +- .github/workflows/on_push_to_main.yaml | 2 +- .gitignore | 2 + .mockery.yaml | 7 + .env.example => .secret.example | 1 - Tiltfile | 54 ++- cmd/container.go | 4 +- cmd/root.go | 7 +- docs/contributing.md | 14 +- docs/usage.md | 1 + earthly => earthly.sh | 5 +- go.mod | 6 +- go.sum | 6 +- justfile | 4 +- localdev/kubechecks/values.yaml | 4 +- .../mocks/mock_IssuesServices.go | 296 ++++++++++++++ .../mocks/mock_PullRequestsServices.go | 302 ++++++++++++++ .../mocks/mock_RepositoriesServices.go | 378 ++++++++++++++++++ pkg/aisummary/diff_summary.go | 2 +- pkg/app_watcher/app_watcher.go | 19 +- pkg/app_watcher/app_watcher_test.go | 16 +- pkg/config/config.go | 9 +- pkg/vcs/github_client/client.go | 56 ++- pkg/vcs/github_client/client_test.go | 284 +++++++++++++ pkg/vcs/github_client/issue.go | 18 + pkg/vcs/github_client/message.go | 2 +- pkg/vcs/github_client/pullrequest.go | 18 + pkg/vcs/github_client/repo.go | 19 + tools/dump_crds/cmd/README.md | 38 ++ tools/dump_crds/cmd/dumpcrd/dump.go | 153 +++++++ tools/dump_crds/cmd/dumpcrd/root.go | 63 +++ tools/dump_crds/cmd/main.go | 10 + tools/dump_crds/dump_crds.go | 90 ----- tools/dump_crds/go.mod | 69 +++- tools/dump_crds/go.sum | 196 +++++++++ tools/dump_crds/internal/logger/logger.go | 71 ++++ 39 files changed, 2084 insertions(+), 150 deletions(-) create mode 100644 .mockery.yaml rename .env.example => .secret.example (75%) rename earthly => earthly.sh (91%) create mode 100644 mocks/github_client/mocks/mock_IssuesServices.go create mode 100644 mocks/github_client/mocks/mock_PullRequestsServices.go create mode 100644 mocks/github_client/mocks/mock_RepositoriesServices.go create mode 100644 pkg/vcs/github_client/issue.go create mode 100644 pkg/vcs/github_client/pullrequest.go create mode 100644 pkg/vcs/github_client/repo.go create mode 100644 tools/dump_crds/cmd/README.md create mode 100644 tools/dump_crds/cmd/dumpcrd/dump.go create mode 100644 tools/dump_crds/cmd/dumpcrd/root.go create mode 100644 tools/dump_crds/cmd/main.go delete mode 100644 tools/dump_crds/dump_crds.go create mode 100644 tools/dump_crds/internal/logger/logger.go diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml index 900082b3..ec7c1cbb 100644 --- a/.github/actions/build-image/action.yaml +++ b/.github/actions/build-image/action.yaml @@ -41,7 +41,7 @@ runs: - name: Build and push the Docker image shell: bash run: >- - ./earthly + ./earthly.sh --push +docker-multiarch ${{ inputs.tag_latest != 'false' && format('--LATEST_IMAGE_NAME=ghcr.io/{0}:latest', github.repository) || '' }} diff --git a/.github/workflows/on_pull-request_docs.yaml b/.github/workflows/on_pull-request_docs.yaml index 8ad846db..8fd52d0d 100644 --- a/.github/workflows/on_pull-request_docs.yaml +++ b/.github/workflows/on_pull-request_docs.yaml @@ -21,7 +21,7 @@ jobs: with: { version: "${{ env.EARTHLY_TOOL_VERSION }}" } - name: rebuild the docs - run: ./earthly +rebuild-docs + run: ./earthly.sh +rebuild-docs - name: verify that the checked in file has not changed run: ./hacks/exit-on-changed-files.sh "Please run './earthly +rebuild-docs' and commit the results to this PR" diff --git a/.github/workflows/on_pull-request_helm.yaml b/.github/workflows/on_pull-request_helm.yaml index 15c6c03a..b77448c1 100644 --- a/.github/workflows/on_pull-request_helm.yaml +++ b/.github/workflows/on_pull-request_helm.yaml @@ -17,4 +17,4 @@ jobs: - uses: earthly/actions-setup@v1 with: { version: "v${{ env.EARTHLY_TOOL_VERSION }}" } - - run: ./earthly +ci-helm + - run: ./earthly.sh +ci-helm diff --git a/.github/workflows/on_pull_request_go.yaml b/.github/workflows/on_pull_request_go.yaml index ac9c7ee0..bc203682 100644 --- a/.github/workflows/on_pull_request_go.yaml +++ b/.github/workflows/on_pull_request_go.yaml @@ -19,4 +19,4 @@ jobs: - uses: earthly/actions-setup@v1 with: { version: "v${{ env.EARTHLY }}" } - - run: ./earthly +ci-golang + - run: ./earthly.sh +ci-golang diff --git a/.github/workflows/on_push_to_main.yaml b/.github/workflows/on_push_to_main.yaml index b4869962..c3a9ac13 100644 --- a/.github/workflows/on_push_to_main.yaml +++ b/.github/workflows/on_push_to_main.yaml @@ -43,7 +43,7 @@ jobs: - name: Build and push the helm charts run: | - ./earthly \ + ./earthly.sh \ --push \ +release-helm \ --repo_owner ${{ github.repository_owner }} \ diff --git a/.gitignore b/.gitignore index af917a10..4790d880 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ localdev/terraform/gitlab/project.url *.DS_Store /kubechecks localdev/terraform/github/project.url +.secret +.arg diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 00000000..b3b375c5 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,7 @@ +with-expecter: true +dir: "mocks/{{.PackageName}}/mocks" +packages: + github.com/zapier/kubechecks/pkg/vcs/github_client: + # place your package-specific config here + config: + all: true diff --git a/.env.example b/.secret.example similarity index 75% rename from .env.example rename to .secret.example index 6722f081..4e0593cc 100644 --- a/.env.example +++ b/.secret.example @@ -1,5 +1,4 @@ GITLAB_TOKEN=xyz -KUBECHECKS_LOG_LEVEL=debug OPENAI_API_TOKEN=xyz GITHUB_TOKEN=xyz KUBECHECKS_WEBHOOK_SECRET=xyz \ No newline at end of file diff --git a/Tiltfile b/Tiltfile index 6c4dec81..90161660 100644 --- a/Tiltfile +++ b/Tiltfile @@ -7,7 +7,12 @@ load('ext://uibutton', 'cmd_button') load('ext://helm_resource', 'helm_resource') load('./.tilt/terraform/Tiltfile', 'local_terraform_resource') load('./.tilt/utils/Tiltfile', 'check_env_set') -dotenv() + +# Check if the .secret file exists +if not os.path.exists('.secret'): + fail('The .secret file is missing. Please copy .secret file from .secret.example and setup before running Tilt.') + +dotenv(fn='.secret') config.define_bool("enable_repo", True, 'create a new project for testing this app') config.define_string("vcs-type") @@ -126,7 +131,7 @@ if cfg.get('enable_repo', True): test_go( 'go-test', '.', recursive=True, - timeout='30s', + timeout='60s', extra_args=['-v'], labels=["kubechecks"], deps=[ @@ -138,12 +143,55 @@ test_go( ], ) + +# get the git commit ref +def get_git_head(): + result = local('git rev-parse --short HEAD') + return result + +# read .tool-versions file and return a dictionary of tools and their versions +def parse_tool_versions(fn): + if not os.path.exists(fn): + warn("tool versions file not found: '%s'" % fn) + return dict() + + f = read_file(fn) + + lines = str(f).splitlines() + + tools = dict() + + for linenumber in range(len(lines)): + line = lines[linenumber] + parts = line.split("#", 1) + if len(parts) == 2: + line = parts[0] + line = line.strip() + if line == "": + continue + parts = line.split(' ', 1) + tools[parts[0].strip()] = parts[1].strip() + return tools + +tool_versions = parse_tool_versions(".tool-versions") +git_commit = str(get_git_head()).strip() + earthly_build( context='.', target="+docker-debug", ref='kubechecks', image_arg='IMAGE_NAME', ignore='./dist', + extra_args=[ + '--CHART_RELEASER_VERSION='+tool_versions.get('helm-cr'), + '--GOLANG_VERSION='+tool_versions.get('golang'), + '--GOLANGCI_LINT_VERSION='+tool_versions.get('golangci-lint'), + '--HELM_VERSION='+tool_versions.get('helm'), + '--KUBECONFORM_VERSION='+tool_versions.get('kubeconform'), + '--KUSTOMIZE_VERSION='+tool_versions.get('kustomize'), + '--STATICCHECK_VERSION='+tool_versions.get('staticcheck'), + '--GIT_COMMIT='+git_commit, + ], ) cmd_button('loc:go mod tidy', @@ -216,4 +264,4 @@ load("localdev/test_appsets/Tiltfile", "install_test_appsets") install_test_appsets(cfg) -force_argocd_cleanup_on_tilt_down() \ No newline at end of file +force_argocd_cleanup_on_tilt_down() diff --git a/cmd/container.go b/cmd/container.go index ec04433c..235d9654 100644 --- a/cmd/container.go +++ b/cmd/container.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/pkg/errors" - + "github.com/rs/zerolog/log" "github.com/zapier/kubechecks/pkg/app_watcher" "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/argo_client" @@ -60,6 +60,8 @@ func newContainer(ctx context.Context, cfg config.ServerConfig, watchApps bool) go ctr.ApplicationWatcher.Run(ctx, 1) } + } else { + log.Info().Msgf("not monitoring applications, MonitorAllApplications: %+v", cfg.MonitorAllApplications) } return ctr, nil diff --git a/cmd/root.go b/cmd/root.go index d395c5c9..12b94ae5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -56,8 +56,13 @@ func init() { withDefault("gitlab")) stringFlag(flags, "vcs-token", "VCS API token.") stringFlag(flags, "argocd-api-token", "ArgoCD API token.") - stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.", newStringOpts().withDefault("argocd-server")) + stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.", + newStringOpts(). + withDefault("argocd-server")) boolFlag(flags, "argocd-api-insecure", "Enable to use insecure connections to the ArgoCD API server.") + stringFlag(flags, "argocd-api-namespace", "ArgoCD namespace where the application watcher will read Custom Resource Definitions (CRD) for Application and ApplicationSet resources.", + newStringOpts(). + withDefault("argocd")) stringFlag(flags, "kubernetes-config", "Path to your kubernetes config file, used to monitor applications.") stringFlag(flags, "otel-collector-port", "The OpenTelemetry collector port.") diff --git a/docs/contributing.md b/docs/contributing.md index 53b593dc..841fb610 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -67,10 +67,18 @@ It creates: To get started do the following: -* Copy the `.env.example` and set required values. +* Copy the `.secret.example` and set required values. ```console - cp .env.example .env + cp .secret.example .secret + ``` + You will need to fill in either `GITLAB_TOKEN` or `GITLAB_TOKEN` + If you are testing with GITHUB, please set the tile_config.json file to specify the vcs-type as the default is `gitlab`. + The token you specify must have ability to get repositories, add/delete comment and webhooks. + ```json + { + "vcs-type": "github" + } ``` * From the root directory of this repo: @@ -110,7 +118,7 @@ If you're using minikube with Tilt we recommend following this [guide](https://g ### Code Changes -We use Earthly to simplify our CI/CD process with `kubechecks`. There's a thin wrapper around earthly that passes some common arguments in the root of the repo called `./earthly` that should be used instead of calling earthly directly. This also simplifies testing changes locally before pushing them up to ensure your PR will pass all required checks. The best command to run is `./earthly +test` this will pull all the required dependencies (including any new ones that you have added). It will then run [go vet](https://pkg.go.dev/cmd/vet), and if those pass it will run `go test` with race detection enabled. You can also always run these commands directly `go test -race ./...` will run all tests in the repo with race detection enabled. Please ensure that `./earthly +test` is passing before opening a PR. +We use Earthly to simplify our CI/CD process with `kubechecks`. There's a thin wrapper around earthly that passes some common arguments in the root of the repo called `./earthly.sh` that should be used instead of calling earthly directly. This also simplifies testing changes locally before pushing them up to ensure your PR will pass all required checks. The best command to run is `./earthly.sh +test` this will pull all the required dependencies (including any new ones that you have added). It will then run [go vet](https://pkg.go.dev/cmd/vet), and if those pass it will run `go test` with race detection enabled. You can also always run these commands directly `go test -race ./...` will run all tests in the repo with race detection enabled. Please ensure that `./earthly.sh +test` is passing before opening a PR. ### Documentation Changes diff --git a/docs/usage.md b/docs/usage.md index d72b433a..b58d5e1f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -37,6 +37,7 @@ The full list of supported environment variables is described below: |Env Var|Description|Default Value| |-----------|-------------|------| |`KUBECHECKS_ARGOCD_API_INSECURE`|Enable to use insecure connections to the ArgoCD API server.|`false`| +|`KUBECHECKS_ARGOCD_API_NAMESPACE`|ArgoCD namespace where the application watcher will read Custom Resource Definitions (CRD) for Application and ApplicationSet resources.|`argocd`| |`KUBECHECKS_ARGOCD_API_SERVER_ADDR`|ArgoCD API Server Address.|`argocd-server`| |`KUBECHECKS_ARGOCD_API_TOKEN`|ArgoCD API token.|| |`KUBECHECKS_ENABLE_CONFTEST`|Set to true to enable conftest policy checking of manifests.|`false`| diff --git a/earthly b/earthly.sh similarity index 91% rename from earthly rename to earthly.sh index 9e577eda..1632fd09 100755 --- a/earthly +++ b/earthly.sh @@ -8,7 +8,7 @@ to_echo() { read_tool_versions_write_to_env() { local -r tool_versions_file="$1" - + cat $tool_versions_file # loop over each line of the .tool-versions file while read -r line; do # split the line into a bash array using the default space delimeter @@ -39,4 +39,5 @@ earthly $* \ --KUBECONFORM_VERSION=${kubeconform_tool_version} \ --KUSTOMIZE_VERSION=${kustomize_tool_version} \ --STATICCHECK_VERSION=${staticcheck_tool_version} \ - --GIT_COMMIT=$(git rev-parse --short HEAD) + --GIT_COMMIT=$(git rev-parse --short HEAD) \ + --KUBECHECKS_LOG_LEVEL=debug diff --git a/go.mod b/go.mod index b3eb9d9a..52b48117 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 github.com/go-logr/zerologr v1.2.3 - github.com/google/go-github/v53 v53.2.0 + github.com/google/go-github/v62 v62.0.0 github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb github.com/labstack/echo-contrib v0.16.0 github.com/labstack/echo/v4 v4.11.4 @@ -27,7 +27,7 @@ require ( github.com/prometheus/client_golang v1.19.0 github.com/rikatz/kubepug v1.4.0 github.com/rs/zerolog v1.32.0 - github.com/sashabaranov/go-openai v1.20.4 + github.com/sashabaranov/go-openai v1.26.2 github.com/shurcooL/githubv4 v0.0.0-20231126234147-1cffa1f02456 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 @@ -131,6 +131,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-github/v53 v53.2.0 // indirect github.com/google/go-jsonnet v0.20.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -215,6 +216,7 @@ require ( github.com/spdx/tools-golang v0.5.3 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/tmccombs/hcl2json v0.3.6 // indirect diff --git a/go.sum b/go.sum index 698ccc24..a76048ba 100644 --- a/go.sum +++ b/go.sum @@ -558,6 +558,8 @@ 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-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= +github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g= github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -884,8 +886,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sashabaranov/go-openai v1.20.4 h1:095xQ/fAtRa0+Rj21sezVJABgKfGPNbyx/sAN/hJUmg= -github.com/sashabaranov/go-openai v1.20.4/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.26.2 h1:cVlQa3gn3eYqNXRW03pPlpy6zLG52EU4g0FrWXc0EFI= +github.com/sashabaranov/go-openai v1.26.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= diff --git a/justfile b/justfile index d3fed4b5..16ed2055 100644 --- a/justfile +++ b/justfile @@ -65,7 +65,7 @@ unit_test_race: go test -race ./... rebuild_docs: - ./earthly +rebuild-docs + ./earthly.sh +rebuild-docs ci-golang: - ./earthly +ci-golang + ./earthly.sh +ci-golang diff --git a/localdev/kubechecks/values.yaml b/localdev/kubechecks/values.yaml index 84b9bf7b..9e5473e3 100644 --- a/localdev/kubechecks/values.yaml +++ b/localdev/kubechecks/values.yaml @@ -5,7 +5,7 @@ configMap: KUBECHECKS_ENABLE_WEBHOOK_CONTROLLER: "false" KUBECHECKS_ARGOCD_API_INSECURE: "true" KUBECHECKS_ARGOCD_API_PATH_PREFIX : '/argocd' - KUBECHECKS_ARGOCD_NAMESPACE: 'kubechecks' + KUBECHECKS_ARGOCD_API_NAMESPACE: 'kubechecks' KUBECHECKS_WEBHOOK_URL_PREFIX: 'kubechecks' KUBECHECKS_NAMESPACE: 'kubechecks' KUBECHECKS_FALLBACK_K8S_VERSION: "1.25.0" @@ -20,7 +20,7 @@ configMap: # KUBECHECKS_LABEL_FILTER: "test" # On your PR/MR, prefix this with "kubechecks:" # KUBECHECKS_SCHEMAS_LOCATION: https://github.com/zapier/kubecheck-schemas.git KUBECHECKS_TIDY_OUTDATED_COMMENTS_MODE: "delete" - KUBECHECKS_ENABLE_CONFTEST: "true" + KUBECHECKS_ENABLE_CONFTEST: "false" deployment: diff --git a/mocks/github_client/mocks/mock_IssuesServices.go b/mocks/github_client/mocks/mock_IssuesServices.go new file mode 100644 index 00000000..b7be7caf --- /dev/null +++ b/mocks/github_client/mocks/mock_IssuesServices.go @@ -0,0 +1,296 @@ +// Code generated by mockery v2.37.1. DO NOT EDIT. + +package github_client + +import ( + context "context" + + github "github.com/google/go-github/v62/github" + + mock "github.com/stretchr/testify/mock" +) + +// MockIssuesServices is an autogenerated mock type for the IssuesServices type +type MockIssuesServices struct { + mock.Mock +} + +type MockIssuesServices_Expecter struct { + mock *mock.Mock +} + +func (_m *MockIssuesServices) EXPECT() *MockIssuesServices_Expecter { + return &MockIssuesServices_Expecter{mock: &_m.Mock} +} + +// CreateComment provides a mock function with given fields: ctx, owner, repo, number, comment +func (_m *MockIssuesServices) CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, number, comment) + + var r0 *github.IssueComment + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, *github.IssueComment) (*github.IssueComment, *github.Response, error)); ok { + return rf(ctx, owner, repo, number, comment) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, *github.IssueComment) *github.IssueComment); ok { + r0 = rf(ctx, owner, repo, number, comment) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.IssueComment) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int, *github.IssueComment) *github.Response); ok { + r1 = rf(ctx, owner, repo, number, comment) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, int, *github.IssueComment) error); ok { + r2 = rf(ctx, owner, repo, number, comment) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockIssuesServices_CreateComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateComment' +type MockIssuesServices_CreateComment_Call struct { + *mock.Call +} + +// CreateComment is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - number int +// - comment *github.IssueComment +func (_e *MockIssuesServices_Expecter) CreateComment(ctx interface{}, owner interface{}, repo interface{}, number interface{}, comment interface{}) *MockIssuesServices_CreateComment_Call { + return &MockIssuesServices_CreateComment_Call{Call: _e.mock.On("CreateComment", ctx, owner, repo, number, comment)} +} + +func (_c *MockIssuesServices_CreateComment_Call) Run(run func(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment)) *MockIssuesServices_CreateComment_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int), args[4].(*github.IssueComment)) + }) + return _c +} + +func (_c *MockIssuesServices_CreateComment_Call) Return(_a0 *github.IssueComment, _a1 *github.Response, _a2 error) *MockIssuesServices_CreateComment_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockIssuesServices_CreateComment_Call) RunAndReturn(run func(context.Context, string, string, int, *github.IssueComment) (*github.IssueComment, *github.Response, error)) *MockIssuesServices_CreateComment_Call { + _c.Call.Return(run) + return _c +} + +// DeleteComment provides a mock function with given fields: ctx, owner, repo, commentID +func (_m *MockIssuesServices) DeleteComment(ctx context.Context, owner string, repo string, commentID int64) (*github.Response, error) { + ret := _m.Called(ctx, owner, repo, commentID) + + var r0 *github.Response + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*github.Response, error)); ok { + return rf(ctx, owner, repo, commentID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.Response); ok { + r0 = rf(ctx, owner, repo, commentID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.Response) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok { + r1 = rf(ctx, owner, repo, commentID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockIssuesServices_DeleteComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteComment' +type MockIssuesServices_DeleteComment_Call struct { + *mock.Call +} + +// DeleteComment is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - commentID int64 +func (_e *MockIssuesServices_Expecter) DeleteComment(ctx interface{}, owner interface{}, repo interface{}, commentID interface{}) *MockIssuesServices_DeleteComment_Call { + return &MockIssuesServices_DeleteComment_Call{Call: _e.mock.On("DeleteComment", ctx, owner, repo, commentID)} +} + +func (_c *MockIssuesServices_DeleteComment_Call) Run(run func(ctx context.Context, owner string, repo string, commentID int64)) *MockIssuesServices_DeleteComment_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int64)) + }) + return _c +} + +func (_c *MockIssuesServices_DeleteComment_Call) Return(_a0 *github.Response, _a1 error) *MockIssuesServices_DeleteComment_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockIssuesServices_DeleteComment_Call) RunAndReturn(run func(context.Context, string, string, int64) (*github.Response, error)) *MockIssuesServices_DeleteComment_Call { + _c.Call.Return(run) + return _c +} + +// EditComment provides a mock function with given fields: ctx, owner, repo, commentID, comment +func (_m *MockIssuesServices) EditComment(ctx context.Context, owner string, repo string, commentID int64, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, commentID, comment) + + var r0 *github.IssueComment + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, *github.IssueComment) (*github.IssueComment, *github.Response, error)); ok { + return rf(ctx, owner, repo, commentID, comment) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, *github.IssueComment) *github.IssueComment); ok { + r0 = rf(ctx, owner, repo, commentID, comment) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.IssueComment) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int64, *github.IssueComment) *github.Response); ok { + r1 = rf(ctx, owner, repo, commentID, comment) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, int64, *github.IssueComment) error); ok { + r2 = rf(ctx, owner, repo, commentID, comment) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockIssuesServices_EditComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EditComment' +type MockIssuesServices_EditComment_Call struct { + *mock.Call +} + +// EditComment is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - commentID int64 +// - comment *github.IssueComment +func (_e *MockIssuesServices_Expecter) EditComment(ctx interface{}, owner interface{}, repo interface{}, commentID interface{}, comment interface{}) *MockIssuesServices_EditComment_Call { + return &MockIssuesServices_EditComment_Call{Call: _e.mock.On("EditComment", ctx, owner, repo, commentID, comment)} +} + +func (_c *MockIssuesServices_EditComment_Call) Run(run func(ctx context.Context, owner string, repo string, commentID int64, comment *github.IssueComment)) *MockIssuesServices_EditComment_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int64), args[4].(*github.IssueComment)) + }) + return _c +} + +func (_c *MockIssuesServices_EditComment_Call) Return(_a0 *github.IssueComment, _a1 *github.Response, _a2 error) *MockIssuesServices_EditComment_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockIssuesServices_EditComment_Call) RunAndReturn(run func(context.Context, string, string, int64, *github.IssueComment) (*github.IssueComment, *github.Response, error)) *MockIssuesServices_EditComment_Call { + _c.Call.Return(run) + return _c +} + +// ListComments provides a mock function with given fields: ctx, owner, repo, number, opts +func (_m *MockIssuesServices) ListComments(ctx context.Context, owner string, repo string, number int, opts *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, number, opts) + + var r0 []*github.IssueComment + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)); ok { + return rf(ctx, owner, repo, number, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, *github.IssueListCommentsOptions) []*github.IssueComment); ok { + r0 = rf(ctx, owner, repo, number, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*github.IssueComment) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int, *github.IssueListCommentsOptions) *github.Response); ok { + r1 = rf(ctx, owner, repo, number, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, int, *github.IssueListCommentsOptions) error); ok { + r2 = rf(ctx, owner, repo, number, opts) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockIssuesServices_ListComments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListComments' +type MockIssuesServices_ListComments_Call struct { + *mock.Call +} + +// ListComments is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - number int +// - opts *github.IssueListCommentsOptions +func (_e *MockIssuesServices_Expecter) ListComments(ctx interface{}, owner interface{}, repo interface{}, number interface{}, opts interface{}) *MockIssuesServices_ListComments_Call { + return &MockIssuesServices_ListComments_Call{Call: _e.mock.On("ListComments", ctx, owner, repo, number, opts)} +} + +func (_c *MockIssuesServices_ListComments_Call) Run(run func(ctx context.Context, owner string, repo string, number int, opts *github.IssueListCommentsOptions)) *MockIssuesServices_ListComments_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int), args[4].(*github.IssueListCommentsOptions)) + }) + return _c +} + +func (_c *MockIssuesServices_ListComments_Call) Return(_a0 []*github.IssueComment, _a1 *github.Response, _a2 error) *MockIssuesServices_ListComments_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockIssuesServices_ListComments_Call) RunAndReturn(run func(context.Context, string, string, int, *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error)) *MockIssuesServices_ListComments_Call { + _c.Call.Return(run) + return _c +} + +// NewMockIssuesServices creates a new instance of MockIssuesServices. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockIssuesServices(t interface { + mock.TestingT + Cleanup(func()) +}) *MockIssuesServices { + mock := &MockIssuesServices{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/github_client/mocks/mock_PullRequestsServices.go b/mocks/github_client/mocks/mock_PullRequestsServices.go new file mode 100644 index 00000000..d77ad006 --- /dev/null +++ b/mocks/github_client/mocks/mock_PullRequestsServices.go @@ -0,0 +1,302 @@ +// Code generated by mockery v2.37.1. DO NOT EDIT. + +package github_client + +import ( + context "context" + + github "github.com/google/go-github/v62/github" + + mock "github.com/stretchr/testify/mock" +) + +// MockPullRequestsServices is an autogenerated mock type for the PullRequestsServices type +type MockPullRequestsServices struct { + mock.Mock +} + +type MockPullRequestsServices_Expecter struct { + mock *mock.Mock +} + +func (_m *MockPullRequestsServices) EXPECT() *MockPullRequestsServices_Expecter { + return &MockPullRequestsServices_Expecter{mock: &_m.Mock} +} + +// Get provides a mock function with given fields: ctx, owner, repo, number +func (_m *MockPullRequestsServices) Get(ctx context.Context, owner string, repo string, number int) (*github.PullRequest, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, number) + + var r0 *github.PullRequest + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int) (*github.PullRequest, *github.Response, error)); ok { + return rf(ctx, owner, repo, number) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int) *github.PullRequest); ok { + r0 = rf(ctx, owner, repo, number) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.PullRequest) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int) *github.Response); ok { + r1 = rf(ctx, owner, repo, number) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, int) error); ok { + r2 = rf(ctx, owner, repo, number) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockPullRequestsServices_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type MockPullRequestsServices_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - number int +func (_e *MockPullRequestsServices_Expecter) Get(ctx interface{}, owner interface{}, repo interface{}, number interface{}) *MockPullRequestsServices_Get_Call { + return &MockPullRequestsServices_Get_Call{Call: _e.mock.On("Get", ctx, owner, repo, number)} +} + +func (_c *MockPullRequestsServices_Get_Call) Run(run func(ctx context.Context, owner string, repo string, number int)) *MockPullRequestsServices_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int)) + }) + return _c +} + +func (_c *MockPullRequestsServices_Get_Call) Return(_a0 *github.PullRequest, _a1 *github.Response, _a2 error) *MockPullRequestsServices_Get_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockPullRequestsServices_Get_Call) RunAndReturn(run func(context.Context, string, string, int) (*github.PullRequest, *github.Response, error)) *MockPullRequestsServices_Get_Call { + _c.Call.Return(run) + return _c +} + +// GetRaw provides a mock function with given fields: ctx, owner, repo, number, opts +func (_m *MockPullRequestsServices) GetRaw(ctx context.Context, owner string, repo string, number int, opts github.RawOptions) (string, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, number, opts) + + var r0 string + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, github.RawOptions) (string, *github.Response, error)); ok { + return rf(ctx, owner, repo, number, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, github.RawOptions) string); ok { + r0 = rf(ctx, owner, repo, number, opts) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int, github.RawOptions) *github.Response); ok { + r1 = rf(ctx, owner, repo, number, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, int, github.RawOptions) error); ok { + r2 = rf(ctx, owner, repo, number, opts) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockPullRequestsServices_GetRaw_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRaw' +type MockPullRequestsServices_GetRaw_Call struct { + *mock.Call +} + +// GetRaw is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - number int +// - opts github.RawOptions +func (_e *MockPullRequestsServices_Expecter) GetRaw(ctx interface{}, owner interface{}, repo interface{}, number interface{}, opts interface{}) *MockPullRequestsServices_GetRaw_Call { + return &MockPullRequestsServices_GetRaw_Call{Call: _e.mock.On("GetRaw", ctx, owner, repo, number, opts)} +} + +func (_c *MockPullRequestsServices_GetRaw_Call) Run(run func(ctx context.Context, owner string, repo string, number int, opts github.RawOptions)) *MockPullRequestsServices_GetRaw_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int), args[4].(github.RawOptions)) + }) + return _c +} + +func (_c *MockPullRequestsServices_GetRaw_Call) Return(_a0 string, _a1 *github.Response, _a2 error) *MockPullRequestsServices_GetRaw_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockPullRequestsServices_GetRaw_Call) RunAndReturn(run func(context.Context, string, string, int, github.RawOptions) (string, *github.Response, error)) *MockPullRequestsServices_GetRaw_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function with given fields: ctx, owner, repo, opts +func (_m *MockPullRequestsServices) List(ctx context.Context, owner string, repo string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, opts) + + var r0 []*github.PullRequest + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error)); ok { + return rf(ctx, owner, repo, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.PullRequestListOptions) []*github.PullRequest); ok { + r0 = rf(ctx, owner, repo, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*github.PullRequest) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.PullRequestListOptions) *github.Response); ok { + r1 = rf(ctx, owner, repo, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.PullRequestListOptions) error); ok { + r2 = rf(ctx, owner, repo, opts) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockPullRequestsServices_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type MockPullRequestsServices_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - opts *github.PullRequestListOptions +func (_e *MockPullRequestsServices_Expecter) List(ctx interface{}, owner interface{}, repo interface{}, opts interface{}) *MockPullRequestsServices_List_Call { + return &MockPullRequestsServices_List_Call{Call: _e.mock.On("List", ctx, owner, repo, opts)} +} + +func (_c *MockPullRequestsServices_List_Call) Run(run func(ctx context.Context, owner string, repo string, opts *github.PullRequestListOptions)) *MockPullRequestsServices_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*github.PullRequestListOptions)) + }) + return _c +} + +func (_c *MockPullRequestsServices_List_Call) Return(_a0 []*github.PullRequest, _a1 *github.Response, _a2 error) *MockPullRequestsServices_List_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockPullRequestsServices_List_Call) RunAndReturn(run func(context.Context, string, string, *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error)) *MockPullRequestsServices_List_Call { + _c.Call.Return(run) + return _c +} + +// ListFiles provides a mock function with given fields: ctx, owner, repo, number, opts +func (_m *MockPullRequestsServices) ListFiles(ctx context.Context, owner string, repo string, number int, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, number, opts) + + var r0 []*github.CommitFile + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, *github.ListOptions) ([]*github.CommitFile, *github.Response, error)); ok { + return rf(ctx, owner, repo, number, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, int, *github.ListOptions) []*github.CommitFile); ok { + r0 = rf(ctx, owner, repo, number, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*github.CommitFile) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, int, *github.ListOptions) *github.Response); ok { + r1 = rf(ctx, owner, repo, number, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, int, *github.ListOptions) error); ok { + r2 = rf(ctx, owner, repo, number, opts) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockPullRequestsServices_ListFiles_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListFiles' +type MockPullRequestsServices_ListFiles_Call struct { + *mock.Call +} + +// ListFiles is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - number int +// - opts *github.ListOptions +func (_e *MockPullRequestsServices_Expecter) ListFiles(ctx interface{}, owner interface{}, repo interface{}, number interface{}, opts interface{}) *MockPullRequestsServices_ListFiles_Call { + return &MockPullRequestsServices_ListFiles_Call{Call: _e.mock.On("ListFiles", ctx, owner, repo, number, opts)} +} + +func (_c *MockPullRequestsServices_ListFiles_Call) Run(run func(ctx context.Context, owner string, repo string, number int, opts *github.ListOptions)) *MockPullRequestsServices_ListFiles_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int), args[4].(*github.ListOptions)) + }) + return _c +} + +func (_c *MockPullRequestsServices_ListFiles_Call) Return(_a0 []*github.CommitFile, _a1 *github.Response, _a2 error) *MockPullRequestsServices_ListFiles_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockPullRequestsServices_ListFiles_Call) RunAndReturn(run func(context.Context, string, string, int, *github.ListOptions) ([]*github.CommitFile, *github.Response, error)) *MockPullRequestsServices_ListFiles_Call { + _c.Call.Return(run) + return _c +} + +// NewMockPullRequestsServices creates a new instance of MockPullRequestsServices. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockPullRequestsServices(t interface { + mock.TestingT + Cleanup(func()) +}) *MockPullRequestsServices { + mock := &MockPullRequestsServices{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/github_client/mocks/mock_RepositoriesServices.go b/mocks/github_client/mocks/mock_RepositoriesServices.go new file mode 100644 index 00000000..a61f7b52 --- /dev/null +++ b/mocks/github_client/mocks/mock_RepositoriesServices.go @@ -0,0 +1,378 @@ +// Code generated by mockery v2.37.1. DO NOT EDIT. + +package github_client + +import ( + context "context" + + github "github.com/google/go-github/v62/github" + + mock "github.com/stretchr/testify/mock" +) + +// MockRepositoriesServices is an autogenerated mock type for the RepositoriesServices type +type MockRepositoriesServices struct { + mock.Mock +} + +type MockRepositoriesServices_Expecter struct { + mock *mock.Mock +} + +func (_m *MockRepositoriesServices) EXPECT() *MockRepositoriesServices_Expecter { + return &MockRepositoriesServices_Expecter{mock: &_m.Mock} +} + +// CreateHook provides a mock function with given fields: ctx, owner, repo, hook +func (_m *MockRepositoriesServices) CreateHook(ctx context.Context, owner string, repo string, hook *github.Hook) (*github.Hook, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, hook) + + var r0 *github.Hook + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.Hook) (*github.Hook, *github.Response, error)); ok { + return rf(ctx, owner, repo, hook) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.Hook) *github.Hook); ok { + r0 = rf(ctx, owner, repo, hook) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.Hook) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.Hook) *github.Response); ok { + r1 = rf(ctx, owner, repo, hook) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.Hook) error); ok { + r2 = rf(ctx, owner, repo, hook) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockRepositoriesServices_CreateHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateHook' +type MockRepositoriesServices_CreateHook_Call struct { + *mock.Call +} + +// CreateHook is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - hook *github.Hook +func (_e *MockRepositoriesServices_Expecter) CreateHook(ctx interface{}, owner interface{}, repo interface{}, hook interface{}) *MockRepositoriesServices_CreateHook_Call { + return &MockRepositoriesServices_CreateHook_Call{Call: _e.mock.On("CreateHook", ctx, owner, repo, hook)} +} + +func (_c *MockRepositoriesServices_CreateHook_Call) Run(run func(ctx context.Context, owner string, repo string, hook *github.Hook)) *MockRepositoriesServices_CreateHook_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*github.Hook)) + }) + return _c +} + +func (_c *MockRepositoriesServices_CreateHook_Call) Return(_a0 *github.Hook, _a1 *github.Response, _a2 error) *MockRepositoriesServices_CreateHook_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockRepositoriesServices_CreateHook_Call) RunAndReturn(run func(context.Context, string, string, *github.Hook) (*github.Hook, *github.Response, error)) *MockRepositoriesServices_CreateHook_Call { + _c.Call.Return(run) + return _c +} + +// CreateStatus provides a mock function with given fields: ctx, owner, repo, ref, status +func (_m *MockRepositoriesServices) CreateStatus(ctx context.Context, owner string, repo string, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, ref, status) + + var r0 *github.RepoStatus + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *github.RepoStatus) (*github.RepoStatus, *github.Response, error)); ok { + return rf(ctx, owner, repo, ref, status) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *github.RepoStatus) *github.RepoStatus); ok { + r0 = rf(ctx, owner, repo, ref, status) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.RepoStatus) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, *github.RepoStatus) *github.Response); ok { + r1 = rf(ctx, owner, repo, ref, status) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, string, *github.RepoStatus) error); ok { + r2 = rf(ctx, owner, repo, ref, status) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockRepositoriesServices_CreateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateStatus' +type MockRepositoriesServices_CreateStatus_Call struct { + *mock.Call +} + +// CreateStatus is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - ref string +// - status *github.RepoStatus +func (_e *MockRepositoriesServices_Expecter) CreateStatus(ctx interface{}, owner interface{}, repo interface{}, ref interface{}, status interface{}) *MockRepositoriesServices_CreateStatus_Call { + return &MockRepositoriesServices_CreateStatus_Call{Call: _e.mock.On("CreateStatus", ctx, owner, repo, ref, status)} +} + +func (_c *MockRepositoriesServices_CreateStatus_Call) Run(run func(ctx context.Context, owner string, repo string, ref string, status *github.RepoStatus)) *MockRepositoriesServices_CreateStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(*github.RepoStatus)) + }) + return _c +} + +func (_c *MockRepositoriesServices_CreateStatus_Call) Return(_a0 *github.RepoStatus, _a1 *github.Response, _a2 error) *MockRepositoriesServices_CreateStatus_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockRepositoriesServices_CreateStatus_Call) RunAndReturn(run func(context.Context, string, string, string, *github.RepoStatus) (*github.RepoStatus, *github.Response, error)) *MockRepositoriesServices_CreateStatus_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: ctx, owner, repo +func (_m *MockRepositoriesServices) Get(ctx context.Context, owner string, repo string) (*github.Repository, *github.Response, error) { + ret := _m.Called(ctx, owner, repo) + + var r0 *github.Repository + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*github.Repository, *github.Response, error)); ok { + return rf(ctx, owner, repo) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *github.Repository); ok { + r0 = rf(ctx, owner, repo) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.Repository) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) *github.Response); ok { + r1 = rf(ctx, owner, repo) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string) error); ok { + r2 = rf(ctx, owner, repo) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockRepositoriesServices_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type MockRepositoriesServices_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +func (_e *MockRepositoriesServices_Expecter) Get(ctx interface{}, owner interface{}, repo interface{}) *MockRepositoriesServices_Get_Call { + return &MockRepositoriesServices_Get_Call{Call: _e.mock.On("Get", ctx, owner, repo)} +} + +func (_c *MockRepositoriesServices_Get_Call) Run(run func(ctx context.Context, owner string, repo string)) *MockRepositoriesServices_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockRepositoriesServices_Get_Call) Return(_a0 *github.Repository, _a1 *github.Response, _a2 error) *MockRepositoriesServices_Get_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockRepositoriesServices_Get_Call) RunAndReturn(run func(context.Context, string, string) (*github.Repository, *github.Response, error)) *MockRepositoriesServices_Get_Call { + _c.Call.Return(run) + return _c +} + +// GetContents provides a mock function with given fields: ctx, owner, repo, path, opts +func (_m *MockRepositoriesServices) GetContents(ctx context.Context, owner string, repo string, path string, opts *github.RepositoryContentGetOptions) (*github.RepositoryContent, []*github.RepositoryContent, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, path, opts) + + var r0 *github.RepositoryContent + var r1 []*github.RepositoryContent + var r2 *github.Response + var r3 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *github.RepositoryContentGetOptions) (*github.RepositoryContent, []*github.RepositoryContent, *github.Response, error)); ok { + return rf(ctx, owner, repo, path, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *github.RepositoryContentGetOptions) *github.RepositoryContent); ok { + r0 = rf(ctx, owner, repo, path, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*github.RepositoryContent) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, *github.RepositoryContentGetOptions) []*github.RepositoryContent); ok { + r1 = rf(ctx, owner, repo, path, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]*github.RepositoryContent) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, string, *github.RepositoryContentGetOptions) *github.Response); ok { + r2 = rf(ctx, owner, repo, path, opts) + } else { + if ret.Get(2) != nil { + r2 = ret.Get(2).(*github.Response) + } + } + + if rf, ok := ret.Get(3).(func(context.Context, string, string, string, *github.RepositoryContentGetOptions) error); ok { + r3 = rf(ctx, owner, repo, path, opts) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// MockRepositoriesServices_GetContents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetContents' +type MockRepositoriesServices_GetContents_Call struct { + *mock.Call +} + +// GetContents is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - path string +// - opts *github.RepositoryContentGetOptions +func (_e *MockRepositoriesServices_Expecter) GetContents(ctx interface{}, owner interface{}, repo interface{}, path interface{}, opts interface{}) *MockRepositoriesServices_GetContents_Call { + return &MockRepositoriesServices_GetContents_Call{Call: _e.mock.On("GetContents", ctx, owner, repo, path, opts)} +} + +func (_c *MockRepositoriesServices_GetContents_Call) Run(run func(ctx context.Context, owner string, repo string, path string, opts *github.RepositoryContentGetOptions)) *MockRepositoriesServices_GetContents_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(*github.RepositoryContentGetOptions)) + }) + return _c +} + +func (_c *MockRepositoriesServices_GetContents_Call) Return(fileContent *github.RepositoryContent, directoryContent []*github.RepositoryContent, resp *github.Response, err error) *MockRepositoriesServices_GetContents_Call { + _c.Call.Return(fileContent, directoryContent, resp, err) + return _c +} + +func (_c *MockRepositoriesServices_GetContents_Call) RunAndReturn(run func(context.Context, string, string, string, *github.RepositoryContentGetOptions) (*github.RepositoryContent, []*github.RepositoryContent, *github.Response, error)) *MockRepositoriesServices_GetContents_Call { + _c.Call.Return(run) + return _c +} + +// ListHooks provides a mock function with given fields: ctx, owner, repo, opts +func (_m *MockRepositoriesServices) ListHooks(ctx context.Context, owner string, repo string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) { + ret := _m.Called(ctx, owner, repo, opts) + + var r0 []*github.Hook + var r1 *github.Response + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) ([]*github.Hook, *github.Response, error)); ok { + return rf(ctx, owner, repo, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, *github.ListOptions) []*github.Hook); ok { + r0 = rf(ctx, owner, repo, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*github.Hook) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, *github.ListOptions) *github.Response); ok { + r1 = rf(ctx, owner, repo, opts) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*github.Response) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, string, string, *github.ListOptions) error); ok { + r2 = rf(ctx, owner, repo, opts) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockRepositoriesServices_ListHooks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListHooks' +type MockRepositoriesServices_ListHooks_Call struct { + *mock.Call +} + +// ListHooks is a helper method to define mock.On call +// - ctx context.Context +// - owner string +// - repo string +// - opts *github.ListOptions +func (_e *MockRepositoriesServices_Expecter) ListHooks(ctx interface{}, owner interface{}, repo interface{}, opts interface{}) *MockRepositoriesServices_ListHooks_Call { + return &MockRepositoriesServices_ListHooks_Call{Call: _e.mock.On("ListHooks", ctx, owner, repo, opts)} +} + +func (_c *MockRepositoriesServices_ListHooks_Call) Run(run func(ctx context.Context, owner string, repo string, opts *github.ListOptions)) *MockRepositoriesServices_ListHooks_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*github.ListOptions)) + }) + return _c +} + +func (_c *MockRepositoriesServices_ListHooks_Call) Return(_a0 []*github.Hook, _a1 *github.Response, _a2 error) *MockRepositoriesServices_ListHooks_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *MockRepositoriesServices_ListHooks_Call) RunAndReturn(run func(context.Context, string, string, *github.ListOptions) ([]*github.Hook, *github.Response, error)) *MockRepositoriesServices_ListHooks_Call { + _c.Call.Return(run) + return _c +} + +// NewMockRepositoriesServices creates a new instance of MockRepositoriesServices. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockRepositoriesServices(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRepositoriesServices { + mock := &MockRepositoriesServices{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/aisummary/diff_summary.go b/pkg/aisummary/diff_summary.go index bf3387aa..3b1163fa 100644 --- a/pkg/aisummary/diff_summary.go +++ b/pkg/aisummary/diff_summary.go @@ -17,7 +17,7 @@ func (c *OpenAiClient) SummarizeDiff(ctx context.Context, appName, diff string) ctx, span := tracer.Start(ctx, "SummarizeDiff") defer span.End() - model := openai.GPT4Turbo0125 + model := openai.GPT4o if len(diff) < 3500 { model = openai.GPT3Dot5Turbo } diff --git a/pkg/app_watcher/app_watcher.go b/pkg/app_watcher/app_watcher.go index 5d60a3c0..7ca46786 100644 --- a/pkg/app_watcher/app_watcher.go +++ b/pkg/app_watcher/app_watcher.go @@ -44,7 +44,7 @@ func NewApplicationWatcher(vcsToArgoMap appdir.VcsToArgoMap, cfg config.ServerCo vcsToArgoMap: vcsToArgoMap, } - appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second * 30) + appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second*30, cfg) ctrl.appInformer = appInformer ctrl.appLister = appLister @@ -118,12 +118,19 @@ func (ctrl *ApplicationWatcher) onApplicationDeleted(obj interface{}) { } /* -This Go function, named newApplicationInformerAndLister, is part of the ApplicationWatcher struct. It sets up a Kubernetes SharedIndexInformer and a Lister for Argo CD Applications. -A SharedIndexInformer is used to watch changes to a specific type of Kubernetes resource in an efficient manner. It significantly reduces the load on the Kubernetes API server by sharing and caching watches between all controllers that need to observe the object. -Listers use the data from the informer's cache to provide a read-optimized view of the cache which reduces the load on the API Server and hides some complexity. +newApplicationInformerAndLister, is part of the ApplicationWatcher struct. It sets up a Kubernetes SharedIndexInformer +and a Lister for Argo CD Applications. + +A SharedIndexInformer is used to watch changes to a specific type of Kubernetes resource in an efficient manner. +It significantly reduces the load on the Kubernetes API server by sharing and caching watches between all controllers +that need to observe the object. + +newApplicationInformerAndLister use the data from the informer's cache to provide a read-optimized view of the cache which reduces +the load on the API Server and hides some complexity. */ -func (ctrl *ApplicationWatcher) newApplicationInformerAndLister(refreshTimeout time.Duration) (cache.SharedIndexInformer, applisters.ApplicationLister) { - informer := informers.NewApplicationInformer(ctrl.applicationClientset, "", refreshTimeout, +func (ctrl *ApplicationWatcher) newApplicationInformerAndLister(refreshTimeout time.Duration, cfg config.ServerConfig) (cache.SharedIndexInformer, applisters.ApplicationLister) { + log.Debug().Msgf("Creating Application informer with namespace: %s", cfg.ArgoCDNamespace) + informer := informers.NewApplicationInformer(ctrl.applicationClientset, cfg.ArgoCDNamespace, refreshTimeout, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, ) diff --git a/pkg/app_watcher/app_watcher_test.go b/pkg/app_watcher/app_watcher_test.go index 608992fd..4af7425c 100644 --- a/pkg/app_watcher/app_watcher_test.go +++ b/pkg/app_watcher/app_watcher_test.go @@ -8,12 +8,18 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" appclientsetfake "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zapier/kubechecks/pkg/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/zapier/kubechecks/pkg/appdir" ) -func initTestObjects() *ApplicationWatcher { +func initTestObjects(t *testing.T) *ApplicationWatcher { + cfg, err := config.New() + // Handle the error appropriately, e.g., log it or fail the test + require.NoError(t, err, "failed to create config") + // set up the fake Application client set and informer. testApp1 := &v1alpha1.Application{ ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, @@ -34,7 +40,7 @@ func initTestObjects() *ApplicationWatcher { vcsToArgoMap: appdir.NewVcsToArgoMap("vcs-username"), } - appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second * 1) + appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second*1, cfg) ctrl.appInformer = appInformer ctrl.appLister = appLister @@ -42,7 +48,7 @@ func initTestObjects() *ApplicationWatcher { } func TestApplicationAdded(t *testing.T) { - appWatcher := initTestObjects() + appWatcher := initTestObjects(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -68,7 +74,7 @@ func TestApplicationAdded(t *testing.T) { } func TestApplicationUpdated(t *testing.T) { - ctrl := initTestObjects() + ctrl := initTestObjects(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -101,7 +107,7 @@ func TestApplicationUpdated(t *testing.T) { } func TestApplicationDeleted(t *testing.T) { - ctrl := initTestObjects() + ctrl := initTestObjects(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/pkg/config/config.go b/pkg/config/config.go index 92026e2c..82844a1b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -21,6 +21,7 @@ type ServerConfig struct { ArgoCDToken string `mapstructure:"argocd-api-token"` ArgoCDPathPrefix string `mapstructure:"argocd-api-path-prefix"` ArgoCDInsecure bool `mapstructure:"argocd-api-insecure"` + ArgoCDNamespace string `mapstructure:"argocd-api-namespace"` KubernetesConfig string `mapstructure:"kubernetes-config"` // otel @@ -29,9 +30,10 @@ type ServerConfig struct { OtelCollectorPort string `mapstructure:"otel-collector-port"` // vcs - VcsBaseUrl string `mapstructure:"vcs-base-url"` - VcsToken string `mapstructure:"vcs-token"` - VcsType string `mapstructure:"vcs-type"` + VcsBaseUrl string `mapstructure:"vcs-base-url"` + VcsUploadUrl string `mapstructure:"vcs-upload-url"` // github enterprise upload URL + VcsToken string `mapstructure:"vcs-token"` + VcsType string `mapstructure:"vcs-type"` // webhooks EnsureWebhooks bool `mapstructure:"ensure-webhooks"` @@ -101,6 +103,7 @@ func NewWithViper(v *viper.Viper) (ServerConfig, error) { log.Info().Msgf("Webhook URL Base: %s", cfg.WebhookUrlBase) log.Info().Msgf("Webhook URL Prefix: %s", cfg.UrlPrefix) log.Info().Msgf("VCS Type: %s", cfg.VcsType) + log.Info().Msgf("ArgoCD Namespace: %s", cfg.ArgoCDNamespace) return cfg, nil } diff --git a/pkg/vcs/github_client/client.go b/pkg/vcs/github_client/client.go index 2c26da76..6b53fd78 100644 --- a/pkg/vcs/github_client/client.go +++ b/pkg/vcs/github_client/client.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/chainguard-dev/git-urls" - "github.com/google/go-github/v53/github" + "github.com/google/go-github/v62/github" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/shurcooL/githubv4" @@ -26,12 +26,19 @@ var tracer = otel.Tracer("pkg/vcs/github_client") type Client struct { shurcoolClient *githubv4.Client - googleClient *github.Client + googleClient *GClient cfg config.ServerConfig username, email string } +// GClient is a struct that holds the services for the GitHub client +type GClient struct { + PullRequests PullRequestsServices + Repositories RepositoriesServices + Issues IssuesServices +} + // CreateGithubClient creates a new GitHub client using the auth token provided. We // can't validate the token at this point, so if it exists we assume it works func CreateGithubClient(cfg config.ServerConfig) (*Client, error) { @@ -54,13 +61,15 @@ func CreateGithubClient(cfg config.ServerConfig) (*Client, error) { tc := oauth2.NewClient(ctx, ts) githubUrl := cfg.VcsBaseUrl - if githubUrl == "" { + githubUploadUrl := cfg.VcsUploadUrl + // we need both urls to be set for github enterprise + if githubUrl == "" || githubUploadUrl == "" { googleClient = github.NewClient(tc) // If this has failed, we'll catch it on first call // Use the client from shurcooL's githubv4 library for queries. shurcoolClient = githubv4.NewClient(tc) } else { - googleClient, err = github.NewEnterpriseClient(githubUrl, githubUrl, tc) + googleClient, err = github.NewClient(tc).WithEnterpriseURLs(githubUrl, githubUploadUrl) if err != nil { log.Fatal().Err(err).Msg("failed to create github enterprise client") } @@ -73,8 +82,12 @@ func CreateGithubClient(cfg config.ServerConfig) (*Client, error) { } client := &Client{ - cfg: cfg, - googleClient: googleClient, + cfg: cfg, + googleClient: &GClient{ + PullRequests: PullRequestsService{googleClient.PullRequests}, + Repositories: RepositoriesService{googleClient.Repositories}, + Issues: IssuesService{googleClient.Issues}, + }, shurcoolClient: shurcoolClient, } if user != nil { @@ -219,9 +232,15 @@ func (c *Client) GetHookByUrl(ctx context.Context, ownerAndRepoName, webhookUrl } for _, item := range items { - if item.URL != nil && *item.URL == webhookUrl { + itemConfig := item.GetConfig() + // check if the hook's config has a URL + hookPayloadURL := "" + if itemConfig != nil { + hookPayloadURL = itemConfig.GetURL() + } + if hookPayloadURL == webhookUrl { return &vcs.WebHookConfig{ - Url: item.GetURL(), + Url: hookPayloadURL, Events: item.Events, // TODO: translate GH specific event names to VCS agnostic }, nil } @@ -232,23 +251,26 @@ func (c *Client) GetHookByUrl(ctx context.Context, ownerAndRepoName, webhookUrl func (c *Client) CreateHook(ctx context.Context, ownerAndRepoName, webhookUrl, webhookSecret string) error { owner, repoName := parseRepo(ownerAndRepoName) - _, _, err := c.googleClient.Repositories.CreateHook(ctx, owner, repoName, &github.Hook{ + _, resp, err := c.googleClient.Repositories.CreateHook(ctx, owner, repoName, &github.Hook{ Active: pkg.Pointer(true), - Config: map[string]interface{}{ - "content_type": "json", - "insecure_ssl": "0", - "secret": webhookSecret, - "url": webhookUrl, + Config: &github.HookConfig{ + ContentType: pkg.Pointer("json"), + InsecureSSL: pkg.Pointer("0"), + URL: pkg.Pointer(webhookUrl), + Secret: pkg.Pointer(webhookSecret), }, Events: []string{ "pull_request", }, Name: pkg.Pointer("web"), }) - if err != nil { - return errors.Wrap(err, "failed to create hook") + if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 { + statusCode := 0 + if resp != nil { + statusCode = resp.StatusCode + } + return errors.Wrap(err, fmt.Sprintf("failed to create hook, statuscode: %d", statusCode)) } - return nil } diff --git a/pkg/vcs/github_client/client_test.go b/pkg/vcs/github_client/client_test.go index 5faf4d0b..bd0704f3 100644 --- a/pkg/vcs/github_client/client_test.go +++ b/pkg/vcs/github_client/client_test.go @@ -1,11 +1,30 @@ package github_client import ( + "context" + "fmt" + "net/http" "testing" + "github.com/google/go-github/v62/github" + "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + githubMocks "github.com/zapier/kubechecks/mocks/github_client/mocks" + "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/vcs" ) +// MockGitHubMethod is a generic function to mock GitHub client methods +func MockGitHubMethod(methodName string, returns []interface{}) *GClient { + mockClient := new(githubMocks.MockRepositoriesServices) + mockClient.On(methodName, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(returns...) + + return &GClient{ + Repositories: mockClient, + } +} + func TestParseRepo(t *testing.T) { testcases := []struct { name, input string @@ -40,3 +59,268 @@ func TestParseRepo(t *testing.T) { }) } } + +func TestClient_CreateHook(t *testing.T) { + type fields struct { + shurcoolClient *githubv4.Client + googleClient *GClient + cfg config.ServerConfig + username string + email string + } + type args struct { + ctx context.Context + ownerAndRepoName string + webhookUrl string + webhookSecret string + } + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + { + name: "normal ok", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("CreateHook", + []interface{}{ + &github.Hook{}, + &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + nil}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + webhookSecret: "dummy-webhook-secret", + }, + wantErr: assert.NoError, + }, + { + name: "github responds with error", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("CreateHook", + []interface{}{ + nil, + &github.Response{Response: &http.Response{StatusCode: http.StatusBadRequest}}, + fmt.Errorf("mock bad request")}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + webhookSecret: "dummy-webhook-secret", + }, + wantErr: assert.Error, + }, + { + name: "mock network error error", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("CreateHook", + []interface{}{ + nil, + nil, + fmt.Errorf("mock network error")}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + webhookSecret: "dummy-webhook-secret", + }, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + shurcoolClient: tt.fields.shurcoolClient, + googleClient: tt.fields.googleClient, + cfg: tt.fields.cfg, + username: tt.fields.username, + email: tt.fields.email, + } + tt.wantErr(t, c.CreateHook(tt.args.ctx, tt.args.ownerAndRepoName, tt.args.webhookUrl, tt.args.webhookSecret), fmt.Sprintf("CreateHook(%v, %v, %v, %v)", tt.args.ctx, tt.args.ownerAndRepoName, tt.args.webhookUrl, tt.args.webhookSecret)) + }) + } +} + +func TestClient_GetHookByUrl(t *testing.T) { + type fields struct { + shurcoolClient *githubv4.Client + googleClient *GClient + cfg config.ServerConfig + username string + email string + } + type args struct { + ctx context.Context + ownerAndRepoName string + webhookUrl string + } + tests := []struct { + name string + fields fields + args args + want *vcs.WebHookConfig + wantErr assert.ErrorAssertionFunc + }{ + { + name: "normal ok", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("ListHooks", + []interface{}{ + []*github.Hook{ + { + Config: &github.HookConfig{ + ContentType: github.String("json"), + InsecureSSL: github.String("0"), + URL: github.String("https://dummywebhooks.local"), + Secret: github.String("dummy-webhook-secret"), + }, + Events: []string{"pull_request"}, + }, + }, + &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + nil}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + }, + want: &vcs.WebHookConfig{ + Url: "https://dummywebhooks.local", + Events: []string{"pull_request"}, + }, + wantErr: assert.NoError, + }, + { + name: "no matching webhook found", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("ListHooks", + []interface{}{ + []*github.Hook{ + { + Config: &github.HookConfig{ + ContentType: github.String("json"), + InsecureSSL: github.String("0"), + URL: github.String("https://differentwebhook.local"), + Secret: github.String("dummy-webhook-secret"), + }, + Events: []string{"pull_request"}, + }, + }, + &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + nil}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + }, + want: nil, + wantErr: assert.Error, + }, + { + name: "0 webhook found", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("ListHooks", + []interface{}{ + nil, + &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + nil}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + }, + want: nil, + wantErr: assert.Error, + }, + { + name: "github error", + fields: fields{ + shurcoolClient: nil, + googleClient: MockGitHubMethod("ListHooks", + []interface{}{ + nil, + &github.Response{Response: &http.Response{StatusCode: http.StatusBadRequest}}, + fmt.Errorf("mock bad request")}), + cfg: config.ServerConfig{ + VcsToken: "ghp_helloworld", + VcsType: "github", + }, + username: "dummy-bot", + email: "dummy@zapier.com", + }, + args: args{ + ctx: context.Background(), + ownerAndRepoName: "https://dummy-bot:********@github.com/dummy-bot-zapier/test-repo.git", + webhookUrl: "https://dummywebhooks.local", + }, + want: nil, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + shurcoolClient: tt.fields.shurcoolClient, + googleClient: tt.fields.googleClient, + cfg: tt.fields.cfg, + username: tt.fields.username, + email: tt.fields.email, + } + got, err := c.GetHookByUrl(tt.args.ctx, tt.args.ownerAndRepoName, tt.args.webhookUrl) + if !tt.wantErr(t, err, fmt.Sprintf("GetHookByUrl(%v, %v, %v)", tt.args.ctx, tt.args.ownerAndRepoName, tt.args.webhookUrl)) { + return + } + assert.Equalf(t, tt.want, got, "GetHookByUrl(%v, %v, %v)", tt.args.ctx, tt.args.ownerAndRepoName, tt.args.webhookUrl) + }) + } +} diff --git a/pkg/vcs/github_client/issue.go b/pkg/vcs/github_client/issue.go new file mode 100644 index 00000000..3f8ca9e2 --- /dev/null +++ b/pkg/vcs/github_client/issue.go @@ -0,0 +1,18 @@ +package github_client + +import ( + "context" + + "github.com/google/go-github/v62/github" +) + +type IssuesServices interface { + CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) + DeleteComment(ctx context.Context, owner string, repo string, commentID int64) (*github.Response, error) + ListComments(ctx context.Context, owner string, repo string, number int, opts *github.IssueListCommentsOptions) ([]*github.IssueComment, *github.Response, error) + EditComment(ctx context.Context, owner string, repo string, commentID int64, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) +} + +type IssuesService struct { + IssuesServices +} diff --git a/pkg/vcs/github_client/message.go b/pkg/vcs/github_client/message.go index e9ff933a..41064a7f 100644 --- a/pkg/vcs/github_client/message.go +++ b/pkg/vcs/github_client/message.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/google/go-github/v53/github" + "github.com/google/go-github/v62/github" "github.com/rs/zerolog/log" "github.com/shurcooL/githubv4" diff --git a/pkg/vcs/github_client/pullrequest.go b/pkg/vcs/github_client/pullrequest.go new file mode 100644 index 00000000..ea803fff --- /dev/null +++ b/pkg/vcs/github_client/pullrequest.go @@ -0,0 +1,18 @@ +package github_client + +import ( + "context" + + "github.com/google/go-github/v62/github" +) + +type PullRequestsServices interface { + List(ctx context.Context, owner string, repo string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error) + ListFiles(ctx context.Context, owner string, repo string, number int, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) + GetRaw(ctx context.Context, owner string, repo string, number int, opts github.RawOptions) (string, *github.Response, error) + Get(ctx context.Context, owner string, repo string, number int) (*github.PullRequest, *github.Response, error) +} + +type PullRequestsService struct { + PullRequestsServices +} diff --git a/pkg/vcs/github_client/repo.go b/pkg/vcs/github_client/repo.go new file mode 100644 index 00000000..a0628051 --- /dev/null +++ b/pkg/vcs/github_client/repo.go @@ -0,0 +1,19 @@ +package github_client + +import ( + "context" + + "github.com/google/go-github/v62/github" +) + +type RepositoriesServices interface { + GetContents(ctx context.Context, owner, repo, path string, opts *github.RepositoryContentGetOptions) (fileContent *github.RepositoryContent, directoryContent []*github.RepositoryContent, resp *github.Response, err error) + Get(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error) + CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) + CreateHook(ctx context.Context, owner, repo string, hook *github.Hook) (*github.Hook, *github.Response, error) + ListHooks(ctx context.Context, owner, repo string, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) +} + +type RepositoriesService struct { + RepositoriesServices +} diff --git a/tools/dump_crds/cmd/README.md b/tools/dump_crds/cmd/README.md new file mode 100644 index 00000000..a147c052 --- /dev/null +++ b/tools/dump_crds/cmd/README.md @@ -0,0 +1,38 @@ +# dumpcrds command + +## SUMMARY + +`dump` command is used to generate schemas for the Kubernetes custom resource definitions. +The schemas are used to validate the kubechecks changes. + +## How does it work + +1. Connect to the cluster you want to generate schemas from and get current version +```json +{ + "clientVersion": { + "major": "1", + "minor": "30", + "gitVersion": "v1.30.1", + "gitCommit": "6911225c3f747e1cd9d109c305436d08b668f086", + "gitTreeState": "clean", + "buildDate": "2024-05-14T10:50:53Z", + "goVersion": "go1.22.2", + "compiler": "gc", + "platform": "darwin/arm64" + }, + "kustomizeVersion": "v5.0.4-0.20230601165947-6ce0bf390ce3" +} +``` + +use the gitVersion attributes to find the current version. + +2. list all CRDs in the cluster +3. create a directory `` for the matching version (e.g. v1.30.1 -> v1.30.0) +4. for each CRD, get the `version.schema.openAPIV3Schema` and save it to a file +5. file name is in the format `crd--.json` + +## How to add a new command + +1. create a new cobra.Command file in the `dumpcrd` directory. +2. make sure the file attaches itself to the rootCmd. (e.g. `rootCmd.AddCommand(newCmd)`) diff --git a/tools/dump_crds/cmd/dumpcrd/dump.go b/tools/dump_crds/cmd/dumpcrd/dump.go new file mode 100644 index 00000000..61333f4d --- /dev/null +++ b/tools/dump_crds/cmd/dumpcrd/dump.go @@ -0,0 +1,153 @@ +package dumpcrd + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver" + "github.com/spf13/cobra" + "github.com/zapier/kubechecks/tools/dump_crds/internal/logger" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// dumpCmd represents the dump command +var dumpCmd = &cobra.Command{ + Use: "dump", + Short: "Custom Resource Definition (CRD) schema extraction tool", + Long: `Custom Resource Definition (CRD) schema extraction tool. +Used to generate openAPIV3Schema for each CRD in the cluster. To be consumed by kubechecks.`, + Run: func(cmd *cobra.Command, args []string) { + dump() + }, +} + +func init() { + rootCmd.AddCommand(dumpCmd) +} + +func dump() { + logHandler := logger.InitLogger(false, true) + slog.SetDefault(logHandler) + ctx := context.Background() + config, err := getKubeConfig() + if err != nil { + slog.Error("failed to get kubeconfig\n\t", "err", err) + cobra.CheckErr("please setup ~/.kube/config or specify kubeconfig flag") + } + serverVersion, err := getServerVersion(config) + if err != nil { + slog.Error("Error getting server version, check the network and vpn\n\t", "err", err) + cobra.CheckErr("failed to connect to the EKS cluster") + } + currentVersion := strings.Split(serverVersion.GitVersion, "-")[0] + + semVer, err := semver.NewVersion(currentVersion) + if err != nil { + slog.Error("Error parsing server version\n\t", "err", err) + cobra.CheckErr("invalid server version") + } + here, _ := os.Getwd() + versionStr := fmt.Sprintf("v%d.%d.0", semVer.Major(), semVer.Minor()) + basepath := filepath.Join(here, versionStr) + err = os.MkdirAll(basepath, os.ModePerm) + if err != nil { + slog.Error("Error creating basepath\n\t", "basepath", basepath, "err", err) + cobra.CheckErr("failed to create a new directory for the schema files") + } + slog.Debug("Basepath: "+basepath, "version", versionStr) + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + slog.Error("Error creating dynamic client\n\t", "err", err) + cobra.CheckErr("failed to create kubernetes client") + } + // List all CRDs in the cluster + crds, err := dynamicClient.Resource(schema.GroupVersionResource{ + Group: "apiextensions.k8s.io", + Version: "v1", + Resource: "customresourcedefinitions", + }).List(ctx, metav1.ListOptions{}) + if err != nil { + slog.Error("Error listing CRDs\n\t", "err", err) + cobra.CheckErr("failed to list CRDs, try again") + } + slog.Info(fmt.Sprintf("got %d items", len(crds.Items))) + // Iterate over each CRD and list the versions.schema.openAPIV3Schema + for _, crd := range crds.Items { + crd.GetResourceVersion() + slog.Info("CRD Name: " + crd.GetName()) + spec, found, err := unstructured.NestedFieldNoCopy(crd.Object, "spec") + if !found || err != nil { + slog.Error("Error finding spec for CRD\n\t", "crd", crd.GetName(), "err", err) + continue + } + versions, found, err := unstructured.NestedSlice(spec.(map[string]interface{}), "versions") + if !found || err != nil { + slog.Error("Error finding versions for CRD\n\t", "crd", crd.GetName(), "err", err) + continue + } + specNames, found, err := unstructured.NestedFieldNoCopy(spec.(map[string]interface{}), "names") + if !found || err != nil { + slog.Error("Error finding names for CRD\n\t", "crd", crd.GetName(), "err", err) + continue + } + specNamesMap := specNames.(map[string]interface{}) + + // Iterate over each version and write the schema to a file only if it contains openAPIV3Schema + for _, ver := range versions { + versionMap := ver.(map[string]interface{}) + versionSchema, foundSchema, err := unstructured.NestedFieldNoCopy(versionMap, "schema") + if foundSchema && err == nil { + openAPIV3Schema, found, err := unstructured.NestedFieldNoCopy(versionSchema.(map[string]interface{}), "openAPIV3Schema") + if found && err == nil { + jsonData, err := json.MarshalIndent(openAPIV3Schema, "", " ") + if err != nil { + slog.Error("Error marshaling schema to JSON\n\t", "err", err) + continue + } + namespace := strings.Split(crd.GetName(), ".")[1] // (e.g. acraccesstokens.generators.external-secrets.io -> generators) + filename := fmt.Sprintf("%s-%s-%s.json", strings.ToLower(specNamesMap["kind"].(string)), namespace, versionMap["name"]) + slog.Info("saving file", "file", filename) + filename = filepath.Join(basepath, filename) + err = os.WriteFile(filename, jsonData, 0644) + if err != nil { + slog.Error("Error writing schema to file\n\t", "err", err) + continue + } + } + } + } + + } +} + +func getServerVersion(config *rest.Config) (*version.Info, error) { + // Create a discovery client + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + discoveryClient := clientset.Discovery() + + // Get the Kubernetes server version + return discoveryClient.ServerVersion() +} + +// getKubeConfig gets the Kubernetes configuration +func getKubeConfig() (*rest.Config, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := clientcmd.ConfigOverrides{} + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &configOverrides) + return kubeConfig.ClientConfig() +} diff --git a/tools/dump_crds/cmd/dumpcrd/root.go b/tools/dump_crds/cmd/dumpcrd/root.go new file mode 100644 index 00000000..e1a067f5 --- /dev/null +++ b/tools/dump_crds/cmd/dumpcrd/root.go @@ -0,0 +1,63 @@ +package dumpcrd + +import ( + "log/slog" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "dumpcrds", + Short: "extract crd from kubernetes cluster", +} + +// 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() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dumpcrd.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".dumpcrd" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".dumpcrd") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + slog.Info("dumpcrds config file", "config", viper.ConfigFileUsed()) + } +} diff --git a/tools/dump_crds/cmd/main.go b/tools/dump_crds/cmd/main.go new file mode 100644 index 00000000..307c5046 --- /dev/null +++ b/tools/dump_crds/cmd/main.go @@ -0,0 +1,10 @@ +/* +Copyright © 2024 NAME HERE +*/ +package main + +import "github.com/zapier/kubechecks/tools/dump_crds/cmd/dumpcrd" + +func main() { + dumpcrd.Execute() +} diff --git a/tools/dump_crds/dump_crds.go b/tools/dump_crds/dump_crds.go deleted file mode 100644 index 9880d695..00000000 --- a/tools/dump_crds/dump_crds.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/Masterminds/semver" -) - -type ServerVersion struct { - GitVersion string `json:"gitVersion"` -} - -func main() { - if len(os.Args) != 2 { - fmt.Printf("usage: %s\n", os.Args[0]) - os.Exit(1) - } - - here, _ := os.Getwd() - basepath := filepath.Join(here, os.Args[1]) - dump(basepath) -} - -func dump(basepath string) { - content, _ := exec.Command("kubectl", "version", "-o", "json").Output() - var versionInfo struct { - ServerVersion ServerVersion `json:"serverVersion"` - } - json.Unmarshal(content, &versionInfo) - - version, _ := semver.NewVersion(strings.Split(versionInfo.ServerVersion.GitVersion, "-")[0]) - versionStr := fmt.Sprintf("v%d.%d.0", version.Major(), version.Minor()) - basepath = filepath.Join(basepath, versionStr) - - content, _ = exec.Command("kubectl", "get", "crds", "-o", "json").Output() - var crdsInfo struct { - Items []json.RawMessage `json:"items"` - } - json.Unmarshal(content, &crdsInfo) - fmt.Printf("got %d items\n", len(crdsInfo.Items)) - - os.MkdirAll(basepath, os.ModePerm) - - for _, item := range crdsInfo.Items { - var crdMetadata struct { - Metadata struct { - Name string `json:"name"` - } `json:"metadata"` - Spec struct { - Names struct { - Kind string `json:"kind"` - } `json:"names"` - Versions []struct { - Name string `json:"name"` - Schema map[string]interface{} `json:"schema"` - } `json:"versions"` - } `json:"spec"` - } - json.Unmarshal(item, &crdMetadata) - - namespace := strings.Split(crdMetadata.Metadata.Name, ".")[1] - kind := strings.ToLower(crdMetadata.Spec.Names.Kind) - - for _, version := range crdMetadata.Spec.Versions { - versionName := version.Name - schema, found := version.Schema["openAPIV3Schema"] - if !found { - continue - } - - filename := fmt.Sprintf("%s-%s-%s.json", kind, namespace, versionName) - filename = filepath.Join(basepath, filename) - - fmt.Printf("writing %s\n", filename) - - if _, err := os.Stat(filename); err == nil { - os.Remove(filename) - } - - schemaJSON, _ := json.MarshalIndent(schema, "", " ") - - os.WriteFile(filename, schemaJSON, 0644) - } - } -} diff --git a/tools/dump_crds/go.mod b/tools/dump_crds/go.mod index 4d80a470..b6503479 100644 --- a/tools/dump_crds/go.mod +++ b/tools/dump_crds/go.mod @@ -1,7 +1,70 @@ module github.com/zapier/kubechecks/tools/dump_crds -go 1.21 +go 1.22.0 -toolchain go1.21.6 +toolchain go1.22.2 -require github.com/Masterminds/semver v1.5.0 +require ( + github.com/Masterminds/semver v1.5.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/lmittmann/tint v1.0.4 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.30.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // 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 + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/tools/dump_crds/go.sum b/tools/dump_crds/go.sum index 4dc271c9..65ae44eb 100644 --- a/tools/dump_crds/go.sum +++ b/tools/dump_crds/go.sum @@ -1,2 +1,198 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/cpuguy83/go-md2man/v2 v2.0.3/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +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/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/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= +github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +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/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +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.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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +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/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +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/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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +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= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +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/tools/dump_crds/internal/logger/logger.go b/tools/dump_crds/internal/logger/logger.go new file mode 100644 index 00000000..ae2f27a7 --- /dev/null +++ b/tools/dump_crds/internal/logger/logger.go @@ -0,0 +1,71 @@ +package logger + +import ( + "log/slog" + "os" + "path/filepath" + "time" + + "github.com/lmittmann/tint" +) + +func InitLogger(jsonOut, debug bool) *slog.Logger { + + var logLevel slog.Level + switch os.Getenv("LOG_LEVEL") { + case "debug": + logLevel = slog.LevelDebug + case "info": + logLevel = slog.LevelInfo + case "warning": + logLevel = slog.LevelWarn + case "error": + logLevel = slog.LevelError + default: + logLevel = slog.LevelInfo + } + if debug { + logLevel = slog.LevelDebug + } + + var handler slog.Handler + if jsonOut { + handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + Level: logLevel, + ReplaceAttr: slogFormatter, + }) + + } else { + handler = tint.NewHandler(os.Stdout, + &tint.Options{ + AddSource: debug, + Level: logLevel, + ReplaceAttr: nil, + NoColor: false, + TimeFormat: time.RFC3339, + }, + ) + } + return slog.New(handler) +} + +func slogFormatter(_ []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + if _, ok := a.Value.Any().(*time.Time); !ok { + return a + } + a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339)) + } + if a.Key == slog.SourceKey { + if _, ok := a.Value.Any().(*slog.Source); !ok { + return a + } + source := a.Value.Any().(*slog.Source) + source.File = filepath.Base(source.File) + // Rename attribute name "source" to "source_info" + // to avoid conflict with "source" attribute in Opensearch index. + a.Key = "source_info" + } + return a +}