diff --git a/api/v1beta1/module_types.go b/api/v1beta1/module_types.go index 2fd6e03..5938e53 100644 --- a/api/v1beta1/module_types.go +++ b/api/v1beta1/module_types.go @@ -46,10 +46,10 @@ const ( ReasonPlanFailed = "PlanFailed" ReasonApplyFailed = "ApplyFailed" - ReasonInitialised = "Initialised" - ReasonDriftDetected = "DriftDetected" - ReasonNoDriftDetected = "NoDriftDetected" - ReasonApplied = "Applied" + ReasonInitialised = "Initialised" + ReasonPlanOnlyDriftDetected = "PlanOnlyDriftDetected" + ReasonNoDriftDetected = "NoDriftDetected" + ReasonApplied = "Applied" ) const ( diff --git a/git/repos.go b/git/repos.go index 3fce743..9f13674 100644 --- a/git/repos.go +++ b/git/repos.go @@ -12,11 +12,10 @@ import ( // testing the full process of an apply run // mirror.RepoPool satisfies this interface and drop in replacement type Repositories interface { - ChangedFiles(ctx context.Context, remote, hash string) ([]string, error) Clone(ctx context.Context, remote, dst, branch, pathspec string, rmGitDir bool) (string, error) Hash(ctx context.Context, remote, ref, path string) (string, error) Mirror(ctx context.Context, remote string) error - ObjectExists(ctx context.Context, remote, obj string) error - Repository(remote string) (*mirror.Repository, error) Subject(ctx context.Context, remote, hash string) (string, error) + BranchCommits(ctx context.Context, remote, branch string) ([]mirror.CommitInfo, error) + MergeCommits(ctx context.Context, remote, mergeCommitHash string) ([]mirror.CommitInfo, error) } diff --git a/git/repos_mock.go b/git/repos_mock.go index 3c1f17e..7516f95 100644 --- a/git/repos_mock.go +++ b/git/repos_mock.go @@ -35,19 +35,19 @@ func (m *MockRepositories) EXPECT() *MockRepositoriesMockRecorder { return m.recorder } -// ChangedFiles mocks base method. -func (m *MockRepositories) ChangedFiles(arg0 context.Context, arg1, arg2 string) ([]string, error) { +// BranchCommits mocks base method. +func (m *MockRepositories) BranchCommits(arg0 context.Context, arg1, arg2 string) ([]mirror.CommitInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ChangedFiles", arg0, arg1, arg2) - ret0, _ := ret[0].([]string) + ret := m.ctrl.Call(m, "BranchCommits", arg0, arg1, arg2) + ret0, _ := ret[0].([]mirror.CommitInfo) ret1, _ := ret[1].(error) return ret0, ret1 } -// ChangedFiles indicates an expected call of ChangedFiles. -func (mr *MockRepositoriesMockRecorder) ChangedFiles(arg0, arg1, arg2 interface{}) *gomock.Call { +// BranchCommits indicates an expected call of BranchCommits. +func (mr *MockRepositoriesMockRecorder) BranchCommits(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangedFiles", reflect.TypeOf((*MockRepositories)(nil).ChangedFiles), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BranchCommits", reflect.TypeOf((*MockRepositories)(nil).BranchCommits), arg0, arg1, arg2) } // Clone mocks base method. @@ -80,47 +80,33 @@ func (mr *MockRepositoriesMockRecorder) Hash(arg0, arg1, arg2, arg3 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockRepositories)(nil).Hash), arg0, arg1, arg2, arg3) } -// Mirror mocks base method. -func (m *MockRepositories) Mirror(arg0 context.Context, arg1 string) error { +// MergeCommits mocks base method. +func (m *MockRepositories) MergeCommits(arg0 context.Context, arg1, arg2 string) ([]mirror.CommitInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Mirror", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 + ret := m.ctrl.Call(m, "MergeCommits", arg0, arg1, arg2) + ret0, _ := ret[0].([]mirror.CommitInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// Mirror indicates an expected call of Mirror. -func (mr *MockRepositoriesMockRecorder) Mirror(arg0, arg1 interface{}) *gomock.Call { +// MergeCommits indicates an expected call of MergeCommits. +func (mr *MockRepositoriesMockRecorder) MergeCommits(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mirror", reflect.TypeOf((*MockRepositories)(nil).Mirror), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MergeCommits", reflect.TypeOf((*MockRepositories)(nil).MergeCommits), arg0, arg1, arg2) } -// ObjectExists mocks base method. -func (m *MockRepositories) ObjectExists(arg0 context.Context, arg1, arg2 string) error { +// Mirror mocks base method. +func (m *MockRepositories) Mirror(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ObjectExists", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Mirror", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } -// ObjectExists indicates an expected call of ObjectExists. -func (mr *MockRepositoriesMockRecorder) ObjectExists(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ObjectExists", reflect.TypeOf((*MockRepositories)(nil).ObjectExists), arg0, arg1, arg2) -} - -// Repository mocks base method. -func (m *MockRepositories) Repository(arg0 string) (*mirror.Repository, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Repository", arg0) - ret0, _ := ret[0].(*mirror.Repository) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Repository indicates an expected call of Repository. -func (mr *MockRepositoriesMockRecorder) Repository(arg0 interface{}) *gomock.Call { +// Mirror indicates an expected call of Mirror. +func (mr *MockRepositoriesMockRecorder) Mirror(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Repository", reflect.TypeOf((*MockRepositories)(nil).Repository), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mirror", reflect.TypeOf((*MockRepositories)(nil).Mirror), arg0, arg1) } // Subject mocks base method. diff --git a/go.mod b/go.mod index f7d6cb0..0ca4459 100644 --- a/go.mod +++ b/go.mod @@ -19,29 +19,29 @@ require ( github.com/redis/go-redis/v9 v9.6.1 github.com/robfig/cron/v3 v3.0.1 github.com/urfave/cli/v2 v2.27.4 - github.com/utilitywarehouse/git-mirror v0.2.3 + github.com/utilitywarehouse/git-mirror v0.2.4 github.com/utilitywarehouse/go-operational v0.0.0-20220413104526-79ce40a50281 - golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/oauth2 v0.23.0 - golang.org/x/time v0.6.0 + golang.org/x/time v0.7.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.31.1 k8s.io/apimachinery v0.31.1 k8s.io/client-go v0.31.1 k8s.io/klog/v2 v2.130.1 sigs.k8s.io/controller-runtime v0.19.0 - sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6 - sigs.k8s.io/controller-tools v0.16.3 + sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20241011141221-469837099f73 + sigs.k8s.io/controller-tools v0.16.4 ) require ( - github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton // indirect + github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.4.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cloudflare/circl v1.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect @@ -56,7 +56,7 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gobuffalo/flect v1.0.2 // indirect + github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -71,14 +71,14 @@ require ( github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect github.com/imdario/mergo v0.3.16 // 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/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -90,7 +90,7 @@ require ( github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect @@ -103,23 +103,23 @@ require ( github.com/zclconf/go-cty v1.15.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.31.0 // indirect - k8s.io/kube-openapi v0.0.0-20240822171749-76de80e0abd9 // indirect - k8s.io/utils v0.0.0-20240821151609-f90d01438635 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + k8s.io/apiextensions-apiserver v0.31.1 // indirect + k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094 // indirect + k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6800144..e3fbdca 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton h1:KVBEgU3CJpmzLChnLiSuEyCuhGhcMt3eOST+7A+ckto= github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton h1:ZGewsAoeSirbUS5cO8L0FMQA+iSop9xR1nmFYifDBPo= +github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -22,8 +24,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= +github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -70,6 +76,8 @@ github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -113,6 +121,8 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= @@ -141,6 +151,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -188,6 +200,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= @@ -225,6 +239,8 @@ github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= github.com/utilitywarehouse/git-mirror v0.2.3 h1:2S/D0X8YXf32ziyQTKNto4NFYIVUvwGvF7PuMRJcLeY= github.com/utilitywarehouse/git-mirror v0.2.3/go.mod h1:4kphH7nZpYg/L0bF9EH2fqf8tqcWV1VDhAxTVX74j60= +github.com/utilitywarehouse/git-mirror v0.2.4 h1:vOg8fASPyfXArwo32cqr5BXZIifuvX0hV3XVA3Qj5mI= +github.com/utilitywarehouse/git-mirror v0.2.4/go.mod h1:HUJ5oj1JodWEKSG1TyjVOOz3q3VMgB6b2lLRZDotMEA= github.com/utilitywarehouse/go-operational v0.0.0-20220413104526-79ce40a50281 h1:o2reBE9vn4ZXmq73rNDmipyJsH0GPqoS8A6CVeWZsGU= github.com/utilitywarehouse/go-operational v0.0.0-20220413104526-79ce40a50281/go.mod h1:NVEoiRSDBsLOEk9X+pwskLIPWL5YGmZMaGP0kXnpvhM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -249,8 +265,12 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -263,6 +283,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -281,15 +303,23 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.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= @@ -297,6 +327,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 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= @@ -305,6 +337,8 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= @@ -326,6 +360,8 @@ k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= +k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= @@ -334,16 +370,26 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240822171749-76de80e0abd9 h1:y+4z/s0h3R97P/o/098DSjlpyNpHzGirNPlTL+GHdqY= k8s.io/kube-openapi v0.0.0-20240822171749-76de80e0abd9/go.mod h1:s4yb9FXajAVNRnxSB5Ckpr/oq2LP4mKSMWeZDVppd30= +k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094 h1:MErs8YA0abvOqJ8gIupA1Tz6PKXYUw34XsGlA7uSL1k= +k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094/go.mod h1:7ioBJr1A6igWjsR2fxq2EZ0mlMwYLejazSIc2bzMp2U= k8s.io/utils v0.0.0-20240821151609-f90d01438635 h1:2wThSvJoW/Ncn9TmQEYXRnevZXi2duqHWf5OX9S3zjI= k8s.io/utils v0.0.0-20240821151609-f90d01438635/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= +k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6 h1:Wzx3QswG7gfzqPDw7Ec6/xvJGyoxAKUEoaxWLrk1V/I= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240820183333-e6c3d139d2b6/go.mod h1:IaDsO8xSPRxRG1/rm9CP7+jPmj0nMNAuNi/yiHnLX8k= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20241011141221-469837099f73 h1:HRQvrOxgD0HtHFqCXbzab9IwtFVp0jdY0rgI+TN4t9I= +sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20241011141221-469837099f73/go.mod h1:IaDsO8xSPRxRG1/rm9CP7+jPmj0nMNAuNi/yiHnLX8k= sigs.k8s.io/controller-tools v0.16.3 h1:z48C5/d4jCVQQvtiSBL5MYyZ3EO2eFIOXrIKMgHVhFY= sigs.k8s.io/controller-tools v0.16.3/go.mod h1:AEj6k+w1kYpLZv2einOH3mj52ips4W/6FUjnB5tkJGs= +sigs.k8s.io/controller-tools v0.16.4 h1:VXmar78eDXbx1by/H09ikEq1hiq3bqInxuV3lMr3GmQ= +sigs.k8s.io/controller-tools v0.16.4/go.mod h1:kcsZyYMXiOFuBhofSPtkB90zTSxVRxVVyvtKQcx3q1A= 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/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 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.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/main.go b/main.go index 38f614e..5f561d2 100644 --- a/main.go +++ b/main.go @@ -340,7 +340,7 @@ func validate(c *cli.Context) { // - If 'path' is set, try to use that // - Otherwise, download the release indicated by 'version' // - If the version isn't defined, download the latest release -func findTerraformExecPath(ctx context.Context, path, ver string) (string, func(), error) { +func findTerraformExecPath(path, ver string) (string, func(), error) { cleanup := func() {} i := hcinstall.NewInstaller() var execPath string @@ -377,7 +377,7 @@ func findTerraformExecPath(ctx context.Context, path, ver string) (string, func( // terraformVersionString returns the terraform version from the terraform binary // indicated by execPath -func terraformVersionString(ctx context.Context, execPath string) (string, error) { +func terraformVersionString(execPath string) (string, error) { tmpDir, err := os.MkdirTemp("", "tfversion") if err != nil { return "", err @@ -583,13 +583,13 @@ func run(c *cli.Context) { // Find the requested version of terraform and log the version // information - execPath, cleanup, err := findTerraformExecPath(ctx, terraformPath, terraformVersion) + execPath, cleanup, err := findTerraformExecPath(terraformPath, terraformVersion) defer cleanup() if err != nil { logger.Error("error finding terraform", "err", err) os.Exit(1) } - version, err := terraformVersionString(ctx, execPath) + version, err := terraformVersionString(execPath) if err != nil { logger.Error("error getting terraform version", "err", err) os.Exit(1) diff --git a/prplanner/outputs.go b/prplanner/outputs.go index 5014bc2..fce0f8d 100644 --- a/prplanner/outputs.go +++ b/prplanner/outputs.go @@ -2,7 +2,7 @@ package prplanner import ( "context" - "regexp" + "strconv" "strings" "github.com/redis/go-redis/v9" @@ -10,11 +10,6 @@ import ( tfaplv1beta1 "github.com/utilitywarehouse/terraform-applier/api/v1beta1" ) -var ( - mergePRRegex = regexp.MustCompile(`Merge pull request #(\d+) from`) - prNumSuffixRegex = regexp.MustCompile(`\(#(\d+)\)$`) -) - func (p *Planner) uploadRequestOutput(ctx context.Context, pr *pr) { // Go through PR comments in reverse order for i := len(pr.Comments.Nodes) - 1; i >= 0; i-- { @@ -62,9 +57,18 @@ func (p *Planner) processRedisKeySetMsg(ctx context.Context, ch <-chan *redis.Me continue } - run, err := p.RedisClient.Run(ctx, msg.Payload) + key := msg.Payload + + // skip non run related keys + // and process default output only once + if !strings.Contains(key, ":default:lastRun") && + !strings.Contains(key, ":PR:") { + continue + } + + run, err := p.RedisClient.Run(ctx, key) if err != nil { - p.Log.Error("unable to get run output", "key", msg.Payload, "err", err) + p.Log.Error("unable to get run output", "key", key, "err", err) continue } @@ -79,6 +83,14 @@ func (p *Planner) processRedisKeySetMsg(ctx context.Context, ch <-chan *redis.Me CommentID = run.Request.PR.CommentID } + // if its not a PR run then also + // check if there is pending task for output upload + if prNum == 0 && strings.Contains(key, "default:lastRun") { + if pr, err := p.RedisClient.PendingApplyUploadPR(ctx, run.Module, run.CommitHash); err == nil { + prNum, _ = strconv.Atoi(pr) + } + } + // this is required in case this run is not a PR run && not apply run if prNum == 0 { continue @@ -87,7 +99,7 @@ func (p *Planner) processRedisKeySetMsg(ctx context.Context, ch <-chan *redis.Me var module tfaplv1beta1.Module err = p.ClusterClt.Get(ctx, run.Module, &module) if err != nil { - p.Log.Error("unable to get module", "module", run.Module, "error", err) + p.Log.Error("unable to get module", "module", run.Module, "pr", prNum, "error", err) continue } @@ -97,15 +109,24 @@ func (p *Planner) processRedisKeySetMsg(ctx context.Context, ch <-chan *redis.Me repo, err := giturl.Parse(module.Spec.RepoURL) if err != nil { - p.Log.Error("unable to parse repo url", "module", run.Module, "error", err) + p.Log.Error("unable to parse repo url", "module", run.Module, "pr", prNum, "error", err) continue } _, err = p.github.postComment(repo.Path, strings.TrimSuffix(repo.Repo, ".git"), CommentID, prNum, comment) if err != nil { - p.Log.Error("error posting PR comment:", "error", err) + p.Log.Error("error posting PR comment:", "module", run.Module, "pr", prNum, "error", err) continue } + p.Log.Info("run output posted", "module", run.Module, "pr", prNum) + + // if apply output is posted then clean up PR runs + if strings.Contains(key, "default:lastRun") { + if err := p.RedisClient.CleanupPRKeys(ctx, run.Module, prNum, run.CommitHash); err != nil { + p.Log.Error("error cleaning PR keys:", "module", run.Module, "pr", prNum, "error", err) + continue + } + } } } diff --git a/prplanner/outputs_test.go b/prplanner/outputs_test.go index 9a235ba..0329e5e 100644 --- a/prplanner/outputs_test.go +++ b/prplanner/outputs_test.go @@ -104,6 +104,26 @@ func Test_processRedisKeySetMsg(t *testing.T) { time.Sleep(2 * time.Second) }) + t.Run("valid apply key", func(t *testing.T) { + key := "foo:admins:default:lastRun" + + testRedis.EXPECT().Run(gomock.Any(), key). + Return(&tfaplv1beta1.Run{Module: types.NamespacedName{Namespace: "foo", Name: "admins"}, Request: &tfaplv1beta1.Request{}, CommitHash: "hash1", CommitMsg: "some commit msg... (#4)", Output: "terraform apply output"}, nil) + + testRedis.EXPECT().PendingApplyUploadPR(gomock.Any(), types.NamespacedName{Namespace: "foo", Name: "admins"}, "hash1"). + Return("4", nil) + + // mock github API Call adding new request info + testGithub.EXPECT().postComment("utilitywarehouse", "terraform-applier", 0, 4, gomock.Any()). + Return(123, nil) + + testRedis.EXPECT().CleanupPRKeys(gomock.Any(), types.NamespacedName{Namespace: "foo", Name: "admins"}, 4, "hash1"). + Return(nil) + + ch <- &redis.Message{Channel: "__keyevent@0__:set", Payload: key} + time.Sleep(2 * time.Second) + }) + t.Run("invalid channel", func(t *testing.T) { key := "foo:admins:PR:4:d91f6ff" // not expecting any other calls @@ -112,6 +132,22 @@ func Test_processRedisKeySetMsg(t *testing.T) { time.Sleep(2 * time.Second) }) + t.Run("invalid key", func(t *testing.T) { + key := "foo:admins:default:lastApply" + // not expecting any other calls + // hence no mock call EXPECT() + ch <- &redis.Message{Channel: "__keyevent@0__:set", Payload: key} + time.Sleep(2 * time.Second) + }) + + t.Run("invalid key 2", func(t *testing.T) { + key := "pending:apply_upload:foo:admins:hash:xxx" + // not expecting any other calls + // hence no mock call EXPECT() + ch <- &redis.Message{Channel: "__keyevent@0__:set", Payload: key} + time.Sleep(2 * time.Second) + }) + t.Run("empty output", func(t *testing.T) { key := "foo:admins:PR:4:d91f6ff" diff --git a/prplanner/planner.go b/prplanner/planner.go index 99bffa2..b6b330d 100644 --- a/prplanner/planner.go +++ b/prplanner/planner.go @@ -106,8 +106,19 @@ func (p *Planner) processPullRequest(ctx context.Context, pr *pr, kubeModuleList return } + if pr.Closed && !pr.Merged { + return + } + + // get list of commits and changed file for the PR branch + commitsInfo, err := p.Repos.BranchCommits(ctx, pr.BaseRepository.URL, pr.HeadRefName) + if err != nil { + p.Log.Error("unable to commit info", "repo", pr.BaseRepository.URL, "branch", pr.HeadRefName, "pr", pr.Number, "error", err) + return + } + // verify if PR belongs to module based on files changed - prModules, err := p.getPRModuleList(pr, kubeModuleList) + prModules, err := p.getPRModuleList(pr, commitsInfo, kubeModuleList) if err != nil { p.Log.Error("error getting a list of modules in PR", "repo", pr.BaseRepository.Name, "pr", pr.Number, "error", err) return @@ -133,18 +144,12 @@ func (p *Planner) processPullRequest(ctx context.Context, pr *pr, kubeModuleList } // ensure plan requests - p.ensurePlanRequests(ctx, pr, prModules, skipCommitRun) + p.ensurePlanRequests(ctx, pr, commitsInfo, prModules, skipCommitRun) p.uploadRequestOutput(ctx, pr) } -func (p *Planner) getPRModuleList(pr *pr, kubeModules *tfaplv1beta1.ModuleList) ([]types.NamespacedName, error) { - var pathList []string - - for _, file := range pr.Files.Nodes { - pathList = append(pathList, file.Path) - } - +func (p *Planner) getPRModuleList(pr *pr, commitsInfo []mirror.CommitInfo, kubeModules *tfaplv1beta1.ModuleList) ([]types.NamespacedName, error) { var modulesUpdated []types.NamespacedName for _, kubeModule := range kubeModules.Items { @@ -152,7 +157,15 @@ func (p *Planner) getPRModuleList(pr *pr, kubeModules *tfaplv1beta1.ModuleList) continue } - if !pathBelongsToModule(pathList, &kubeModule) { + updated := false + for _, commit := range commitsInfo { + if isModuleUpdated(&kubeModule, commit) { + updated = true + break + } + } + + if !updated { continue } @@ -168,8 +181,8 @@ func (p *Planner) getPRModuleList(pr *pr, kubeModules *tfaplv1beta1.ModuleList) return modulesUpdated, nil } -func pathBelongsToModule(pathList []string, module *tfaplv1beta1.Module) bool { - for _, path := range pathList { +func isModuleUpdated(module *tfaplv1beta1.Module, commit mirror.CommitInfo) bool { + for _, path := range commit.ChangedFiles { if strings.HasPrefix(path, module.Spec.Path) { return true } diff --git a/prplanner/planner_test.go b/prplanner/planner_test.go index 940f651..069400f 100644 --- a/prplanner/planner_test.go +++ b/prplanner/planner_test.go @@ -6,7 +6,9 @@ import ( "testing" gomock "github.com/golang/mock/gomock" + "github.com/utilitywarehouse/git-mirror/pkg/mirror" tfaplv1beta1 "github.com/utilitywarehouse/terraform-applier/api/v1beta1" + "github.com/utilitywarehouse/terraform-applier/git" ) func Test_processPullRequest(t *testing.T) { @@ -21,16 +23,17 @@ func Test_processPullRequest(t *testing.T) { {Spec: tfaplv1beta1.ModuleSpec{Path: "six", RepoURL: "https://github.com/utilitywarehouse/foo.git"}}, }, } + goMockCtrl := gomock.NewController(t) + testGit := git.NewMockRepositories(goMockCtrl) planner := &Planner{ - Log: slog.Default(), + Log: slog.Default(), + Repos: testGit, } t.Run("skip draft PR", func(t *testing.T) { p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", "random comment", "random comment"}, - []string{"foo1/bar1", "foo2/bar2"}, ) p.IsDraft = true @@ -40,11 +43,18 @@ func Test_processPullRequest(t *testing.T) { t.Run("len PR modules == 0", func(t *testing.T) { kubeModuleList := &tfaplv1beta1.ModuleList{} p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", "random comment", "random comment"}, - []string{}, ) + testGit.EXPECT().BranchCommits(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, _, hash string) ([]mirror.CommitInfo, error) { + return []mirror.CommitInfo{ + {Hash: "hash3"}, + {Hash: "hash2"}, + {Hash: "hash1"}, + }, nil + }) + planner.processPullRequest(ctx, p, kubeModuleList) }) @@ -54,14 +64,21 @@ func Test_processPullRequest(t *testing.T) { planner.github = testGithub p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", "random comment", "random comment"}, - []string{"one", "two", "three", "four", "five", "six"}, ) p.BaseRepository.Owner.Login = "utilitywarehouse" p.BaseRepository.Name = "foo" p.BaseRepository.URL = "git@github.com:utilitywarehouse/foo.git" + testGit.EXPECT().BranchCommits(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, _, hash string) ([]mirror.CommitInfo, error) { + return []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"four", "three"}}, + {Hash: "hash2", ChangedFiles: []string{"two", "five"}}, + {Hash: "hash1", ChangedFiles: []string{"one", "six"}}, + }, nil + }) + testGithub.EXPECT().postComment(gomock.Any(), gomock.Any(), 0, 123, gomock.Any()). DoAndReturn(func(repoOwner, repoName string, commentID, prNumber int, commentBody prComment) (int, error) { t.Skip("comment posted. test passed") diff --git a/prplanner/requests.go b/prplanner/requests.go index 2c8dbe5..481b663 100644 --- a/prplanner/requests.go +++ b/prplanner/requests.go @@ -6,12 +6,13 @@ import ( "fmt" "time" + "github.com/utilitywarehouse/git-mirror/pkg/mirror" tfaplv1beta1 "github.com/utilitywarehouse/terraform-applier/api/v1beta1" "github.com/utilitywarehouse/terraform-applier/sysutil" "k8s.io/apimachinery/pkg/types" ) -func (p *Planner) ensurePlanRequests(ctx context.Context, pr *pr, prModules []types.NamespacedName, skipCommitRun bool) { +func (p *Planner) ensurePlanRequests(ctx context.Context, pr *pr, commitsInfo []mirror.CommitInfo, prModules []types.NamespacedName, skipCommitRun bool) { for _, moduleName := range prModules { // Check if module has a pending plan request module, err := sysutil.GetModule(ctx, p.ClusterClt, moduleName) @@ -25,7 +26,7 @@ func (p *Planner) ensurePlanRequests(ctx context.Context, pr *pr, prModules []ty continue } - req, err := p.ensurePlanRequest(ctx, pr, module, skipCommitRun) + req, err := p.ensurePlanRequest(ctx, pr, commitsInfo, module, skipCommitRun) if err != nil { p.Log.Error("unable to generate new plan request", "module", moduleName, "error", err) continue @@ -38,10 +39,10 @@ func (p *Planner) ensurePlanRequests(ctx context.Context, pr *pr, prModules []ty } } -func (p *Planner) ensurePlanRequest(ctx context.Context, pr *pr, module *tfaplv1beta1.Module, skipCommitRun bool) (*tfaplv1beta1.Request, error) { +func (p *Planner) ensurePlanRequest(ctx context.Context, pr *pr, commitsInfo []mirror.CommitInfo, module *tfaplv1beta1.Module, skipCommitRun bool) (*tfaplv1beta1.Request, error) { if !skipCommitRun { // loop through commits from latest to oldest - req, err := p.checkPRCommits(ctx, pr, module) + req, err := p.checkPRCommits(ctx, pr, commitsInfo, module) if err != nil { return req, err } @@ -54,56 +55,40 @@ func (p *Planner) ensurePlanRequest(ctx context.Context, pr *pr, module *tfaplv1 return p.checkPRCommentsForPlanRequests(pr, module) } -func (p *Planner) checkPRCommits(ctx context.Context, pr *pr, module *tfaplv1beta1.Module) (*tfaplv1beta1.Request, error) { +func (p *Planner) checkPRCommits(ctx context.Context, pr *pr, commitsInfo []mirror.CommitInfo, module *tfaplv1beta1.Module) (*tfaplv1beta1.Request, error) { // loop through commits to check if module path is updated - for i := len(pr.Commits.Nodes) - 1; i >= 0; i-- { - commit := pr.Commits.Nodes[i].Commit - - // check if module path is updated in this commit - updated, err := p.isModuleUpdated(ctx, commit.Oid, module) - if err != nil { - return nil, err - } - if !updated { + for _, commit := range commitsInfo { + if !isModuleUpdated(module, commit) { continue } // check if we have already processed (uploaded output) this commit - if isPlanOutputPostedForCommit(p.ClusterEnvName, pr, commit.Oid, module.Spec.Path, module.NamespacedName()) { + if isPlanOutputPostedForCommit(p.ClusterEnvName, pr, commit.Hash, module.Spec.Path, module.NamespacedName()) { return nil, nil } - if isPlanRequestAckPostedForCommit(p.ClusterEnvName, pr, commit.Oid, module.Spec.Path, module.NamespacedName()) { + if isPlanRequestAckPostedForCommit(p.ClusterEnvName, pr, commit.Hash, module.Spec.Path, module.NamespacedName()) { return nil, nil } // check if run is already completed for this commit - runOutput, err := p.RedisClient.PRRun(ctx, module.NamespacedName(), pr.Number, commit.Oid) + runOutput, err := p.RedisClient.PRRun(ctx, module.NamespacedName(), pr.Number, commit.Hash) if err != nil && !errors.Is(err, sysutil.ErrKeyNotFound) { return nil, err } - if runOutput != nil && runOutput.CommitHash == commit.Oid { + if runOutput != nil && runOutput.CommitHash == commit.Hash { return nil, nil } // request run p.Log.Info("triggering plan due to new commit", "module", module.NamespacedName(), "pr", pr.Number, "author", pr.Author.Login) - return p.addNewRequest(module, pr, commit.Oid) + return p.addNewRequest(module, pr, commit.Hash) } return nil, nil } -func (p *Planner) isModuleUpdated(ctx context.Context, commitHash string, module *tfaplv1beta1.Module) (bool, error) { - filesChangedInCommit, err := p.Repos.ChangedFiles(ctx, module.Spec.RepoURL, commitHash) - if err != nil { - return false, fmt.Errorf("error getting commit info: %w", err) - } - - return pathBelongsToModule(filesChangedInCommit, module), nil -} - func (p *Planner) checkPRCommentsForPlanRequests(pr *pr, module *tfaplv1beta1.Module) (*tfaplv1beta1.Request, error) { // Go through PR comments in reverse order for i := len(pr.Comments.Nodes) - 1; i >= 0; i-- { diff --git a/prplanner/requests_test.go b/prplanner/requests_test.go index 965be4a..9d86218 100644 --- a/prplanner/requests_test.go +++ b/prplanner/requests_test.go @@ -10,6 +10,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/utilitywarehouse/git-mirror/pkg/mirror" tfaplv1beta1 "github.com/utilitywarehouse/terraform-applier/api/v1beta1" "github.com/utilitywarehouse/terraform-applier/git" "github.com/utilitywarehouse/terraform-applier/sysutil" @@ -19,23 +20,16 @@ import ( var cmpIgnoreRandFields = cmpopts.IgnoreFields(tfaplv1beta1.Request{}, "ID", "RequestedAt") -func generateMockPR(num int, ref string, hash, comments, paths []string) *pr { +func generateMockPR(num int, ref string, comments []string) *pr { p := &pr{ Number: num, HeadRefName: ref, } - for _, v := range hash { - pc := prCommit{} - pc.Commit.Oid = v - p.Commits.Nodes = append(p.Commits.Nodes, pc) - } for _, v := range comments { p.Comments.Nodes = append(p.Comments.Nodes, prComment{1, author{}, v}) } - for _, v := range paths { - p.Files.Nodes = append(p.Files.Nodes, prFiles{v}) - } + return p } @@ -53,21 +47,6 @@ func TestCheckPRCommits(t *testing.T) { slog.SetLogLoggerLevel(slog.LevelDebug) - // Mock Repo calls with files changed - testGit.EXPECT().ChangedFiles(gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ context.Context, _, hash string) ([]string, error) { - switch hash { - case "hash1": - return []string{"foo/one"}, nil - case "hash2": - return []string{"foo/two"}, nil - case "hash3": - return []string{"foo/two", "foo/three"}, nil - default: - return nil, fmt.Errorf("hash not found") - } - }).AnyTimes() - t.Run("generate req for updated module", func(t *testing.T) { testRedis := sysutil.NewMockRedisInterface(goMockCtrl) testGithub := NewMockGithubInterface(goMockCtrl) @@ -75,11 +54,15 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", "random comment", "random comment"}, - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } + module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "one"}, Spec: tfaplv1beta1.ModuleSpec{ @@ -104,7 +87,7 @@ func TestCheckPRCommits(t *testing.T) { }) // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -130,11 +113,15 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", "random comment", "random comment"}, - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } + module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, Spec: tfaplv1beta1.ModuleSpec{ @@ -159,7 +146,7 @@ func TestCheckPRCommits(t *testing.T) { }) // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -185,11 +172,14 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash2", "hash3"}, []string{"random comment", runOutputMsg("default", "foo/two", "foo/two", &tfaplv1beta1.Run{CommitHash: "hash2", Summary: "Plan: x to add, x to change, x to destroy.", Output: "some output"}), "random comment"}, - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + } + module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "one"}, Spec: tfaplv1beta1.ModuleSpec{ @@ -199,7 +189,7 @@ func TestCheckPRCommits(t *testing.T) { } // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -218,11 +208,13 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", runOutputMsg("default", "foo/two", "foo/two", &tfaplv1beta1.Run{CommitHash: "hash3", Summary: "Plan: x to add, x to change, x to destroy.", Output: "some output"}), "random comment"}, - - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, @@ -233,7 +225,7 @@ func TestCheckPRCommits(t *testing.T) { } // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -252,11 +244,13 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", runOutputMsg("diff-cluster", "foo/two", "foo/two", &tfaplv1beta1.Run{CommitHash: "hash3", Summary: "Plan: x to add, x to change, x to destroy.", Output: "some output"}), "random comment"}, - - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, @@ -282,7 +276,7 @@ func TestCheckPRCommits(t *testing.T) { }) // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -308,11 +302,13 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", requestAcknowledgedMsg("default", "foo/two", "foo/two", "hash3", &metav1.Time{Time: time.Now()}), "random comment"}, - - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, @@ -323,7 +319,7 @@ func TestCheckPRCommits(t *testing.T) { } // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -342,11 +338,13 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", requestAcknowledgedMsg("diff-cluster", "foo/two", "foo/two", "hash3", &metav1.Time{Time: time.Now()}), "random comment"}, - - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, @@ -372,7 +370,7 @@ func TestCheckPRCommits(t *testing.T) { }) // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -398,10 +396,13 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", runOutputMsg("default", "foo/two", "foo/two", &tfaplv1beta1.Run{CommitHash: "hash2", Summary: "Plan: x to add, x to change, x to destroy.", Output: "some output"}), "random comment"}, - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, @@ -427,7 +428,7 @@ func TestCheckPRCommits(t *testing.T) { }) // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -453,10 +454,13 @@ func TestCheckPRCommits(t *testing.T) { planner.RedisClient = testRedis p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{"random comment", "random comment", "random comment"}, - nil, ) + commitsInfo := []mirror.CommitInfo{ + {Hash: "hash3", ChangedFiles: []string{"foo/two", "foo/three"}}, + {Hash: "hash2", ChangedFiles: []string{"foo/two"}}, + {Hash: "hash1", ChangedFiles: []string{"foo/one"}}, + } module := &tfaplv1beta1.Module{ ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "two"}, @@ -472,7 +476,7 @@ func TestCheckPRCommits(t *testing.T) { Return(&tfaplv1beta1.Run{CommitHash: "hash3"}, nil) // Call Test function - gotReq, err := planner.checkPRCommits(ctx, p, module) + gotReq, err := planner.checkPRCommits(ctx, p, commitsInfo, module) if err != nil { t.Fatalf("unexpected error: %s", err) } @@ -512,13 +516,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { // module might not be annotated by the time the loop checks it, which in this // case would mean plan out is ready ot be posted and NOT run hasn't been requested yet pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan two", requestAcknowledgedMsg("default", "foo/two", "path/foo/two", "hash2", mustParseMetaTime("2023-04-02T15:04:05Z")), requestAcknowledgedMsg("default", "foo/three", "path/foo/three", "hash3", mustParseMetaTime("2023-04-02T15:04:05Z")), }, - nil, ) gotReq, err := planner.checkPRCommentsForPlanRequests(pr, module) @@ -537,13 +539,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { // module might not be annotated by the time the loop checks it, which in this // case would mean plan out is ready ot be posted and NOT run hasn't been requested yet pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan path/foo/two", requestAcknowledgedMsg("default", "foo/two", "path/foo/two", "hash2", mustParseMetaTime("2023-04-02T15:04:05Z")), requestAcknowledgedMsg("default", "foo/three", "path/foo/three", "hash3", mustParseMetaTime("2023-04-02T15:04:05Z")), }, - nil, ) gotReq, err := planner.checkPRCommentsForPlanRequests(pr, module) @@ -558,13 +558,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { t.Run("plan out posted for module (by name)", func(t *testing.T) { pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan two", runOutputMsg("default", "foo/two", "path/foo/two", &tfaplv1beta1.Run{CommitHash: "hash2", Summary: "Plan: x to add, x to change, x to destroy.", Output: "tf plan output"}), runOutputMsg("default", "foo/three", "path/foo/three", &tfaplv1beta1.Run{CommitHash: "hash3", Summary: "Plan: x to add, x to change, x to destroy.", Output: "tf plan output"}), }, - nil, ) gotReq, err := planner.checkPRCommentsForPlanRequests(pr, module) @@ -579,13 +577,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { t.Run("plan out posted for module (by path)", func(t *testing.T) { pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan path/foo/two", runOutputMsg("default", "foo/two", "path/foo/two", &tfaplv1beta1.Run{CommitHash: "hash2", Summary: "Plan: x to add, x to change, x to destroy.", Output: "tf plan output"}), runOutputMsg("default", "foo/three", "path/foo/three", &tfaplv1beta1.Run{CommitHash: "hash3", Summary: "Plan: x to add, x to change, x to destroy.", Output: "tf plan output"}), }, - nil, ) gotReq, err := planner.checkPRCommentsForPlanRequests(pr, module) @@ -600,13 +596,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { t.Run("plan run is not requested for current module", func(t *testing.T) { pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan one", "@terraform-applier plan path/foo/one", "@terraform-applier plan path/foo/three", }, - nil, ) gotReq, err := planner.checkPRCommentsForPlanRequests(pr, module) @@ -624,30 +618,13 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { planner.github = testGithub p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan foo/one", "@terraform-applier plan path/foo/two", "@terraform-applier plan foo/three", }, - nil, ) - // Mock Repo calls with files changed - testGit.EXPECT().ChangedFiles(gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(_ context.Context, _, hash string) ([]string, error) { - switch hash { - case "hash1": - return []string{"path/foo/one"}, nil - case "hash2": - return []string{"path/foo/two"}, nil - case "hash3": - return []string{"path/foo/one", "path/foo/three"}, nil - default: - return nil, fmt.Errorf("hash not found") - } - }).AnyTimes() - testGit.EXPECT().Hash(gomock.Any(), gomock.Any(), "ref1", "path/foo/two"). Return("hash1", nil) @@ -677,7 +654,7 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { } if diff := cmp.Diff(wantReq, gotReq, cmpIgnoreRandFields); diff != "" { - t.Errorf("checkPRCommits() mismatch (-want +got):\n%s", diff) + t.Errorf("checkPRCommentsForPlanRequests() mismatch (-want +got):\n%s", diff) } }) @@ -686,13 +663,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { planner.github = testGithub p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan foo/one", "@terraform-applier plan two", "@terraform-applier plan three", }, - nil, ) // mock github API Call adding new request info @@ -724,7 +699,7 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { } if diff := cmp.Diff(wantReq, gotReq, cmpIgnoreRandFields); diff != "" { - t.Errorf("checkPRCommits() mismatch (-want +got):\n%s", diff) + t.Errorf("checkPRCommentsForPlanRequests() mismatch (-want +got):\n%s", diff) } }) @@ -737,13 +712,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { // module might not be annotated by the time the loop checks it, which in this // case would mean plan out is ready ot be posted and NOT run hasn't been requested yet pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan two", requestAcknowledgedMsg("diff-cluster", "foo/two", "path/foo/two", "hash2", mustParseMetaTime("2023-04-02T15:04:05Z")), requestAcknowledgedMsg("default", "foo/three", "path/foo/three", "hash3", mustParseMetaTime("2023-04-02T15:04:05Z")), }, - nil, ) // mock github API Call adding new request info @@ -774,7 +747,7 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { } if diff := cmp.Diff(wantReq, gotReq, cmpIgnoreRandFields); diff != "" { - t.Errorf("checkPRCommits() mismatch (-want +got):\n%s", diff) + t.Errorf("checkPRCommentsForPlanRequests() mismatch (-want +got):\n%s", diff) } }) @@ -783,13 +756,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { planner.github = testGithub pr := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan two", runOutputMsg("diff-cluster", "foo/two", "path/foo/two", &tfaplv1beta1.Run{CommitHash: "hash2", Summary: "Plan: x to add, x to change, x to destroy.", Output: "tf plan output"}), runOutputMsg("default", "foo/three", "path/foo/three", &tfaplv1beta1.Run{CommitHash: "hash3", Summary: "Plan: x to add, x to change, x to destroy.", Output: "tf plan output"}), }, - nil, ) // mock github API Call adding new request info @@ -820,13 +791,12 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { } if diff := cmp.Diff(wantReq, gotReq, cmpIgnoreRandFields); diff != "" { - t.Errorf("checkPRCommits() mismatch (-want +got):\n%s", diff) + t.Errorf("checkPRCommentsForPlanRequests() mismatch (-want +got):\n%s", diff) } }) t.Run("plan run is requested for module in different Namespace or diff path", func(t *testing.T) { p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan foo/one", "@terraform-applier plan bar/two", @@ -834,7 +804,6 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { "@terraform-applier plan path/foo", "@terraform-applier plan foo/three", }, - nil, ) // Call Test function @@ -853,13 +822,11 @@ func Test_checkPRCommentsForPlanRequests(t *testing.T) { planner.github = testGithub p := generateMockPR(123, "ref1", - []string{"hash1", "hash2", "hash3"}, []string{ "@terraform-applier plan foo/one", "@terraform-applier plan two please", "@terraform-applier plan three", }, - nil, ) // Call Test function diff --git a/prplanner/type.go b/prplanner/type.go index a8c51d2..e4ef4c1 100644 --- a/prplanner/type.go +++ b/prplanner/type.go @@ -24,16 +24,14 @@ baseRepository { number headRefName isDraft +closed +merged +mergeCommit { + oid +} author { login } -commits(last: 20) { - nodes { - commit { - oid - } - } -} comments(last:50) { nodes { databaseId @@ -43,11 +41,6 @@ comments(last:50) { } } } -files(first: 100) { - nodes { - path - } -} ` type gitPRRequest struct { @@ -94,16 +87,13 @@ type pr struct { BaseRepository prRepo `json:"baseRepository"` HeadRefName string `json:"headRefName"` IsDraft bool `json:"isDraft"` + Closed bool `json:"closed"` + Merged bool `json:"merged"` + MergeCommit Commit `json:"mergeCommit"` Author author `json:"author"` - Commits struct { - Nodes []prCommit `json:"nodes"` - } `json:"commits"` - Comments struct { + Comments struct { Nodes []prComment `json:"nodes"` } `json:"comments"` - Files struct { - Nodes []prFiles `json:"nodes"` - } `json:"files"` } type author struct { @@ -118,10 +108,8 @@ type prRepo struct { } `json:"owner"` } -type prCommit struct { - Commit struct { - Oid string `json:"oid"` - } `json:"commit"` +type Commit struct { + Oid string `json:"oid"` } type prComment struct { @@ -130,10 +118,6 @@ type prComment struct { Body string `json:"body"` } -type prFiles struct { - Path string `json:"path"` -} - type GitHubWebhook struct { Action string `json:"action"` Number int `json:"number"` @@ -146,9 +130,24 @@ type GitHubWebhook struct { URL string `json:"html_url"` } `json:"repository"` + PullRequest struct { + Number int `json:"number"` + State string `json:"state"` + Draft bool `json:"draft"` + Merged bool `json:"merged"` + MergeCommitSHA string `json:"merge_commit_sha"` + Head struct { + Ref string `json:"ref"` + } `json:"head"` + Base struct { + Ref string `json:"ref"` + } `json:"base"` + } `json:"pull_request"` + // only for comments Issue struct { - Number int `json:"number"` + Number int `json:"number"` + Draft bool `json:"draft"` } `json:"issue"` Comment struct { diff --git a/prplanner/webhook.go b/prplanner/webhook.go index 45defb0..cd6a7b3 100644 --- a/prplanner/webhook.go +++ b/prplanner/webhook.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "io" "net/http" @@ -55,36 +56,37 @@ func (p *Planner) handleWebhook(w http.ResponseWriter, r *http.Request) { return } - // Verify event and action - if (event == "pull_request" && payload.Action == "opened") || - (event == "pull_request" && payload.Action == "synchronize") || - (event == "pull_request" && payload.Action == "reopened") { - - go p.processPRWebHookEvent(payload, payload.Number) - w.WriteHeader(http.StatusOK) - return - } + if event == "pull_request" { + if payload.PullRequest.Draft { + return + } - if event == "pull_request" && payload.Action == "closed" { - // TODO:clean-up: remove run from Redis - w.WriteHeader(http.StatusOK) - return + switch payload.Action { + case "opened", "synchronize", "reopened": + go p.processPRWebHookEvent(payload, payload.Number) + case "closed": + if !payload.PullRequest.Merged { + return + } + go p.processPRCloseEvent(payload) + } } - if event == "issue_comment" && payload.Action == "created" || - event == "issue_comment" && payload.Action == "edited" { + if event == "issue_comment" { + if payload.Issue.Draft { + return + } if isSelfComment(payload.Comment.Body) { - w.WriteHeader(http.StatusOK) return } - // we know the body, but we still need to know the module user is requesting - // plan run for belongs to this PR hence we need to do full reconcile of PR - go p.processPRWebHookEvent(payload, payload.Issue.Number) - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusOK) + switch payload.Action { + case "created", "edited": + // we know the body, but we still need to know the module user is requesting + // plan run for belongs to this PR hence we need to do full reconcile of PR + go p.processPRWebHookEvent(payload, payload.Issue.Number) + } + } } func (p *Planner) processPRWebHookEvent(event GitHubWebhook, prNumber int) { @@ -112,6 +114,59 @@ func (p *Planner) processPRWebHookEvent(event GitHubWebhook, prNumber int) { p.processPullRequest(ctx, pr, kubeModuleList) } +func (p *Planner) processPRCloseEvent(e GitHubWebhook) { + ctx := context.Background() + + if e.Action != "closed" || + e.PullRequest.Draft || + !e.PullRequest.Merged { + return + } + + err := p.Repos.Mirror(ctx, e.Repository.URL) + if err != nil { + p.Log.Error("unable to mirror repository", "url", e.Repository.URL, "pr", e.Number, "err", err) + return + } + + kubeModuleList := &tfaplv1beta1.ModuleList{} + if err := p.ClusterClt.List(ctx, kubeModuleList); err != nil { + p.Log.Error("error retrieving list of modules", "pr", e.Number, "error", err) + return + } + + // get list of commits and changed file for the merged commit + commitsInfo, err := p.Repos.MergeCommits(ctx, e.Repository.URL, e.PullRequest.MergeCommitSHA) + if err != nil { + p.Log.Error("unable to commit info", "repo", e.Repository.URL, "pr", e.Number, "mergeCommit", e.PullRequest.MergeCommitSHA, "error", err) + return + } + + for _, module := range kubeModuleList.Items { + // make sure there was actually plan runs on the PR + // this is to avoid uploading apply output on filtered PR + runs, _ := p.RedisClient.Runs(ctx, module.NamespacedName(), fmt.Sprintf("PR:%d:*", e.Number)) + if len(runs) == 0 { + continue + } + + for _, commit := range commitsInfo { + if !isModuleUpdated(&module, commit) { + continue + } + + err := p.RedisClient.SetPendingApplyUpload(ctx, module.NamespacedName(), commit.Hash, e.Number) + if err != nil { + p.Log.Error("unable to set pending apply upload", "module", module.NamespacedName(), "repo", e.Repository.URL, "pr", e.Number, "mergeCommit", e.PullRequest.MergeCommitSHA, "error", err) + break + } + // only process 1 latest commit /module + break + } + } + +} + func (p *Planner) isValidSignature(r *http.Request, message []byte, secret string) bool { gotSignature := r.Header.Get("X-Hub-Signature-256") diff --git a/runner/delegation.go b/runner/delegation.go index 5e265ca..88de8bc 100644 --- a/runner/delegation.go +++ b/runner/delegation.go @@ -104,7 +104,7 @@ func fetchEnvVars(ctx context.Context, client kubernetes.Interface, module *tfap return kvPairs, nil } -func (r *Runner) generateVaultAWSCreds(ctx context.Context, module *tfaplv1beta1.Module, jwt string, envs map[string]string) error { +func (r *Runner) generateVaultAWSCreds(module *tfaplv1beta1.Module, jwt string, envs map[string]string) error { creds, err := r.AWSSecretsEngineConfig.GenerateCreds(jwt, module.Spec.VaultRequests.AWS) if err != nil { diff --git a/runner/runner.go b/runner/runner.go index 359a570..6797ffd 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -256,7 +256,7 @@ func (r *Runner) process(run *tfaplv1beta1.Run, cancelChan <-chan struct{}, envs if module.Spec.VaultRequests != nil { if module.Spec.VaultRequests.AWS != nil { - err = r.generateVaultAWSCreds(ctx, module, jwt, envs) + err = r.generateVaultAWSCreds(module, jwt, envs) if err != nil { msg := fmt.Sprintf("unable to generate vault aws secrets: err:%s", err) log.Error(msg) @@ -342,7 +342,11 @@ func (r *Runner) runTF( // return if plan only mode if run.PlanOnly { - if err = r.SetRunFinishedStatus(run, module, tfaplv1beta1.ReasonDriftDetected, "PlanOnly/"+planStatus, r.Clock.Now()); err != nil { + reason := tfaplv1beta1.ReasonNoDriftDetected + if diffDetected { + reason = tfaplv1beta1.ReasonPlanOnlyDriftDetected + } + if err = r.SetRunFinishedStatus(run, module, reason, planStatus, r.Clock.Now()); err != nil { log.Error("unable to set drift status", "err", err) return false } @@ -416,7 +420,7 @@ func (r *Runner) SetRunFinishedStatus(run *tfaplv1beta1.Run, m *tfaplv1beta1.Mod m.Status.StateReason = reason m.Status.CurrentState = string(tfaplv1beta1.StatusOk) - if reason == tfaplv1beta1.ReasonDriftDetected { + if reason == tfaplv1beta1.ReasonPlanOnlyDriftDetected { m.Status.CurrentState = string(tfaplv1beta1.StatusDriftDetected) } diff --git a/sysutil/redis.go b/sysutil/redis.go index fe13cc4..a31663d 100644 --- a/sysutil/redis.go +++ b/sysutil/redis.go @@ -13,8 +13,9 @@ import ( ) var ( - PRKeyExpirationDur = 7 * 24 * time.Hour - ErrKeyNotFound = errors.New("key not found") + PRKeyExpirationDur = 7 * 24 * time.Hour + PRApplyUploadExpDur = time.Hour + ErrKeyNotFound = errors.New("key not found") ) //go:generate go run github.com/golang/mock/mockgen -package sysutil -destination redis_mock.go github.com/utilitywarehouse/terraform-applier/sysutil RedisInterface @@ -27,10 +28,14 @@ type RedisInterface interface { Run(ctx context.Context, key string) (*tfaplv1beta1.Run, error) Runs(ctx context.Context, module types.NamespacedName, keySuffix string) ([]*tfaplv1beta1.Run, error) GetCommitHash(ctx context.Context, key string) (string, error) + PendingApplyUploadPR(ctx context.Context, module types.NamespacedName, commit string) (string, error) SetDefaultLastRun(ctx context.Context, run *tfaplv1beta1.Run) error SetDefaultApply(ctx context.Context, run *tfaplv1beta1.Run) error SetPRRun(ctx context.Context, run *tfaplv1beta1.Run) error + SetPendingApplyUpload(ctx context.Context, module types.NamespacedName, commit string, prNumber int) error + + CleanupPRKeys(ctx context.Context, module types.NamespacedName, pr int, commit string) error } type Redis struct { @@ -53,6 +58,10 @@ func DefaultPRLastRunsKey(module types.NamespacedName, pr int, hash string) stri return fmt.Sprintf("%sPR:%d:%s", keyPrefix(module), pr, hash) } +func PendingApplyRunOutputUploadKey(module types.NamespacedName, hash string) string { + return fmt.Sprintf("pending:apply_upload:%shash:%s", keyPrefix(module), hash) +} + // DefaultLastRun will return last run result for the default branch func (r Redis) DefaultLastRun(ctx context.Context, module types.NamespacedName) (*tfaplv1beta1.Run, error) { return r.Run(ctx, defaultLastRunKey(module)) @@ -87,6 +96,21 @@ func (r Redis) Runs(ctx context.Context, module types.NamespacedName, patternSuf return runs, nil } +func (r Redis) CleanupPRKeys(ctx context.Context, module types.NamespacedName, pr int, commit string) error { + keys, err := r.Client.Keys(ctx, keyPrefix(module)+fmt.Sprintf("PR:%d:*", pr)).Result() + if err != nil && err != redis.Nil { + return fmt.Errorf("unable to get module pr keys err:%w", err) + } + + keys = append(keys, PendingApplyRunOutputUploadKey(module, commit)) + + return r.Client.Del(ctx, keys...).Err() +} + +func (r Redis) PendingApplyUploadPR(ctx context.Context, module types.NamespacedName, commit string) (string, error) { + return r.Client.Get(ctx, PendingApplyRunOutputUploadKey(module, commit)).Result() +} + // SetDefaultLastRun puts given run in to cache with no expiration func (r Redis) SetDefaultLastRun(ctx context.Context, run *tfaplv1beta1.Run) error { return r.setKV(ctx, defaultLastRunKey(run.Module), run, 0) @@ -102,6 +126,10 @@ func (r Redis) SetPRRun(ctx context.Context, run *tfaplv1beta1.Run) error { return r.setKV(ctx, DefaultPRLastRunsKey(run.Module, run.Request.PR.Number, run.CommitHash), run, PRKeyExpirationDur) } +func (r Redis) SetPendingApplyUpload(ctx context.Context, module types.NamespacedName, commit string, prNumber int) error { + return r.Client.Set(ctx, PendingApplyRunOutputUploadKey(module, commit), prNumber, PRApplyUploadExpDur).Err() +} + func (r Redis) setKV(ctx context.Context, key string, run *tfaplv1beta1.Run, exp time.Duration) error { str, err := json.Marshal(run) if err != nil { diff --git a/sysutil/redis_mock.go b/sysutil/redis_mock.go index 89c2076..c1836ef 100644 --- a/sysutil/redis_mock.go +++ b/sysutil/redis_mock.go @@ -36,6 +36,20 @@ func (m *MockRedisInterface) EXPECT() *MockRedisInterfaceMockRecorder { return m.recorder } +// CleanupPRKeys mocks base method. +func (m *MockRedisInterface) CleanupPRKeys(arg0 context.Context, arg1 types.NamespacedName, arg2 int, arg3 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CleanupPRKeys", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// CleanupPRKeys indicates an expected call of CleanupPRKeys. +func (mr *MockRedisInterfaceMockRecorder) CleanupPRKeys(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupPRKeys", reflect.TypeOf((*MockRedisInterface)(nil).CleanupPRKeys), arg0, arg1, arg2, arg3) +} + // DefaultApply mocks base method. func (m *MockRedisInterface) DefaultApply(arg0 context.Context, arg1 types.NamespacedName) (*v1beta1.Run, error) { m.ctrl.T.Helper() @@ -96,6 +110,21 @@ func (mr *MockRedisInterfaceMockRecorder) PRRun(arg0, arg1, arg2, arg3 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PRRun", reflect.TypeOf((*MockRedisInterface)(nil).PRRun), arg0, arg1, arg2, arg3) } +// PendingApplyUploadPR mocks base method. +func (m *MockRedisInterface) PendingApplyUploadPR(arg0 context.Context, arg1 types.NamespacedName, arg2 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PendingApplyUploadPR", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PendingApplyUploadPR indicates an expected call of PendingApplyUploadPR. +func (mr *MockRedisInterfaceMockRecorder) PendingApplyUploadPR(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingApplyUploadPR", reflect.TypeOf((*MockRedisInterface)(nil).PendingApplyUploadPR), arg0, arg1, arg2) +} + // Run mocks base method. func (m *MockRedisInterface) Run(arg0 context.Context, arg1 string) (*v1beta1.Run, error) { m.ctrl.T.Helper() @@ -167,3 +196,17 @@ func (mr *MockRedisInterfaceMockRecorder) SetPRRun(arg0, arg1 interface{}) *gomo mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPRRun", reflect.TypeOf((*MockRedisInterface)(nil).SetPRRun), arg0, arg1) } + +// SetPendingApplyUpload mocks base method. +func (m *MockRedisInterface) SetPendingApplyUpload(arg0 context.Context, arg1 types.NamespacedName, arg2 string, arg3 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetPendingApplyUpload", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetPendingApplyUpload indicates an expected call of SetPendingApplyUpload. +func (mr *MockRedisInterfaceMockRecorder) SetPendingApplyUpload(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPendingApplyUpload", reflect.TypeOf((*MockRedisInterface)(nil).SetPendingApplyUpload), arg0, arg1, arg2, arg3) +}