diff --git a/controllers/gitlab/gitlab_client.go b/controllers/gitlab/gitlab_client.go index bed2b43d3e9104efede287a6bf14967daa4bee9a..abc9052a346e665283ef3e63abb58aeb30fa294e 100644 --- a/controllers/gitlab/gitlab_client.go +++ b/controllers/gitlab/gitlab_client.go @@ -1,26 +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) -} - -// 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...) -} \ No newline at end of file +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.go b/controllers/gitlab/group_controller.go index dd26e43d966455ab906148e1aa6fc22ed3bc5a41..db8c208cfed76d2caf87eba338b621074818ea8f 100644 --- a/controllers/gitlab/group_controller.go +++ b/controllers/gitlab/group_controller.go @@ -1,325 +1,316 @@ -/* -Copyright 2021. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gitlab - -import ( - "context" - "fmt" - "github.com/go-logr/logr" - "github.com/xanzy/go-gitlab" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "time" - gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" -) - -// 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" -) - -// Group Status -const ( - CredentialNotFound = "CredentialNotFound" - CredentialSecretNotFound = "CredentialSecretNotFound" - GroupCreated = "Created" - GroupOK = "OK" -) - -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 { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - gitlabClient Client -} - -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch -//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. -func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("group", req.NamespacedName) - - group := &gitlabv1alpha1.Group{} - - // Get the Group Object - if err := r.Get(ctx, req.NamespacedName, group); err != nil { - log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) - // we'll ignore not-found errors, since they can't be fixed by an immediate - // requeue (we'll need to wait for a new notification), and we can get them - // on deleted requests. - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - // Get the Gitlab Credentials - var gitLabCredentialsName = types.NamespacedName{ - Namespace: req.Namespace, - Name: group.Spec.GitlabCredentialsName, - } - - var gitlabCredentials gitlabv1alpha1.GitlabCredentials - if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { - - log.Error(err, errorUnableToFetchGitlabCredentials) - group.Status.State = CredentialNotFound - _ = r.updateStatus(ctx, 0, group) - return ctrl.Result{Requeue: true}, err - } - - var secretName = types.NamespacedName{ - Namespace: gitlabCredentials.Spec.AccessToken.Namespace, - Name: gitlabCredentials.Spec.AccessToken.Name, - } - - var secret v1.Secret - // Get the Secret - if err := r.Get(ctx, secretName, &secret); err != nil { - - log.Error(err, errorUnableToFetchSecret) - group.Status.State = CredentialSecretNotFound - _ = r.updateStatus(ctx, 0, group) - - return ctrl.Result{Requeue: true}, err - } - - // Login to Gitlab - var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) - - var gitlabClient *gitlab.Client - var err error - if gitlabClient, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { - log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) - return ctrl.Result{Requeue: true}, err - } - - // See if the Group Exists - var gitlabGroup *gitlab.Group - found, _, gitlabGroup, err := r.groupExists(gitlabClient, group) - if err != nil { - log.Error(err, errorWhileSearchingGroups, "status") - return ctrl.Result{Requeue: true}, err - } - - if !found { - if gitlabGroup, _, err = r.createGroup(gitlabClient, group); err != nil { - - log.Error(err, errorWhileCreatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } else { - if gitlabGroup, _, err = r.updateGroup(gitlabClient, group); err != nil { - log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) - return ctrl.Result{Requeue: true}, err - } - } - - if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { - - log.Error(err, errorUnableToUpdateStatus, "group", group) - return ctrl.Result{ Requeue: true }, err - } - - if err := r.processProjects(ctx, group, !found, group.Spec.ProjectSpecs); err != nil { - log.Error(err, errorUnableToProcessProjects, "group", group) - return ctrl.Result{Requeue: true}, err - } - - return ctrl.Result{}, nil -} - -func (r *GroupReconciler) groupExists(gitlabClient *gitlab.Client, group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { - - var grouplist []*gitlab.Group - var response *gitlab.Response - var err error - if grouplist, response, err = gitlabClient.Groups.ListGroups( - &gitlab.ListGroupsOptions{ - Search: &group.Spec.Name, - }); err != nil { - return false, response, nil, err - } - - // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. - found := false - for _, groupInList := range grouplist { - found = group.Name == groupInList.Name - if found { - return found, response, groupInList, nil - } - } - return found, response, nil, nil -} - -func (r *GroupReconciler) createGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.CreateGroupOptions{ - Name: &group.Spec.Name, - Description: &group.Spec.Description, - } - gitlabGroup, response, err := client.Groups.CreateGroup(&opt) - group.Status.CreatedTime = metav1.Time{ - Time: time.Now(), - } - group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) - group.ObjectMeta.Annotations["Creator"] = valkyrie - group.Status.State = GroupCreated - - return gitlabGroup, response, err -} - -func (r *GroupReconciler) updateGroup(client *gitlab.Client, group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { - opt := gitlab.UpdateGroupOptions{ - Name: &group.Spec.Name, - Description: &group.Spec.Description, - } - gitlabGroup, response, err := client.Groups.UpdateGroup(group.Annotations["ID"], &opt) - group.Status.State = GroupOK - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - - 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 - } - } else { - if project, foundProject, err := r.getProject(ctx, group, projectSpec); err != nil { - r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) - return err - } else if foundProject { - if _, err := r.updateProject(ctx,group, projectSpec, project); err != nil { - - r.Log.Error(err, errorUpdatingProject, "project", project) - } - } - } - } - return nil -} - -func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { - var project gitlabv1alpha1.Project - projectName := types.NamespacedName{ - Namespace: group.Namespace, - Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), - } - err := r.Get(ctx, projectName, &project) - - projectNotFound := client.IgnoreNotFound(err) - - if projectNotFound == nil { - return nil, false, nil - } - - return &project, true, err -} - -func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { - blockOwnerDeletion := true - managingController := true - return r.Create(ctx, &gitlabv1alpha1.Project{ - ObjectMeta: ctrl.ObjectMeta{ - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: group.APIVersion, - Kind: group.Kind, - Name: group.Name, - UID: group.UID, - Controller: &managingController, - BlockOwnerDeletion: &blockOwnerDeletion, - }, - }, - }, - Spec: gitlabv1alpha1.ProjectSpec{ - Name: spec.Name, - Path: spec.Path, - ImpactLevel: spec.ImpactLevel, - StorageTypes: spec.StorageTypes, - VirtualService: spec.VirtualService, - ServicePort: spec.ServicePort, - Language: spec.Language, - }, - }) -} - -func (r *GroupReconciler) updateProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { - project.Spec.Name = spec.Name - project.Spec.Path = spec.Path - project.Spec.ImpactLevel = spec.ImpactLevel - project.Spec.StorageTypes = spec.StorageTypes - project.Spec.VirtualService = spec.VirtualService - project.Spec.ServicePort = spec.ServicePort - project.Spec.Language = spec.Language - return project, r.Update(ctx, project) -} - -func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { - if id != 0 { - id64 := int64(id) - group.Status.GitlabID = &id64 - group.Status.LastUpdatedTime = metav1.Time{ - Time: time.Now(), - } - } - - return r.Status().Update(ctx, group) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { - // Setup a default GitlabClient implementation - r.gitlabClient = &ClientImpl{} - - return ctrl.NewControllerManagedBy(mgr). - For(&gitlabv1alpha1.Group{}). - Owns(&gitlabv1alpha1.Project{}). - Complete(r) +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitlab + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + "github.com/xanzy/go-gitlab" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" + gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1" +) + +// 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" +) + +// Group Status +const ( + CredentialNotFound = "CredentialNotFound" + CredentialSecretNotFound = "CredentialSecretNotFound" + GroupCreated = "Created" + GroupOK = "OK" +) + +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 { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + gitlabClient Client +} + +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups/finalizers,verbs=update +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=gitlabcredentials,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secretreference,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secret,verbs=get;list;watch +//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// The reconcile loop for the Gitlab Group will update the Group and any of the child Project API Objects. +func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("group", req.NamespacedName) + + group := &gitlabv1alpha1.Group{} + + // Get the Group Object + if err := r.Get(ctx, req.NamespacedName, group); err != nil { + log.Error(err, errorUnableToFetchGroup, "group", req.NamespacedName.Name) + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Get the Gitlab Credentials + var gitLabCredentialsName = types.NamespacedName{ + Namespace: req.Namespace, + Name: group.Spec.GitlabCredentialsName, + } + + var gitlabCredentials gitlabv1alpha1.GitlabCredentials + if err := r.Get(ctx, gitLabCredentialsName, &gitlabCredentials); err != nil { + + log.Error(err, errorUnableToFetchGitlabCredentials) + group.Status.State = CredentialNotFound + _ = r.updateStatus(ctx, 0, group) + return ctrl.Result{Requeue: true}, err + } + + var secretName = types.NamespacedName{ + Namespace: gitlabCredentials.Spec.AccessToken.Namespace, + Name: gitlabCredentials.Spec.AccessToken.Name, + } + + var secret v1.Secret + // Get the Secret + if err := r.Get(ctx, secretName, &secret); err != nil { + + log.Error(err, errorUnableToFetchSecret) + group.Status.State = CredentialSecretNotFound + _ = r.updateStatus(ctx, 0, group) + + return ctrl.Result{Requeue: true}, err + } + + // Login to Gitlab + var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey]) + + var err error + if _, err = r.gitlabClient.NewClient(accessToken, gitlab.WithBaseURL(gitlabCredentials.Spec.URL)); err != nil { + log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL) + return ctrl.Result{Requeue: true}, err + } + + // See if the Group Exists + var gitlabGroup *gitlab.Group + found, _, gitlabGroup, err := r.groupExists(group) + if err != nil { + log.Error(err, errorWhileSearchingGroups, "status") + return ctrl.Result{Requeue: true}, err + } + + if !found { + if gitlabGroup, _, err = r.createGroup(group); err != nil { + + log.Error(err, errorWhileCreatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } else { + if gitlabGroup, _, err = r.updateGroup(group); err != nil { + log.Error(err, errorWhileUpdatingGitlabGroup, "group", group) + return ctrl.Result{Requeue: true}, err + } + } + + if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil { + log.Error(err, errorUnableToUpdateStatus, "group", group) + return ctrl.Result{ Requeue: true }, err + } + + if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil { + log.Error(err, errorUnableToProcessProjects, "group", group) + return ctrl.Result{Requeue: true}, err + } + + return ctrl.Result{}, nil +} + +func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitlab.Response, *gitlab.Group, error) { + + var grouplist []*gitlab.Group + var response *gitlab.Response + var err error + if grouplist, response, err = r.gitlabClient.ListGroupsByName(group.Spec.Name); err != nil { + return false, response, nil, err + } + + // TODO: (jvb) For work beyond the MVP we'll need to handle pagination. May be superceeded by adam's client work. + found := false + for _, groupInList := range grouplist { + found = group.Spec.Name == groupInList.Name + if found { + return found, response, groupInList, nil + } + } + return found, response, nil, nil +} + +func (r *GroupReconciler) createGroup(group *gitlabv1alpha1.Group) (*gitlab.Group, *gitlab.Response, error) { + gitlabGroup, response, err := r.gitlabClient.CreateGroup(group.Spec.Name, group.Spec.Description) + + if err == nil { + group.Status.CreatedTime = metav1.Time{ + Time: time.Now(), + } + group.ObjectMeta.Annotations["ID"] = fmt.Sprint(gitlabGroup.ID) + group.ObjectMeta.Annotations["Creator"] = valkyrie + group.Status.State = GroupCreated + } + + return gitlabGroup, response, err +} + +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) + + if err == nil { + group.Status.State = GroupOK + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + 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 { + r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name) + return err + } else if foundProject { + if _, err := r.updateProject(ctx, projectSpec, project); err != nil { + + r.Log.Error(err, errorUpdatingProject, "project", project) + } + } else { + if err := r.createProject(ctx, group, projectSpec); err != nil { + + r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec) + return err + } + } + } + return nil +} + +func (r *GroupReconciler) getProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) (*gitlabv1alpha1.Project, bool, error) { + var project gitlabv1alpha1.Project + projectName := types.NamespacedName{ + Namespace: group.Namespace, + Name: fmt.Sprintf("%s-%s", group.Name, spec.Name), + } + err := r.Get(ctx, projectName, &project) + + projectNotFound := client.IgnoreNotFound(err) + + if projectNotFound == nil { + return nil, false, nil + } + + return &project, true, err +} + +func (r *GroupReconciler) createProject(ctx context.Context, group *gitlabv1alpha1.Group, spec gitlabv1alpha1.ProjectSpec) error { + blockOwnerDeletion := true + managingController := true + return r.Create(ctx, &gitlabv1alpha1.Project{ + ObjectMeta: ctrl.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: group.APIVersion, + Kind: group.Kind, + Name: group.Name, + UID: group.UID, + Controller: &managingController, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + }, + }, + Spec: gitlabv1alpha1.ProjectSpec{ + Name: spec.Name, + Path: spec.Path, + ImpactLevel: spec.ImpactLevel, + StorageTypes: spec.StorageTypes, + VirtualService: spec.VirtualService, + ServicePort: spec.ServicePort, + Language: spec.Language, + }, + }) +} + +func (r *GroupReconciler) updateProject(ctx context.Context, spec gitlabv1alpha1.ProjectSpec, project *gitlabv1alpha1.Project) (*gitlabv1alpha1.Project, error) { + project.Spec.Name = spec.Name + project.Spec.Path = spec.Path + project.Spec.ImpactLevel = spec.ImpactLevel + project.Spec.StorageTypes = spec.StorageTypes + project.Spec.VirtualService = spec.VirtualService + project.Spec.ServicePort = spec.ServicePort + project.Spec.Language = spec.Language + return project, r.Update(ctx, project) +} + +func (r *GroupReconciler) updateStatus(ctx context.Context, id int, group *gitlabv1alpha1.Group) error { + if id != 0 { + id64 := int64(id) + group.Status.GitlabID = &id64 + group.Status.LastUpdatedTime = metav1.Time{ + Time: time.Now(), + } + } + + return r.Status().Update(ctx, group) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + // Setup a default GitlabClient implementation + r.gitlabClient = &ClientImpl{} + + return ctrl.NewControllerManagedBy(mgr). + 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 86c076bbb918bc2e79e7d0e8fe220cfd0063e203..3dd202447977976ce5207c650b1bbb24ac05935a 100755 --- a/controllers/gitlab/group_controller_test.go +++ b/controllers/gitlab/group_controller_test.go @@ -1,249 +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" - 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{} - 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{} - 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, - }, - 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{} - 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})) - }) - }) - }) +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)) + }) }) \ No newline at end of file diff --git a/controllers/gitlab/mocks_test.go b/controllers/gitlab/mocks_test.go index 40340bb00667422597c6ede1f12752348c42ccec..ecca40c9d36b295f6b2d2546b9c4c31fde1a9d62 100755 --- a/controllers/gitlab/mocks_test.go +++ b/controllers/gitlab/mocks_test.go @@ -24,10 +24,14 @@ import ( ) 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 + 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 { @@ -66,7 +70,10 @@ func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...c } func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - panic("implement me") + 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 { @@ -74,7 +81,11 @@ func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...clie } func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { - panic("implement me") + 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 { @@ -86,11 +97,11 @@ func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts .. } 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 nil + return m.updateFunction(ctx, obj, opts...) } func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { @@ -98,7 +109,7 @@ func (m MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch cl } func (m *MockClient) Status() client.StatusWriter { - return MockStatusWriter{} + return m.statusWriter } func (m *MockClient) Scheme() *runtime.Scheme { @@ -386,6 +397,22 @@ 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) + 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) {