From 5e8ca6b4c5a82f13758f71a206b572829a6006ed Mon Sep 17 00:00:00 2001 From: Joe Lombrozo Date: Thu, 7 Nov 2024 22:54:05 -0500 Subject: [PATCH] I think it ... works? --- .mockery.yaml | 3 + .tool-versions | 1 + cmd/controller.go | 2 + mocks/affected_apps/mocks/mock_Matcher.go | 6 +- mocks/affected_apps/mocks/mock_argoClient.go | 10 +- mocks/generator/mocks/mock_AppsGenerator.go | 6 +- mocks/generator/mocks/mock_Generator.go | 14 +- .../mocks/mock_IssuesServices.go | 18 +- .../mocks/mock_PullRequestsServices.go | 18 +- .../mocks/mock_RepositoriesServices.go | 22 +- mocks/vcs/mocks/mock_Client.go | 699 ++++++++++++++++++ pkg/argo_client/client.go | 32 +- pkg/argo_client/manifests.go | 445 +++++++++-- pkg/config/config.go | 19 +- pkg/events/check.go | 28 +- pkg/events/check_test.go | 31 +- pkg/events/worker.go | 66 +- pkg/git/repo.go | 9 +- pkg/repoUrl.go | 10 + 19 files changed, 1276 insertions(+), 163 deletions(-) create mode 100644 mocks/vcs/mocks/mock_Client.go diff --git a/.mockery.yaml b/.mockery.yaml index 4ca7eead..5b881f38 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -1,6 +1,9 @@ with-expecter: true dir: "mocks/{{.PackageName}}/mocks" packages: + github.com/zapier/kubechecks/pkg/vcs: + config: + all: true github.com/zapier/kubechecks/pkg/vcs/github_client: # place your package-specific config here config: diff --git a/.tool-versions b/.tool-versions index 75586296..ebc9a4be 100644 --- a/.tool-versions +++ b/.tool-versions @@ -6,5 +6,6 @@ helm-cr 1.6.1 helm-ct 3.8.0 kubeconform 0.6.3 kustomize 5.1.0 +mockery 2.46.3 staticcheck 2024.1.1 tilt 0.33.2 diff --git a/cmd/controller.go b/cmd/controller.go index 2345b3f0..88b55abb 100644 --- a/cmd/controller.go +++ b/cmd/controller.go @@ -158,6 +158,8 @@ func init() { newStringOpts().withDefault("1.23.0")) boolFlag(flags, "show-debug-info", "Set to true to print debug info to the footer of MR comments (KUBECHECKS_SHOW_DEBUG_INFO).") + stringFlag(flags, "argocd-repository-endpoint", `Location of the argocd repository service endpoint.`, + newStringOpts().withDefault("argocd-repo-server.argocd:8081")) stringFlag(flags, "label-filter", `(Optional) If set, The label that must be set on an MR (as "kubechecks:") for kubechecks to process the merge request webhook (KUBECHECKS_LABEL_FILTER).`) stringFlag(flags, "openai-api-token", "OpenAI API Token.") stringFlag(flags, "webhook-url-base", "The endpoint to listen on for incoming PR/MR event webhooks. For example, 'https://checker.mycompany.com'.") diff --git a/mocks/affected_apps/mocks/mock_Matcher.go b/mocks/affected_apps/mocks/mock_Matcher.go index 6a0fb41c..e9f34d82 100644 --- a/mocks/affected_apps/mocks/mock_Matcher.go +++ b/mocks/affected_apps/mocks/mock_Matcher.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package affected_apps @@ -29,6 +29,10 @@ func (_m *MockMatcher) EXPECT() *MockMatcher_Expecter { func (_m *MockMatcher) AffectedApps(ctx context.Context, changeList []string, targetBranch string, repo *git.Repo) (affected_apps.AffectedItems, error) { ret := _m.Called(ctx, changeList, targetBranch, repo) + if len(ret) == 0 { + panic("no return value specified for AffectedApps") + } + var r0 affected_apps.AffectedItems var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string, string, *git.Repo) (affected_apps.AffectedItems, error)); ok { diff --git a/mocks/affected_apps/mocks/mock_argoClient.go b/mocks/affected_apps/mocks/mock_argoClient.go index 4b61928b..db3bfb0f 100644 --- a/mocks/affected_apps/mocks/mock_argoClient.go +++ b/mocks/affected_apps/mocks/mock_argoClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package affected_apps @@ -26,6 +26,10 @@ func (_m *MockargoClient) EXPECT() *MockargoClient_Expecter { func (_m *MockargoClient) GetApplications(ctx context.Context) (*v1alpha1.ApplicationList, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for GetApplications") + } + var r0 *v1alpha1.ApplicationList var r1 error if rf, ok := ret.Get(0).(func(context.Context) (*v1alpha1.ApplicationList, error)); ok { @@ -80,6 +84,10 @@ func (_c *MockargoClient_GetApplications_Call) RunAndReturn(run func(context.Con func (_m *MockargoClient) GetApplicationsByAppset(ctx context.Context, appsetName string) (*v1alpha1.ApplicationList, error) { ret := _m.Called(ctx, appsetName) + if len(ret) == 0 { + panic("no return value specified for GetApplicationsByAppset") + } + var r0 *v1alpha1.ApplicationList var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*v1alpha1.ApplicationList, error)); ok { diff --git a/mocks/generator/mocks/mock_AppsGenerator.go b/mocks/generator/mocks/mock_AppsGenerator.go index 8ceb12f1..b9215f75 100644 --- a/mocks/generator/mocks/mock_AppsGenerator.go +++ b/mocks/generator/mocks/mock_AppsGenerator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package generator @@ -29,6 +29,10 @@ func (_m *MockAppsGenerator) EXPECT() *MockAppsGenerator_Expecter { func (_m *MockAppsGenerator) GenerateApplicationSetApps(ctx context.Context, appset v1alpha1.ApplicationSet, ctr *container.Container) ([]v1alpha1.Application, error) { ret := _m.Called(ctx, appset, ctr) + if len(ret) == 0 { + panic("no return value specified for GenerateApplicationSetApps") + } + var r0 []v1alpha1.Application var r1 error if rf, ok := ret.Get(0).(func(context.Context, v1alpha1.ApplicationSet, *container.Container) ([]v1alpha1.Application, error)); ok { diff --git a/mocks/generator/mocks/mock_Generator.go b/mocks/generator/mocks/mock_Generator.go index fc7365ff..faff846b 100644 --- a/mocks/generator/mocks/mock_Generator.go +++ b/mocks/generator/mocks/mock_Generator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package generator @@ -27,6 +27,10 @@ func (_m *MockGenerator) EXPECT() *MockGenerator_Expecter { func (_m *MockGenerator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet) ([]map[string]interface{}, error) { ret := _m.Called(appSetGenerator, applicationSetInfo) + if len(ret) == 0 { + panic("no return value specified for GenerateParams") + } + var r0 []map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet) ([]map[string]interface{}, error)); ok { @@ -82,6 +86,10 @@ func (_c *MockGenerator_GenerateParams_Call) RunAndReturn(run func(*v1alpha1.App func (_m *MockGenerator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration { ret := _m.Called(appSetGenerator) + if len(ret) == 0 { + panic("no return value specified for GetRequeueAfter") + } + var r0 time.Duration if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok { r0 = rf(appSetGenerator) @@ -124,6 +132,10 @@ func (_c *MockGenerator_GetRequeueAfter_Call) RunAndReturn(run func(*v1alpha1.Ap func (_m *MockGenerator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate { ret := _m.Called(appSetGenerator) + if len(ret) == 0 { + panic("no return value specified for GetTemplate") + } + var r0 *v1alpha1.ApplicationSetTemplate if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok { r0 = rf(appSetGenerator) diff --git a/mocks/github_client/mocks/mock_IssuesServices.go b/mocks/github_client/mocks/mock_IssuesServices.go index b7be7caf..ec6288f1 100644 --- a/mocks/github_client/mocks/mock_IssuesServices.go +++ b/mocks/github_client/mocks/mock_IssuesServices.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package github_client @@ -27,6 +27,10 @@ func (_m *MockIssuesServices) EXPECT() *MockIssuesServices_Expecter { 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) + if len(ret) == 0 { + panic("no return value specified for CreateComment") + } + var r0 *github.IssueComment var r1 *github.Response var r2 error @@ -94,6 +98,10 @@ func (_c *MockIssuesServices_CreateComment_Call) RunAndReturn(run func(context.C func (_m *MockIssuesServices) DeleteComment(ctx context.Context, owner string, repo string, commentID int64) (*github.Response, error) { ret := _m.Called(ctx, owner, repo, commentID) + if len(ret) == 0 { + panic("no return value specified for DeleteComment") + } + var r0 *github.Response var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) (*github.Response, error)); ok { @@ -151,6 +159,10 @@ func (_c *MockIssuesServices_DeleteComment_Call) RunAndReturn(run func(context.C 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) + if len(ret) == 0 { + panic("no return value specified for EditComment") + } + var r0 *github.IssueComment var r1 *github.Response var r2 error @@ -218,6 +230,10 @@ func (_c *MockIssuesServices_EditComment_Call) RunAndReturn(run func(context.Con 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) + if len(ret) == 0 { + panic("no return value specified for ListComments") + } + var r0 []*github.IssueComment var r1 *github.Response var r2 error diff --git a/mocks/github_client/mocks/mock_PullRequestsServices.go b/mocks/github_client/mocks/mock_PullRequestsServices.go index d77ad006..cafa0548 100644 --- a/mocks/github_client/mocks/mock_PullRequestsServices.go +++ b/mocks/github_client/mocks/mock_PullRequestsServices.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package github_client @@ -27,6 +27,10 @@ func (_m *MockPullRequestsServices) EXPECT() *MockPullRequestsServices_Expecter 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) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *github.PullRequest var r1 *github.Response var r2 error @@ -93,6 +97,10 @@ func (_c *MockPullRequestsServices_Get_Call) RunAndReturn(run func(context.Conte 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) + if len(ret) == 0 { + panic("no return value specified for GetRaw") + } + var r0 string var r1 *github.Response var r2 error @@ -158,6 +166,10 @@ func (_c *MockPullRequestsServices_GetRaw_Call) RunAndReturn(run func(context.Co 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) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 []*github.PullRequest var r1 *github.Response var r2 error @@ -224,6 +236,10 @@ func (_c *MockPullRequestsServices_List_Call) RunAndReturn(run func(context.Cont 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) + if len(ret) == 0 { + panic("no return value specified for ListFiles") + } + var r0 []*github.CommitFile var r1 *github.Response var r2 error diff --git a/mocks/github_client/mocks/mock_RepositoriesServices.go b/mocks/github_client/mocks/mock_RepositoriesServices.go index a61f7b52..72ecf3a8 100644 --- a/mocks/github_client/mocks/mock_RepositoriesServices.go +++ b/mocks/github_client/mocks/mock_RepositoriesServices.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package github_client @@ -27,6 +27,10 @@ func (_m *MockRepositoriesServices) EXPECT() *MockRepositoriesServices_Expecter 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) + if len(ret) == 0 { + panic("no return value specified for CreateHook") + } + var r0 *github.Hook var r1 *github.Response var r2 error @@ -93,6 +97,10 @@ func (_c *MockRepositoriesServices_CreateHook_Call) RunAndReturn(run func(contex 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) + if len(ret) == 0 { + panic("no return value specified for CreateStatus") + } + var r0 *github.RepoStatus var r1 *github.Response var r2 error @@ -160,6 +168,10 @@ func (_c *MockRepositoriesServices_CreateStatus_Call) RunAndReturn(run func(cont func (_m *MockRepositoriesServices) Get(ctx context.Context, owner string, repo string) (*github.Repository, *github.Response, error) { ret := _m.Called(ctx, owner, repo) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *github.Repository var r1 *github.Response var r2 error @@ -225,6 +237,10 @@ func (_c *MockRepositoriesServices_Get_Call) RunAndReturn(run func(context.Conte 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) + if len(ret) == 0 { + panic("no return value specified for GetContents") + } + var r0 *github.RepositoryContent var r1 []*github.RepositoryContent var r2 *github.Response @@ -301,6 +317,10 @@ func (_c *MockRepositoriesServices_GetContents_Call) RunAndReturn(run func(conte 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) + if len(ret) == 0 { + panic("no return value specified for ListHooks") + } + var r0 []*github.Hook var r1 *github.Response var r2 error diff --git a/mocks/vcs/mocks/mock_Client.go b/mocks/vcs/mocks/mock_Client.go new file mode 100644 index 00000000..9ab55290 --- /dev/null +++ b/mocks/vcs/mocks/mock_Client.go @@ -0,0 +1,699 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package vcs + +import ( + context "context" + http "net/http" + + mock "github.com/stretchr/testify/mock" + + msg "github.com/zapier/kubechecks/pkg/msg" + + pkg "github.com/zapier/kubechecks/pkg" + + vcs "github.com/zapier/kubechecks/pkg/vcs" +) + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// CommitStatus provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockClient) CommitStatus(_a0 context.Context, _a1 vcs.PullRequest, _a2 pkg.CommitState) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CommitStatus") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, vcs.PullRequest, pkg.CommitState) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockClient_CommitStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommitStatus' +type MockClient_CommitStatus_Call struct { + *mock.Call +} + +// CommitStatus is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 vcs.PullRequest +// - _a2 pkg.CommitState +func (_e *MockClient_Expecter) CommitStatus(_a0 interface{}, _a1 interface{}, _a2 interface{}) *MockClient_CommitStatus_Call { + return &MockClient_CommitStatus_Call{Call: _e.mock.On("CommitStatus", _a0, _a1, _a2)} +} + +func (_c *MockClient_CommitStatus_Call) Run(run func(_a0 context.Context, _a1 vcs.PullRequest, _a2 pkg.CommitState)) *MockClient_CommitStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(vcs.PullRequest), args[2].(pkg.CommitState)) + }) + return _c +} + +func (_c *MockClient_CommitStatus_Call) Return(_a0 error) *MockClient_CommitStatus_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_CommitStatus_Call) RunAndReturn(run func(context.Context, vcs.PullRequest, pkg.CommitState) error) *MockClient_CommitStatus_Call { + _c.Call.Return(run) + return _c +} + +// CreateHook provides a mock function with given fields: ctx, repoName, webhookUrl, webhookSecret +func (_m *MockClient) CreateHook(ctx context.Context, repoName string, webhookUrl string, webhookSecret string) error { + ret := _m.Called(ctx, repoName, webhookUrl, webhookSecret) + + if len(ret) == 0 { + panic("no return value specified for CreateHook") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok { + r0 = rf(ctx, repoName, webhookUrl, webhookSecret) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockClient_CreateHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateHook' +type MockClient_CreateHook_Call struct { + *mock.Call +} + +// CreateHook is a helper method to define mock.On call +// - ctx context.Context +// - repoName string +// - webhookUrl string +// - webhookSecret string +func (_e *MockClient_Expecter) CreateHook(ctx interface{}, repoName interface{}, webhookUrl interface{}, webhookSecret interface{}) *MockClient_CreateHook_Call { + return &MockClient_CreateHook_Call{Call: _e.mock.On("CreateHook", ctx, repoName, webhookUrl, webhookSecret)} +} + +func (_c *MockClient_CreateHook_Call) Run(run func(ctx context.Context, repoName string, webhookUrl string, webhookSecret string)) *MockClient_CreateHook_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MockClient_CreateHook_Call) Return(_a0 error) *MockClient_CreateHook_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_CreateHook_Call) RunAndReturn(run func(context.Context, string, string, string) error) *MockClient_CreateHook_Call { + _c.Call.Return(run) + return _c +} + +// Email provides a mock function with given fields: +func (_m *MockClient) Email() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Email") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// MockClient_Email_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Email' +type MockClient_Email_Call struct { + *mock.Call +} + +// Email is a helper method to define mock.On call +func (_e *MockClient_Expecter) Email() *MockClient_Email_Call { + return &MockClient_Email_Call{Call: _e.mock.On("Email")} +} + +func (_c *MockClient_Email_Call) Run(run func()) *MockClient_Email_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_Email_Call) Return(_a0 string) *MockClient_Email_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_Email_Call) RunAndReturn(run func() string) *MockClient_Email_Call { + _c.Call.Return(run) + return _c +} + +// GetHookByUrl provides a mock function with given fields: ctx, repoName, webhookUrl +func (_m *MockClient) GetHookByUrl(ctx context.Context, repoName string, webhookUrl string) (*vcs.WebHookConfig, error) { + ret := _m.Called(ctx, repoName, webhookUrl) + + if len(ret) == 0 { + panic("no return value specified for GetHookByUrl") + } + + var r0 *vcs.WebHookConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*vcs.WebHookConfig, error)); ok { + return rf(ctx, repoName, webhookUrl) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) *vcs.WebHookConfig); ok { + r0 = rf(ctx, repoName, webhookUrl) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*vcs.WebHookConfig) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, repoName, webhookUrl) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_GetHookByUrl_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHookByUrl' +type MockClient_GetHookByUrl_Call struct { + *mock.Call +} + +// GetHookByUrl is a helper method to define mock.On call +// - ctx context.Context +// - repoName string +// - webhookUrl string +func (_e *MockClient_Expecter) GetHookByUrl(ctx interface{}, repoName interface{}, webhookUrl interface{}) *MockClient_GetHookByUrl_Call { + return &MockClient_GetHookByUrl_Call{Call: _e.mock.On("GetHookByUrl", ctx, repoName, webhookUrl)} +} + +func (_c *MockClient_GetHookByUrl_Call) Run(run func(ctx context.Context, repoName string, webhookUrl string)) *MockClient_GetHookByUrl_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockClient_GetHookByUrl_Call) Return(_a0 *vcs.WebHookConfig, _a1 error) *MockClient_GetHookByUrl_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_GetHookByUrl_Call) RunAndReturn(run func(context.Context, string, string) (*vcs.WebHookConfig, error)) *MockClient_GetHookByUrl_Call { + _c.Call.Return(run) + return _c +} + +// GetName provides a mock function with given fields: +func (_m *MockClient) GetName() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetName") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// MockClient_GetName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetName' +type MockClient_GetName_Call struct { + *mock.Call +} + +// GetName is a helper method to define mock.On call +func (_e *MockClient_Expecter) GetName() *MockClient_GetName_Call { + return &MockClient_GetName_Call{Call: _e.mock.On("GetName")} +} + +func (_c *MockClient_GetName_Call) Run(run func()) *MockClient_GetName_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_GetName_Call) Return(_a0 string) *MockClient_GetName_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_GetName_Call) RunAndReturn(run func() string) *MockClient_GetName_Call { + _c.Call.Return(run) + return _c +} + +// LoadHook provides a mock function with given fields: ctx, repoAndId +func (_m *MockClient) LoadHook(ctx context.Context, repoAndId string) (vcs.PullRequest, error) { + ret := _m.Called(ctx, repoAndId) + + if len(ret) == 0 { + panic("no return value specified for LoadHook") + } + + var r0 vcs.PullRequest + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (vcs.PullRequest, error)); ok { + return rf(ctx, repoAndId) + } + if rf, ok := ret.Get(0).(func(context.Context, string) vcs.PullRequest); ok { + r0 = rf(ctx, repoAndId) + } else { + r0 = ret.Get(0).(vcs.PullRequest) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, repoAndId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_LoadHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LoadHook' +type MockClient_LoadHook_Call struct { + *mock.Call +} + +// LoadHook is a helper method to define mock.On call +// - ctx context.Context +// - repoAndId string +func (_e *MockClient_Expecter) LoadHook(ctx interface{}, repoAndId interface{}) *MockClient_LoadHook_Call { + return &MockClient_LoadHook_Call{Call: _e.mock.On("LoadHook", ctx, repoAndId)} +} + +func (_c *MockClient_LoadHook_Call) Run(run func(ctx context.Context, repoAndId string)) *MockClient_LoadHook_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockClient_LoadHook_Call) Return(_a0 vcs.PullRequest, _a1 error) *MockClient_LoadHook_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_LoadHook_Call) RunAndReturn(run func(context.Context, string) (vcs.PullRequest, error)) *MockClient_LoadHook_Call { + _c.Call.Return(run) + return _c +} + +// ParseHook provides a mock function with given fields: _a0, _a1 +func (_m *MockClient) ParseHook(_a0 *http.Request, _a1 []byte) (vcs.PullRequest, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ParseHook") + } + + var r0 vcs.PullRequest + var r1 error + if rf, ok := ret.Get(0).(func(*http.Request, []byte) (vcs.PullRequest, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*http.Request, []byte) vcs.PullRequest); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(vcs.PullRequest) + } + + if rf, ok := ret.Get(1).(func(*http.Request, []byte) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_ParseHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ParseHook' +type MockClient_ParseHook_Call struct { + *mock.Call +} + +// ParseHook is a helper method to define mock.On call +// - _a0 *http.Request +// - _a1 []byte +func (_e *MockClient_Expecter) ParseHook(_a0 interface{}, _a1 interface{}) *MockClient_ParseHook_Call { + return &MockClient_ParseHook_Call{Call: _e.mock.On("ParseHook", _a0, _a1)} +} + +func (_c *MockClient_ParseHook_Call) Run(run func(_a0 *http.Request, _a1 []byte)) *MockClient_ParseHook_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*http.Request), args[1].([]byte)) + }) + return _c +} + +func (_c *MockClient_ParseHook_Call) Return(_a0 vcs.PullRequest, _a1 error) *MockClient_ParseHook_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_ParseHook_Call) RunAndReturn(run func(*http.Request, []byte) (vcs.PullRequest, error)) *MockClient_ParseHook_Call { + _c.Call.Return(run) + return _c +} + +// PostMessage provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockClient) PostMessage(_a0 context.Context, _a1 vcs.PullRequest, _a2 string) *msg.Message { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for PostMessage") + } + + var r0 *msg.Message + if rf, ok := ret.Get(0).(func(context.Context, vcs.PullRequest, string) *msg.Message); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*msg.Message) + } + } + + return r0 +} + +// MockClient_PostMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostMessage' +type MockClient_PostMessage_Call struct { + *mock.Call +} + +// PostMessage is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 vcs.PullRequest +// - _a2 string +func (_e *MockClient_Expecter) PostMessage(_a0 interface{}, _a1 interface{}, _a2 interface{}) *MockClient_PostMessage_Call { + return &MockClient_PostMessage_Call{Call: _e.mock.On("PostMessage", _a0, _a1, _a2)} +} + +func (_c *MockClient_PostMessage_Call) Run(run func(_a0 context.Context, _a1 vcs.PullRequest, _a2 string)) *MockClient_PostMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(vcs.PullRequest), args[2].(string)) + }) + return _c +} + +func (_c *MockClient_PostMessage_Call) Return(_a0 *msg.Message) *MockClient_PostMessage_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_PostMessage_Call) RunAndReturn(run func(context.Context, vcs.PullRequest, string) *msg.Message) *MockClient_PostMessage_Call { + _c.Call.Return(run) + return _c +} + +// TidyOutdatedComments provides a mock function with given fields: _a0, _a1 +func (_m *MockClient) TidyOutdatedComments(_a0 context.Context, _a1 vcs.PullRequest) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for TidyOutdatedComments") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, vcs.PullRequest) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockClient_TidyOutdatedComments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TidyOutdatedComments' +type MockClient_TidyOutdatedComments_Call struct { + *mock.Call +} + +// TidyOutdatedComments is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 vcs.PullRequest +func (_e *MockClient_Expecter) TidyOutdatedComments(_a0 interface{}, _a1 interface{}) *MockClient_TidyOutdatedComments_Call { + return &MockClient_TidyOutdatedComments_Call{Call: _e.mock.On("TidyOutdatedComments", _a0, _a1)} +} + +func (_c *MockClient_TidyOutdatedComments_Call) Run(run func(_a0 context.Context, _a1 vcs.PullRequest)) *MockClient_TidyOutdatedComments_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(vcs.PullRequest)) + }) + return _c +} + +func (_c *MockClient_TidyOutdatedComments_Call) Return(_a0 error) *MockClient_TidyOutdatedComments_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_TidyOutdatedComments_Call) RunAndReturn(run func(context.Context, vcs.PullRequest) error) *MockClient_TidyOutdatedComments_Call { + _c.Call.Return(run) + return _c +} + +// ToEmoji provides a mock function with given fields: _a0 +func (_m *MockClient) ToEmoji(_a0 pkg.CommitState) string { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for ToEmoji") + } + + var r0 string + if rf, ok := ret.Get(0).(func(pkg.CommitState) string); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// MockClient_ToEmoji_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ToEmoji' +type MockClient_ToEmoji_Call struct { + *mock.Call +} + +// ToEmoji is a helper method to define mock.On call +// - _a0 pkg.CommitState +func (_e *MockClient_Expecter) ToEmoji(_a0 interface{}) *MockClient_ToEmoji_Call { + return &MockClient_ToEmoji_Call{Call: _e.mock.On("ToEmoji", _a0)} +} + +func (_c *MockClient_ToEmoji_Call) Run(run func(_a0 pkg.CommitState)) *MockClient_ToEmoji_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(pkg.CommitState)) + }) + return _c +} + +func (_c *MockClient_ToEmoji_Call) Return(_a0 string) *MockClient_ToEmoji_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_ToEmoji_Call) RunAndReturn(run func(pkg.CommitState) string) *MockClient_ToEmoji_Call { + _c.Call.Return(run) + return _c +} + +// UpdateMessage provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockClient) UpdateMessage(_a0 context.Context, _a1 *msg.Message, _a2 string) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for UpdateMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *msg.Message, string) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockClient_UpdateMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateMessage' +type MockClient_UpdateMessage_Call struct { + *mock.Call +} + +// UpdateMessage is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *msg.Message +// - _a2 string +func (_e *MockClient_Expecter) UpdateMessage(_a0 interface{}, _a1 interface{}, _a2 interface{}) *MockClient_UpdateMessage_Call { + return &MockClient_UpdateMessage_Call{Call: _e.mock.On("UpdateMessage", _a0, _a1, _a2)} +} + +func (_c *MockClient_UpdateMessage_Call) Run(run func(_a0 context.Context, _a1 *msg.Message, _a2 string)) *MockClient_UpdateMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*msg.Message), args[2].(string)) + }) + return _c +} + +func (_c *MockClient_UpdateMessage_Call) Return(_a0 error) *MockClient_UpdateMessage_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_UpdateMessage_Call) RunAndReturn(run func(context.Context, *msg.Message, string) error) *MockClient_UpdateMessage_Call { + _c.Call.Return(run) + return _c +} + +// Username provides a mock function with given fields: +func (_m *MockClient) Username() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Username") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// MockClient_Username_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Username' +type MockClient_Username_Call struct { + *mock.Call +} + +// Username is a helper method to define mock.On call +func (_e *MockClient_Expecter) Username() *MockClient_Username_Call { + return &MockClient_Username_Call{Call: _e.mock.On("Username")} +} + +func (_c *MockClient_Username_Call) Run(run func()) *MockClient_Username_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_Username_Call) Return(_a0 string) *MockClient_Username_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockClient_Username_Call) RunAndReturn(run func() string) *MockClient_Username_Call { + _c.Call.Return(run) + return _c +} + +// VerifyHook provides a mock function with given fields: _a0, _a1 +func (_m *MockClient) VerifyHook(_a0 *http.Request, _a1 string) ([]byte, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for VerifyHook") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(*http.Request, string) ([]byte, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*http.Request, string) []byte); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(*http.Request, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_VerifyHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VerifyHook' +type MockClient_VerifyHook_Call struct { + *mock.Call +} + +// VerifyHook is a helper method to define mock.On call +// - _a0 *http.Request +// - _a1 string +func (_e *MockClient_Expecter) VerifyHook(_a0 interface{}, _a1 interface{}) *MockClient_VerifyHook_Call { + return &MockClient_VerifyHook_Call{Call: _e.mock.On("VerifyHook", _a0, _a1)} +} + +func (_c *MockClient_VerifyHook_Call) Run(run func(_a0 *http.Request, _a1 string)) *MockClient_VerifyHook_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*http.Request), args[1].(string)) + }) + return _c +} + +func (_c *MockClient_VerifyHook_Call) Return(_a0 []byte, _a1 error) *MockClient_VerifyHook_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_VerifyHook_Call) RunAndReturn(run func(*http.Request, string) ([]byte, error)) *MockClient_VerifyHook_Call { + _c.Call.Return(run) + return _c +} + +// NewMockClient creates a new instance of MockClient. 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 NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/argo_client/client.go b/pkg/argo_client/client.go index a93cbcb7..0bb6bef8 100644 --- a/pkg/argo_client/client.go +++ b/pkg/argo_client/client.go @@ -1,6 +1,7 @@ package argo_client import ( + "crypto/tls" "io" "sync" @@ -8,8 +9,12 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset" "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings" + repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient" + "github.com/pkg/errors" "github.com/rs/zerolog/log" client "github.com/zapier/kubechecks/pkg/kubernetes" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -23,9 +28,10 @@ type ArgoClient struct { manifestsLock sync.Mutex - namespace string - k8s kubernetes.Interface - k8sConfig *rest.Config + repoClient repoapiclient.RepoServerServiceClient + namespace string + k8s kubernetes.Interface + k8sConfig *rest.Config } func NewArgoClient( @@ -52,11 +58,23 @@ func NewArgoClient( return nil, err } + log.Info().Msg("creating client") + tlsConfig := tls.Config{InsecureSkipVerify: true} + conn, err := grpc.NewClient(cfg.ArgoCDRepositoryEndpoint, + grpc.WithTransportCredentials( + credentials.NewTLS(&tlsConfig), + ), + ) + if err != nil { + return nil, errors.Wrap(err, "failed to create client") + } + return &ArgoClient{ - client: argo, - namespace: cfg.ArgoCDNamespace, - k8s: k8s.ClientSet(), - k8sConfig: k8s.Config(), + repoClient: repoapiclient.NewRepoServerServiceClient(conn), + client: argo, + namespace: cfg.ArgoCDNamespace, + k8s: k8s.ClientSet(), + k8sConfig: k8s.Config(), }, nil } diff --git a/pkg/argo_client/manifests.go b/pkg/argo_client/manifests.go index f22527d2..23d7643e 100644 --- a/pkg/argo_client/manifests.go +++ b/pkg/argo_client/manifests.go @@ -1,31 +1,40 @@ package argo_client import ( + "bufio" "context" "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" "time" "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/project" "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings" - argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient" - "github.com/argoproj/argo-cd/v2/reposerver/repository" - "github.com/argoproj/argo-cd/v2/util/git" - "github.com/ghodss/yaml" + "github.com/argoproj/argo-cd/v2/util/argo" + "github.com/argoproj/argo-cd/v2/util/db" + argosettings "github.com/argoproj/argo-cd/v2/util/settings" + "github.com/argoproj/argo-cd/v2/util/tgzstream" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "k8s.io/apimachinery/pkg/api/resource" - - "github.com/zapier/kubechecks/telemetry" + "github.com/zapier/kubechecks/pkg" + "github.com/zapier/kubechecks/pkg/git" + "github.com/zapier/kubechecks/pkg/vcs" ) -func (a *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir, changedAppFilePath string, app argoappv1.Application) ([]string, error) { - var err error +type getRepo func(ctx context.Context, cloneURL string, branchName string) (*git.Repo, error) - ctx, span := tracer.Start(ctx, "GetManifestsLocal") +func (a *ArgoClient) GetManifests(ctx context.Context, name string, app v1alpha1.Application, pullRequest vcs.PullRequest, getRepo getRepo) ([]string, error) { + ctx, span := tracer.Start(ctx, "GetManifests") defer span.End() - log.Debug().Str("name", name).Msg("GetManifestsLocal") + log.Debug().Str("name", name).Msg("GetManifests") start := time.Now() defer func() { @@ -33,111 +42,389 @@ func (a *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir, c getManifestsDuration.WithLabelValues(name).Observe(duration.Seconds()) }() + contents, refs := a.preprocessSources(&app, pullRequest) + + var manifests []string + for _, source := range contents { + moreManifests, err := a.generateManifests(ctx, app, source, refs, pullRequest, getRepo) + if err != nil { + return nil, errors.Wrap(err, "failed to generate manifests") + } + manifests = append(manifests, moreManifests...) + } + + getManifestsSuccess.WithLabelValues(name).Inc() + return manifests, nil +} + +func (a *ArgoClient) preprocessSources(app *v1alpha1.Application, pullRequest vcs.PullRequest) ([]v1alpha1.ApplicationSource, []v1alpha1.ApplicationSource) { + if !app.Spec.HasMultipleSources() { + return []v1alpha1.ApplicationSource{app.Spec.GetSource()}, nil + } + + // collect all ref sources, map by name + var contentSources []v1alpha1.ApplicationSource + var refSources []v1alpha1.ApplicationSource + + for _, source := range app.Spec.Sources { + if source.Ref == "" { + contentSources = append(contentSources, source) + continue + } + + if source.TargetRevision == pullRequest.BaseRef { + source.TargetRevision = pullRequest.HeadRef + } + + refSources = append(refSources, source) + } + + return contentSources, refSources +} + +func (a *ArgoClient) generateManifests(ctx context.Context, app v1alpha1.Application, source v1alpha1.ApplicationSource, refs []v1alpha1.ApplicationSource, pullRequest vcs.PullRequest, getRepo func(ctx context.Context, cloneURL string, branchName string) (*git.Repo, error)) ([]string, error) { + // multisource apps must adhere to the following rules: + // 1. first source must be a non-ref source + // 2. there must be one and only one non-ref source + // 3. ref sources that match the pull requests's repo and target branch need to have their target branch swapped to the head branch of the pull request + clusterCloser, clusterClient := a.GetClusterClient() defer clusterCloser.Close() - settingsCloser, settingsClient := a.GetSettingsClient() - defer settingsCloser.Close() - - log.Debug(). - Str("clusterName", app.Spec.Destination.Name). - Str("clusterServer", app.Spec.Destination.Server). - Msg("getting cluster") cluster, err := clusterClient.Get(ctx, &cluster.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server}) if err != nil { - telemetry.SetError(span, err, "Argo Get Cluster") - getManifestsFailed.WithLabelValues(name).Inc() + getManifestsFailed.WithLabelValues(app.Name).Inc() return nil, errors.Wrap(err, "failed to get cluster") } + settingsCloser, settingsClient := a.GetSettingsClient() + defer settingsCloser.Close() + + log.Info().Msg("get settings") argoSettings, err := settingsClient.Get(ctx, &settings.SettingsQuery{}) if err != nil { - telemetry.SetError(span, err, "Argo Get Settings") - getManifestsFailed.WithLabelValues(name).Inc() + getManifestsFailed.WithLabelValues(app.Name).Inc() return nil, errors.Wrap(err, "failed to get settings") } - log.Debug().Str("name", name).Msg("generating diff for application...") - res, err := a.generateManifests(ctx, fmt.Sprintf("%s/%s", tempRepoDir, changedAppFilePath), tempRepoDir, app, argoSettings, cluster) + settingsMgr := argosettings.NewSettingsManager(ctx, a.k8s, a.namespace) + argoDB := db.NewDB(a.namespace, settingsMgr, a.k8s) + + repoTarget := source.TargetRevision + if areSameRepos(source.RepoURL, pullRequest.CloneURL) && areSameTargetRef(source.TargetRevision, pullRequest.BaseRef) { + repoTarget = pullRequest.HeadRef + } + + log.Info().Msg("get repo") + repo, err := getRepo(ctx, source.RepoURL, repoTarget) if err != nil { - telemetry.SetError(span, err, "Generate Manifests") - return nil, errors.Wrap(err, "failed to generate manifests") + return nil, errors.Wrap(err, "failed to get repo") } - if res.Manifests == nil { - return nil, nil + log.Info().Msg("packaging app") + packageDir, err := packageApp(ctx, source, refs, repo, getRepo) + if err != nil { + return nil, errors.Wrap(err, "failed to package application") } - getManifestsSuccess.WithLabelValues(name).Inc() - return res.Manifests, nil -} -type repoRef struct { - // revision is the git revision - can be any valid revision like a branch, tag, or commit SHA. - revision string - // commitSHA is the actual commit to which revision refers. - commitSHA string - // key is the name of the key which was used to reference this repo. - key string -} + log.Info().Msg("compressing files") + f, filesWritten, checksum, err := tgzstream.CompressFiles(packageDir, []string{"*"}, []string{".git"}) + if err != nil { + return nil, fmt.Errorf("failed to compress files: %w", err) + } + log.Info().Msgf("%d files compressed", filesWritten) + //if filesWritten == 0 { + // return nil, fmt.Errorf("no files to send") + //} -func (a *ArgoClient) generateManifests( - ctx context.Context, appPath, tempRepoDir string, app argoappv1.Application, argoSettings *settings.Settings, cluster *argoappv1.Cluster, -) (*repoapiclient.ManifestResponse, error) { - a.manifestsLock.Lock() - defer a.manifestsLock.Unlock() + closer, projectClient, err := a.client.NewProjectClient() + if err != nil { + return nil, errors.Wrap(err, "failed to get project client") + } + defer closer.Close() + + proj, err := projectClient.Get(ctx, &project.ProjectQuery{Name: app.Spec.Project}) + if err != nil { + return nil, fmt.Errorf("error getting app project: %w", err) + } + + helmRepos, err := argoDB.ListHelmRepositories(ctx) + if err != nil { + return nil, fmt.Errorf("error listing helm repositories: %w", err) + } + permittedHelmRepos, err := argo.GetPermittedRepos(proj, helmRepos) + if err != nil { + return nil, fmt.Errorf("error retrieving permitted repos: %w", err) + } + helmRepositoryCredentials, err := argoDB.GetAllHelmRepositoryCredentials(ctx) + if err != nil { + return nil, fmt.Errorf("error getting helm repository credentials: %w", err) + } + helmOptions, err := settingsMgr.GetHelmSettings() + if err != nil { + return nil, fmt.Errorf("error getting helm settings: %w", err) + } + permittedHelmCredentials, err := argo.GetPermittedReposCredentials(proj, helmRepositoryCredentials) + if err != nil { + return nil, fmt.Errorf("error getting permitted repos credentials: %w", err) + } + enabledSourceTypes, err := settingsMgr.GetEnabledSourceTypes() + if err != nil { + return nil, fmt.Errorf("error getting settings enabled source types: %w", err) + } - source := app.Spec.GetSource() + refSources, err := argo.GetRefSources(context.Background(), app.Spec, argoDB) + if err != nil { + return nil, fmt.Errorf("failed to get ref sources: %w", err) + } - var projectSourceRepos []string - var helmRepos []*argoappv1.Repository - var helmCreds []*argoappv1.RepoCreds - var enableGenerateManifests map[string]bool - var helmOptions *argoappv1.HelmOptions - var refSources map[string]*argoappv1.RefTarget + app.Spec.Sources = append([]v1alpha1.ApplicationSource{source}, refs...) q := repoapiclient.ManifestRequest{ - Repo: &argoappv1.Repository{Repo: source.RepoURL}, + Repo: &v1alpha1.Repository{Repo: source.RepoURL}, Revision: source.TargetRevision, AppLabelKey: argoSettings.AppLabelKey, AppName: app.Name, Namespace: app.Spec.Destination.Namespace, ApplicationSource: &source, - Repos: helmRepos, + Repos: permittedHelmRepos, KustomizeOptions: argoSettings.KustomizeOptions, KubeVersion: cluster.Info.ServerVersion, ApiVersions: cluster.Info.APIVersions, - HelmRepoCreds: helmCreds, - TrackingMethod: argoSettings.TrackingMethod, - EnabledSourceTypes: enableGenerateManifests, + HelmRepoCreds: permittedHelmCredentials, HelmOptions: helmOptions, + TrackingMethod: argoSettings.TrackingMethod, + EnabledSourceTypes: enabledSourceTypes, + ProjectName: proj.Name, + ProjectSourceRepos: proj.Spec.SourceRepos, HasMultipleSources: app.Spec.HasMultipleSources(), RefSources: refSources, - ProjectSourceRepos: projectSourceRepos, - ProjectName: app.Spec.Project, - } - - return repository.GenerateManifests( - ctx, - appPath, - tempRepoDir, - source.TargetRevision, - &q, - true, - new(git.NoopCredsStore), - resource.MustParse("0"), - nil, - ) + } + + log.Info().Msg("generating manifest with files") + stream, err := a.repoClient.GenerateManifestWithFiles(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get manifests with files") + } + + log.Info().Msg("sending request") + if err := stream.Send(&repoapiclient.ManifestRequestWithFiles{ + Part: &repoapiclient.ManifestRequestWithFiles_Request{ + Request: &q, + }, + }); err != nil { + return nil, errors.Wrap(err, "failed to send request") + } + + log.Info().Msg("sending metadata") + if err := stream.Send(&repoapiclient.ManifestRequestWithFiles{ + Part: &repoapiclient.ManifestRequestWithFiles_Metadata{ + Metadata: &repoapiclient.ManifestFileMetadata{ + Checksum: checksum, + }, + }, + }); err != nil { + return nil, errors.Wrap(err, "failed to send metadata") + } + + log.Info().Msg("sending file") + err = sendFile(ctx, stream, f) + if err != nil { + return nil, fmt.Errorf("failed to send manifest stream file: %w", err) + } + + log.Info().Msg("receiving repsonse") + response, err := stream.CloseAndRecv() + if err != nil { + return nil, errors.Wrap(err, "failed to get response") + } + + log.Info().Msg("done!") + return response.Manifests, nil } -func ConvertJsonToYamlManifests(jsonManifests []string) []string { - var manifests []string - for _, manifest := range jsonManifests { - ret, err := yaml.JSONToYAML([]byte(manifest)) +func copyFile(srcpath, dstpath string) error { + dstdir := filepath.Dir(dstpath) + if err := os.MkdirAll(dstdir, 0o777); err != nil { + return errors.Wrap(err, "failed to make directories") + } + + r, err := os.Open(srcpath) + if err != nil { + return err + } + defer r.Close() // ignore error: file was opened read-only. + + w, err := os.Create(dstpath) + if err != nil { + return err + } + + defer func() { + // Report the error, if any, from Close, but do so + // only if there isn't already an outgoing error. + if c := w.Close(); err == nil { + err = c + } + }() + + _, err = io.Copy(w, r) + return err +} + +func packageApp(ctx context.Context, source v1alpha1.ApplicationSource, refs []v1alpha1.ApplicationSource, repo *git.Repo, getRepo getRepo) (string, error) { + tempDir, err := os.MkdirTemp("", "package-*") + if err != nil { + return "", errors.Wrap(err, "failed to make temp dir") + } + + tempAppDir := filepath.Join(tempDir, source.Path) + appPath := filepath.Join(repo.Directory, source.Path) + + // copy app files to the temp dir + if err = filepath.Walk(appPath, func(path string, info fs.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + relPath, err := filepath.Rel(appPath, path) if err != nil { - log.Warn().Err(err).Msg("Failed to format manifest") - continue + return errors.Wrapf(err, "failed to calculate rel between %q and %q", appPath, path) + } + src := path + dst := filepath.Join(tempAppDir, relPath) + if err := copyFile(src, dst); err != nil { + return errors.Wrapf(err, "failed to %s => %s", src, dst) } - manifests = append(manifests, fmt.Sprintf("---\n%s", string(ret))) + return nil + }); err != nil { + return "", errors.Wrap(err, "failed to copy files") } - return manifests + + if source.Helm != nil { + refsByName := make(map[string]v1alpha1.ApplicationSource) + for _, ref := range refs { + refsByName[ref.Ref] = ref + } + + for index, valueFile := range source.Helm.ValueFiles { + if strings.HasPrefix(valueFile, "$") { + refName, refPath, err := splitRefFromPath(valueFile) + if err != nil { + return "", errors.Wrap(err, "failed to parse value file") + } + + ref, ok := refsByName[refName] + if !ok { + return "", errors.Wrap(err, "value file points at missing ref") + } + + refRepo := repo + if !areSameRepos(ref.RepoURL, repo.CloneURL) { + refRepo, err = getRepo(ctx, ref.RepoURL, ref.TargetRevision) + if err != nil { + return "", errors.Wrapf(err, "failed to clone repo: %q", ref.RepoURL) + } + } + + src := filepath.Join(refRepo.Directory, refPath) + dst := filepath.Join(tempDir, refPath) + if err = copyFile(src, dst); err != nil { + return "", errors.Wrapf(err, "failed to copy referenced value file: %q", valueFile) + } + + relPath, err := filepath.Rel(tempAppDir, dst) + if err != nil { + return "", errors.Wrap(err, "failed to find a relative path") + } + source.Helm.ValueFiles[index] = relPath + continue + } + + relPath, err := filepath.Rel(source.Path, valueFile) + if err != nil { + return "", errors.Wrap(err, "failed to calculate relative path") + } + + if !strings.HasPrefix(relPath, "../") { + continue // this values file is already copied + } + + src := filepath.Join(appPath, valueFile) + dst := filepath.Join(tempAppDir, valueFile) + if err = copyFile(src, dst); err != nil { + return "", errors.Wrapf(err, "failed to copy file: %q", valueFile) + } + } + } + + return tempDir, nil +} + +var valueRef = regexp.MustCompile(`^\$([^/]+)/(.*)$`) +var ErrInvalidSourceRef = errors.New("invalid value ref") + +func splitRefFromPath(file string) (string, string, error) { + match := valueRef.FindStringSubmatch(file) + if match == nil { + return "", "", ErrInvalidSourceRef + } + + return match[1], match[2], nil +} + +type sender interface { + Send(*repoapiclient.ManifestRequestWithFiles) error +} + +func sendFile(ctx context.Context, sender sender, file *os.File) error { + reader := bufio.NewReader(file) + chunk := make([]byte, 1024) + for { + if ctx != nil { + if err := ctx.Err(); err != nil { + return fmt.Errorf("client stream context error: %w", err) + } + } + n, err := reader.Read(chunk) + if n > 0 { + fr := &repoapiclient.ManifestRequestWithFiles{ + Part: &repoapiclient.ManifestRequestWithFiles_Chunk{ + Chunk: &repoapiclient.ManifestFileChunk{ + Chunk: chunk[:n], + }, + }, + } + if e := sender.Send(fr); e != nil { + return fmt.Errorf("error sending stream: %w", e) + } + } + if err != nil { + if err == io.EOF { + break + } + return fmt.Errorf("buffer reader error: %w", err) + } + } + return nil +} + +func areSameRepos(url1, url2 string) bool { + repo1, err := pkg.Canonicalize(url1) + if err != nil { + log.Warn().Msgf("failed to canonicalize %q", url1) + return false + } + + repo2, err := pkg.Canonicalize(url2) + if err != nil { + log.Warn().Msgf("failed to canonicalize %q", url2) + return false + } + + return repo1 == repo2 +} + +func areSameTargetRef(ref1, ref2 string) bool { + return ref1 == ref2 } diff --git a/pkg/config/config.go b/pkg/config/config.go index 8aa7549a..3cf0a70a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -17,15 +17,16 @@ import ( type ServerConfig struct { // argocd - ArgoCDServerAddr string `mapstructure:"argocd-api-server-addr"` - 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"` - ArgoCDPlainText bool `mapstructure:"argocd-api-plaintext"` - KubernetesConfig string `mapstructure:"kubernetes-config"` - KubernetesType string `mapstructure:"kubernetes-type"` - KubernetesClusterID string `mapstructure:"kubernetes-clusterid"` + ArgoCDServerAddr string `mapstructure:"argocd-api-server-addr"` + 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"` + ArgoCDPlainText bool `mapstructure:"argocd-api-plaintext"` + ArgoCDRepositoryEndpoint string `mapstructure:"argocd-repository-endpoint"` + KubernetesConfig string `mapstructure:"kubernetes-config"` + KubernetesType string `mapstructure:"kubernetes-type"` + KubernetesClusterID string `mapstructure:"kubernetes-clusterid"` // otel EnableOtel bool `mapstructure:"otel-enabled"` diff --git a/pkg/events/check.go b/pkg/events/check.go index 8e0ee609..1e3c8e48 100644 --- a/pkg/events/check.go +++ b/pkg/events/check.go @@ -164,11 +164,7 @@ func generateRepoKey(cloneURL pkg.RepoURL, branchName string) string { return fmt.Sprintf("%s|||%s", cloneURL.CloneURL(""), branchName) } -type hasUsername interface { - Username() string -} - -func (ce *CheckEvent) getRepo(ctx context.Context, vcsClient hasUsername, cloneURL, branchName string) (*git.Repo, error) { +func (ce *CheckEvent) getRepo(ctx context.Context, cloneURL, branchName string) (*git.Repo, error) { var ( err error repo *git.Repo @@ -181,7 +177,7 @@ func (ce *CheckEvent) getRepo(ctx context.Context, vcsClient hasUsername, cloneU if err != nil { return nil, errors.Wrap(err, "failed to parse clone url") } - cloneURL = parsed.CloneURL(vcsClient.Username()) + cloneURL = parsed.CloneURL(ce.ctr.VcsClient.Username()) branchName = strings.TrimSpace(branchName) if branchName == "" { @@ -227,6 +223,22 @@ func (ce *CheckEvent) getRepo(ctx context.Context, vcsClient hasUsername, cloneU return repo, nil } +func (ce *CheckEvent) mergeIntoTarget(ctx context.Context, repo *git.Repo, branch string) error { + if err := repo.MergeIntoTarget(ctx, fmt.Sprintf("origin/%s", branch)); err != nil { + return errors.Wrap(err, "failed to merge into target") + } + + parsed, err := canonicalize(repo.CloneURL) + if err != nil { + return errors.Wrap(err, "failed to canonicalize url") + } + + reposKey := generateRepoKey(parsed, branch) + ce.clonedRepos[reposKey] = repo + + return nil +} + func (ce *CheckEvent) Process(ctx context.Context) error { start := time.Now() @@ -234,13 +246,13 @@ func (ce *CheckEvent) Process(ctx context.Context) error { defer span.End() // Clone the repo's BaseRef (main, etc.) locally into the temp dir we just made - repo, err := ce.getRepo(ctx, ce.ctr.VcsClient, ce.pullRequest.CloneURL, ce.pullRequest.BaseRef) + repo, err := ce.getRepo(ctx, ce.pullRequest.CloneURL, ce.pullRequest.BaseRef) if err != nil { return errors.Wrap(err, "failed to clone repo") } // Merge the most recent changes into the branch we just cloned - if err = repo.MergeIntoTarget(ctx, ce.pullRequest.SHA); err != nil { + if err = ce.mergeIntoTarget(ctx, repo, ce.pullRequest.HeadRef); err != nil { return errors.Wrap(err, "failed to merge into target") } diff --git a/pkg/events/check_test.go b/pkg/events/check_test.go index ff7505b4..2de8c510 100644 --- a/pkg/events/check_test.go +++ b/pkg/events/check_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" affectedappsmocks "github.com/zapier/kubechecks/mocks/affected_apps/mocks" generatorsmocks "github.com/zapier/kubechecks/mocks/generator/mocks" + vcsmocks "github.com/zapier/kubechecks/mocks/vcs/mocks" "github.com/zapier/kubechecks/pkg/affected_apps" "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/config" @@ -65,12 +66,6 @@ func TestCleanupGetManifestsError(t *testing.T) { } } -type mockVcsClient struct{} - -func (m mockVcsClient) Username() string { - return "username" -} - func TestCheckEventGetRepo(t *testing.T) { cloneURL := "https://github.com/zapier/kubechecks.git" canonical, err := canonicalize(cloneURL) @@ -80,12 +75,16 @@ func TestCheckEventGetRepo(t *testing.T) { ctx := context.TODO() t.Run("empty branch name", func(t *testing.T) { + vcsClient := new(vcsmocks.MockClient) + vcsClient.EXPECT().Username().Return("username") + ce := CheckEvent{ clonedRepos: make(map[string]*git.Repo), repoManager: git.NewRepoManager(cfg), + ctr: container.Container{VcsClient: vcsClient}, } - repo, err := ce.getRepo(ctx, mockVcsClient{}, cloneURL, "") + repo, err := ce.getRepo(ctx, cloneURL, "") require.NoError(t, err) assert.Equal(t, "main", repo.BranchName) assert.Len(t, ce.clonedRepos, 2) @@ -94,12 +93,16 @@ func TestCheckEventGetRepo(t *testing.T) { }) t.Run("branch is HEAD", func(t *testing.T) { + vcsClient := new(vcsmocks.MockClient) + vcsClient.EXPECT().Username().Return("username") + ce := CheckEvent{ clonedRepos: make(map[string]*git.Repo), repoManager: git.NewRepoManager(cfg), + ctr: container.Container{VcsClient: vcsClient}, } - repo, err := ce.getRepo(ctx, mockVcsClient{}, cloneURL, "HEAD") + repo, err := ce.getRepo(ctx, cloneURL, "HEAD") require.NoError(t, err) assert.Equal(t, "main", repo.BranchName) assert.Len(t, ce.clonedRepos, 2) @@ -108,12 +111,16 @@ func TestCheckEventGetRepo(t *testing.T) { }) t.Run("branch is the same as HEAD", func(t *testing.T) { + vcsClient := new(vcsmocks.MockClient) + vcsClient.EXPECT().Username().Return("username") + ce := CheckEvent{ clonedRepos: make(map[string]*git.Repo), repoManager: git.NewRepoManager(cfg), + ctr: container.Container{VcsClient: vcsClient}, } - repo, err := ce.getRepo(ctx, mockVcsClient{}, cloneURL, "main") + repo, err := ce.getRepo(ctx, cloneURL, "main") require.NoError(t, err) assert.Equal(t, "main", repo.BranchName) assert.Len(t, ce.clonedRepos, 2) @@ -122,12 +129,16 @@ func TestCheckEventGetRepo(t *testing.T) { }) t.Run("branch is not the same as HEAD", func(t *testing.T) { + vcsClient := new(vcsmocks.MockClient) + vcsClient.EXPECT().Username().Return("username") + ce := CheckEvent{ clonedRepos: make(map[string]*git.Repo), repoManager: git.NewRepoManager(cfg), + ctr: container.Container{VcsClient: vcsClient}, } - repo, err := ce.getRepo(ctx, mockVcsClient{}, cloneURL, "gh-pages") + repo, err := ce.getRepo(ctx, cloneURL, "gh-pages") require.NoError(t, err) assert.Equal(t, "gh-pages", repo.BranchName) assert.Len(t, ce.clonedRepos, 1) diff --git a/pkg/events/worker.go b/pkg/events/worker.go index b99f3bc3..c527f98b 100644 --- a/pkg/events/worker.go +++ b/pkg/events/worker.go @@ -6,6 +6,8 @@ import ( "runtime/debug" "sync/atomic" + "github.com/ghodss/yaml" + "github.com/rs/zerolog/log" "github.com/zapier/kubechecks/pkg/vcs" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -13,7 +15,6 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/rs/zerolog" "github.com/zapier/kubechecks/pkg" - "github.com/zapier/kubechecks/pkg/argo_client" "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/git" @@ -30,7 +31,7 @@ type worker struct { vcsNote *msg.Message done func() - getRepo func(ctx context.Context, vcsClient hasUsername, cloneURL, branchName string) (*git.Repo, error) + getRepo func(ctx context.Context, cloneURL, branchName string) (*git.Repo, error) queueApp, removeApp func(application v1alpha1.Application) } @@ -120,46 +121,20 @@ func (w *worker) processApp(ctx context.Context, app v1alpha1.Application) { } }() - var jsonManifests []string - sources := getAppSources(app) - for _, appSrc := range sources { - var ( - appPath = appSrc.Path - appRepoUrl = appSrc.RepoURL - logger = rootLogger.With(). - Str("app_path", appPath). - Logger() - ) - - repo, err := w.getRepo(ctx, w.ctr.VcsClient, appRepoUrl, appSrc.TargetRevision) - if err != nil { - logger.Error().Err(err).Msg("Unable to clone repository") - w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ - State: pkg.StateError, - Summary: "failed to clone repo", - Details: fmt.Sprintf("Clone URL: `%s`\nTarget Revision: `%s`\n```\n%s\n```", appRepoUrl, appSrc.TargetRevision, err.Error()), - }) - return - } - repoPath := repo.Directory - - logger.Debug().Str("repo_path", repoPath).Msg("Getting manifests") - someJsonManifests, err := w.ctr.ArgoClient.GetManifestsLocal(ctx, appName, repoPath, appPath, app) - if err != nil { - logger.Error().Err(err).Msg("Unable to get manifests") - w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ - State: pkg.StateError, - Summary: "Unable to get manifests", - Details: fmt.Sprintf("```\n%s\n```", cleanupGetManifestsError(err, repo.Directory)), - }) - return - } - - jsonManifests = append(jsonManifests, someJsonManifests...) + rootLogger.Debug().Msg("Getting manifests") + jsonManifests, err := w.ctr.ArgoClient.GetManifests(ctx, appName, app, w.pullRequest, w.getRepo) + if err != nil { + rootLogger.Error().Err(err).Msg("Unable to get manifests") + w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ + State: pkg.StateError, + Summary: "Unable to get manifests", + Details: fmt.Sprintf("```\n%s\n```", err), + }) + return } // Argo diff logic wants unformatted manifests but everything else wants them as YAML, so we prepare both - yamlManifests := argo_client.ConvertJsonToYamlManifests(jsonManifests) + yamlManifests := convertJsonToYamlManifests(jsonManifests) rootLogger.Trace().Msgf("Manifests:\n%+v\n", yamlManifests) k8sVersion, err := w.ctr.ArgoClient.GetKubernetesVersionByApplication(ctx, app) @@ -179,3 +154,16 @@ func (w *worker) processApp(ctx context.Context, app v1alpha1.Application) { runner.Wait() } + +func convertJsonToYamlManifests(jsonManifests []string) []string { + var manifests []string + for _, manifest := range jsonManifests { + ret, err := yaml.JSONToYAML([]byte(manifest)) + if err != nil { + log.Warn().Err(err).Msg("Failed to format manifest") + continue + } + manifests = append(manifests, fmt.Sprintf("---\n%s", string(ret))) + } + return manifests +} diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 05652154..ae9a40ca 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -81,6 +81,7 @@ func (r *Repo) Clone(ctx context.Context) error { } } + log.Info().Msg("repo has been cloned") return nil } @@ -107,22 +108,22 @@ func (r *Repo) GetRemoteHead() (string, error) { return branchName, nil } -func (r *Repo) MergeIntoTarget(ctx context.Context, sha string) error { +func (r *Repo) MergeIntoTarget(ctx context.Context, ref string) error { // Merge the last commit into a tmp branch off of the target branch _, span := tracer.Start(ctx, "Repo - RepoMergeIntoTarget", trace.WithAttributes( attribute.String("branch_name", r.BranchName), attribute.String("clone_url", r.CloneURL), attribute.String("directory", r.Directory), - attribute.String("sha", sha), + attribute.String("sha", ref), )) defer span.End() - cmd := r.execCommand("git", "merge", sha) + cmd := r.execCommand("git", "merge", ref) out, err := cmd.CombinedOutput() if err != nil { telemetry.SetError(span, err, "merge commit into branch") - log.Error().Err(err).Msgf("unable to merge %s, %s", sha, out) + log.Error().Err(err).Msgf("unable to merge %s, %s", ref, out) return err } diff --git a/pkg/repoUrl.go b/pkg/repoUrl.go index cfc5a677..99a3723f 100644 --- a/pkg/repoUrl.go +++ b/pkg/repoUrl.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/chainguard-dev/git-urls" + "github.com/pkg/errors" ) type RepoURL struct { @@ -41,3 +42,12 @@ func NormalizeRepoUrl(s string) (RepoURL, url.Values, error) { Path: r.Path, }, r.Query(), nil } + +func Canonicalize(cloneURL string) (RepoURL, error) { + parsed, _, err := NormalizeRepoUrl(cloneURL) + if err != nil { + return RepoURL{}, errors.Wrap(err, "failed to parse clone url") + } + + return parsed, nil +}