From e92fa3910ef4eb335c8d2551a5343a0eada0b217 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Mon, 14 Jun 2021 16:58:19 -0600 Subject: [PATCH 01/20] add gitlab api facade class. add integration test. --- clients/gitlab/api/client.go | 33 ++++ clients/gitlab/api/client_test.go | 58 +++++++ clients/http_utils.go | 6 +- clients/http_utils_test.go | 2 +- controllers/utils_test.go | 2 +- go.mod | 3 +- go.sum | 157 ++++++++++++++++++ .../gitlab/api/gitlab_api_test.go | 21 +++ 8 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 clients/gitlab/api/client.go create mode 100644 clients/gitlab/api/client_test.go create mode 100644 integration-tests/gitlab/api/gitlab_api_test.go diff --git a/clients/gitlab/api/client.go b/clients/gitlab/api/client.go new file mode 100644 index 0000000..b6f7b46 --- /dev/null +++ b/clients/gitlab/api/client.go @@ -0,0 +1,33 @@ +package api + +// Facade built around go-gitlab + +import ( + "github.com/xanzy/go-gitlab" + "log" +) + +type Client struct { + client *gitlab.Client +} + +func NewClient(apiURL string, token string) Client { + // const apiURLPrefix = "/api/v4" + // apiUrl https://code.il2.dso.mil/api/v4 + git, err := gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) + if err != nil { + log.Fatalf("Failed to create client: %v", err) + } + + return Client{ + client: git, + } +} + +func (r Client) GetUsers() ([]*gitlab.User, error) { + users, _, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{}) + if err != nil { + return nil, err + } + return users, nil +} diff --git a/clients/gitlab/api/client_test.go b/clients/gitlab/api/client_test.go new file mode 100644 index 0000000..a0e6ab2 --- /dev/null +++ b/clients/gitlab/api/client_test.go @@ -0,0 +1,58 @@ +package api + +import ( + "reflect" + "testing" + + "github.com/xanzy/go-gitlab" +) + +func TestNewClient(t *testing.T) { + type args struct { + apiURL string + token string + } + tests := []struct { + name string + args args + want Client + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewClient(tt.args.apiURL, tt.args.token); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewClient() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClient_getUsers(t *testing.T) { + type fields struct { + client *gitlab.Client + } + tests := []struct { + name string + fields fields + want []*gitlab.User + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + } + got, err := r.GetUsers() + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetUsers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.getUsers() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/clients/http_utils.go b/clients/http_utils.go index 876afe4..e22ca4c 100644 --- a/clients/http_utils.go +++ b/clients/http_utils.go @@ -42,7 +42,7 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu requestBodyBytes, ) if err != nil { - return nil,err + return nil, err } req.Header.Set("Content-type", "application/json") if authHeaderValue != "" { @@ -53,7 +53,7 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu var resp *http.Response resp, err = client.Do(req) if err != nil { - return nil,err + return nil, err } //if resp.StatusCode != successStatusCode { // err = errors.New("unexpected http status code: " + resp.Status) @@ -65,7 +65,7 @@ func callAPI(client *http.Client, method string, endpoint string, authHeaderValu responseBody, err = ioutil.ReadAll(resp.Body) if err != nil { log.Println("error reading response body: ", err) - return nil,err + return nil, err } return responseBody, nil } diff --git a/clients/http_utils_test.go b/clients/http_utils_test.go index 695bc50..eca73a5 100644 --- a/clients/http_utils_test.go +++ b/clients/http_utils_test.go @@ -186,4 +186,4 @@ func TestGetPostPut(t *testing.T) { }) } httpmock.DeactivateAndReset() -} \ No newline at end of file +} diff --git a/controllers/utils_test.go b/controllers/utils_test.go index 8af6798..2c67e76 100644 --- a/controllers/utils_test.go +++ b/controllers/utils_test.go @@ -1,6 +1,6 @@ package controllers -import "testing" +import "testing" import "os" func TestGetEnvOrExit(t *testing.T) { diff --git a/go.mod b/go.mod index a14e729..643ae7a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,8 @@ require ( github.com/jarcoal/httpmock v1.0.8 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + github.com/xanzy/go-gitlab v0.50.0 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.2 diff --git a/go.sum b/go.sum index 7b8ed91..b7c5f35 100644 --- a/go.sum +++ b/go.sum @@ -7,11 +7,17 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0= @@ -32,14 +38,20 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= @@ -47,69 +59,103 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= @@ -121,51 +167,64 @@ github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70t github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY= github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5 h1:QhCBKRYqZR+SKo4gl1lPhPahope8/RLt6EVgY8X80w0= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -174,6 +233,7 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -190,6 +250,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -198,80 +259,116 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc h1:DLpL8pWq0v4JYoRpEhDfsJhhJyGKCcQM2WPW2TJs31c= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0 h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -280,13 +377,18 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -303,8 +405,11 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -312,6 +417,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -333,58 +439,87 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= +github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5 h1:Gqga3zA9tdAcfqobUGjSoCob5L3f8Dt5EuOp3ihNZko= go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -418,8 +553,10 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299 h1:zQpM52jfKHG6II1ISZY1ZcpygvuSFZpLwfluuF89XOg= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -431,6 +568,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -467,6 +605,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= @@ -477,6 +616,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -567,8 +707,10 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -584,6 +726,7 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -591,6 +734,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -610,13 +754,19 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -631,7 +781,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -645,13 +797,16 @@ k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJ k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg= k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apiserver v0.19.2 h1:xq2dXAzsAoHv7S4Xc/p7PKhiowdHV/PgdePWo3MxIYM= k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA= k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/code-generator v0.19.2 h1:7uaWJll6fyCPj2j3sfNN1AiY2gZU1VFN2dFR2uoxGWI= k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= k8s.io/component-base v0.19.2 h1:jW5Y9RcZTb79liEhW3XDVTW7MuvEGP0tQZnfSX6/+gs= k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14 h1:t4L10Qfx/p7ASH3gXCdIUtPbbIuegCoUJf3TMSFekjw= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= @@ -661,7 +816,9 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200912215256-4140de9c8800 h1:9ZNvfPvVIEsp/T1ez4GQuzCcCTEQWhovSofhqR73A6g= k8s.io/utils v0.0.0-20200912215256-4140de9c8800/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9 h1:rusRLrDhjBp6aYtl9sGEvQJr6faoHoDLd0YcUBTZguI= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= sigs.k8s.io/controller-runtime v0.7.2 h1:gD2JZp0bBLLuvSRYVNvox+bRCz1UUUxKDjPUCb56Ukk= sigs.k8s.io/controller-runtime v0.7.2/go.mod h1:pJ3YBrJiAqMAZKi6UVGuE98ZrroV1p+pIhoHsMm9wdU= diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go new file mode 100644 index 0000000..9cf9259 --- /dev/null +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -0,0 +1,21 @@ +// +build integration + +package integration + + +import ( + "testing" + gitlabapi "valkyrie.dso.mil/valkyrie-api/clients/gitlab/api" +) + +func TestClient_getUsers(t *testing.T) { + var r = gitlabapi.NewClient("","") + t.Run("test", func(t *testing.T) { + got, err := r.GetUsers() + if (err != nil) { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + t.Logf("GetUsers %d", len(got)) + }) +} -- GitLab From a169d2c3173d67f10e3fd3b10370b236e3f93dbd Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Tue, 15 Jun 2021 09:07:55 -0600 Subject: [PATCH 02/20] add readme --- README.md | 17 +++++++++++++++++ .../gitlab/api/gitlab_api_test.go | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef856e3 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +## + + + + + + +### Integration testing +- to run integration tests +- tag using test file with a build tag at the beginning of the file +``` +// +build integration + +package integration +``` +- run integration tests as follows + - `go test -tags "integration" ./integration-tests/...` \ No newline at end of file diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 9cf9259..581ca7e 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -4,14 +4,29 @@ package integration import ( + "os" + "errors" "testing" gitlabapi "valkyrie.dso.mil/valkyrie-api/clients/gitlab/api" ) +func getClient() (gitlabapi.Client, error) { + gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") + if (!ok) { + return gitlabapi.Client{}, errors.New("env variable GITLAB_API_URL undefinded") + } + gitlabAPIToken, _ := os.LookupEnv("GITLAB_API_TOKEN") + var client = gitlabapi.NewClient(gitlabAPIURL,gitlabAPIToken) + return client, nil +} func TestClient_getUsers(t *testing.T) { - var r = gitlabapi.NewClient("","") t.Run("test", func(t *testing.T) { - got, err := r.GetUsers() + c, err := getClient() + if (err != nil) { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + got, err := c.GetUsers() if (err != nil) { t.Errorf("Client.GetUsers() error = %v", err) return -- GitLab From 66b93cbfe0f1a86821d21ef1ed8d749c5d2c83c7 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Tue, 15 Jun 2021 12:47:41 -0600 Subject: [PATCH 03/20] add comments --- clients/gitlab/api/client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clients/gitlab/api/client.go b/clients/gitlab/api/client.go index b6f7b46..7dd0638 100644 --- a/clients/gitlab/api/client.go +++ b/clients/gitlab/api/client.go @@ -7,10 +7,12 @@ import ( "log" ) +// Client- type Client struct { client *gitlab.Client } +// NewClient - func NewClient(apiURL string, token string) Client { // const apiURLPrefix = "/api/v4" // apiUrl https://code.il2.dso.mil/api/v4 @@ -24,6 +26,7 @@ func NewClient(apiURL string, token string) Client { } } +// GetUsers - func (r Client) GetUsers() ([]*gitlab.User, error) { users, _, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{}) if err != nil { -- GitLab From 1a508efddced1a14dd234eb2332f539162acd0a4 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Tue, 15 Jun 2021 13:43:02 -0600 Subject: [PATCH 04/20] update gitlab client --- clients/gitlab/{api => }/client.go | 4 ++-- clients/gitlab/{api => }/client_test.go | 2 +- integration-tests/gitlab/api/gitlab_api_test.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename clients/gitlab/{api => }/client.go (95%) rename clients/gitlab/{api => }/client_test.go (98%) diff --git a/clients/gitlab/api/client.go b/clients/gitlab/client.go similarity index 95% rename from clients/gitlab/api/client.go rename to clients/gitlab/client.go index 7dd0638..3ce6318 100644 --- a/clients/gitlab/api/client.go +++ b/clients/gitlab/client.go @@ -1,4 +1,4 @@ -package api +package gitlab // Facade built around go-gitlab @@ -7,7 +7,7 @@ import ( "log" ) -// Client- +// Client - type Client struct { client *gitlab.Client } diff --git a/clients/gitlab/api/client_test.go b/clients/gitlab/client_test.go similarity index 98% rename from clients/gitlab/api/client_test.go rename to clients/gitlab/client_test.go index a0e6ab2..b7c23cf 100644 --- a/clients/gitlab/api/client_test.go +++ b/clients/gitlab/client_test.go @@ -1,4 +1,4 @@ -package api +package gitlab import ( "reflect" diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 581ca7e..3a3416a 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -7,16 +7,16 @@ import ( "os" "errors" "testing" - gitlabapi "valkyrie.dso.mil/valkyrie-api/clients/gitlab/api" + gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) -func getClient() (gitlabapi.Client, error) { +func getClient() (gitlab.Client, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") if (!ok) { - return gitlabapi.Client{}, errors.New("env variable GITLAB_API_URL undefinded") + return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") } gitlabAPIToken, _ := os.LookupEnv("GITLAB_API_TOKEN") - var client = gitlabapi.NewClient(gitlabAPIURL,gitlabAPIToken) + var client = gitlab.NewClient(gitlabAPIURL,gitlabAPIToken) return client, nil } func TestClient_getUsers(t *testing.T) { -- GitLab From 49f33415ca3a241918f2917bfd30c1d6526310ce Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Tue, 15 Jun 2021 16:34:34 -0600 Subject: [PATCH 05/20] gitlab client facade --- README.md | 2 +- clients/gitlab/client.go | 41 +++++++++- clients/gitlab/client_test.go | 4 +- go.sum | 1 + .../gitlab/api/gitlab_api_test.go | 80 +++++++++++++++++-- 5 files changed, 113 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ef856e3..8a8ecdf 100644 --- a/README.md +++ b/README.md @@ -14,4 +14,4 @@ package integration ``` - run integration tests as follows - - `go test -tags "integration" ./integration-tests/...` \ No newline at end of file + - `go test -v -tags "integration" ./integration-tests/...` \ No newline at end of file diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 3ce6318..854ab8c 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -1,19 +1,27 @@ package gitlab // Facade built around go-gitlab +// https://github.com/xanzy/go-gitlab +// https://docs.gitlab.com/ee/api/api_resources.html +// https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation import ( "github.com/xanzy/go-gitlab" "log" ) +type GitlabClientI interface { + NewClient(apiURL string, token string) GitlabClient + GetUsers() ([]*gitlab.User, error) +} + // Client - -type Client struct { +type GitlabClient struct { client *gitlab.Client } // NewClient - -func NewClient(apiURL string, token string) Client { +func NewClient(apiURL string, token string) GitlabClient { // const apiURLPrefix = "/api/v4" // apiUrl https://code.il2.dso.mil/api/v4 git, err := gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) @@ -21,16 +29,41 @@ func NewClient(apiURL string, token string) Client { log.Fatalf("Failed to create client: %v", err) } - return Client{ + return GitlabClient{ client: git, } } // GetUsers - -func (r Client) GetUsers() ([]*gitlab.User, error) { +func (r GitlabClient) GetUsers() ([]*gitlab.User, error) { users, _, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{}) if err != nil { return nil, err } return users, nil } + +// AddUser - +func (r GitlabClient) AddUser(user *gitlab.User, password string) (*gitlab.User, error, int) { + var opts = gitlab.CreateUserOptions{Username: &user.Username, + Email: &user.Email, Name: &user.Name, Password: &password} + newUser, res, err := r.client.Users.CreateUser(&opts) + if err != nil { + return nil, err, res.StatusCode + } + return newUser, nil, res.StatusCode +} + +// DeleteUser - +func (r GitlabClient) DeleteUser(username string) (error, int) { + var opts = gitlab.ListUsersOptions{Username: &username} + users, lres, err := r.client.Users.ListUsers(&opts) + if err != nil { + return err, lres.StatusCode + } + res, err := r.client.Users.DeleteUser(users[0].ID) + if err != nil { + return err, res.StatusCode + } + return nil, res.StatusCode +} diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go index b7c23cf..b24485c 100644 --- a/clients/gitlab/client_test.go +++ b/clients/gitlab/client_test.go @@ -15,7 +15,7 @@ func TestNewClient(t *testing.T) { tests := []struct { name string args args - want Client + want GitlabClient }{ // TODO: Add test cases. } @@ -42,7 +42,7 @@ func TestClient_getUsers(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - r := Client{ + r := GitlabClient{ client: tt.fields.client, } got, err := r.GetUsers() diff --git a/go.sum b/go.sum index 8aa0102..1b1e377 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,7 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 3a3416a..98a0fae 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -2,35 +2,99 @@ package integration - import ( - "os" "errors" + "fmt" + "os" "testing" + + gogitlab "github.com/xanzy/go-gitlab" gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) -func getClient() (gitlab.Client, error) { +func getClient() (gitlab.GitlabClient, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") - if (!ok) { - return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") + if !ok { + return gitlab.GitlabClient{}, errors.New("env variable GITLAB_API_URL undefinded") } gitlabAPIToken, _ := os.LookupEnv("GITLAB_API_TOKEN") - var client = gitlab.NewClient(gitlabAPIURL,gitlabAPIToken) + var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken) return client, nil } func TestClient_getUsers(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() - if (err != nil) { + if err != nil { t.Errorf("Client.GetUsers() error = %v", err) return } got, err := c.GetUsers() - if (err != nil) { + if err != nil { t.Errorf("Client.GetUsers() error = %v", err) return } t.Logf("GetUsers %d", len(got)) }) } + +func TestClient_addUsers(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + + for i := 1; i <= 200; i++ { + username := "testuser" + fmt.Sprintf("%d",i) + name := "test user name " + fmt.Sprintf("%d",i) + email := username + "@email.com" + userObj := gogitlab.User{Username: username, Email: email, Name: name} + got, err, statusCode := c.AddUser(&userObj, "password") + + if err != nil { + t.Errorf("Client.AddUser() error = %v %d", err, statusCode) + } else { + t.Logf("AddUser %s %d",got.Username,statusCode) + } + } + + }) +} + +func TestClient_getUsersLarge(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + got, err := c.GetUsers() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + t.Logf("GetUsers %d", len(got)) + }) +} +func TestClient_deleteUsers(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + + for i := 1; i <= 200; i++ { + username := "testuser" + fmt.Sprintf("%d",i) + err, statusCode := c.DeleteUser(username) + + if err != nil { + t.Errorf("Client.DeleteUser() error = %v %d", err, statusCode) + } else { + t.Logf("Deleted user %s %d",username,statusCode) + } + } + + }) +} \ No newline at end of file -- GitLab From 60000e87dc62925c72e6a7b02c82693611c738f7 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Tue, 15 Jun 2021 21:38:03 -0600 Subject: [PATCH 06/20] add gitlab group access --- clients/gitlab/client.go | 121 ++++++++++++++--- clients/gitlab/client_test.go | 58 --------- .../gitlab/api/gitlab_api_adhoc_test.go | 122 ++++++++++++++++++ .../gitlab/api/gitlab_api_test.go | 8 +- 4 files changed, 227 insertions(+), 82 deletions(-) delete mode 100644 clients/gitlab/client_test.go create mode 100644 integration-tests/gitlab/api/gitlab_api_adhoc_test.go diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 854ab8c..794a331 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -6,22 +6,25 @@ package gitlab // https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation import ( - "github.com/xanzy/go-gitlab" + gitlab "github.com/xanzy/go-gitlab" "log" ) -type GitlabClientI interface { - NewClient(apiURL string, token string) GitlabClient +type ClientI interface { + NewClient(apiURL string, token string) Client GetUsers() ([]*gitlab.User, error) + DeleteUser(username string) (int, error) } +const itemsPerPage = 50 + // Client - -type GitlabClient struct { +type Client struct { client *gitlab.Client } // NewClient - -func NewClient(apiURL string, token string) GitlabClient { +func NewClient(apiURL string, token string) Client { // const apiURLPrefix = "/api/v4" // apiUrl https://code.il2.dso.mil/api/v4 git, err := gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) @@ -29,41 +32,119 @@ func NewClient(apiURL string, token string) GitlabClient { log.Fatalf("Failed to create client: %v", err) } - return GitlabClient{ + return Client{ client: git, } } // GetUsers - -func (r GitlabClient) GetUsers() ([]*gitlab.User, error) { - users, _, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{}) - if err != nil { - return nil, err +func (r Client) GetUsers() ([]*gitlab.User, error) { + listOptions := gitlab.ListOptions{ + Page: 1, + PerPage: itemsPerPage, } - return users, nil + + userList := []*gitlab.User{} + var more = true + for more { + users, res, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{ListOptions: listOptions}) + if err != nil { + return nil, err + } + for x := 0; x < len(users); x++ { + userList = append(userList, users[x]) + } + + if res.NextPage > 0 { + listOptions.Page = listOptions.Page + 1 + } else { + more = false + } + + } + return userList, nil } // AddUser - -func (r GitlabClient) AddUser(user *gitlab.User, password string) (*gitlab.User, error, int) { - var opts = gitlab.CreateUserOptions{Username: &user.Username, - Email: &user.Email, Name: &user.Name, Password: &password} +func (r Client) AddUser(user *gitlab.User, password string) (*gitlab.User, int, error) { + var opts = gitlab.CreateUserOptions{ + Username: &user.Username, + Email: &user.Email, + Name: &user.Name, + Password: &password, + } newUser, res, err := r.client.Users.CreateUser(&opts) if err != nil { - return nil, err, res.StatusCode + return nil, res.StatusCode, err } - return newUser, nil, res.StatusCode + return newUser, res.StatusCode, nil } // DeleteUser - -func (r GitlabClient) DeleteUser(username string) (error, int) { +func (r Client) DeleteUser(username string) (int, error) { var opts = gitlab.ListUsersOptions{Username: &username} users, lres, err := r.client.Users.ListUsers(&opts) if err != nil { - return err, lres.StatusCode + return lres.StatusCode, err } res, err := r.client.Users.DeleteUser(users[0].ID) if err != nil { - return err, res.StatusCode + return res.StatusCode, err + } + return res.StatusCode, nil +} + +// GetGroups - +func (r Client) GetGroups() ([]*gitlab.Group, error) { + listOptions := gitlab.ListOptions{ + Page: 1, + PerPage: itemsPerPage, + } + + groupList := []*gitlab.Group{} + var more = true + for more { + users, res, err := r.client.Groups.ListGroups(&gitlab.ListGroupsOptions{ListOptions: listOptions}) + if err != nil { + return nil, err + } + for x := 0; x < len(users); x++ { + groupList = append(groupList, users[x]) + } + + if res.NextPage > 0 { + listOptions.Page = listOptions.Page + 1 + } else { + more = false + } + + } + return groupList, nil +} + +// AddGroup - +func (r Client) AddGroup(group *gitlab.Group) (*gitlab.Group, int, error) { + var visibility = gitlab.PrivateVisibility + var opts = gitlab.CreateGroupOptions{Name: &group.Name, + Path: &group.Path, Description: &group.Description, + Visibility: &visibility} + newGroup, res, err := r.client.Groups.CreateGroup(&opts) + if err != nil { + return nil, res.StatusCode, err + } + return newGroup, res.StatusCode, nil +} + +// DeleteUser - +func (r Client) DeleteGroup(path string) (int, error) { + var opts = gitlab.ListGroupsOptions{Search: &path} + groups, lres, err := r.client.Groups.ListGroups(&opts) + if err != nil { + return lres.StatusCode, err + } + res, err := r.client.Groups.DeleteGroup(groups[0].ID) + if err != nil { + return res.StatusCode, err } - return nil, res.StatusCode + return res.StatusCode, nil } diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go deleted file mode 100644 index b24485c..0000000 --- a/clients/gitlab/client_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package gitlab - -import ( - "reflect" - "testing" - - "github.com/xanzy/go-gitlab" -) - -func TestNewClient(t *testing.T) { - type args struct { - apiURL string - token string - } - tests := []struct { - name string - args args - want GitlabClient - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewClient(tt.args.apiURL, tt.args.token); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewClient() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestClient_getUsers(t *testing.T) { - type fields struct { - client *gitlab.Client - } - tests := []struct { - name string - fields fields - want []*gitlab.User - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := GitlabClient{ - client: tt.fields.client, - } - got, err := r.GetUsers() - if (err != nil) != tt.wantErr { - t.Errorf("Client.GetUsers() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Client.getUsers() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go new file mode 100644 index 0000000..da2a197 --- /dev/null +++ b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go @@ -0,0 +1,122 @@ +// +build integration + +package integration + +import ( + "errors" + // "fmt" + "os" + "testing" + + // gogitlab "github.com/xanzy/go-gitlab" + gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" +) + +func getClient() (gitlab.Client, error) { + gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") + if !ok { + return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") + } + gitlabAPIToken, _ := os.LookupEnv("GITLAB_API_TOKEN") + var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken) + return client, nil +} +func TestClient_getUsers(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + got, err := c.GetUsers() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + t.Logf("GetUsers %d", len(got)) + for x:=0;x Date: Wed, 16 Jun 2021 09:02:56 -0600 Subject: [PATCH 07/20] add group methods --- .../gitlab/api/gitlab_api_adhoc_test.go | 89 +++++-------------- .../gitlab/api/gitlab_api_test.go | 48 +++++++++- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go index da2a197..0fa966f 100644 --- a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go +++ b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go @@ -3,15 +3,14 @@ package integration import ( + "testing" "errors" - // "fmt" "os" - "testing" - - // gogitlab "github.com/xanzy/go-gitlab" + gogitlab "github.com/xanzy/go-gitlab" gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) +// getClient - func getClient() (gitlab.Client, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") if !ok { @@ -21,7 +20,7 @@ func getClient() (gitlab.Client, error) { var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken) return client, nil } -func TestClient_getUsers(t *testing.T) { +func TestClient_AdHoc_getUsers(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() if err != nil { @@ -40,7 +39,7 @@ func TestClient_getUsers(t *testing.T) { }) } -func TestClient_getGroupss(t *testing.T) { +func TestClient_AdHoc_getGroups(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() if err != nil { @@ -59,64 +58,20 @@ func TestClient_getGroupss(t *testing.T) { }) } -// func TestClient_addUsers(t *testing.T) { -// t.Run("test", func(t *testing.T) { -// c, err := getClient() -// if err != nil { -// t.Errorf("Client.GetUsers() error = %v", err) -// return -// } - -// for i := 1; i <= 200; i++ { -// username := "testuser" + fmt.Sprintf("%d",i) -// name := "test user name " + fmt.Sprintf("%d",i) -// email := username + "@email.com" -// userObj := gogitlab.User{Username: username, Email: email, Name: name} -// got, statusCode, err := c.AddUser(&userObj, "password") - -// if err != nil { -// t.Errorf("Client.AddUser() error = %v %d", err, statusCode) -// } else { -// t.Logf("AddUser %s %d",got.Username,statusCode) -// } -// } - -// }) -// } - -// func TestClient_getUsersLarge(t *testing.T) { -// t.Run("test", func(t *testing.T) { -// c, err := getClient() -// if err != nil { -// t.Errorf("Client.GetUsers() error = %v", err) -// return -// } -// got, err := c.GetUsers() -// if err != nil { -// t.Errorf("Client.GetUsers() error = %v", err) -// return -// } -// t.Logf("GetUsers %d", len(got)) -// }) -// } -// func TestClient_deleteUsers(t *testing.T) { -// t.Run("test", func(t *testing.T) { -// c, err := getClient() -// if err != nil { -// t.Errorf("Client.GetUsers() error = %v", err) -// return -// } - -// for i := 1; i <= 200; i++ { -// username := "testuser" + fmt.Sprintf("%d",i) -// statusCode ,err := c.DeleteUser(username) - -// if err != nil { -// t.Errorf("Client.DeleteUser() error = %v %d", err, statusCode) -// } else { -// t.Logf("Deleted user %s %d",username,statusCode) -// } -// } - -// }) -// } \ No newline at end of file +func TestClient_AdHoc_addGroup(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.getClient() error = %v", err) + return + } + group := gogitlab.Group{Name: "test group 1", Description: "Test group desc", Path: "test-group", FullPath: "top-group/test-group"} + got, statusCode, err := c.AddGroup(&group) + if err != nil { + t.Errorf("Client.GetGroups() error = %v", err) + return + } else { + t.Logf("AddGroup %s statusCdde %d", got.FullName, statusCode) + } + }) +} diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 9b820a5..4fc3579 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -12,6 +12,7 @@ import ( gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) +// getClient - func getClient() (gitlab.Client, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") if !ok { @@ -21,6 +22,8 @@ func getClient() (gitlab.Client, error) { var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken) return client, nil } + +//TestClient_getUsers - func TestClient_getUsers(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() @@ -36,7 +39,7 @@ func TestClient_getUsers(t *testing.T) { t.Logf("GetUsers %d", len(got)) }) } - +// TestClient_addUsers - func TestClient_addUsers(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() @@ -96,5 +99,48 @@ func TestClient_deleteUsers(t *testing.T) { } } + }) +} + + +func TestClient_getGroups(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.GetGroups() error = %v", err) + return + } + got, err := c.GetGroups() + if err != nil { + t.Errorf("Client.GetGroups() error = %v", err) + return + } + t.Logf("GetGroups number of groups %d", len(got)) + }) +} + + +func TestClient_addGroups(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + + for i := 1; i <= 200; i++ { + username := "testuser" + fmt.Sprintf("%d",i) + name := "test user name " + fmt.Sprintf("%d",i) + email := username + "@email.com" + userObj := gogitlab.User{Username: username, Email: email, Name: name} + got, statusCode, err := c.AddUser(&userObj, "password") + + if err != nil { + t.Errorf("Client.AddUser() error = %v %d", err, statusCode) + } else { + t.Logf("AddUser %s %d",got.Username,statusCode) + } + } + }) } \ No newline at end of file -- GitLab From ae17f7872fafc7d8f8e5bee74c33692de9a32073 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Wed, 16 Jun 2021 09:53:01 -0600 Subject: [PATCH 08/20] group test --- integration-tests/gitlab/api/gitlab_api_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 4fc3579..0ea2dee 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -129,16 +129,16 @@ func TestClient_addGroups(t *testing.T) { } for i := 1; i <= 200; i++ { - username := "testuser" + fmt.Sprintf("%d",i) - name := "test user name " + fmt.Sprintf("%d",i) - email := username + "@email.com" - userObj := gogitlab.User{Username: username, Email: email, Name: name} - got, statusCode, err := c.AddUser(&userObj, "password") + path := "test-group-" + fmt.Sprintf("%d",i) + name := "test grpup " + fmt.Sprintf("%d",i) + + groupObj := gogitlab.Group{Name: name} + got, statusCode, err := c.AddGroup(&groupObj) if err != nil { - t.Errorf("Client.AddUser() error = %v %d", err, statusCode) + t.Errorf("Client.AddGroup() error = %v %d", err, statusCode) } else { - t.Logf("AddUser %s %d",got.Username,statusCode) + t.Logf("AddGroup %s %d",got.,statusCode) } } -- GitLab From 6de1e0440679989b4bc1485a596f81b4108984f6 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Wed, 16 Jun 2021 09:54:55 -0600 Subject: [PATCH 09/20] sytnax fixes from make fmt --- apis/gitlab/v1alpha1/group_types_test.go | 4 +- controllers/gitlab/gitlab_client.go | 2 +- controllers/gitlab/group_controller.go | 46 +- controllers/gitlab/group_controller_test.go | 377 +++++----- controllers/gitlab/mocks_test.go | 793 ++++++++++---------- 5 files changed, 607 insertions(+), 615 deletions(-) diff --git a/apis/gitlab/v1alpha1/group_types_test.go b/apis/gitlab/v1alpha1/group_types_test.go index 03871c4..bf3d636 100644 --- a/apis/gitlab/v1alpha1/group_types_test.go +++ b/apis/gitlab/v1alpha1/group_types_test.go @@ -70,13 +70,13 @@ func initVarsGroup() testVarsGroup { testVars.testObjectSpec1 = GroupSpec{ Name: "testGroup1", GitlabCredentialsName: "nameOfTheCredentials", - Description: "testDescription1", + Description: "testDescription1", } testVars.testObjectSpec2 = GroupSpec{ Name: "testGroup2", GitlabCredentialsName: "nameOfCredentials2", ProjectSpecs: nil, - Description: "testDescription2", + Description: "testDescription2", } id1 := int64(1) diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index bed2b43..fab4741 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -23,4 +23,4 @@ func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) // ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { return c.client.Groups.ListGroups(opt, options...) -} \ No newline at end of file +} diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index dd26e43..081e3ee 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -33,18 +33,18 @@ import ( // Errors const ( - errorUnableToFetchGroup = "unable to fetch group" - errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" - errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" - errorUnableToCreateGitlabClient = "unable to create gitlab client" - errorWhileSearchingGroups = "Error while searching groups." - errorWhileCreatingGitlabGroup = "Error while creating GitLab group." - errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." - errorUnableToUpdateStatus = "Unable to update status." - errorUnableToProcessProjects = "Unable to process projects" - errorTryingToCreateProject = "Error trying to create project" - errorLookingUpProject = "Error looking up project" - errorUpdatingProject = "Error updating project" + errorUnableToFetchGroup = "unable to fetch group" + errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" + errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" + errorUnableToCreateGitlabClient = "unable to create gitlab client" + errorWhileSearchingGroups = "Error while searching groups." + errorWhileCreatingGitlabGroup = "Error while creating GitLab group." + errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." + errorUnableToUpdateStatus = "Unable to update status." + errorUnableToProcessProjects = "Unable to process projects" + errorTryingToCreateProject = "Error trying to create project" + errorLookingUpProject = "Error looking up project" + errorUpdatingProject = "Error updating project" ) // Group Status @@ -57,7 +57,6 @@ const ( const valkyrie = "valkyrie" - // GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab // with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. type GroupReconciler struct { @@ -143,7 +142,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if !found { if gitlabGroup, _, err = r.createGroup(gitlabClient, group); err != nil { - + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) return ctrl.Result{Requeue: true}, err } @@ -155,9 +154,9 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - + log.Error(err, errorUnableToUpdateStatus, "group", group) - return ctrl.Result{ Requeue: true }, err + return ctrl.Result{Requeue: true}, err } if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { @@ -193,7 +192,7 @@ func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlab func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { opt := gitlab.CreateGroupOptions{ - Name: &group.Spec.Name, + Name: &group.Spec.Name, Description: &group.Spec.Description, } gitlabGroup, response, err := client.Groups.CreateGroup(&opt) @@ -209,7 +208,7 @@ func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alph func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { opt := gitlab.UpdateGroupOptions{ - Name: &group.Spec.Name, + Name: &group.Spec.Name, Description: &group.Spec.Description, } gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) @@ -221,12 +220,11 @@ func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alph return gitlabGroup, response, err } - func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, newProject bool, specs []gitlabv1alpha1.ProjectSpec) error { for _, projectSpec := range specs { if newProject { if err := r.createProject(ctx, group, projectSpec); err != nil { - + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) return err } @@ -235,8 +233,8 @@ func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1al r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) return err } else if foundProject { - if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { - + if _, err := r.updateProject(ctx, group, projectSpec, project); err != nil { + r.Log.Error(err, errorUpdatingProject, "project", project) } } @@ -278,7 +276,7 @@ func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alph }, }, }, - Spec: gitlabv1alpha1.ProjectSpec{ + Spec: gitlabv1alpha1.ProjectSpec{ Name: spec.Name, Path: spec.Path, ImpactLevel: spec.ImpactLevel, @@ -322,4 +320,4 @@ func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&gitlabv1alpha1.Group{}). Owns(&gitlabv1alpha1.Project{}). Complete(r) -} \ No newline at end of file +} diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 86c076b..c9ca793 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -13,9 +13,7 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) - -var _ = -Describe("Reconcile", func() { +var _ = Describe("Reconcile", func() { builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) scheme, _ := builder.Build() When("it looks up the API Object", func() { @@ -48,202 +46,199 @@ Describe("Reconcile", func() { }) }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - clientMock.GetFunction = nil - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) - }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) - Expect(err).ToNot(BeNil()) - }) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) }) }) }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - Status: gitlabv1alpha1.GroupStatus{}, - } + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) }) }) }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { + Context("can't create client", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) }) }) - }) \ No newline at end of file + }) +}) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index 40340bb..83fd1c0 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -1,397 +1,396 @@ -package gitlab - -import ( - "context" - "github.com/go-logr/logr" - "github.com/jinzhu/copier" - "github.com/xanzy/go-gitlab" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "time" - "valkyrie.dso.mil/valkyrie-api/controllers" -) - -type MockClient struct { - GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error - GetCalled bool - expectedObjects map[client.ObjectKey]client.Object - NotFoundError error -} - -func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - m.GetCalled = true - if m.GetFunction != nil { - return m.GetFunction(ctx, key, obj) - } - - if m.expectedObjects == nil { - return nil - } - - if m.expectedObjects[key] == nil { - return &errors.StatusError{ - ErrStatus: metav1.Status{ - TypeMeta: metav1.TypeMeta{}, - ListMeta: metav1.ListMeta{}, - Status: string(metav1.StatusReasonNotFound), - Message: "NotFound", - Reason: metav1.StatusReasonNotFound, - Details: nil, - Code: 0, - }, - } - } - - foundObject := m.expectedObjects[key] - - copier.Copy(obj, foundObject) - - return nil -} - -func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - panic("implement me") -} - -func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - panic("implement me") -} - -func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - panic("implement me") -} - -func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - panic("implement me") -} - -func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - panic("implement me") -} - -type MockStatusWriter struct { - -} - -func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil -} - -func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) Status() client.StatusWriter { - return MockStatusWriter{} -} - -func (m *MockClient) Scheme() *runtime.Scheme { - panic("implement me") -} - -func (m *MockClient) RESTMapper() meta.RESTMapper { - panic("implement me") -} - -type MockContext struct { -} - -func (m *MockContext) Deadline() (deadline time.Time, ok bool) { - panic("implement me") -} - -func (m *MockContext) Done() <-chan struct{} { - panic("implement me") -} - -func (m *MockContext) Err() error { - panic("implement me") -} - -func (m *MockContext) Value(key interface{}) interface{} { - panic("implement me") -} - -type MockLogger struct { - WithValuesKeysAndValues []interface{} - WithValuesCalled bool - WithNameValue string - WithNameCalled bool - loggedMessage string - keysAndValues []interface{} - logFunction func(msg string, keysAndValues ...interface{}) - logLevelCalled string - level int -} - -func (m *MockLogger) Enabled() bool { - return true -} - -func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Info" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Error" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) V(level int) logr.Logger { - m.level = level - return m -} - -func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { - m.WithValuesCalled = true - for i := 0; i < len(keysAndValues); i++ { - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) - } - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) - - return m -} - -func (m *MockLogger) WithName(name string) logr.Logger { - m.WithNameCalled = true - m.WithNameValue = name - return m -} - -type MockController struct { - setupWithManagerWasCalled bool -} - -func (m *MockController) SetupWithManager(manager manager.Manager) error { - m.setupWithManagerWasCalled = true - return nil -} - -type MockManager struct { - Log logr.Logger - addHealthzCheckFunction func(name string, check healthz.Checker) error - addHealthzCheckWasCalled bool - addReadyzCheckFunction func(name string, check healthz.Checker) error - addReadyzCheckWasCalled bool - startFunction func(ctx context.Context) error - startWasCalled bool - builder *scheme.Builder - Fields []interface{} - runnable manager.Runnable -} - -func (m *MockManager) Add(runnable manager.Runnable) error { - m.runnable = runnable - - return nil -} - -func (m *MockManager) Elected() <-chan struct{} { - panic("implement me") -} - -func (m *MockManager) SetFields(i interface{}) error { - if m.Fields == nil { - m.Fields = make([]interface{}, 0) - } - m.Fields = append(m.Fields, i) - - return nil -} - -func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { - panic("implement me") -} - -func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { - m.addHealthzCheckWasCalled = true - if m.addHealthzCheckFunction == nil { - return nil - } - return m.addHealthzCheckFunction(name, check) -} - -func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { - m.addReadyzCheckWasCalled = true - if m.addReadyzCheckFunction == nil { - return nil - } - - return m.addReadyzCheckFunction(name, check) -} - -func (m *MockManager) Start(ctx context.Context) error { - m.startWasCalled = true - if m.startFunction == nil { - return nil - } - return m.startFunction(ctx) -} - -func (m *MockManager) GetConfig() *rest.Config { - return &rest.Config{} -} - -func (m *MockManager) GetScheme() *runtime.Scheme { - scheme, _ := m.builder.Build() - return scheme -} - -func (m *MockManager) GetClient() client.Client { - return nil -} - -func (m *MockManager) GetFieldIndexer() client.FieldIndexer { - panic("implement me") -} - -func (m *MockManager) GetCache() cache.Cache { - panic("implement me") -} - -func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { - panic("implement me") -} - -func (m *MockManager) GetRESTMapper() meta.RESTMapper { - panic("implement me") -} - -func (m *MockManager) GetAPIReader() client.Reader { - panic("implement me") -} - -func (m *MockManager) GetWebhookServer() *webhook.Server { - panic("implement me") -} - -func (m *MockManager) GetLogger() logr.Logger { - return m.Log -} - -type MockRunFunctionParameters struct { - options zap.Options - metricsAddress string - healthProbeAddress string - enableLeaderElection bool -} - -type MockDriver struct { - parseCommandLineCalled bool - parseCommandLineFunction func() (string, bool, string, zap.Options, error) - runCalled bool - runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) - runFunctionParameters MockRunFunctionParameters - newManagerCalled bool - newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) - instantiateControllersCalled bool - instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController - setupControllerWithManagerCalled bool - setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error -} - -func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { - m.parseCommandLineCalled = true - if m.parseCommandLineFunction == nil { - return "", true, "", zap.Options{}, nil - } - - return m.parseCommandLineFunction() -} - -func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { - m.runCalled = true - m.runFunctionParameters = MockRunFunctionParameters{ - options: opts, - metricsAddress: metricsAddress, - healthProbeAddress: healthProbeAddress, - enableLeaderElection: enableLeaderElection, - } - if m.runFunction == nil { - return 0, nil - } - - return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { - m.newManagerCalled = true - if m.newManagerFunction == nil { - return &MockManager{}, nil - } - - return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { - m.instantiateControllersCalled = true - - if m.instantiateControllersFunction == nil { - return []controllers.ManagedController{ - &MockController{}, - } - } - - return m.instantiateControllersFunction(mgr) -} - -func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { - m.setupControllerWithManagerCalled = true - - if m.setupControllersFunction == nil { - return nil - } - - return m.setupControllersFunction(controller, manager) -} - -type MockError struct { - message string -} - -func (m *MockError) Error() string { - if m.message == "" { - m.message = "mock Error" - } - return m.message -} - -type MockGitlabClient struct { - newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) -} - -func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return m.newClientFunction(token, options...) -} - -func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsFunction(opt, options...) -} +package gitlab + +import ( + "context" + "github.com/go-logr/logr" + "github.com/jinzhu/copier" + "github.com/xanzy/go-gitlab" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" + "valkyrie.dso.mil/valkyrie-api/controllers" +) + +type MockClient struct { + GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error + GetCalled bool + expectedObjects map[client.ObjectKey]client.Object + NotFoundError error +} + +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + m.GetCalled = true + if m.GetFunction != nil { + return m.GetFunction(ctx, key, obj) + } + + if m.expectedObjects == nil { + return nil + } + + if m.expectedObjects[key] == nil { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Status: string(metav1.StatusReasonNotFound), + Message: "NotFound", + Reason: metav1.StatusReasonNotFound, + Details: nil, + Code: 0, + }, + } + } + + foundObject := m.expectedObjects[key] + + copier.Copy(obj, foundObject) + + return nil +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("implement me") +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + panic("implement me") +} + +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + panic("implement me") +} + +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + panic("implement me") +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + panic("implement me") +} + +type MockStatusWriter struct { +} + +func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil +} + +func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) Status() client.StatusWriter { + return MockStatusWriter{} +} + +func (m *MockClient) Scheme() *runtime.Scheme { + panic("implement me") +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + panic("implement me") +} + +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool + loggedMessage string + keysAndValues []interface{} + logFunction func(msg string, keysAndValues ...interface{}) + logLevelCalled string + level int +} + +func (m *MockLogger) Enabled() bool { + return true +} + +func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Info" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Error" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) V(level int) logr.Logger { + m.level = level + return m +} + +func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + for i := 0; i < len(keysAndValues); i++ { + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) + } + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m *MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { + setupWithManagerWasCalled bool +} + +func (m *MockController) SetupWithManager(manager manager.Manager) error { + m.setupWithManagerWasCalled = true + return nil +} + +type MockManager struct { + Log logr.Logger + addHealthzCheckFunction func(name string, check healthz.Checker) error + addHealthzCheckWasCalled bool + addReadyzCheckFunction func(name string, check healthz.Checker) error + addReadyzCheckWasCalled bool + startFunction func(ctx context.Context) error + startWasCalled bool + builder *scheme.Builder + Fields []interface{} + runnable manager.Runnable +} + +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil +} + +func (m *MockManager) Elected() <-chan struct{} { + panic("implement me") +} + +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil +} + +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { + panic("implement me") +} + +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + m.addHealthzCheckWasCalled = true + if m.addHealthzCheckFunction == nil { + return nil + } + return m.addHealthzCheckFunction(name, check) +} + +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + m.addReadyzCheckWasCalled = true + if m.addReadyzCheckFunction == nil { + return nil + } + + return m.addReadyzCheckFunction(name, check) +} + +func (m *MockManager) Start(ctx context.Context) error { + m.startWasCalled = true + if m.startFunction == nil { + return nil + } + return m.startFunction(ctx) +} + +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} +} + +func (m *MockManager) GetScheme() *runtime.Scheme { + scheme, _ := m.builder.Build() + return scheme +} + +func (m *MockManager) GetClient() client.Client { + return nil +} + +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { + panic("implement me") +} + +func (m *MockManager) GetCache() cache.Cache { + panic("implement me") +} + +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { + panic("implement me") +} + +func (m *MockManager) GetRESTMapper() meta.RESTMapper { + panic("implement me") +} + +func (m *MockManager) GetAPIReader() client.Reader { + panic("implement me") +} + +func (m *MockManager) GetWebhookServer() *webhook.Server { + panic("implement me") +} + +func (m *MockManager) GetLogger() logr.Logger { + return m.Log +} + +type MockRunFunctionParameters struct { + options zap.Options + metricsAddress string + healthProbeAddress string + enableLeaderElection bool +} + +type MockDriver struct { + parseCommandLineCalled bool + parseCommandLineFunction func() (string, bool, string, zap.Options, error) + runCalled bool + runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) + runFunctionParameters MockRunFunctionParameters + newManagerCalled bool + newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) + instantiateControllersCalled bool + instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController + setupControllerWithManagerCalled bool + setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error +} + +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { + m.parseCommandLineCalled = true + if m.parseCommandLineFunction == nil { + return "", true, "", zap.Options{}, nil + } + + return m.parseCommandLineFunction() +} + +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { + m.runCalled = true + m.runFunctionParameters = MockRunFunctionParameters{ + options: opts, + metricsAddress: metricsAddress, + healthProbeAddress: healthProbeAddress, + enableLeaderElection: enableLeaderElection, + } + if m.runFunction == nil { + return 0, nil + } + + return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { + m.newManagerCalled = true + if m.newManagerFunction == nil { + return &MockManager{}, nil + } + + return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { + m.instantiateControllersCalled = true + + if m.instantiateControllersFunction == nil { + return []controllers.ManagedController{ + &MockController{}, + } + } + + return m.instantiateControllersFunction(mgr) +} + +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { + m.setupControllerWithManagerCalled = true + + if m.setupControllersFunction == nil { + return nil + } + + return m.setupControllersFunction(controller, manager) +} + +type MockError struct { + message string +} + +func (m *MockError) Error() string { + if m.message == "" { + m.message = "mock Error" + } + return m.message +} + +type MockGitlabClient struct { + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) +} + +func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return m.newClientFunction(token, options...) +} + +func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsFunction(opt, options...) +} -- GitLab From 7e2f2fd6e6b187b57780f0819bfea5286e26f9fc Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Wed, 16 Jun 2021 10:15:56 -0600 Subject: [PATCH 10/20] fix lint issues in template file --- scripts/scaffold_types_test.tpl | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/scripts/scaffold_types_test.tpl b/scripts/scaffold_types_test.tpl index cda5b4d..3fca1a2 100644 --- a/scripts/scaffold_types_test.tpl +++ b/scripts/scaffold_types_test.tpl @@ -10,12 +10,12 @@ import ( // testVars[[API_NAME]] - Reusable test variables type testVars[[API_NAME]] = struct { testKind string - testApiversion string + testAPIVersion string testSpec string testStatus string expectedKind string - expectedApiversion string + expectedAPIVersion string expectedSpec string expectedStatus string @@ -42,16 +42,16 @@ func initVars[[API_NAME]]() (testVars[[API_NAME]]) { testVars := testVars[[API_NAME]]{} testVars.testKind = "TestKind" - testVars.testApiversion = "v22" + testVars.testAPIVersion = "v22" testVars.testSpec = "test spec value" testVars.testStatus = "test status value" - testVars.expectedApiversion = testVars.testApiversion + testVars.expectedAPIVersion = testVars.testAPIVersion testVars.expectedKind = testVars.testKind testVars.expectedSpec = testVars.testSpec testVars.expectedStatus = testVars.testStatus - var object1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testApiversion} + var object1MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: testVars.testKind, APIVersion: testVars.testAPIVersion} testVars.testObject1 = [[API_NAME]]{TypeMeta: object1MetaType} var object2MetaType metav1.TypeMeta = metav1.TypeMeta{Kind: "TestKind2", APIVersion: "V99"} @@ -95,8 +95,7 @@ func TestGroupVars[[API_NAME]](t *testing.T) { // TestTypes[[API_NAME]] - func TestTypes[[API_NAME]](t *testing.T) { lTestVars := initVars[[API_NAME]]() - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion got := lTestVars.testObject1.APIVersion if got != want { t.Errorf("got %s want %s", got, want) @@ -112,8 +111,7 @@ func TestDeepCopyDeepCopy[[API_NAME]](t *testing.T) { // check api version got := newObject.APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -142,8 +140,7 @@ func TestDeepCopyDeepCopyInto[[API_NAME]](t *testing.T) { lTestVars.testObject1.DeepCopyInto(&lTestVars.testObject2) got := lTestVars.testObject2.APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -157,8 +154,7 @@ func TestDeepCopyDeepCopyObject[[API_NAME]](t *testing.T) { newRuntimeObject := lTestVars.testObject1.DeepCopyObject() newObject := newRuntimeObject.(*[[API_NAME]]) got := newObject.APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -171,8 +167,7 @@ func TestDeepCopyDeepCopyList[[API_NAME]](t *testing.T) { newObjectList := lTestVars.objectList1.DeepCopy() got := newObjectList.Items[0].APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -193,8 +188,7 @@ func TestDeepCopyDeepCopyIntoList[[API_NAME]](t *testing.T) { lTestVars.objectList1.DeepCopyInto(&lTestVars.objectList2) got := lTestVars.objectList2.Items[0].APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } @@ -207,8 +201,7 @@ func TestDeepCopyDeepCopyListObject[[API_NAME]](t *testing.T) { newRuntimeObject := lTestVars.objectList1.DeepCopyObject() newObject := newRuntimeObject.(*[[API_NAME]]List) got := newObject.Items[0].APIVersion - want := lTestVars.expectedApiversion - + want := lTestVars.expectedAPIVersion if got != want { t.Errorf("got %s want %s", got, want) } -- GitLab From e4abf7f7c212a8091f4c45ac39e14a65b8c68c75 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Wed, 16 Jun 2021 10:27:24 -0600 Subject: [PATCH 11/20] fix lint issues --- Makefile | 3 +++ clients/gitlab/client.go | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b55cf44..0244139 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,9 @@ fmt: ## Run go fmt against code. vet: ## Run go vet against code. go vet ./... +lint: ## Run go lint against code + golint -set_exit_status ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin test: # manifests generate ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 794a331..60cc627 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -10,6 +10,7 @@ import ( "log" ) +// ClientI - client interface type ClientI interface { NewClient(apiURL string, token string) Client GetUsers() ([]*gitlab.User, error) @@ -135,7 +136,7 @@ func (r Client) AddGroup(group *gitlab.Group) (*gitlab.Group, int, error) { return newGroup, res.StatusCode, nil } -// DeleteUser - +// DeleteGroup - func (r Client) DeleteGroup(path string) (int, error) { var opts = gitlab.ListGroupsOptions{Search: &path} groups, lres, err := r.client.Groups.ListGroups(&opts) -- GitLab From 6f7a7b6fa491f7ec25099aa4383730238fd7bd12 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Wed, 16 Jun 2021 15:38:58 -0600 Subject: [PATCH 12/20] update gitlab groups --- clients/gitlab/client.go | 32 +++--- .../gitlab/api/gitlab_api_adhoc_test.go | 5 +- .../gitlab/api/gitlab_api_test.go | 97 +++++++++++++------ 3 files changed, 92 insertions(+), 42 deletions(-) diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 60cc627..1ee7d4f 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -96,25 +96,36 @@ func (r Client) DeleteUser(username string) (int, error) { } // GetGroups - -func (r Client) GetGroups() ([]*gitlab.Group, error) { +func (r Client) GetGroups(search *string) ([]*gitlab.Group, error) { listOptions := gitlab.ListOptions{ Page: 1, PerPage: itemsPerPage, } + // some valid values path, name + var orderBy ="path" + var opts = gitlab.ListGroupsOptions{ + ListOptions: listOptions, + OrderBy: &orderBy, + } + // if search defined add it to opts + + if search != nil { + opts.Search = search + } groupList := []*gitlab.Group{} var more = true for more { - users, res, err := r.client.Groups.ListGroups(&gitlab.ListGroupsOptions{ListOptions: listOptions}) + groups, res, err := r.client.Groups.ListGroups(&opts) if err != nil { return nil, err } - for x := 0; x < len(users); x++ { - groupList = append(groupList, users[x]) + for x := 0; x < len(groups); x++ { + groupList = append(groupList, groups[x]) } if res.NextPage > 0 { - listOptions.Page = listOptions.Page + 1 + opts.ListOptions.Page = opts.ListOptions.Page + 1 } else { more = false } @@ -125,10 +136,12 @@ func (r Client) GetGroups() ([]*gitlab.Group, error) { // AddGroup - func (r Client) AddGroup(group *gitlab.Group) (*gitlab.Group, int, error) { + // force visibility to private var visibility = gitlab.PrivateVisibility var opts = gitlab.CreateGroupOptions{Name: &group.Name, Path: &group.Path, Description: &group.Description, Visibility: &visibility} + newGroup, res, err := r.client.Groups.CreateGroup(&opts) if err != nil { return nil, res.StatusCode, err @@ -137,13 +150,8 @@ func (r Client) AddGroup(group *gitlab.Group) (*gitlab.Group, int, error) { } // DeleteGroup - -func (r Client) DeleteGroup(path string) (int, error) { - var opts = gitlab.ListGroupsOptions{Search: &path} - groups, lres, err := r.client.Groups.ListGroups(&opts) - if err != nil { - return lres.StatusCode, err - } - res, err := r.client.Groups.DeleteGroup(groups[0].ID) +func (r Client) DeleteGroup(groupID int) (int, error) { + res, err := r.client.Groups.DeleteGroup(groupID) if err != nil { return res.StatusCode, err } diff --git a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go index 0fa966f..2b49e64 100644 --- a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go +++ b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go @@ -46,7 +46,8 @@ func TestClient_AdHoc_getGroups(t *testing.T) { t.Errorf("Client.getClient() error = %v", err) return } - got, err := c.GetGroups() + s := string("adam") + got, err := c.GetGroups(&s) if err != nil { t.Errorf("Client.GetGroups() error = %v", err) return @@ -68,7 +69,7 @@ func TestClient_AdHoc_addGroup(t *testing.T) { group := gogitlab.Group{Name: "test group 1", Description: "Test group desc", Path: "test-group", FullPath: "top-group/test-group"} got, statusCode, err := c.AddGroup(&group) if err != nil { - t.Errorf("Client.GetGroups() error = %v", err) + t.Errorf("Client.AddGroups() error = %v", err) return } else { t.Logf("AddGroup %s statusCdde %d", got.FullName, statusCode) diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 0ea2dee..e19e438 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -12,7 +12,8 @@ import ( gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) -// getClient - +const testItemCount = 65 +// getClient - func getClient() (gitlab.Client, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") if !ok { @@ -39,6 +40,7 @@ func TestClient_getUsers(t *testing.T) { t.Logf("GetUsers %d", len(got)) }) } + // TestClient_addUsers - func TestClient_addUsers(t *testing.T) { t.Run("test", func(t *testing.T) { @@ -48,28 +50,28 @@ func TestClient_addUsers(t *testing.T) { return } - for i := 1; i <= 200; i++ { - username := "testuser" + fmt.Sprintf("%d",i) - name := "test user name " + fmt.Sprintf("%d",i) + for i := 1; i <= testItemCount; i++ { + username := "testuser" + fmt.Sprintf("%d", i) + name := "test user name " + fmt.Sprintf("%d", i) email := username + "@email.com" userObj := gogitlab.User{Username: username, Email: email, Name: name} - got, statusCode, err := c.AddUser(&userObj, "password") + got, statusCode, err := c.AddUser(&userObj, "password") if err != nil { t.Errorf("Client.AddUser() error = %v %d", err, statusCode) } else { - t.Logf("AddUser %s %d",got.Username,statusCode) + t.Logf("AddUser %s %d", got.Username, statusCode) } } }) } -func TestClient_getUsersLarge(t *testing.T) { +func TestClient_getUsersCount(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() if err != nil { - t.Errorf("Client.GetUsers() error = %v", err) + t.Errorf("Client.getClient() error = %v", err) return } got, err := c.GetUsers() @@ -77,7 +79,7 @@ func TestClient_getUsersLarge(t *testing.T) { t.Errorf("Client.GetUsers() error = %v", err) return } - t.Logf("GetUsers %d", len(got)) + t.Logf("GetUsers - user count: %d", len(got)) }) } func TestClient_deleteUsers(t *testing.T) { @@ -88,59 +90,98 @@ func TestClient_deleteUsers(t *testing.T) { return } - for i := 1; i <= 200; i++ { - username := "testuser" + fmt.Sprintf("%d",i) - statusCode ,err := c.DeleteUser(username) + for i := 1; i <= testItemCount; i++ { + username := "testuser" + fmt.Sprintf("%d", i) + statusCode, err := c.DeleteUser(username) if err != nil { t.Errorf("Client.DeleteUser() error = %v %d", err, statusCode) } else { - t.Logf("Deleted user %s %d",username,statusCode) + t.Logf("Deleted user %s %d", username, statusCode) } } }) } - -func TestClient_getGroups(t *testing.T) { +func TestClient_getGroupsCount(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() if err != nil { - t.Errorf("Client.GetGroups() error = %v", err) + t.Errorf("Client.getClient() error = %v", err) return } - got, err := c.GetGroups() + got, err := c.GetGroups(nil) if err != nil { t.Errorf("Client.GetGroups() error = %v", err) return } - t.Logf("GetGroups number of groups %d", len(got)) + t.Logf("GetGroups - number of groups %d", len(got)) }) } - func TestClient_addGroups(t *testing.T) { t.Run("test", func(t *testing.T) { c, err := getClient() if err != nil { - t.Errorf("Client.GetUsers() error = %v", err) + t.Errorf("Client.getClient() error = %v", err) return } - for i := 1; i <= 200; i++ { - path := "test-group-" + fmt.Sprintf("%d",i) - name := "test grpup " + fmt.Sprintf("%d",i) - - groupObj := gogitlab.Group{Name: name} - got, statusCode, err := c.AddGroup(&groupObj) + for i := 1; i <= testItemCount; i++ { + path := "test-group-" + fmt.Sprintf("%d", i) + name := "test group " + fmt.Sprintf("%d", i) + + groupObj := gogitlab.Group{Name: name, Path: path} + got, statusCode, err := c.AddGroup(&groupObj) if err != nil { t.Errorf("Client.AddGroup() error = %v %d", err, statusCode) } else { - t.Logf("AddGroup %s %d",got.,statusCode) + t.Logf("AddGroup %s %s %s %s %d", got.Name, got.Path, got.FullPath, got.WebURL, statusCode) } } }) -} \ No newline at end of file +} + +func TestClient_getGroupsCount2(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.getClient() error = %v", err) + return + } + got, err := c.GetGroups(nil) + if err != nil { + t.Errorf("Client.GetGroups() error = %v", err) + return + } + t.Logf("GetGroups - number of groups %d", len(got)) + }) +} + +func TestClient_deleteGroups(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.getClient() error = %v", err) + return + } + + search := "test-group" + groupList, err := c.GetGroups(&search) + + for x := 0; x < len(groupList); x++ { + group := groupList[x] + statusCode, err := c.DeleteGroup(group.ID) + + if err != nil { + t.Errorf("Client.DeleteGroup() error = %v %d", err, statusCode) + } else { + t.Logf("DeleteGroup %s %d", group.Name, statusCode) + } + } + + }) +} -- GitLab From ab591e581628e25e12d3230c6f7f92e73bac5daa Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Wed, 16 Jun 2021 15:58:42 -0600 Subject: [PATCH 13/20] add search to gitlab getusers --- .gitignore | 2 + README.md | 4 +- clients/gitlab/client.go | 22 +++-- .../gitlab/api/gitlab_api_adhoc_test.go | 82 ++++++++++++++++--- .../gitlab/api/gitlab_api_test.go | 4 +- 5 files changed, 94 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index a5d5d1f..a887153 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ coverage.html *.swp *.swo *~ + +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 8a8ecdf..eac9967 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ - +### lint +- run linter + - `golint -set_exit_status ./... ` ### Integration testing - to run integration tests - tag using test file with a build tag at the beginning of the file diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 1ee7d4f..7b67453 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -39,16 +39,28 @@ func NewClient(apiURL string, token string) Client { } // GetUsers - -func (r Client) GetUsers() ([]*gitlab.User, error) { +func (r Client) GetUsers(search *string) ([]*gitlab.User, error) { listOptions := gitlab.ListOptions{ Page: 1, PerPage: itemsPerPage, } + // some valid values path, name + var orderBy = "name" + var opts = gitlab.ListUsersOptions{ + ListOptions: listOptions, + OrderBy: &orderBy, + } + // if search defined add it to opts + + if search != nil { + opts.Search = search + } + userList := []*gitlab.User{} var more = true for more { - users, res, err := r.client.Users.ListUsers(&gitlab.ListUsersOptions{ListOptions: listOptions}) + users, res, err := r.client.Users.ListUsers(&opts) if err != nil { return nil, err } @@ -57,7 +69,7 @@ func (r Client) GetUsers() ([]*gitlab.User, error) { } if res.NextPage > 0 { - listOptions.Page = listOptions.Page + 1 + opts.ListOptions.Page = opts.ListOptions.Page + 1 } else { more = false } @@ -103,10 +115,10 @@ func (r Client) GetGroups(search *string) ([]*gitlab.Group, error) { } // some valid values path, name - var orderBy ="path" + var orderBy = "path" var opts = gitlab.ListGroupsOptions{ ListOptions: listOptions, - OrderBy: &orderBy, + OrderBy: &orderBy, } // if search defined add it to opts diff --git a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go index 2b49e64..01af512 100644 --- a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go +++ b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go @@ -6,12 +6,11 @@ import ( "testing" "errors" "os" - gogitlab "github.com/xanzy/go-gitlab" gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) // getClient - -func getClient() (gitlab.Client, error) { +func getClient_AdHoc() (gitlab.Client, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") if !ok { return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") @@ -22,12 +21,50 @@ func getClient() (gitlab.Client, error) { } func TestClient_AdHoc_getUsers(t *testing.T) { t.Run("test", func(t *testing.T) { - c, err := getClient() + c, err := getClient_AdHoc() if err != nil { t.Errorf("Client.GetUsers() error = %v", err) return } - got, err := c.GetUsers() + got, err := c.GetUsers(nil) + if err != nil { + t.Errorf("Client.GetUsers() error = %v", err) + return + } + t.Logf("GetUsers %d", len(got)) + for x:=0;x Date: Wed, 16 Jun 2021 19:02:17 -0600 Subject: [PATCH 14/20] add ability to set custom http client in the gitlab api --- clients/gitlab/client.go | 26 ++-- clients/gitlab/client_test.go | 271 ++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 clients/gitlab/client_test.go diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 7b67453..e7510b9 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -6,8 +6,10 @@ package gitlab // https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation import ( + "net/http" + "strings" + gitlab "github.com/xanzy/go-gitlab" - "log" ) // ClientI - client interface @@ -22,22 +24,28 @@ const itemsPerPage = 50 // Client - type Client struct { client *gitlab.Client + token string + apiURL string } -// NewClient - + +// NewClient - create new gitlab api client func NewClient(apiURL string, token string) Client { - // const apiURLPrefix = "/api/v4" // apiUrl https://code.il2.dso.mil/api/v4 - git, err := gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) - if err != nil { - log.Fatalf("Failed to create client: %v", err) - } - + // token gitlab user access token + gitlabClient, _ := gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) return Client{ - client: git, + client: gitlabClient, + token: token, + apiURL: apiURL, } } +// SetHTTPClient - create gitlab client with custom httpClient +func (r Client) SetHTTPClient(httpClient *http.Client) { + r.client, _ = gitlab.NewClient(r.token, gitlab.WithBaseURL(r.apiURL), gitlab.WithHTTPClient(httpClient)) +} + // GetUsers - func (r Client) GetUsers(search *string) ([]*gitlab.User, error) { listOptions := gitlab.ListOptions{ diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go new file mode 100644 index 0000000..f6ab890 --- /dev/null +++ b/clients/gitlab/client_test.go @@ -0,0 +1,271 @@ +package gitlab + +import ( + "github.com/jarcoal/httpmock" + "reflect" + "testing" + + gitlab "github.com/xanzy/go-gitlab" +) + +func TestNewClient(t *testing.T) { + type args struct { + apiURL string + token string + } + testToken := "abcdef" + testURL := "https://test" + testWantURL := "https://test/api/v4/" + tests := []struct { + name string + args args + wantBaseURL string + }{ + { + name: "New Client 1", + args: args{testURL, testToken}, + wantBaseURL: testWantURL, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewClient(tt.args.apiURL, tt.args.token) + t.Logf("baseUrl %s", got.client.BaseURL()) + if got.client.BaseURL().String() != tt.wantBaseURL { + t.Errorf("NewClient() = %v, want %v", got.client.BaseURL().String(), tt.wantBaseURL) + } + }) + } +} + +func TestClient_GetUsers(t *testing.T) { + httpmock.Activate() + httpmock.ActivateNonDefault(client) + defer httpmock.DeactivateAndReset() + // Get "https://test/api/v4/users?order_by=name&page=1&per_page=50" + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(200, &gitlab.Response{}), + ) + + type fields struct { + client *gitlab.Client + } + type args struct { + search *string + } + + gitlabClient := NewClient("https://test/api/v4/", "").client + + gitlabHTTPClient := gitlabClient.Client.HTTPClient + + ua := []*gitlab.User{} + + tests := []struct { + name string + fields fields + args args + want []*gitlab.User + wantErr bool + }{ + { + name: "New Client 1", + fields: fields{client: client}, + args: args{search: nil}, + want: ua, + wantErr: false, + }, + } + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + } + + got, err := r.GetUsers(tt.args.search) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetUsers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetUsers() = %v, want %v", got, tt.want) + } + }) + + } +} + +// func TestClient_AddUser(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// } +// type args struct { +// user *gitlab.User +// password string +// } +// tests := []struct { +// name string +// fields fields +// args args +// want *gitlab.User +// want1 int +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// } +// got, got1, err := r.AddUser(tt.args.user, tt.args.password) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.AddUser() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.AddUser() got = %v, want %v", got, tt.want) +// } +// if got1 != tt.want1 { +// t.Errorf("Client.AddUser() got1 = %v, want %v", got1, tt.want1) +// } +// }) +// } +// } + +// func TestClient_DeleteUser(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// } +// type args struct { +// username string +// } +// tests := []struct { +// name string +// fields fields +// args args +// want int +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// } +// got, err := r.DeleteUser(tt.args.username) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.DeleteUser() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if got != tt.want { +// t.Errorf("Client.DeleteUser() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func TestClient_GetGroups(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// } +// type args struct { +// search *string +// } +// tests := []struct { +// name string +// fields fields +// args args +// want []*gitlab.Group +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// } +// got, err := r.GetGroups(tt.args.search) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.GetGroups() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.GetGroups() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func TestClient_AddGroup(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// } +// type args struct { +// group *gitlab.Group +// } +// tests := []struct { +// name string +// fields fields +// args args +// want *gitlab.Group +// want1 int +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// } +// got, got1, err := r.AddGroup(tt.args.group) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.AddGroup() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.AddGroup() got = %v, want %v", got, tt.want) +// } +// if got1 != tt.want1 { +// t.Errorf("Client.AddGroup() got1 = %v, want %v", got1, tt.want1) +// } +// }) +// } +// } + +// func TestClient_DeleteGroup(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// } +// type args struct { +// groupID int +// } +// tests := []struct { +// name string +// fields fields +// args args +// want int +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// } +// got, err := r.DeleteGroup(tt.args.groupID) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.DeleteGroup() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if got != tt.want { +// t.Errorf("Client.DeleteGroup() = %v, want %v", got, tt.want) +// } +// }) +// } +// } -- GitLab From fc5da73ad6ecf303d32bd7c254f74d297f5ae277 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Thu, 17 Jun 2021 01:16:08 -0600 Subject: [PATCH 15/20] add tests for gitlab client --- README.md | 31 +- clients/gitlab/client.go | 23 +- clients/gitlab/client_test.go | 400 ++++++++++++------ .../gitlab/api/gitlab_api_adhoc_test.go | 9 +- .../gitlab/api/gitlab_api_test.go | 7 +- 5 files changed, 315 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index eac9967..3dc6dd2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,30 @@ -## +## Project Management - - - - -### lint +### lint fmt vet - run linter - `golint -set_exit_status ./... ` +- use make + - `make fmt vet lint` + +### Tests +- run tests locally +``` +make build +make test +``` +- generate coverage report + - run AFTER running `make test` +``` +# AFTER running make test +go tool cover -html=cover.out -o coverage.html +open coverage.html +``` ### Integration testing -- to run integration tests -- tag using test file with a build tag at the beginning of the file +- integration tests may require setting environment variables +- integration tests may require availability of service + - examples: gitlab, twistlock, fortify, sonarqube +- place integration test files in the ./integration-tests/ folder +- tag test files with a build tag at the beginning of the file ``` // +build integration diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index e7510b9..49f73ac 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -7,7 +7,6 @@ package gitlab import ( "net/http" - "strings" gitlab "github.com/xanzy/go-gitlab" ) @@ -24,28 +23,30 @@ const itemsPerPage = 50 // Client - type Client struct { client *gitlab.Client - token string + token string apiURL string } - // NewClient - create new gitlab api client -func NewClient(apiURL string, token string) Client { +func NewClient(apiURL string, token string, httpClient *http.Client) Client { + // httpClient optional injection of http client object to be used by gitlab api // apiUrl https://code.il2.dso.mil/api/v4 // token gitlab user access token - gitlabClient, _ := gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) + + var gitlabClient *gitlab.Client + if httpClient == nil { + gitlabClient, _ = gitlab.NewClient(token, gitlab.WithBaseURL(apiURL)) + } else { + gitlabClient, _ = gitlab.NewClient(token, gitlab.WithBaseURL(apiURL), gitlab.WithHTTPClient(httpClient)) + } + return Client{ client: gitlabClient, - token: token, + token: token, apiURL: apiURL, } } -// SetHTTPClient - create gitlab client with custom httpClient -func (r Client) SetHTTPClient(httpClient *http.Client) { - r.client, _ = gitlab.NewClient(r.token, gitlab.WithBaseURL(r.apiURL), gitlab.WithHTTPClient(httpClient)) -} - // GetUsers - func (r Client) GetUsers(search *string) ([]*gitlab.User, error) { listOptions := gitlab.ListOptions{ diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go index f6ab890..dd43128 100644 --- a/clients/gitlab/client_test.go +++ b/clients/gitlab/client_test.go @@ -2,34 +2,44 @@ package gitlab import ( "github.com/jarcoal/httpmock" + "net/http" "reflect" "testing" - gitlab "github.com/xanzy/go-gitlab" + gogitlab "github.com/xanzy/go-gitlab" ) func TestNewClient(t *testing.T) { - type args struct { - apiURL string - token string - } + testToken := "abcdef" testURL := "https://test" testWantURL := "https://test/api/v4/" + testHTTPClient := &http.Client{} + + type args struct { + apiURL string + token string + httpClient *http.Client + } tests := []struct { name string args args wantBaseURL string }{ { - name: "New Client 1", - args: args{testURL, testToken}, + name: "New Client", + args: args{testURL, testToken, nil}, + wantBaseURL: testWantURL, + }, + { + name: "New Client with httpClient injection", + args: args{testURL, testToken, testHTTPClient}, wantBaseURL: testWantURL, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := NewClient(tt.args.apiURL, tt.args.token) + got := NewClient(tt.args.apiURL, tt.args.token, tt.args.httpClient) t.Logf("baseUrl %s", got.client.BaseURL()) if got.client.BaseURL().String() != tt.wantBaseURL { t.Errorf("NewClient() = %v, want %v", got.client.BaseURL().String(), tt.wantBaseURL) @@ -39,48 +49,66 @@ func TestNewClient(t *testing.T) { } func TestClient_GetUsers(t *testing.T) { - httpmock.Activate() - httpmock.ActivateNonDefault(client) + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) defer httpmock.DeactivateAndReset() - // Get "https://test/api/v4/users?order_by=name&page=1&per_page=50" + + // empty gitlab User array + testUserArray := []*gogitlab.User{} + testUser1 := gogitlab.User{ID: 1, Username: "joedirt"} + testUser2 := gogitlab.User{ID: 2, Username: "spongebob"} + testUserArray = append(testUserArray, &testUser1, &testUser2) + + // use regex url matcher httpmock.RegisterResponder("GET", `=~^https://test/api/v4/users.*`, - httpmock.NewJsonResponderOrPanic(200, &gitlab.Response{}), + httpmock.NewJsonResponderOrPanic(200, testUserArray), ) + // test objects + search := "searchstring" + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + type fields struct { - client *gitlab.Client + client *gogitlab.Client + token string + apiURL string } type args struct { search *string } - - gitlabClient := NewClient("https://test/api/v4/", "").client - - gitlabHTTPClient := gitlabClient.Client.HTTPClient - - ua := []*gitlab.User{} - tests := []struct { name string fields fields args args - want []*gitlab.User + want []*gogitlab.User wantErr bool }{ { - name: "New Client 1", - fields: fields{client: client}, + name: "GetUsers 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, args: args{search: nil}, - want: ua, + want: testUserArray, + wantErr: false, + }, + { + name: "GetUsers 2", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: &search}, + want: testUserArray, wantErr: false, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { r := Client{ client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, } got, err := r.GetUsers(tt.args.search) @@ -88,55 +116,86 @@ func TestClient_GetUsers(t *testing.T) { t.Errorf("Client.GetUsers() error = %v, wantErr %v", err, tt.wantErr) return } + for _, user := range got { + t.Logf("user id %d, username %s", user.ID, user.Username) + } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Client.GetUsers() = %v, want %v", got, tt.want) } }) - } } -// func TestClient_AddUser(t *testing.T) { -// type fields struct { -// client *gitlab.Client -// } -// type args struct { -// user *gitlab.User -// password string -// } -// tests := []struct { -// name string -// fields fields -// args args -// want *gitlab.User -// want1 int -// wantErr bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := Client{ -// client: tt.fields.client, -// } -// got, got1, err := r.AddUser(tt.args.user, tt.args.password) -// if (err != nil) != tt.wantErr { -// t.Errorf("Client.AddUser() error = %v, wantErr %v", err, tt.wantErr) -// return -// } -// if !reflect.DeepEqual(got, tt.want) { -// t.Errorf("Client.AddUser() got = %v, want %v", got, tt.want) -// } -// if got1 != tt.want1 { -// t.Errorf("Client.AddUser() got1 = %v, want %v", got1, tt.want1) -// } -// }) -// } -// } +func TestClient_AddUser(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testUser1 := gogitlab.User{ID: 1, Username: "joedirt"} + + httpmock.RegisterResponder("POST", + "https://test/api/v4/users", + httpmock.NewJsonResponderOrPanic(200, testUser1), + ) + + // test objects + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + user *gogitlab.User + password string + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.User + want1 int + wantErr bool + }{ + { + name: "AddUser 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{user: &testUser1, password: "test"}, + want: &testUser1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, _, err := r.AddUser(tt.args.user, tt.args.password) + if (err != nil) != tt.wantErr { + t.Errorf("Client.AddUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.ID != tt.want.ID { + t.Errorf("Client.AddUser() got = %d, want %d", got.ID, tt.want.ID) + return + } + t.Logf("Client.AddUser() got = %d, want %d", got.ID, tt.want.ID) + }) + } +} // func TestClient_DeleteUser(t *testing.T) { // type fields struct { // client *gitlab.Client +// token string +// apiURL string // } // type args struct { // username string @@ -154,6 +213,8 @@ func TestClient_GetUsers(t *testing.T) { // t.Run(tt.name, func(t *testing.T) { // r := Client{ // client: tt.fields.client, +// token: tt.fields.token, +// apiURL: tt.fields.apiURL, // } // got, err := r.DeleteUser(tt.args.username) // if (err != nil) != tt.wantErr { @@ -167,79 +228,154 @@ func TestClient_GetUsers(t *testing.T) { // } // } -// func TestClient_GetGroups(t *testing.T) { -// type fields struct { -// client *gitlab.Client -// } -// type args struct { -// search *string -// } -// tests := []struct { -// name string -// fields fields -// args args -// want []*gitlab.Group -// wantErr bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := Client{ -// client: tt.fields.client, -// } -// got, err := r.GetGroups(tt.args.search) -// if (err != nil) != tt.wantErr { -// t.Errorf("Client.GetGroups() error = %v, wantErr %v", err, tt.wantErr) -// return -// } -// if !reflect.DeepEqual(got, tt.want) { -// t.Errorf("Client.GetGroups() = %v, want %v", got, tt.want) -// } -// }) -// } -// } +func TestClient_GetGroups(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() -// func TestClient_AddGroup(t *testing.T) { -// type fields struct { -// client *gitlab.Client -// } -// type args struct { -// group *gitlab.Group -// } -// tests := []struct { -// name string -// fields fields -// args args -// want *gitlab.Group -// want1 int -// wantErr bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := Client{ -// client: tt.fields.client, -// } -// got, got1, err := r.AddGroup(tt.args.group) -// if (err != nil) != tt.wantErr { -// t.Errorf("Client.AddGroup() error = %v, wantErr %v", err, tt.wantErr) -// return -// } -// if !reflect.DeepEqual(got, tt.want) { -// t.Errorf("Client.AddGroup() got = %v, want %v", got, tt.want) -// } -// if got1 != tt.want1 { -// t.Errorf("Client.AddGroup() got1 = %v, want %v", got1, tt.want1) -// } -// }) -// } -// } + // empty gitlab User array + testGroupArray := []*gogitlab.Group{} + testGroup1 := gogitlab.Group{ID: 1, Name: "joedirt"} + testGroup2 := gogitlab.Group{ID: 2, Name: "spongebob"} + testGroupArray = append(testGroupArray, &testGroup1, &testGroup2) + + // use regex url matcher + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(200, testGroupArray), + ) + + // test objects + search := "searchstring" + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + search *string + } + tests := []struct { + name string + fields fields + args args + want []*gogitlab.Group + wantErr bool + }{ + { + name: "GetGroups 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: nil}, + want: testGroupArray, + wantErr: false, + }, + { + name: "GetGroups 2", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{search: &search}, + want: testGroupArray, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + + got, err := r.GetGroups(tt.args.search) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetGroups() error = %v, wantErr %v", err, tt.wantErr) + return + } + for _, group := range got { + t.Logf("user id %d, groupname %s", group.ID, group.Name) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Client.GetGroups() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClient_AddGroup(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + // empty gitlab User array + testGroup := gogitlab.Group{ID: 1, Name: "joedirt"} + + httpmock.RegisterResponder("POST", + "https://test/api/v4/groups", + httpmock.NewJsonResponderOrPanic(200, testGroup), + ) + + // test objects + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + group *gogitlab.Group + } + tests := []struct { + name string + fields fields + args args + want *gogitlab.Group + want1 int + wantErr bool + }{ + { + name: "AddGroup 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{group: &testGroup}, + want: &testGroup, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, _, err := r.AddGroup(tt.args.group) + if (err != nil) != tt.wantErr { + t.Errorf("Client.AddGroup() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.ID != tt.want.ID { + t.Errorf("Client.AddGroup() got = %d, want %d", got.ID, tt.want.ID) + return + } + t.Logf("Client.AddGroup() got = %d %s, want %d %s", got.ID, got.Name, tt.want.ID, tt.want.Name) + }) + } +} // func TestClient_DeleteGroup(t *testing.T) { // type fields struct { // client *gitlab.Client +// token string +// apiURL string // } // type args struct { // groupID int @@ -257,6 +393,8 @@ func TestClient_GetUsers(t *testing.T) { // t.Run(tt.name, func(t *testing.T) { // r := Client{ // client: tt.fields.client, +// token: tt.fields.token, +// apiURL: tt.fields.apiURL, // } // got, err := r.DeleteGroup(tt.args.groupID) // if (err != nil) != tt.wantErr { diff --git a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go index 01af512..c3f4cca 100644 --- a/integration-tests/gitlab/api/gitlab_api_adhoc_test.go +++ b/integration-tests/gitlab/api/gitlab_api_adhoc_test.go @@ -9,14 +9,17 @@ import ( gitlab "valkyrie.dso.mil/valkyrie-api/clients/gitlab" ) -// getClient - +// getClient_AdHoc - func getClient_AdHoc() (gitlab.Client, error) { gitlabAPIURL, ok := os.LookupEnv("GITLAB_API_URL") if !ok { return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") } - gitlabAPIToken, _ := os.LookupEnv("GITLAB_API_TOKEN") - var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken) + gitlabAPIToken, ok := os.LookupEnv("GITLAB_API_TOKEN") + if !ok { + return gitlab.Client{}, errors.New("env variable GITLAB_API_TOKEN undefinded") + } + var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken,nil) return client, nil } func TestClient_AdHoc_getUsers(t *testing.T) { diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 3fa5112..7921998 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -19,8 +19,11 @@ func getClient() (gitlab.Client, error) { if !ok { return gitlab.Client{}, errors.New("env variable GITLAB_API_URL undefinded") } - gitlabAPIToken, _ := os.LookupEnv("GITLAB_API_TOKEN") - var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken) + gitlabAPIToken, ok := os.LookupEnv("GITLAB_API_TOKEN") + if !ok { + return gitlab.Client{}, errors.New("env variable GITLAB_API_TOKEN undefinded") + } + var client = gitlab.NewClient(gitlabAPIURL, gitlabAPIToken,nil) return client, nil } -- GitLab From 709cbd0e4724118aa52b3b67a373207b96a63eed Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Thu, 17 Jun 2021 01:50:47 -0600 Subject: [PATCH 16/20] finish test coverage on gitlab client --- clients/gitlab/client.go | 9 +- clients/gitlab/client_test.go | 212 ++++++++++++++++++++++------------ 2 files changed, 141 insertions(+), 80 deletions(-) diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 49f73ac..201274c 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -11,13 +11,6 @@ import ( gitlab "github.com/xanzy/go-gitlab" ) -// ClientI - client interface -type ClientI interface { - NewClient(apiURL string, token string) Client - GetUsers() ([]*gitlab.User, error) - DeleteUser(username string) (int, error) -} - const itemsPerPage = 50 // Client - @@ -29,9 +22,9 @@ type Client struct { // NewClient - create new gitlab api client func NewClient(apiURL string, token string, httpClient *http.Client) Client { - // httpClient optional injection of http client object to be used by gitlab api // apiUrl https://code.il2.dso.mil/api/v4 // token gitlab user access token + // httpClient optional injection of http client object to be used by gitlab api var gitlabClient *gitlab.Client if httpClient == nil { diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go index dd43128..0669db8 100644 --- a/clients/gitlab/client_test.go +++ b/clients/gitlab/client_test.go @@ -191,42 +191,75 @@ func TestClient_AddUser(t *testing.T) { } } -// func TestClient_DeleteUser(t *testing.T) { -// type fields struct { -// client *gitlab.Client -// token string -// apiURL string -// } -// type args struct { -// username string -// } -// tests := []struct { -// name string -// fields fields -// args args -// want int -// wantErr bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := Client{ -// client: tt.fields.client, -// token: tt.fields.token, -// apiURL: tt.fields.apiURL, -// } -// got, err := r.DeleteUser(tt.args.username) -// if (err != nil) != tt.wantErr { -// t.Errorf("Client.DeleteUser() error = %v, wantErr %v", err, tt.wantErr) -// return -// } -// if got != tt.want { -// t.Errorf("Client.DeleteUser() = %v, want %v", got, tt.want) -// } -// }) -// } -// } +func TestClient_DeleteUser(t *testing.T) { + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + + testUser := gogitlab.User{ID: 1, Username: "joedirt"} + testUserArray := []*gogitlab.User{} + testUserArray = append(testUserArray, &testUser) + + httpmock.RegisterResponder("DELETE", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(202, testUser), + ) + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/users.*`, + httpmock.NewJsonResponderOrPanic(200, testUserArray), + ) + + // test objects + testUsername := "testusername" + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + username string + } + tests := []struct { + name string + fields fields + args args + want int + wantErr bool + }{ + { + name: "DeleteUser 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{username: testUsername}, + want: 202, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, err := r.DeleteUser(tt.args.username) + if (err != nil) != tt.wantErr { + t.Errorf("Client.DeleteUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Client.DeleteUser() = %v, want %v", got, tt.want) + return + } + t.Logf("Client.DeleteUser() statusCode = %d ", got) + }) + } +} func TestClient_GetGroups(t *testing.T) { // setup a http client for use in mocking @@ -371,39 +404,74 @@ func TestClient_AddGroup(t *testing.T) { } } -// func TestClient_DeleteGroup(t *testing.T) { -// type fields struct { -// client *gitlab.Client -// token string -// apiURL string -// } -// type args struct { -// groupID int -// } -// tests := []struct { -// name string -// fields fields -// args args -// want int -// wantErr bool -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := Client{ -// client: tt.fields.client, -// token: tt.fields.token, -// apiURL: tt.fields.apiURL, -// } -// got, err := r.DeleteGroup(tt.args.groupID) -// if (err != nil) != tt.wantErr { -// t.Errorf("Client.DeleteGroup() error = %v, wantErr %v", err, tt.wantErr) -// return -// } -// if got != tt.want { -// t.Errorf("Client.DeleteGroup() = %v, want %v", got, tt.want) -// } -// }) -// } -// } +func TestClient_DeleteGroup(t *testing.T) { + + // setup a http client for use in mocking + testHTTPClient := &http.Client{} + httpmock.ActivateNonDefault(testHTTPClient) + defer httpmock.DeactivateAndReset() + // empty gitlab User array + testGroupArray := []*gogitlab.Group{} + testGroup1 := gogitlab.Group{ID: 1, Name: "group joedirt"} + testGroup2 := gogitlab.Group{ID: 2, Name: "group spongebob"} + testGroupArray = append(testGroupArray, &testGroup1, &testGroup2) + + httpmock.RegisterResponder("DELETE", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(202, testGroup1), + ) + httpmock.RegisterResponder("GET", + `=~^https://test/api/v4/groups.*`, + httpmock.NewJsonResponderOrPanic(200, testGroupArray), + ) + + // test objects + testGroupID := 1 + testAPIUrl := "https://test/api/v4/" + testToken := "token" + // create a gitlab Client object, inject http client to allow for mocking using httpmock + testGitlabClient, _ := gogitlab.NewClient(testToken, gogitlab.WithBaseURL(testAPIUrl), gogitlab.WithHTTPClient(testHTTPClient)) + + type fields struct { + client *gogitlab.Client + token string + apiURL string + } + type args struct { + groupID int + } + tests := []struct { + name string + fields fields + args args + want int + wantErr bool + }{ + { + name: "DeleteGroup 1", + fields: fields{client: testGitlabClient, token: testToken, apiURL: testAPIUrl}, + args: args{groupID: testGroupID}, + want: 202, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := Client{ + client: tt.fields.client, + token: tt.fields.token, + apiURL: tt.fields.apiURL, + } + got, err := r.DeleteGroup(tt.args.groupID) + if (err != nil) != tt.wantErr { + t.Errorf("Client.DeleteGroup() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Client.ç() = %v, want %v", got, tt.want) + return + } + t.Logf("Client.DeleteGroup() statusCode = %d ", got) + }) + } +} -- GitLab From da9174f24919501ac2db20ef09a48e4dcd3f513c Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Thu, 17 Jun 2021 10:03:21 -0600 Subject: [PATCH 17/20] add rlog update readme --- README.md | 14 ++++++++++++-- clients/gitlab/client.go | 8 +++++++- go.mod | 1 + go.sum | 2 ++ valkyrie.png | Bin 0 -> 34798 bytes 5 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 valkyrie.png diff --git a/README.md b/README.md index 3dc6dd2..948202f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,14 @@ -## Project Management +## Valkyrie Automated CICD Provisioning Tool + +### Golang Frameworks +- rlog + - https://github.com/romana/rlog +- go-gitlab + - https://github.com/xanzy/go-gitlab + - https://docs.gitlab.com/ee/api/api_resources.html + - https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation +- http-mock + - https://github.com/jarcoal/httpmock ### lint fmt vet - run linter @@ -6,7 +16,7 @@ - use make - `make fmt vet lint` -### Tests +### Testing - run tests locally ``` make build diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 201274c..56fe3a1 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -6,8 +6,9 @@ package gitlab // https://pkg.go.dev/github.com/xanzy/go-gitlab?utm_source=godoc#section-documentation import ( + "fmt" "net/http" - + "github.com/romana/rlog" gitlab "github.com/xanzy/go-gitlab" ) @@ -33,6 +34,7 @@ func NewClient(apiURL string, token string, httpClient *http.Client) Client { gitlabClient, _ = gitlab.NewClient(token, gitlab.WithBaseURL(apiURL), gitlab.WithHTTPClient(httpClient)) } + rlog.Debug("Created new Client instance") return Client{ client: gitlabClient, token: token, @@ -77,6 +79,7 @@ func (r Client) GetUsers(search *string) ([]*gitlab.User, error) { } } + rlog.Debug(fmt.Sprintf("Completed call to GetUsers. Records returned: %d", len(userList))) return userList, nil } @@ -92,6 +95,7 @@ func (r Client) AddUser(user *gitlab.User, password string) (*gitlab.User, int, if err != nil { return nil, res.StatusCode, err } + rlog.Debug(fmt.Sprintf("Completed call to AddUser. Status Code: %d", res.StatusCode)) return newUser, res.StatusCode, nil } @@ -106,6 +110,7 @@ func (r Client) DeleteUser(username string) (int, error) { if err != nil { return res.StatusCode, err } + rlog.Debug(fmt.Sprintf("Completed call to DeleteUser. Status Code: %d", res.StatusCode)) return res.StatusCode, nil } @@ -145,6 +150,7 @@ func (r Client) GetGroups(search *string) ([]*gitlab.Group, error) { } } + rlog.Debug(fmt.Sprintf("Completed call to GetGroups. Records returned: %d", len(groupList))) return groupList, nil } diff --git a/go.mod b/go.mod index 57c0834..0577e83 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/jinzhu/copier v0.3.2 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 + github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 // indirect github.com/xanzy/go-gitlab v0.50.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect diff --git a/go.sum b/go.sum index 7fe0772..f0aca23 100644 --- a/go.sum +++ b/go.sum @@ -344,6 +344,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 h1:jkvpcEatpwuMF5O5LVxTnehj6YZ/aEZN4NWD/Xml4pI= +github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7/go.mod h1:KTrHyWpO1sevuXPZwyeZc72ddWRFqNSKDFl7uVWKpg0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= diff --git a/valkyrie.png b/valkyrie.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d118c1748d1a7f32ca8a91a74107414dd9f49a GIT binary patch literal 34798 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 rbUt^?ya@TA6?^QilIO0F-r3 literal 0 HcmV?d00001 -- GitLab From 0273f320d6a8feb061fd7fbbe51b2b2c5255bd6b Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Thu, 17 Jun 2021 10:45:22 -0600 Subject: [PATCH 18/20] add int test --- clients/gitlab/client.go | 55 +++++++++++++- clients/gitlab/client_test.go | 76 ++++++++++++++++++- go.mod | 2 +- .../gitlab/api/gitlab_api_test.go | 16 ++++ 4 files changed, 146 insertions(+), 3 deletions(-) diff --git a/clients/gitlab/client.go b/clients/gitlab/client.go index 56fe3a1..c254baa 100644 --- a/clients/gitlab/client.go +++ b/clients/gitlab/client.go @@ -7,9 +7,9 @@ package gitlab import ( "fmt" - "net/http" "github.com/romana/rlog" gitlab "github.com/xanzy/go-gitlab" + "net/http" ) const itemsPerPage = 50 @@ -177,3 +177,56 @@ func (r Client) DeleteGroup(groupID int) (int, error) { } return res.StatusCode, nil } + +// GetProject - +func (r Client) GetProject(projectID int) (*gitlab.Project, error) { + opts := gitlab.GetProjectOptions{} + project, res, err := r.client.Projects.GetProject(projectID, &opts) + if err != nil { + return nil, err + } + + rlog.Debug(fmt.Sprintf("Completed call to GetProject. status Code: %d", res.StatusCode)) + return project, nil +} + +// GetProjects - +func (r Client) GetProjects(search *string) ([]*gitlab.Project, error) { + listOptions := gitlab.ListOptions{ + Page: 1, + PerPage: itemsPerPage, + } + + // some valid values path, name + var orderBy = "path" + var opts = gitlab.ListProjectsOptions{ + ListOptions: listOptions, + OrderBy: &orderBy, + } + // if search defined add it to opts + + if search != nil { + opts.Search = search + } + projectList := []*gitlab.Project{} + var more = true + for more { + opts := gitlab.ListProjectsOptions{} + projects, res, err := r.client.Projects.ListProjects(&opts) + if err != nil { + return nil, err + } + for x := 0; x < len(projects); x++ { + projectList = append(projectList, projects[x]) + } + + if res.NextPage > 0 { + opts.ListOptions.Page = opts.ListOptions.Page + 1 + } else { + more = false + } + + } + rlog.Debug(fmt.Sprintf("Completed call to GetProjectss. Records returned: %d", len(projectList))) + return projectList, nil +} diff --git a/clients/gitlab/client_test.go b/clients/gitlab/client_test.go index 0669db8..5185c49 100644 --- a/clients/gitlab/client_test.go +++ b/clients/gitlab/client_test.go @@ -1,11 +1,11 @@ package gitlab import ( - "github.com/jarcoal/httpmock" "net/http" "reflect" "testing" + "github.com/jarcoal/httpmock" gogitlab "github.com/xanzy/go-gitlab" ) @@ -475,3 +475,77 @@ func TestClient_DeleteGroup(t *testing.T) { }) } } + +// func TestClient_GetProject(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// token string +// apiURL string +// } +// type args struct { +// projectID int +// } +// tests := []struct { +// name string +// fields fields +// args args +// want *gitlab.Project +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// token: tt.fields.token, +// apiURL: tt.fields.apiURL, +// } +// got, err := r.GetProject(tt.args.projectID) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.GetProject() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.GetProject() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func TestClient_GetProjects(t *testing.T) { +// type fields struct { +// client *gitlab.Client +// token string +// apiURL string +// } +// type args struct { +// search *string +// } +// tests := []struct { +// name string +// fields fields +// args args +// want []*gitlab.Project +// wantErr bool +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Client{ +// client: tt.fields.client, +// token: tt.fields.token, +// apiURL: tt.fields.apiURL, +// } +// got, err := r.GetProjects(tt.args.search) +// if (err != nil) != tt.wantErr { +// t.Errorf("Client.GetProjects() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if !reflect.DeepEqual(got, tt.want) { +// t.Errorf("Client.GetProjects() = %v, want %v", got, tt.want) +// } +// }) +// } +// } diff --git a/go.mod b/go.mod index 0577e83..3f509c0 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/jinzhu/copier v0.3.2 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 - github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 // indirect + github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 github.com/xanzy/go-gitlab v0.50.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect diff --git a/integration-tests/gitlab/api/gitlab_api_test.go b/integration-tests/gitlab/api/gitlab_api_test.go index 7921998..3f4e54e 100644 --- a/integration-tests/gitlab/api/gitlab_api_test.go +++ b/integration-tests/gitlab/api/gitlab_api_test.go @@ -188,3 +188,19 @@ func TestClient_deleteGroups(t *testing.T) { }) } + +func TestClient_getProjectsCount(t *testing.T) { + t.Run("test", func(t *testing.T) { + c, err := getClient() + if err != nil { + t.Errorf("Client.getClient() error = %v", err) + return + } + got, err := c.GetProjects(nil) + if err != nil { + t.Errorf("Client.GetGroups() error = %v", err) + return + } + t.Logf("Projects - number of projects %d", len(got)) + }) +} \ No newline at end of file -- GitLab From 793de8eb3cf208aaf324e53b64feff7d2337687d Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Thu, 17 Jun 2021 12:06:53 -0600 Subject: [PATCH 19/20] fix bad end of lines --- controllers/gitlab/gitlab_client.go | 112 +- controllers/gitlab/group_controller_test.go | 2312 +++++++++---------- controllers/gitlab/mocks_test.go | 848 +++---- 3 files changed, 1636 insertions(+), 1636 deletions(-) diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index abc9052..1e8b3dd 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -1,56 +1,56 @@ -package gitlab - -import gitlab "github.com/xanzy/go-gitlab" - -// Client is a facade for go-gitlab -type Client interface { - NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) - ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) - CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) - UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - -} - -// ClientImpl is the Default implementation for the facade. -type ClientImpl struct { - client *gitlab.Client -} - -// NewClient is a facade for the go-gitlab client NewClient call -func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - client, err := gitlab.NewClient(token, options...) - c.client = client - return client, err -} - -// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. -func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return c.client.Groups.ListGroups(opt, options...) -} - -// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. -func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return c.client.Groups.ListGroups( - &gitlab.ListGroupsOptions{ - Search: &name, - }) -} - -// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup -func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.CreateGroupOptions{ - Name: &name, - Description: &description, - } - return c.client.Groups.CreateGroup(&opt) -} - -// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup -func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.UpdateGroupOptions{ - Name: &name, - Description: &descrption, - } - return c.client.Groups.UpdateGroup(id, &opt) -} +package gitlab + +import gitlab "github.com/xanzy/go-gitlab" + +// Client is a facade for go-gitlab +type Client interface { + NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) + CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) + UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) + +} + +// ClientImpl is the Default implementation for the facade. +type ClientImpl struct { + client *gitlab.Client +} + +// NewClient is a facade for the go-gitlab client NewClient call +func (c ClientImpl) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + client, err := gitlab.NewClient(token, options...) + c.client = client + return client, err +} + +// ListGroups is a facade for the go-gitlab client.Groups.ListGroups call. +func (c ClientImpl) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups(opt, options...) +} + +// ListGroupsByName is a facade for the go-gitlab client.Groups.ListGroups call, with the group name as a search option. +func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return c.client.Groups.ListGroups( + &gitlab.ListGroupsOptions{ + Search: &name, + }) +} + +// CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup +func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.CreateGroupOptions{ + Name: &name, + Description: &description, + } + return c.client.Groups.CreateGroup(&opt) +} + +// UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup +func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { + opt := gitlab.UpdateGroupOptions{ + Name: &name, + Description: &descrption, + } + return c.client.Groups.UpdateGroup(id, &opt) +} diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 7f7f008..17e3c55 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1,1156 +1,1156 @@ -package gitlab - -import ( - "context" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "net/http" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the API Object", func() { - Context("the API Object isn't found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) - }) - It("should return an empty result, and ignore the error.", func() { - Expect(result).Should(Equal(ctrl.Result{})) - Expect(err).Should(BeNil()) - }) - }) - }) -}) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - clientMock.GetFunction = nil - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) - }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("groupExists fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("createGroup fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ &gitlab.Group{ - ID: 0, - Name: "", - Path: "", - Description: "", - MembershipLock: false, - Visibility: "", - LFSEnabled: false, - AvatarURL: "", - WebURL: "", - RequestAccessEnabled: false, - FullName: "", - FullPath: "", - ParentID: 0, - Projects: nil, - Statistics: nil, - CustomAttributes: nil, - ShareWithGroupLock: false, - RequireTwoFactorAuth: false, - TwoFactorGracePeriod: 0, - ProjectCreationLevel: "", - AutoDevopsEnabled: false, - SubGroupCreationLevel: "", - EmailsDisabled: false, - MentionsDisabled: false, - RunnersToken: "", - SharedProjects: nil, - SharedWithGroups: nil, - LDAPCN: "", - LDAPAccess: 0, - LDAPGroupLinks: nil, - SharedRunnersMinutesLimit: 0, - ExtraSharedRunnersMinutesLimit: 0, - MarkedForDeletionOn: nil, - CreatedAt: nil, - } } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateGroup fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateStatus fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return &MockError{ "error"} - }, - }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("processProjects fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, - createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ - return &MockError{message: "error"} - }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) - }) - }) - -var _ = - Describe("groupExists", func() { - When("the gitlab client fails", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "mockedError", - } - }, - }, - } - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) - It("should return that it didn't find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return a nil group", func() { - Expect(groups).To(BeNil()) - }) - It("should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - }) - When("the group is in the list", func() { - group := &gitlab.Group{ Name: "A group" } - returnGroups := []*gitlab.Group{ group } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return returnGroups, &gitlab.Response{}, nil - }, - }, - } - - found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it found the group", func() { - Expect(found).To(Equal(true)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return list with the group", func() { - Expect(returnedGroup).To(Equal(group)) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - When("the group is not in the list", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, nil - }, - }, - } - - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it did not find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return nil for the group list", func() { - Expect(groups).To(BeNil()) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - }) -var _ = - Describe("createGroup", func() { - expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, - }, - } - group, response, err := sut.createGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) - }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) - }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = - Describe("updateGroup", func() { - expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil - }, - }, - } - group, response, err := sut.updateGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) - }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) - }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = - Describe("SetupWithManager", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - err := sut.SetupWithManager(&MockManager{ - Log: &loggerMock, - builder: builder, - }) - It("should setup the default gitlab client", func() { - Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) - }) - It("should return no error", func() { - Expect(err).To(BeNil()) - }) - }) - -var _ = Describe("updateStatus", func() { - When("group id is not 0", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }} - sut.updateStatus(&MockContext{}, 1, &group) - - It("should update the gitlab id", func() { - Expect(*group.Status.GitlabID).To(Equal(int64(1))) - }) - It("should update the last updated time", func() { - Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) - }) - }) -}) - -var _ = - Describe("updateProject", func() { - spec := gitlabv1alpha1.ProjectSpec{ - Name: "a project", - Path: "https://example.com.path", - ImpactLevel: "2", - StorageTypes: nil, - VirtualService: "default", - ServicePort: 8081, - Language: "golang", - } - project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - mockError := &MockError{"true"} - clientMock := MockClient{ - statusWriter: MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return mockError - }, - } - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - - returnedProject, err := sut.updateProject(context.TODO(), spec, &project) - It("should update the project from the project specification", func() { - Expect(project.Spec.Name).To(Equal(spec.Name)) - Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) - Expect(project.Spec.Language).To(Equal(spec.Language)) - Expect(project.Spec.Path).To(Equal(spec.Path)) - Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) - Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) - Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) - }) - It("should call the kubernetes client update function", func() { - Expect(clientMock.updateFunctionCalled).To(BeTrue()) - }) - It("should return the result of the update function", func() { - Expect(err).To(Equal(mockError)) - }) - It("should return the updated project", func() { - Expect(project).To(Equal(*returnedProject)) - }) - }) +package gitlab + +import ( + "context" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "net/http" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the API Object", func() { + Context("the API Object isn't found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch group")) + }) + It("should return an empty result, and ignore the error.", func() { + Expect(result).Should(Equal(ctrl.Result{})) + Expect(err).Should(BeNil()) + }) + }) + }) +}) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + clientMock.GetFunction = nil + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + result, err := sut.Reconcile(contextMock, requestMock) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) + }) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) + }) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log an error regarding the missing credentials", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) + }) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) + }) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { + Context("can't create client", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return nil, &MockError{ + message: "mocked error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("groupExists fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("createGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ &gitlab.Group{ + ID: 0, + Name: "", + Path: "", + Description: "", + MembershipLock: false, + Visibility: "", + LFSEnabled: false, + AvatarURL: "", + WebURL: "", + RequestAccessEnabled: false, + FullName: "", + FullPath: "", + ParentID: 0, + Projects: nil, + Statistics: nil, + CustomAttributes: nil, + ShareWithGroupLock: false, + RequireTwoFactorAuth: false, + TwoFactorGracePeriod: 0, + ProjectCreationLevel: "", + AutoDevopsEnabled: false, + SubGroupCreationLevel: "", + EmailsDisabled: false, + MentionsDisabled: false, + RunnersToken: "", + SharedProjects: nil, + SharedWithGroups: nil, + LDAPCN: "", + LDAPAccess: 0, + LDAPGroupLinks: nil, + SharedRunnersMinutesLimit: 0, + ExtraSharedRunnersMinutesLimit: 0, + MarkedForDeletionOn: nil, + CreatedAt: nil, + } } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateStatus fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return &MockError{ "error"} + }, + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("processProjects fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ + return &MockError{message: "error"} + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string { "accessToken" : "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{ {Name: "agroup"} } + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) + }) + +var _ = + Describe("groupExists", func() { + When("the gitlab client fails", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "mockedError", + } + }, + }, + } + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) + It("should return that it didn't find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return a nil group", func() { + Expect(groups).To(BeNil()) + }) + It("should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + }) + When("the group is in the list", func() { + group := &gitlab.Group{ Name: "A group" } + returnGroups := []*gitlab.Group{ group } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return returnGroups, &gitlab.Response{}, nil + }, + }, + } + + found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it found the group", func() { + Expect(found).To(Equal(true)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return list with the group", func() { + Expect(returnedGroup).To(Equal(group)) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + When("the group is not in the list", func() { + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, nil + }, + }, + } + + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it did not find the group", func() { + Expect(found).To(Equal(false)) + }) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) + }) + It("should return nil for the group list", func() { + Expect(groups).To(BeNil()) + }) + It("should not return an error", func() { + Expect(err).To(BeNil()) + }) + }) + }) +var _ = + Describe("createGroup", func() { + expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + }, + } + group, response, err := sut.createGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func(){ + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = + Describe("updateGroup", func() { + expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil + }, + }, + } + group, response, err := sut.updateGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func(){ + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = + Describe("SetupWithManager", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + err := sut.SetupWithManager(&MockManager{ + Log: &loggerMock, + builder: builder, + }) + It("should setup the default gitlab client", func() { + Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) + }) + +var _ = Describe("updateStatus", func() { + When("group id is not 0", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + group := gitlabv1alpha1.Group{Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }} + sut.updateStatus(&MockContext{}, 1, &group) + + It("should update the gitlab id", func() { + Expect(*group.Status.GitlabID).To(Equal(int64(1))) + }) + It("should update the last updated time", func() { + Expect(group.Status.LastUpdatedTime.Time).To(Not(BeNil())) + }) + }) +}) + +var _ = + Describe("updateProject", func() { + spec := gitlabv1alpha1.ProjectSpec{ + Name: "a project", + Path: "https://example.com.path", + ImpactLevel: "2", + StorageTypes: nil, + VirtualService: "default", + ServicePort: 8081, + Language: "golang", + } + project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + mockError := &MockError{"true"} + clientMock := MockClient{ + statusWriter: MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }, + }, + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return mockError + }, + } + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + + returnedProject, err := sut.updateProject(context.TODO(), spec, &project) + It("should update the project from the project specification", func() { + Expect(project.Spec.Name).To(Equal(spec.Name)) + Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) + Expect(project.Spec.Language).To(Equal(spec.Language)) + Expect(project.Spec.Path).To(Equal(spec.Path)) + Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) + Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) + Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) + }) + It("should call the kubernetes client update function", func() { + Expect(clientMock.updateFunctionCalled).To(BeTrue()) + }) + It("should return the result of the update function", func() { + Expect(err).To(Equal(mockError)) + }) + It("should return the updated project", func() { + Expect(project).To(Equal(*returnedProject)) + }) + }) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index ecca40c..d96b11d 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -1,424 +1,424 @@ -package gitlab - -import ( - "context" - "github.com/go-logr/logr" - "github.com/jinzhu/copier" - "github.com/xanzy/go-gitlab" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - "net/http" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "time" - "valkyrie.dso.mil/valkyrie-api/controllers" -) - -type MockClient struct { - GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error - GetCalled bool - expectedObjects map[client.ObjectKey]client.Object - NotFoundError error - statusWriter client.StatusWriter - createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error - updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error - updateFunctionCalled bool -} - -func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { - m.GetCalled = true - if m.GetFunction != nil { - return m.GetFunction(ctx, key, obj) - } - - if m.expectedObjects == nil { - return nil - } - - if m.expectedObjects[key] == nil { - return &errors.StatusError{ - ErrStatus: metav1.Status{ - TypeMeta: metav1.TypeMeta{}, - ListMeta: metav1.ListMeta{}, - Status: string(metav1.StatusReasonNotFound), - Message: "NotFound", - Reason: metav1.StatusReasonNotFound, - Details: nil, - Code: 0, - }, - } - } - - foundObject := m.expectedObjects[key] - - copier.Copy(obj, foundObject) - - return nil -} - -func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - panic("implement me") -} - -func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - if m.createFunction == nil { - return nil - } - return m.createFunction(ctx, obj, opts...) -} - -func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { - panic("implement me") -} - -func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - m.updateFunctionCalled = true - if m.updateFunction == nil { - return nil - } - return m.updateFunction(ctx, obj, opts...) -} - -func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { - panic("implement me") -} - -type MockStatusWriter struct { - updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error -} - -func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return m.updateFunction(ctx, obj, opts...) -} - -func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - panic("implement me") -} - -func (m *MockClient) Status() client.StatusWriter { - return m.statusWriter -} - -func (m *MockClient) Scheme() *runtime.Scheme { - panic("implement me") -} - -func (m *MockClient) RESTMapper() meta.RESTMapper { - panic("implement me") -} - -type MockContext struct { -} - -func (m *MockContext) Deadline() (deadline time.Time, ok bool) { - panic("implement me") -} - -func (m *MockContext) Done() <-chan struct{} { - panic("implement me") -} - -func (m *MockContext) Err() error { - panic("implement me") -} - -func (m *MockContext) Value(key interface{}) interface{} { - panic("implement me") -} - -type MockLogger struct { - WithValuesKeysAndValues []interface{} - WithValuesCalled bool - WithNameValue string - WithNameCalled bool - loggedMessage string - keysAndValues []interface{} - logFunction func(msg string, keysAndValues ...interface{}) - logLevelCalled string - level int -} - -func (m *MockLogger) Enabled() bool { - return true -} - -func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Info" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { - m.loggedMessage = msg - m.keysAndValues = keysAndValues - m.logLevelCalled = "Error" - if m.logFunction != nil { - m.logFunction(msg, keysAndValues) - return - } -} - -func (m *MockLogger) V(level int) logr.Logger { - m.level = level - return m -} - -func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { - m.WithValuesCalled = true - for i := 0; i < len(keysAndValues); i++ { - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) - } - m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) - - return m -} - -func (m *MockLogger) WithName(name string) logr.Logger { - m.WithNameCalled = true - m.WithNameValue = name - return m -} - -type MockController struct { - setupWithManagerWasCalled bool -} - -func (m *MockController) SetupWithManager(manager manager.Manager) error { - m.setupWithManagerWasCalled = true - return nil -} - -type MockManager struct { - Log logr.Logger - addHealthzCheckFunction func(name string, check healthz.Checker) error - addHealthzCheckWasCalled bool - addReadyzCheckFunction func(name string, check healthz.Checker) error - addReadyzCheckWasCalled bool - startFunction func(ctx context.Context) error - startWasCalled bool - builder *scheme.Builder - Fields []interface{} - runnable manager.Runnable -} - -func (m *MockManager) Add(runnable manager.Runnable) error { - m.runnable = runnable - - return nil -} - -func (m *MockManager) Elected() <-chan struct{} { - panic("implement me") -} - -func (m *MockManager) SetFields(i interface{}) error { - if m.Fields == nil { - m.Fields = make([]interface{}, 0) - } - m.Fields = append(m.Fields, i) - - return nil -} - -func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { - panic("implement me") -} - -func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { - m.addHealthzCheckWasCalled = true - if m.addHealthzCheckFunction == nil { - return nil - } - return m.addHealthzCheckFunction(name, check) -} - -func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { - m.addReadyzCheckWasCalled = true - if m.addReadyzCheckFunction == nil { - return nil - } - - return m.addReadyzCheckFunction(name, check) -} - -func (m *MockManager) Start(ctx context.Context) error { - m.startWasCalled = true - if m.startFunction == nil { - return nil - } - return m.startFunction(ctx) -} - -func (m *MockManager) GetConfig() *rest.Config { - return &rest.Config{} -} - -func (m *MockManager) GetScheme() *runtime.Scheme { - scheme, _ := m.builder.Build() - return scheme -} - -func (m *MockManager) GetClient() client.Client { - return nil -} - -func (m *MockManager) GetFieldIndexer() client.FieldIndexer { - panic("implement me") -} - -func (m *MockManager) GetCache() cache.Cache { - panic("implement me") -} - -func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { - panic("implement me") -} - -func (m *MockManager) GetRESTMapper() meta.RESTMapper { - panic("implement me") -} - -func (m *MockManager) GetAPIReader() client.Reader { - panic("implement me") -} - -func (m *MockManager) GetWebhookServer() *webhook.Server { - panic("implement me") -} - -func (m *MockManager) GetLogger() logr.Logger { - return m.Log -} - -type MockRunFunctionParameters struct { - options zap.Options - metricsAddress string - healthProbeAddress string - enableLeaderElection bool -} - -type MockDriver struct { - parseCommandLineCalled bool - parseCommandLineFunction func() (string, bool, string, zap.Options, error) - runCalled bool - runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) - runFunctionParameters MockRunFunctionParameters - newManagerCalled bool - newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) - instantiateControllersCalled bool - instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController - setupControllerWithManagerCalled bool - setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error -} - -func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { - m.parseCommandLineCalled = true - if m.parseCommandLineFunction == nil { - return "", true, "", zap.Options{}, nil - } - - return m.parseCommandLineFunction() -} - -func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { - m.runCalled = true - m.runFunctionParameters = MockRunFunctionParameters{ - options: opts, - metricsAddress: metricsAddress, - healthProbeAddress: healthProbeAddress, - enableLeaderElection: enableLeaderElection, - } - if m.runFunction == nil { - return 0, nil - } - - return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { - m.newManagerCalled = true - if m.newManagerFunction == nil { - return &MockManager{}, nil - } - - return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) -} - -func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { - m.instantiateControllersCalled = true - - if m.instantiateControllersFunction == nil { - return []controllers.ManagedController{ - &MockController{}, - } - } - - return m.instantiateControllersFunction(mgr) -} - -func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { - m.setupControllerWithManagerCalled = true - - if m.setupControllersFunction == nil { - return nil - } - - return m.setupControllersFunction(controller, manager) -} - -type MockError struct { - message string -} - -func (m *MockError) Error() string { - if m.message == "" { - m.message = "mock Error" - } - return m.message -} - -type MockGitlabClient struct { - newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) - listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) - createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) - updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - -} - -func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return m.createGroupFunction(name, description) -} - -func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return m.updateGroupFunction(id, name, description) -} - -func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsByNameFunction(name) -} - -func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return m.newClientFunction(token, options...) -} - -func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { - return m.listGroupsFunction(opt, options...) -} +package gitlab + +import ( + "context" + "github.com/go-logr/logr" + "github.com/jinzhu/copier" + "github.com/xanzy/go-gitlab" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + "net/http" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "time" + "valkyrie.dso.mil/valkyrie-api/controllers" +) + +type MockClient struct { + GetFunction func(ctx context.Context, key client.ObjectKey, obj client.Object) error + GetCalled bool + expectedObjects map[client.ObjectKey]client.Object + NotFoundError error + statusWriter client.StatusWriter + createFunction func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error + updateFunctionCalled bool +} + +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + m.GetCalled = true + if m.GetFunction != nil { + return m.GetFunction(ctx, key, obj) + } + + if m.expectedObjects == nil { + return nil + } + + if m.expectedObjects[key] == nil { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Status: string(metav1.StatusReasonNotFound), + Message: "NotFound", + Reason: metav1.StatusReasonNotFound, + Details: nil, + Code: 0, + }, + } + } + + foundObject := m.expectedObjects[key] + + copier.Copy(obj, foundObject) + + return nil +} + +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + panic("implement me") +} + +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + if m.createFunction == nil { + return nil + } + return m.createFunction(ctx, obj, opts...) +} + +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + panic("implement me") +} + +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.updateFunctionCalled = true + if m.updateFunction == nil { + return nil + } + return m.updateFunction(ctx, obj, opts...) +} + +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + panic("implement me") +} + +type MockStatusWriter struct { + updateFunction func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error +} + +func (m MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return m.updateFunction(ctx, obj, opts...) +} + +func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + panic("implement me") +} + +func (m *MockClient) Status() client.StatusWriter { + return m.statusWriter +} + +func (m *MockClient) Scheme() *runtime.Scheme { + panic("implement me") +} + +func (m *MockClient) RESTMapper() meta.RESTMapper { + panic("implement me") +} + +type MockContext struct { +} + +func (m *MockContext) Deadline() (deadline time.Time, ok bool) { + panic("implement me") +} + +func (m *MockContext) Done() <-chan struct{} { + panic("implement me") +} + +func (m *MockContext) Err() error { + panic("implement me") +} + +func (m *MockContext) Value(key interface{}) interface{} { + panic("implement me") +} + +type MockLogger struct { + WithValuesKeysAndValues []interface{} + WithValuesCalled bool + WithNameValue string + WithNameCalled bool + loggedMessage string + keysAndValues []interface{} + logFunction func(msg string, keysAndValues ...interface{}) + logLevelCalled string + level int +} + +func (m *MockLogger) Enabled() bool { + return true +} + +func (m *MockLogger) Info(msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Info" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) Error(err error, msg string, keysAndValues ...interface{}) { + m.loggedMessage = msg + m.keysAndValues = keysAndValues + m.logLevelCalled = "Error" + if m.logFunction != nil { + m.logFunction(msg, keysAndValues) + return + } +} + +func (m *MockLogger) V(level int) logr.Logger { + m.level = level + return m +} + +func (m *MockLogger) WithValues(keysAndValues ...interface{}) logr.Logger { + m.WithValuesCalled = true + for i := 0; i < len(keysAndValues); i++ { + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues[i]) + } + m.WithValuesKeysAndValues = append(m.WithValuesKeysAndValues, keysAndValues) + + return m +} + +func (m *MockLogger) WithName(name string) logr.Logger { + m.WithNameCalled = true + m.WithNameValue = name + return m +} + +type MockController struct { + setupWithManagerWasCalled bool +} + +func (m *MockController) SetupWithManager(manager manager.Manager) error { + m.setupWithManagerWasCalled = true + return nil +} + +type MockManager struct { + Log logr.Logger + addHealthzCheckFunction func(name string, check healthz.Checker) error + addHealthzCheckWasCalled bool + addReadyzCheckFunction func(name string, check healthz.Checker) error + addReadyzCheckWasCalled bool + startFunction func(ctx context.Context) error + startWasCalled bool + builder *scheme.Builder + Fields []interface{} + runnable manager.Runnable +} + +func (m *MockManager) Add(runnable manager.Runnable) error { + m.runnable = runnable + + return nil +} + +func (m *MockManager) Elected() <-chan struct{} { + panic("implement me") +} + +func (m *MockManager) SetFields(i interface{}) error { + if m.Fields == nil { + m.Fields = make([]interface{}, 0) + } + m.Fields = append(m.Fields, i) + + return nil +} + +func (m *MockManager) AddMetricsExtraHandler(path string, handler http.Handler) error { + panic("implement me") +} + +func (m *MockManager) AddHealthzCheck(name string, check healthz.Checker) error { + m.addHealthzCheckWasCalled = true + if m.addHealthzCheckFunction == nil { + return nil + } + return m.addHealthzCheckFunction(name, check) +} + +func (m *MockManager) AddReadyzCheck(name string, check healthz.Checker) error { + m.addReadyzCheckWasCalled = true + if m.addReadyzCheckFunction == nil { + return nil + } + + return m.addReadyzCheckFunction(name, check) +} + +func (m *MockManager) Start(ctx context.Context) error { + m.startWasCalled = true + if m.startFunction == nil { + return nil + } + return m.startFunction(ctx) +} + +func (m *MockManager) GetConfig() *rest.Config { + return &rest.Config{} +} + +func (m *MockManager) GetScheme() *runtime.Scheme { + scheme, _ := m.builder.Build() + return scheme +} + +func (m *MockManager) GetClient() client.Client { + return nil +} + +func (m *MockManager) GetFieldIndexer() client.FieldIndexer { + panic("implement me") +} + +func (m *MockManager) GetCache() cache.Cache { + panic("implement me") +} + +func (m *MockManager) GetEventRecorderFor(name string) record.EventRecorder { + panic("implement me") +} + +func (m *MockManager) GetRESTMapper() meta.RESTMapper { + panic("implement me") +} + +func (m *MockManager) GetAPIReader() client.Reader { + panic("implement me") +} + +func (m *MockManager) GetWebhookServer() *webhook.Server { + panic("implement me") +} + +func (m *MockManager) GetLogger() logr.Logger { + return m.Log +} + +type MockRunFunctionParameters struct { + options zap.Options + metricsAddress string + healthProbeAddress string + enableLeaderElection bool +} + +type MockDriver struct { + parseCommandLineCalled bool + parseCommandLineFunction func() (string, bool, string, zap.Options, error) + runCalled bool + runFunction func(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) + runFunctionParameters MockRunFunctionParameters + newManagerCalled bool + newManagerFunction func(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) + instantiateControllersCalled bool + instantiateControllersFunction func(mgr manager.Manager) []controllers.ManagedController + setupControllerWithManagerCalled bool + setupControllersFunction func(controller controllers.ManagedController, manager manager.Manager) error +} + +func (m *MockDriver) parseCommandLine() (string, bool, string, zap.Options, error) { + m.parseCommandLineCalled = true + if m.parseCommandLineFunction == nil { + return "", true, "", zap.Options{}, nil + } + + return m.parseCommandLineFunction() +} + +func (m *MockDriver) run(opts zap.Options, metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (int, error) { + m.runCalled = true + m.runFunctionParameters = MockRunFunctionParameters{ + options: opts, + metricsAddress: metricsAddress, + healthProbeAddress: healthProbeAddress, + enableLeaderElection: enableLeaderElection, + } + if m.runFunction == nil { + return 0, nil + } + + return m.runFunction(opts, metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) newManager(metricsAddress string, healthProbeAddress string, enableLeaderElection bool) (manager.Manager, error) { + m.newManagerCalled = true + if m.newManagerFunction == nil { + return &MockManager{}, nil + } + + return m.newManagerFunction(metricsAddress, healthProbeAddress, enableLeaderElection) +} + +func (m *MockDriver) instantiateControllers(mgr manager.Manager) []controllers.ManagedController { + m.instantiateControllersCalled = true + + if m.instantiateControllersFunction == nil { + return []controllers.ManagedController{ + &MockController{}, + } + } + + return m.instantiateControllersFunction(mgr) +} + +func (m *MockDriver) setupControllerWithManager(controller controllers.ManagedController, manager manager.Manager) error { + m.setupControllerWithManagerCalled = true + + if m.setupControllersFunction == nil { + return nil + } + + return m.setupControllersFunction(controller, manager) +} + +type MockError struct { + message string +} + +func (m *MockError) Error() string { + if m.message == "" { + m.message = "mock Error" + } + return m.message +} + +type MockGitlabClient struct { + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) + createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) + updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) + +} + +func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.createGroupFunction(name, description) +} + +func (m *MockGitlabClient) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return m.updateGroupFunction(id, name, description) +} + +func (m *MockGitlabClient) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsByNameFunction(name) +} + +func (m *MockGitlabClient) NewClient(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return m.newClientFunction(token, options...) +} + +func (m *MockGitlabClient) ListGroups(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) { + return m.listGroupsFunction(opt, options...) +} -- GitLab From 55f2a520c43ba612e62fd829957fe2d558f9f4a1 Mon Sep 17 00:00:00 2001 From: "adam.richards" Date: Thu, 17 Jun 2021 12:10:04 -0600 Subject: [PATCH 20/20] after make build - does make fmt --- controllers/gitlab/gitlab_client.go | 5 +- controllers/gitlab/group_controller.go | 34 +- controllers/gitlab/group_controller_test.go | 1711 +++++++++---------- controllers/gitlab/mocks_test.go | 9 +- 4 files changed, 870 insertions(+), 889 deletions(-) diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index 1e8b3dd..57999a4 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -9,7 +9,6 @@ type Client interface { ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Response, error) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) UpdateGroup(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - } // ClientImpl is the Default implementation for the facade. @@ -40,7 +39,7 @@ func (c ClientImpl) ListGroupsByName(name string) ([]*gitlab.Group, *gitlab.Resp // CreateGroup is a facade for the go-gitlab client.Groups.CreateGroup func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { opt := gitlab.CreateGroupOptions{ - Name: &name, + Name: &name, Description: &description, } return c.client.Groups.CreateGroup(&opt) @@ -49,7 +48,7 @@ func (c ClientImpl) CreateGroup(name string, description string) (*gitlab.Group, // UpdateGroup is a facade for the go-gitlab client.Group.UpdateGroup func (c ClientImpl) UpdateGroup(id string, name string, descrption string) (*gitlab.Group, *gitlab.Response, error) { opt := gitlab.UpdateGroupOptions{ - Name: &name, + Name: &name, Description: &descrption, } return c.client.Groups.UpdateGroup(id, &opt) diff --git a/controllers/gitlab/group_controller.go b/controllers/gitlab/group_controller.go index 08c6d0d..46f433d 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -33,18 +33,18 @@ import ( // Errors const ( - errorUnableToFetchGroup = "unable to fetch group" - errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" - errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" - errorUnableToCreateGitlabClient = "unable to create gitlab client" - errorWhileSearchingGroups = "Error while searching groups." - errorWhileCreatingGitlabGroup = "Error while creating GitLab group." - errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." - errorUnableToUpdateStatus = "Unable to update status." - errorUnableToProcessProjects = "Unable to process projects" - errorTryingToCreateProject = "Error trying to create project" - errorLookingUpProject = "Error looking up project" - errorUpdatingProject = "Error updating project" + errorUnableToFetchGroup = "unable to fetch group" + errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials" + errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials" + errorUnableToCreateGitlabClient = "unable to create gitlab client" + errorWhileSearchingGroups = "Error while searching groups." + errorWhileCreatingGitlabGroup = "Error while creating GitLab group." + errorWhileUpdatingGitlabGroup = "Error while updating GitLab group." + errorUnableToUpdateStatus = "Unable to update status." + errorUnableToProcessProjects = "Unable to process projects" + errorTryingToCreateProject = "Error trying to create project" + errorLookingUpProject = "Error looking up project" + errorUpdatingProject = "Error updating project" ) // Group Status @@ -57,7 +57,6 @@ const ( const valkyrie = "valkyrie" - // GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab // with credentials in the secret provided. It will then check the state of the Group, and create it if necessary. type GroupReconciler struct { @@ -142,7 +141,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if !found { if gitlabGroup, _, err = r.createGroup(group); err != nil { - + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) return ctrl.Result{Requeue: true}, err } @@ -155,7 +154,7 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { log.Error(err, errorUnableToUpdateStatus, "group", group) - return ctrl.Result{ Requeue: true }, err + return ctrl.Result{Requeue: true}, err } if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { @@ -202,7 +201,7 @@ func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Grou } func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) + gitlabGroup, response, err := r.gitlabClient.UpdateGroup(group.ObjectMeta.Annotations["ID"], group.Spec.Name, group.Spec.Description) if err == nil { group.Status.State = GroupOK @@ -214,7 +213,6 @@ func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Grou return gitlabGroup, response, err } - func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1alpha1.Group, specs []gitlabv1alpha1.ProjectSpec) error { for _, projectSpec := range specs { if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { @@ -269,7 +267,7 @@ func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alph }, }, }, - Spec: gitlabv1alpha1.ProjectSpec{ + Spec: gitlabv1alpha1.ProjectSpec{ Name: spec.Name, Path: spec.Path, ImpactLevel: spec.ImpactLevel, diff --git a/controllers/gitlab/group_controller_test.go b/controllers/gitlab/group_controller_test.go index 17e3c55..c97e086 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -14,9 +14,7 @@ import ( gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" ) - -var _ = - Describe("Reconcile", func() { +var _ = Describe("Reconcile", func() { builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) scheme, _ := builder.Build() When("it looks up the API Object", func() { @@ -28,9 +26,9 @@ var _ = WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) requestMock := ctrl.Request{} sut := GroupReconciler{ @@ -50,344 +48,73 @@ var _ = }) }) }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("it looks up the GitlabCredentals", func() { - Context("gitlab credentials are not found", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - clientMock.GetFunction = nil - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{}, - Status: gitlabv1alpha1.GroupStatus{}, - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - result, err := sut.Reconcile(contextMock, requestMock) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) - }) - It("should return a reconcile result, and the error.", func() { - Expect(result).To(Equal(ctrl.Result{ - Requeue: true, - })) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("getting the secret from the GitlabCredentials", func() { - Context("Secret doesn't exist", func() { - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log an error regarding the missing credentials", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) - }) - It("Should return a requeue result and an error", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - Expect(err).ToNot(BeNil()) - }) - }) - }) - }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("logging in to GitLab", func() { - Context("can't create client", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return nil, &MockError{ - message: "mocked error", - } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("Should log the failure", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) - }) - It("Should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - It("should requeue the group", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - }) - }) - }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("groupExists fails", func() { - loggerMock := &MockLogger{ +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("it looks up the GitlabCredentals", func() { + Context("gitlab credentials are not found", func() { + loggerMock := MockLogger{ WithValuesKeysAndValues: nil, WithValuesCalled: false, WithNameValue: "", WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, - Log: loggerMock, + Log: &loggerMock, Scheme: scheme, } requestMock.NamespacedName = types.NamespacedName{ Namespace: "default", Name: "testGroup", } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", - }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } + clientMock.GetFunction = nil group := gitlabv1alpha1.Group{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", + Spec: gitlabv1alpha1.GroupSpec{}, + Status: gitlabv1alpha1.GroupStatus{}, } clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, &MockError{ - message: "Error", - } - }, - } result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) It("should log the error", func() { Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch gitlab credentials")) }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) + It("should return a reconcile result, and the error.", func() { + Expect(result).To(Equal(ctrl.Result{ + Requeue: true, + })) + Expect(err).ToNot(BeNil()) }) }) }) - -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("createGroup fails", func() { - loggerMock := &MockLogger{ +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("getting the secret from the GitlabCredentials", func() { + Context("Secret doesn't exist", func() { + loggerMock := MockLogger{ WithValuesKeysAndValues: nil, WithValuesCalled: false, WithNameValue: "", WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, - Log: loggerMock, + Log: &loggerMock, Scheme: scheme, } requestMock.NamespacedName = types.NamespacedName{ @@ -417,114 +144,30 @@ var _ = }, Status: gitlabv1alpha1.GroupStatus{}, } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) clientMock.expectedObjects[requestMock.NamespacedName] = &group clientMock.expectedObjects[types.NamespacedName{ Namespace: "default", Name: group.Spec.GitlabCredentialsName, }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ &gitlab.Group{ - ID: 0, - Name: "", - Path: "", - Description: "", - MembershipLock: false, - Visibility: "", - LFSEnabled: false, - AvatarURL: "", - WebURL: "", - RequestAccessEnabled: false, - FullName: "", - FullPath: "", - ParentID: 0, - Projects: nil, - Statistics: nil, - CustomAttributes: nil, - ShareWithGroupLock: false, - RequireTwoFactorAuth: false, - TwoFactorGracePeriod: 0, - ProjectCreationLevel: "", - AutoDevopsEnabled: false, - SubGroupCreationLevel: "", - EmailsDisabled: false, - MentionsDisabled: false, - RunnersToken: "", - SharedProjects: nil, - SharedWithGroups: nil, - LDAPCN: "", - LDAPAccess: 0, - LDAPGroupLinks: nil, - SharedRunnersMinutesLimit: 0, - ExtraSharedRunnersMinutesLimit: 0, - MarkedForDeletionOn: nil, - CreatedAt: nil, - } } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "Error", - } - }, - } result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { + It("Should log an error regarding the missing credentials", func() { Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) + Expect(loggerMock.loggedMessage).To(Equal("unable to fetch secret from gitlab credentials")) }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) + It("Should return a requeue result and an error", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + Expect(err).ToNot(BeNil()) }) }) }) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateGroup fails", func() { +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("logging in to GitLab", func() { + Context("can't create client", func() { loggerMock := &MockLogger{ WithValuesKeysAndValues: nil, WithValuesCalled: false, @@ -532,9 +175,9 @@ var _ = WithNameCalled: false, } contextMock := context.TODO() - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} requestMock := ctrl.Request{} sut := GroupReconciler{ Client: &clientMock, @@ -572,7 +215,7 @@ var _ = Namespace: gitlabCred.Spec.AccessToken.Namespace, Name: gitlabCred.Spec.AccessToken.Name, } - stringData := map[string]string { "accessToken" : "password"} + stringData := map[string]string{"accessToken": "password"} secret := v1.Secret{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{}, @@ -588,12 +231,98 @@ var _ = clientMock.expectedObjects[secretNamespacedName] = &secret sut.gitlabClient = &MockGitlabClient{ newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil + return nil, &MockError{ + message: "mocked error", + } }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ + } + result, err := sut.Reconcile(contextMock, requestMock) + It("Should log the failure", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToCreateGitlabClient)) + }) + It("Should return an error", func() { + Expect(err).ToNot(BeNil()) + }) + It("should requeue the group", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + }) + }) +}) + +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("groupExists fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", + }, + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{ + Response: &http.Response{ Status: "", StatusCode: 0, Proto: "", @@ -615,452 +344,709 @@ var _ = CurrentPage: 0, NextPage: 0, PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ + }, &MockError{ message: "Error", } - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileSearchingGroups)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) }) }) +}) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("updateStatus fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return &MockError{ "error"} - }, +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("createGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{&gitlab.Group{ + ID: 0, + Name: "", + Path: "", + Description: "", + MembershipLock: false, + Visibility: "", + LFSEnabled: false, + AvatarURL: "", + WebURL: "", + RequestAccessEnabled: false, + FullName: "", + FullPath: "", + ParentID: 0, + Projects: nil, + Statistics: nil, + CustomAttributes: nil, + ShareWithGroupLock: false, + RequireTwoFactorAuth: false, + TwoFactorGracePeriod: 0, + ProjectCreationLevel: "", + AutoDevopsEnabled: false, + SubGroupCreationLevel: "", + EmailsDisabled: false, + MentionsDisabled: false, + RunnersToken: "", + SharedProjects: nil, + SharedWithGroups: nil, + LDAPCN: "", + LDAPAccess: 0, + LDAPGroupLinks: nil, + SharedRunnersMinutesLimit: 0, + ExtraSharedRunnersMinutesLimit: 0, + MarkedForDeletionOn: nil, + CreatedAt: nil, + }} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileCreatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) + }) + }) +}) +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateGroup fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: nil, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{{Name: "agroup"}} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, + }, + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "Error", + } + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroup)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) }) }) +}) -var _ = - Describe("Reconcile", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - When("processProjects fails", func() { - loggerMock := &MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - contextMock := context.TODO() - clientMock := MockClient{ - statusWriter: &MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("updateStatus fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return &MockError{"error"} }, - createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error{ - return &MockError{message: "error"} + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - } - requestMock := ctrl.Request{} - sut := GroupReconciler{ - Client: &clientMock, - Log: loggerMock, - Scheme: scheme, - } - requestMock.NamespacedName = types.NamespacedName{ - Namespace: "default", - Name: "testGroup", - } - gitlabCred := gitlabv1alpha1.GitlabCredentials{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GitlabCredentialsSpec{ - URL: "https://example.com", - Username: "ausername", - AccessToken: v1.SecretReference{ - Name: "asecret", - Namespace: "default", + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: nil, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{{Name: "agroup"}} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, }, - }, - Status: gitlabv1alpha1.GitlabCredentialsStatus{}, - } - group := gitlabv1alpha1.Group{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Spec: gitlabv1alpha1.GroupSpec{ - Name: "agroup", - GitlabCredentialsName: "gitlab-credentials", - ProjectSpecs: []gitlabv1alpha1.ProjectSpec{ { Name: "ProjectSpec"} }, - }, - Status: gitlabv1alpha1.GroupStatus{}, - } - secretNamespacedName := types.NamespacedName{ - Namespace: gitlabCred.Spec.AccessToken.Namespace, - Name: gitlabCred.Spec.AccessToken.Name, - } - stringData := map[string]string { "accessToken" : "password"} - secret := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - StringData: stringData, - Type: "opaque", - } - clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) - clientMock.expectedObjects[requestMock.NamespacedName] = &group - clientMock.expectedObjects[types.NamespacedName{ - Namespace: "default", - Name: group.Spec.GitlabCredentialsName, - }] = &gitlabCred - clientMock.expectedObjects[secretNamespacedName] = &secret - sut.gitlabClient = &MockGitlabClient{ - newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { - return &gitlab.Client{}, nil - }, - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - groups := []*gitlab.Group{ {Name: "agroup"} } - return groups, &gitlab.Response{ - Response: &http.Response{ - Status: "", - StatusCode: 0, - Proto: "", - ProtoMajor: 0, - ProtoMinor: 0, - Header: nil, - Body: nil, - ContentLength: 0, - TransferEncoding: nil, - Close: false, - Uncompressed: false, - Trailer: nil, - Request: nil, - TLS: nil, - }, - TotalItems: 0, - TotalPages: 0, - ItemsPerPage: 0, - CurrentPage: 0, - NextPage: 0, - PreviousPage: 0, - }, nil - }, - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return &gitlab.Group{ Name: "agroup" }, &gitlab.Response{}, nil - }, - } - result, err := sut.Reconcile(contextMock, requestMock) - It("should requeue the object", func() { - Expect(result).To(Equal(ctrl.Result{Requeue: true})) - }) - It("should log the error", func() { - Expect(loggerMock.logLevelCalled).To(Equal("Error")) - Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) - }) - It("should return an error", func() { - Expect(err).NotTo(BeNil()) - }) + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{Name: "agroup"}, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToUpdateStatus)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) }) }) +}) -var _ = - Describe("groupExists", func() { - When("the gitlab client fails", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, &MockError{ - message: "mockedError", - } - }, +var _ = Describe("Reconcile", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + When("processProjects fails", func() { + loggerMock := &MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + contextMock := context.TODO() + clientMock := MockClient{ + statusWriter: &MockStatusWriter{ + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil }, - } - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) - It("should return that it didn't find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return a nil group", func() { - Expect(groups).To(BeNil()) - }) - It("should return an error", func() { - Expect(err).ToNot(BeNil()) - }) - }) - When("the group is in the list", func() { - group := &gitlab.Group{ Name: "A group" } - returnGroups := []*gitlab.Group{ group } - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return returnGroups, &gitlab.Response{}, nil - }, + }, + createFunction: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + return &MockError{message: "error"} + }, + } + requestMock := ctrl.Request{} + sut := GroupReconciler{ + Client: &clientMock, + Log: loggerMock, + Scheme: scheme, + } + requestMock.NamespacedName = types.NamespacedName{ + Namespace: "default", + Name: "testGroup", + } + gitlabCred := gitlabv1alpha1.GitlabCredentials{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GitlabCredentialsSpec{ + URL: "https://example.com", + Username: "ausername", + AccessToken: v1.SecretReference{ + Name: "asecret", + Namespace: "default", }, - } - - found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it found the group", func() { - Expect(found).To(Equal(true)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return list with the group", func() { - Expect(returnedGroup).To(Equal(group)) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) - }) - When("the group is not in the list", func() { - sut := GroupReconciler{ - gitlabClient: &MockGitlabClient{ - listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return nil, &gitlab.Response{}, nil + }, + Status: gitlabv1alpha1.GitlabCredentialsStatus{}, + } + group := gitlabv1alpha1.Group{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: gitlabv1alpha1.GroupSpec{ + Name: "agroup", + GitlabCredentialsName: "gitlab-credentials", + ProjectSpecs: []gitlabv1alpha1.ProjectSpec{{Name: "ProjectSpec"}}, + }, + Status: gitlabv1alpha1.GroupStatus{}, + } + secretNamespacedName := types.NamespacedName{ + Namespace: gitlabCred.Spec.AccessToken.Namespace, + Name: gitlabCred.Spec.AccessToken.Name, + } + stringData := map[string]string{"accessToken": "password"} + secret := v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + StringData: stringData, + Type: "opaque", + } + clientMock.expectedObjects = make(map[client.ObjectKey]client.Object) + clientMock.expectedObjects[requestMock.NamespacedName] = &group + clientMock.expectedObjects[types.NamespacedName{ + Namespace: "default", + Name: group.Spec.GitlabCredentialsName, + }] = &gitlabCred + clientMock.expectedObjects[secretNamespacedName] = &secret + sut.gitlabClient = &MockGitlabClient{ + newClientFunction: func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) { + return &gitlab.Client{}, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + groups := []*gitlab.Group{{Name: "agroup"}} + return groups, &gitlab.Response{ + Response: &http.Response{ + Status: "", + StatusCode: 0, + Proto: "", + ProtoMajor: 0, + ProtoMinor: 0, + Header: nil, + Body: nil, + ContentLength: 0, + TransferEncoding: nil, + Close: false, + Uncompressed: false, + Trailer: nil, + Request: nil, + TLS: nil, }, - }, - } - - found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{ Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) - It("should return that it did not find the group", func() { - Expect(found).To(Equal(false)) - }) - It("should return the gitlab response", func() { - Expect(response).ToNot(BeNil()) - }) - It("should return nil for the group list", func() { - Expect(groups).To(BeNil()) - }) - It("should not return an error", func() { - Expect(err).To(BeNil()) - }) + TotalItems: 0, + TotalPages: 0, + ItemsPerPage: 0, + CurrentPage: 0, + NextPage: 0, + PreviousPage: 0, + }, nil + }, + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return &gitlab.Group{Name: "agroup"}, &gitlab.Response{}, nil + }, + } + result, err := sut.Reconcile(contextMock, requestMock) + It("should requeue the object", func() { + Expect(result).To(Equal(ctrl.Result{Requeue: true})) + }) + It("should log the error", func() { + Expect(loggerMock.logLevelCalled).To(Equal("Error")) + Expect(loggerMock.loggedMessage).To(Equal(errorUnableToProcessProjects)) + }) + It("should return an error", func() { + Expect(err).NotTo(BeNil()) }) }) -var _ = - Describe("createGroup", func() { - expectedGroup := &gitlab.Group{ ID: 1, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } +}) + +var _ = Describe("groupExists", func() { + When("the gitlab client fails", func() { sut := GroupReconciler{ gitlabClient: &MockGitlabClient{ - createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, &MockError{ + message: "mockedError", + } }, }, } - group, response, err := sut.createGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{Spec: gitlabv1alpha1.GroupSpec{Name: "An error"}}) + It("should return that it didn't find the group", func() { + Expect(found).To(Equal(false)) }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) }) - It("should return the error created by the call, or nil", func() { - Expect(err).To(BeNil()) + It("should return a nil group", func() { + Expect(groups).To(BeNil()) + }) + It("should return an error", func() { + Expect(err).ToNot(BeNil()) }) }) - -var _ = - Describe("updateGroup", func() { - expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} - expectedResponse := &gitlab.Response{ - Response: &http.Response{}, - TotalItems: 1, - TotalPages: 1, - ItemsPerPage: 10, - CurrentPage: 1, - NextPage: 0, - PreviousPage: 0, - } + When("the group is in the list", func() { + group := &gitlab.Group{Name: "A group"} + returnGroups := []*gitlab.Group{group} sut := GroupReconciler{ gitlabClient: &MockGitlabClient{ - updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { - return expectedGroup, expectedResponse, nil - }, listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { - return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil + return returnGroups, &gitlab.Response{}, nil }, }, } - group, response, err := sut.updateGroup( - &gitlabv1alpha1.Group{ - ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, - Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, - Status: gitlabv1alpha1.GroupStatus{ - GitlabID: nil, - CreatedTime: metav1.Time{}, - LastUpdatedTime: metav1.Time{}, - State: "", - }, - }) - It("should return the gitlab group created", func() { - Expect(group).To(Equal(expectedGroup)) + + found, response, returnedGroup, err := sut.groupExists(&gitlabv1alpha1.Group{Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it found the group", func() { + Expect(found).To(Equal(true)) }) - It("should return the gitlab response", func(){ - Expect(response).To(Equal(expectedResponse)) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) }) - It("should return the error created by the call, or nil", func() { + It("should return list with the group", func() { + Expect(returnedGroup).To(Equal(group)) + }) + It("should not return an error", func() { Expect(err).To(BeNil()) }) }) - -var _ = - Describe("SetupWithManager", func() { - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }}} + When("the group is not in the list", func() { sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, + gitlabClient: &MockGitlabClient{ + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return nil, &gitlab.Response{}, nil + }, + }, } - err := sut.SetupWithManager(&MockManager{ - Log: &loggerMock, - builder: builder, + + found, response, groups, err := sut.groupExists(&gitlabv1alpha1.Group{Spec: gitlabv1alpha1.GroupSpec{Name: "A group"}}) + It("should return that it did not find the group", func() { + Expect(found).To(Equal(false)) }) - It("should setup the default gitlab client", func() { - Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) + It("should return the gitlab response", func() { + Expect(response).ToNot(BeNil()) }) - It("should return no error", func() { + It("should return nil for the group list", func() { + Expect(groups).To(BeNil()) + }) + It("should not return an error", func() { Expect(err).To(BeNil()) }) }) +}) +var _ = Describe("createGroup", func() { + expectedGroup := &gitlab.Group{ID: 1, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + createGroupFunction: func(name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + }, + } + group, response, err := sut.createGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func() { + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) +}) + +var _ = Describe("updateGroup", func() { + expectedGroup := &gitlab.Group{ID: 2, Name: "A test group"} + expectedResponse := &gitlab.Response{ + Response: &http.Response{}, + TotalItems: 1, + TotalPages: 1, + ItemsPerPage: 10, + CurrentPage: 1, + NextPage: 0, + PreviousPage: 0, + } + sut := GroupReconciler{ + gitlabClient: &MockGitlabClient{ + updateGroupFunction: func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) { + return expectedGroup, expectedResponse, nil + }, + listGroupsByNameFunction: func(name string) ([]*gitlab.Group, *gitlab.Response, error) { + return []*gitlab.Group{{Name: "a group"}}, &gitlab.Response{}, nil + }, + }, + } + group, response, err := sut.updateGroup( + &gitlabv1alpha1.Group{ + ObjectMeta: ctrl.ObjectMeta{Annotations: make(map[string]string)}, + Spec: gitlabv1alpha1.GroupSpec{Name: "a group"}, + Status: gitlabv1alpha1.GroupStatus{ + GitlabID: nil, + CreatedTime: metav1.Time{}, + LastUpdatedTime: metav1.Time{}, + State: "", + }, + }) + It("should return the gitlab group created", func() { + Expect(group).To(Equal(expectedGroup)) + }) + It("should return the gitlab response", func() { + Expect(response).To(Equal(expectedResponse)) + }) + It("should return the error created by the call, or nil", func() { + Expect(err).To(BeNil()) + }) +}) + +var _ = Describe("SetupWithManager", func() { + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return nil + }}} + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } + err := sut.SetupWithManager(&MockManager{ + Log: &loggerMock, + builder: builder, + }) + It("should setup the default gitlab client", func() { + Expect(sut.gitlabClient).To(BeAssignableToTypeOf(&ClientImpl{})) + }) + It("should return no error", func() { + Expect(err).To(BeNil()) + }) +}) var _ = Describe("updateStatus", func() { When("group id is not 0", func() { @@ -1072,7 +1058,7 @@ var _ = Describe("updateStatus", func() { WithNameValue: "", WithNameCalled: false, } - clientMock := MockClient{ statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + clientMock := MockClient{statusWriter: MockStatusWriter{updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { return nil }}} sut := GroupReconciler{ @@ -1097,60 +1083,59 @@ var _ = Describe("updateStatus", func() { }) }) -var _ = - Describe("updateProject", func() { - spec := gitlabv1alpha1.ProjectSpec{ - Name: "a project", - Path: "https://example.com.path", - ImpactLevel: "2", - StorageTypes: nil, - VirtualService: "default", - ServicePort: 8081, - Language: "golang", - } - project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} - builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) - scheme, _ := builder.Build() - loggerMock := MockLogger{ - WithValuesKeysAndValues: nil, - WithValuesCalled: false, - WithNameValue: "", - WithNameCalled: false, - } - mockError := &MockError{"true"} - clientMock := MockClient{ - statusWriter: MockStatusWriter{ - updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return nil - }, - }, +var _ = Describe("updateProject", func() { + spec := gitlabv1alpha1.ProjectSpec{ + Name: "a project", + Path: "https://example.com.path", + ImpactLevel: "2", + StorageTypes: nil, + VirtualService: "default", + ServicePort: 8081, + Language: "golang", + } + project := gitlabv1alpha1.Project{Spec: gitlabv1alpha1.ProjectSpec{}} + builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Group{}, &gitlabv1alpha1.GroupList{}) + scheme, _ := builder.Build() + loggerMock := MockLogger{ + WithValuesKeysAndValues: nil, + WithValuesCalled: false, + WithNameValue: "", + WithNameCalled: false, + } + mockError := &MockError{"true"} + clientMock := MockClient{ + statusWriter: MockStatusWriter{ updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - return mockError + return nil }, - } - sut := GroupReconciler{ - Client: &clientMock, - Log: &loggerMock, - Scheme: scheme, - } + }, + updateFunction: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + return mockError + }, + } + sut := GroupReconciler{ + Client: &clientMock, + Log: &loggerMock, + Scheme: scheme, + } - returnedProject, err := sut.updateProject(context.TODO(), spec, &project) - It("should update the project from the project specification", func() { - Expect(project.Spec.Name).To(Equal(spec.Name)) - Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) - Expect(project.Spec.Language).To(Equal(spec.Language)) - Expect(project.Spec.Path).To(Equal(spec.Path)) - Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) - Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) - Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) - }) - It("should call the kubernetes client update function", func() { - Expect(clientMock.updateFunctionCalled).To(BeTrue()) - }) - It("should return the result of the update function", func() { - Expect(err).To(Equal(mockError)) - }) - It("should return the updated project", func() { - Expect(project).To(Equal(*returnedProject)) - }) + returnedProject, err := sut.updateProject(context.TODO(), spec, &project) + It("should update the project from the project specification", func() { + Expect(project.Spec.Name).To(Equal(spec.Name)) + Expect(project.Spec.ServicePort).To(Equal(spec.ServicePort)) + Expect(project.Spec.Language).To(Equal(spec.Language)) + Expect(project.Spec.Path).To(Equal(spec.Path)) + Expect(project.Spec.VirtualService).To(Equal(spec.VirtualService)) + Expect(project.Spec.StorageTypes).To(Equal(spec.StorageTypes)) + Expect(project.Spec.ImpactLevel).To(Equal(spec.ImpactLevel)) + }) + It("should call the kubernetes client update function", func() { + Expect(clientMock.updateFunctionCalled).To(BeTrue()) + }) + It("should return the result of the update function", func() { + Expect(err).To(Equal(mockError)) }) + It("should return the updated project", func() { + Expect(project).To(Equal(*returnedProject)) + }) +}) diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index d96b11d..1716f5d 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -395,12 +395,11 @@ func (m *MockError) Error() string { } type MockGitlabClient struct { - newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) - listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) + newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error) + listGroupsFunction func(opt *gitlab.ListGroupsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Group, *gitlab.Response, error) listGroupsByNameFunction func(name string) ([]*gitlab.Group, *gitlab.Response, error) - createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) - updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) - + createGroupFunction func(name string, description string) (*gitlab.Group, *gitlab.Response, error) + updateGroupFunction func(id string, name string, description string) (*gitlab.Group, *gitlab.Response, error) } func (m *MockGitlabClient) CreateGroup(name string, description string) (*gitlab.Group, *gitlab.Response, error) { -- GitLab