UNCLASSIFIED

Commit 111f45c6 authored by Jason van Brackel's avatar Jason van Brackel
Browse files

refactor: move gitlab client configuration logic out of reconcilers into it's...

refactor: move gitlab client configuration logic out of reconcilers into it's own struct. Create logic for project contrller. Needs more tests.
parent c8473478
package gitlab
import (
"context"
"github.com/go-logr/logr"
v1 "k8s.io/api/core/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"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
const (
errorUnableToFetchGitlabCredentials = "unable to fetch gitlab credentials"
errorUnableToFetchSecret = "unable to fetch secret from gitlab credentials"
errorUnableToCreateGitlabClient = "unable to create gitlab client"
)
// ClientConfiguration represents an object whose purpose is to setup a Gitlab client given
type ClientConfiguration interface {
// SetupClient pulls the GitlabCredentials from Kubernetes and supplies the Gitlab user and access token.
SetupClient(client client.Client, credentialsName string) (gitlabClient.Client, error)
}
// ClientConfigurationImpl is the default implementation for the ClientConfiguration interface
// it will setup a gitlab client for the reconciler.
type ClientConfigurationImpl struct {
Log logr.Logger
Ctx context.Context
Req ctrl.Request
}
// SetupClient looks up the gitlab credentials to get the access token and username for connecting to Gitlab
// on behalf of the reconciler.
func (c ClientConfigurationImpl) SetupClient(client client.Client, credentialsName string) (gitlabClient.Client, error) {
// Get the Gitlab Credentials
var gitLabCredentialsName = types.NamespacedName{
Namespace: c.Req.Namespace,
Name: credentialsName,
}
var gitlabCredentials gitlabv1alpha1.GitlabCredentials
if err := client.Get(c.Ctx, gitLabCredentialsName, &gitlabCredentials); err != nil {
c.Log.Error(err, errorUnableToFetchGitlabCredentials)
return nil, err
}
var secretName = types.NamespacedName{
Namespace: gitlabCredentials.Spec.AccessToken.Namespace,
Name: gitlabCredentials.Spec.AccessToken.Name,
}
var secret v1.Secret
// Get the Secret
if err := client.Get(c.Ctx, secretName, &secret); err != nil {
c.Log.Error(err, errorUnableToFetchSecret)
return nil, err
}
// Login to Gitlab
var accessToken = string(secret.Data[gitlabCredentials.Spec.AccessTokenKey])
var returnClient gitlabClient.Client
var err error
if returnClient, err = gitlabClient.NewClient(accessToken, gitlabCredentials.Spec.URL, nil); err != nil {
c.Log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL)
return nil, err
}
return returnClient, nil
}
......@@ -34,6 +34,10 @@ type ProjectSpec struct {
// +kubebuilder:validation:required
FullPath string `json:"path"`
// GitlabCredentialsName is the name of the object in this namespace that contains authentication
// information for logging into the
GitlabCredentialsName string `json:"gitlabCredentialsName"`
// ImpactLevel is the RMF Impact Level for this Project
// +kubebuilder:validation:Enum=2;4;5;6
// +kubebuilder:validation:required
......
......@@ -21,7 +21,6 @@ import (
"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"
......@@ -29,32 +28,29 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"strconv"
"time"
apisGitlab "valkyrie.dso.mil/valkyrie-api/apis/gitlab"
gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
// 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"
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"
errorUnableToSetupGitlabClient = "Error unable to setup Gitlab client"
)
// Group Status
const (
CredentialNotFound = "CredentialNotFound"
CredentialSecretNotFound = "CredentialSecretNotFound"
GroupCreated = "Created"
GroupOK = "OK"
GroupCreated = "Created"
GroupOK = "OK"
)
const valkyrie = "valkyrie"
......@@ -63,9 +59,10 @@ const valkyrie = "valkyrie"
// 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 gitlabClient.Client
Log logr.Logger
Scheme *runtime.Scheme
gitlabClient gitlabClient.Client
gitlabClientConfiguration apisGitlab.ClientConfiguration
}
//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=groups,verbs=get;list;watch;create;update;patch;delete
......@@ -80,60 +77,27 @@ type GroupReconciler struct {
// 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)
r.Log = r.Log.WithValues("group", req.NamespacedName)
var err error
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)
if err = r.Get(ctx, req.NamespacedName, group); err != nil {
r.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
// We check for a nil client here so we can inject a mock for testing.
// Do a nil check here so we can use mock gitlabClients
if r.gitlabClient == nil {
if r.gitlabClient, err = gitlabClient.NewClient(accessToken, gitlabCredentials.Spec.URL, nil); err != nil {
log.Error(err, errorUnableToCreateGitlabClient, "username", gitlabCredentials.Spec.Username, "url", gitlabCredentials.Spec.URL)
if r.gitlabClientConfiguration == nil {
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{}
}
if r.gitlabClient, err = r.gitlabClientConfiguration.SetupClient(r.Client, group.Spec.GitlabCredentialsName); err != nil {
r.Log.Error(err, errorUnableToSetupGitlabClient)
return ctrl.Result{Requeue: true}, err
}
}
......@@ -142,30 +106,30 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
var gitlabGroup *gitlab.Group
found, gitlabGroup, err := r.groupExists(group)
if err != nil {
log.Error(err, errorWhileSearchingGroups, "status")
r.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)
r.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)
r.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)
r.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)
r.Log.Error(err, errorUnableToProcessProjects, "group", group)
return ctrl.Result{Requeue: true}, err
}
......@@ -180,7 +144,6 @@ func (r *GroupReconciler) groupExists(group *gitlabv1alpha1.Group) (bool, *gitla
return false, 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
......@@ -215,6 +178,7 @@ func (r *GroupReconciler) updateGroup(group *gitlabv1alpha1.Group) (*gitlab.Grou
&gitlab.UpdateGroupOptions{
Name: &group.Spec.Name,
Description: &group.Spec.Description,
Path: &group.Spec.FullPath,
},
)
......
This diff is collapsed.
......@@ -20,6 +20,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/scheme"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"time"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
"valkyrie.dso.mil/valkyrie-api/controllers"
)
......@@ -419,6 +420,7 @@ type MockGitlabClient struct {
updateGroupFunction func(id int, options *gitlab.UpdateGroupOptions) (*gitlab.Group, int, error)
getGroupFunction func(groupID int) (*gitlab.Group, int, error)
expectedGroups map[int]*gitlab.Group
expectedProjects map[int]*gitlab.Project
}
func (m *MockGitlabClient) GetUser(userID int) (*gitlab.User, int, error) {
......@@ -454,6 +456,27 @@ func (m *MockGitlabClient) GetGroupByFullPath(fullPath *string) (*gitlab.Group,
}
func (m *MockGitlabClient) GetGroups(search *string) ([]*gitlab.Group, error) {
if m.getGroupsFunction == nil {
if m.expectedGroups == nil {
return make([]*gitlab.Group, 0), nil
}
groups := make([]*gitlab.Group, 0, len(m.expectedGroups))
for _, group := range m.expectedGroups {
groups = append(groups, group)
}
for _, group := range groups {
if group.Name == *search {
returnGroups := make([]*gitlab.Group, 1)
returnGroups[0] = group
return returnGroups, nil
}
}
return groups, nil
}
groups, _, err := m.getGroupsFunction(search)
return groups, err
}
......@@ -467,7 +490,21 @@ func (m *MockGitlabClient) DeleteGroup(groupID int, waitInterval int, waitCount
}
func (m *MockGitlabClient) GetProject(projectID int) (*gitlab.Project, int, error) {
panic("implement me")
var returnProject *gitlab.Project
var statusCode int
var err error
if m.expectedProjects == nil {
m.expectedProjects = make(map[int]*gitlab.Project)
}
returnProject = m.expectedProjects[projectID]
if returnProject == nil {
statusCode = 404
err = &MockError{"not found"}
} else {
statusCode = 200
}
return m.expectedProjects[projectID], statusCode, err
}
func (m *MockGitlabClient) GetProjectByFullPath(fullPath *string) (*gitlab.Project, int, error) {
......@@ -479,7 +516,16 @@ func (m *MockGitlabClient) GetProjects(search *string) ([]*gitlab.Project, error
}
func (m *MockGitlabClient) AddProject(createProjectOptions gitlab.CreateProjectOptions) (*gitlab.Project, int, error) {
panic("implement me")
if m.expectedProjects == nil {
m.expectedProjects = make(map[int]*gitlab.Project)
}
m.expectedProjects[1] = &gitlab.Project{
ID: 1,
Name: *createProjectOptions.Name,
Path: *createProjectOptions.Path,
}
return m.expectedProjects[1], 200, nil
}
func (m *MockGitlabClient) UpdateProject(projectID int, editProjectOptions gitlab.EditProjectOptions) (*gitlab.Project, int, error) {
......@@ -503,9 +549,51 @@ func (m *MockGitlabClient) GetGroup(groupID int) (*gitlab.Group, int, error) {
}
func (m *MockGitlabClient) AddGroup(options gitlab.CreateGroupOptions) (*gitlab.Group, int, error) {
if m.addGroupFunction == nil {
if m.expectedGroups == nil {
m.expectedGroups = make(map[int]*gitlab.Group)
}
m.expectedGroups[1] = &gitlab.Group{
ID: 1,
Name: *options.Name,
Description: *options.Description,
}
return m.expectedGroups[1], 1, nil
}
return m.addGroupFunction(options)
}
func (m *MockGitlabClient) UpdateGroup(id int, options *gitlab.UpdateGroupOptions) (*gitlab.Group, int, error) {
if m.updateGroupFunction == nil {
if m.expectedGroups == nil {
m.expectedGroups = make(map[int]*gitlab.Group)
}
m.expectedGroups[id] = &gitlab.Group{
ID: id,
FullPath: *options.Path,
Name: *options.Name,
Description: *options.Description,
}
return m.expectedGroups[id], id, nil
}
return m.updateGroupFunction(id, options)
}
type MockClientConfiguration struct {
GitlabClient gitlabClient.Client
SetupClientFunction func(client client.Client, credentialsName string) (gitlabClient.Client, error)
SetupClientCalled bool
}
func (m *MockClientConfiguration) SetupClient(client client.Client, credentialsName string) (gitlabClient.Client, error) {
m.SetupClientCalled = true
if m.SetupClientFunction == nil {
if m.GitlabClient == nil {
return &MockGitlabClient{}, nil
}
return m.GitlabClient, nil
}
return m.SetupClientFunction(client, credentialsName)
}
......@@ -23,16 +23,19 @@ import (
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
"strconv"
apisGitlab "valkyrie.dso.mil/valkyrie-api/apis/gitlab"
v1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
// ProjectReconciler reconciles a Project object
type ProjectReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
gitlabClient gitlabClient.Client
Log logr.Logger
Scheme *runtime.Scheme
gitlabClient gitlabClient.Client
gitlabClientConfiguration apisGitlab.ClientConfiguration
}
//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=projects,verbs=get;list;watch;create;update;patch;delete
......@@ -41,9 +44,13 @@ type ProjectReconciler struct {
// errors
const (
errorWhileLookingUpProject string = "error while looking up project"
errorGettingGroupFromGitlab string = "Error while getting Group from Gitlab."
errorUpdatingStatusOfProject = "Error updating status of project"
errorWhileLookingUpProject = "error while looking up project."
errorGettingGroupFromGitlab = "Error while getting Group from Gitlab."
errorUpdatingStatusOfProject = "Error updating status of project."
errorGettingProjectFromGitlab = "Error getting project from Gitlab."
errorCreatingGitlabProject = "Error creating Gitlab project."
errorUpdatingGitlabProject = "Error updating Gitlab project."
errorUpdatingProjectIDAnnotation = "Error updating project ID annotation."
)
// statuses
......@@ -51,30 +58,73 @@ const (
groupDoesntExist string = "GitlabGroupDoesNotExist"
)
const annotationKeyID = "ID"
// Reconcile is the main reconciliation loop that will create/edit/delete Gitlab Projects.
func (r *ProjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = r.Log.WithValues("project", req.NamespacedName)
r.Log = r.Log.WithValues("project", req.NamespacedName)
var err error
// TODO Setup the gitlab client
var project *gitlabv1alpha1.Project
var project *v1alpha1.Project
// Get the Project API Object from Kubernetes
if project, err = r.getProject(ctx, req); err != nil {
r.Log.Error(err, errorWhileLookingUpProject, "request", req)
return ctrl.Result{Requeue: client.IgnoreNotFound(err) != nil}, client.IgnoreNotFound(err)
}
if _, err = r.getGroup(project.Spec.GroupID); err != nil {
// Do a nil check here so we can use mock gitlabClients
if r.gitlabClient == nil {
if r.gitlabClientConfiguration == nil {
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{}
}
if r.gitlabClient, err = r.gitlabClientConfiguration.SetupClient(r.Client, project.Spec.GitlabCredentialsName); err != nil {
r.Log.Error(err, errorUnableToSetupGitlabClient)
return ctrl.Result{Requeue: true}, err
}
}
// Make Sure the Group Exists in GitLab
if _, err = r.getGitlabGroup(ctx, project, req); err != nil {
r.Log.Error(err, errorGettingGroupFromGitlab, "request", req)
r.updateStatus(ctx, project, groupDoesntExist)
_ = r.updateStatus(ctx, project, groupDoesntExist)
return ctrl.Result{Requeue: true}, err
}
// Get the Project if it exists, create it if not
var gitlabProject *gitlab.Project
id, _ := strconv.Atoi(project.ObjectMeta.Annotations["ID"])
if gitlabProject, err = r.getGitlabProject(id); err != nil {
r.Log.Error(err, errorGettingProjectFromGitlab)
_ = r.updateStatus(ctx, project, errorGettingProjectFromGitlab)
return ctrl.Result{}, err
}
if gitlabProject == nil {
if gitlabProject, err = r.createGitlabProject(project); err != nil {
r.Log.Error(err, errorCreatingGitlabProject)
_ = r.updateStatus(ctx, project, errorCreatingGitlabProject)
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{}, nil
}
if gitlabProject, err = r.updateGitlabProject(id, project); err != nil {
r.Log.Error(err, errorUpdatingGitlabProject)
_ = r.updateStatus(ctx, project, errorUpdatingGitlabProject)
return ctrl.Result{Requeue: true}, err
}
if err = r.updateProjectIDAnnotation(ctx, project, gitlabProject); err != nil {
r.Log.Error(err, errorUpdatingProjectIDAnnotation)
_ = r.updateStatus(ctx, project, errorUpdatingProjectIDAnnotation)
}
return ctrl.Result{}, nil
}
func (r *ProjectReconciler) updateStatus(ctx context.Context, project *gitlabv1alpha1.Project, status string) error {
func (r *ProjectReconciler) updateStatus(ctx context.Context, project *v1alpha1.Project, status string) error {
project.Status.State = status
if err := r.Status().Update(ctx, project); err != nil {
r.Log.Error(err, errorUpdatingStatusOfProject, "project", project, "status", status)
......@@ -86,13 +136,13 @@ func (r *ProjectReconciler) updateStatus(ctx context.Context, project *gitlabv1a
// SetupWithManager sets up the controller with the Manager.
func (r *ProjectReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&gitlabv1alpha1.Project{}).
Owns(&gitlabv1alpha1.Pipeline{}).
For(&v1alpha1.Project{}).
Owns(&v1alpha1.Pipeline{}).
Complete(r)
}
func (r *ProjectReconciler) getProject(ctx context.Context, request ctrl.Request) (*gitlabv1alpha1.Project, error) {
var project gitlabv1alpha1.Project
func (r *ProjectReconciler) getProject(ctx context.Context, request ctrl.Request) (*v1alpha1.Project, error) {
var project v1alpha1.Project
if err := r.Client.Get(ctx, request.NamespacedName, &project); err != nil {
if client.IgnoreNotFound(err) == nil {
return nil, err
......@@ -102,15 +152,73 @@ func (r *ProjectReconciler) getProject(ctx context.Context, request ctrl.Request
return &project, nil
}
func (r *ProjectReconciler) getGroup(groupID int) (*gitlab.Group, error) {
var group *gitlab.Group
func (r *ProjectReconciler) getGitlabGroup(ctx context.Context, project *v1alpha1.Project, req ctrl.Request) (*gitlab.Group, error) {
var gitlabGroup *gitlab.Group
var response int
var err error
if group, response, err = r.gitlabClient.GetGroup(groupID); err != nil {
// We do a nil check here so that we can inject mocks for testing.
// Do a nil check here so we can use mock gitlabClients
if r.gitlabClient == nil {
if r.gitlabClientConfiguration == nil {
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{
Log: r.Log,
Ctx: ctx,
Req: req,
}
}
r.gitlabClientConfiguration.SetupClient(r.Client, project.Spec.GitlabCredentialsName)
}
r.Log.Error(err, errorGettingGroupFromGitlab, "response", response, "groupID", groupID)
if gitlabGroup, response, err = r.gitlabClient.GetGroup(project.Spec.GroupID); err != nil {
r.Log.Error(err, errorGettingGroupFromGitlab, "response", response, "groupID", project)
return nil, err
}
return group, nil
return gitlabGroup, nil
}
func (r *ProjectReconciler) getGitlabProject(id int) (*gitlab.Project, error) {
var project *gitlab.Project
var statusCode int
var err error
if project, statusCode, err = r.gitlabClient.GetProject(id); err != nil {
if statusCode == 404 {
return nil, nil
}
return nil, err
}
return project, nil
}
func (r *ProjectReconciler) createGitlabProject(project *v1alpha1.Project) (*gitlab.Project, error) {
var gitLabProject *gitlab.Project
var err error
createProjectOptions := gitlab.CreateProjectOptions{
Name: &project.Spec.Name,
Path: &project.Spec.FullPath,
}
if gitLabProject, _, err = r.gitlabClient.AddProject(createProjectOptions); err != nil {
return nil, err
}
return gitLabProject, nil
}
func (r *ProjectReconciler) updateGitlabProject(id int, project *v1alpha1.Project) (*gitlab.Project, error) {
var gitLabProject *gitlab.Project
var err error
if gitLabProject, _, err = r.gitlabClient.UpdateProject(id, gitlab.EditProjectOptions{
Name: &project.Spec.Name,
Path: &project.Spec.FullPath,
}); err != nil {
return nil, err
}
return gitLabProject, nil
}
func (r *ProjectReconciler) updateProjectIDAnnotation(ctx context.Context, project *v1alpha1.Project, gitlabProject *gitlab.Project) error {
project.ObjectMeta.Annotations[annotationKeyID] = strconv.Itoa(gitlabProject.ID)
return r.Client.Update(ctx, project)
}
......@@ -12,11 +12,41 @@ import (
gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
)
const (
GreenProjectName = "TestProject"
GreenNamespace = "default"
)
func getGreenProject() gitlabv1alpha1.Project {
project := gitlabv1alpha1.Project{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: GreenProjectName,
Namespace: GreenNamespace,
},
Spec: gitlabv1alpha1.ProjectSpec{
Name: GreenProjectName,
FullPath: "https://example.com/TestProject",
ImpactLevel: "2",
StorageTypes: nil,
VirtualService: GreenNamespace,
ServicePort: 8081,
Language: "golang",
},
Status: gitlabv1alpha1.ProjectStatus{
URL: "https://example.com/TestProject",
State: "Created",
},
}
return project
}
func getGreenRequest() ctrl.Request {
request := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "default",
Name: "TestProject",
Namespace: GreenNamespace,
Name: GreenProjectName,
},
}
return request
......@@ -27,6 +57,7 @@ func getControllerWithMocksInGreenTestState() (ProjectReconciler, *MockManager,
scheme, _ := builder.Build()
mockStatusWriter := MockStatusWriter{}
project := getGreenProject()
loggerMock := MockLogger{
WithValuesKeysAndValues: nil,
WithValuesCalled: false,
......@@ -37,26 +68,7 @@ func getControllerWithMocksInGreenTestState() (ProjectReconciler, *MockManager,
statusWriter: &mockStatusWriter,
expectedObjects: make(map[client.ObjectKey]client.Object),
}
clientMock.expectedObjects[getRequestWithDefaultNamespacedTestProject().NamespacedName] = &gitlabv1alpha1.Project{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "TestProject",
Namespace: "default",
},
Spec: gitlabv1alpha1.ProjectSpec{
Name: "TestProject",
FullPath: "https://example.com/TestProject",
ImpactLevel: "2",
StorageTypes: nil,
VirtualService: "default",
ServicePort: 8081,
Language: "golang",
},
Status: gitlabv1alpha1.ProjectStatus{
URL: "",
State: "",
},
}
clientMock.expectedObjects[getRequestWithDefaultNamespacedTestProject().NamespacedName] = &project
mockManager := &MockManager{
Log: &loggerMock,
builder: builder,
......@@ -76,8 +88,8 @@ func getControllerWithMocksInGreenTestState() (ProjectReconciler, *MockManager,
func getRequestWithDefaultNamespacedTestProject() ctrl.Request {
return ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "default",
Name: "TestProject",
Namespace: GreenNamespace,
Name: GreenProjectName,
},
}
}
......@@ -131,7 +143,7 @@ var _ = Describe("reconcile", func() {
sut.Client = &failingClient
request := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "default",
Namespace: GreenNamespace,
Name: "notGonnaFindIt",
},
}
......@@ -146,11 +158,11 @@ var _ = Describe("reconcile", func() {
Expect(log.loggedMessage).To(Equal(errorWhileLookingUpProject))
})
})
Context("getGroup returns an error", func() {
Context("getGitlabGroup returns an error", func() {
failingGitlabClient := MockGitlabClient{
getGroupFunction: func(groupID int) (*gitlab.Group, int, error) {
return nil, 500, &MockError{
message: "failure in getGroup",
message: "failure in getGitlabGroup",
}
},
}
......@@ -192,7 +204,7 @@ var _ = Describe("getProject", func() {
sut, _, _, _ := getControllerWithMocksInGreenTestState()
request := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "default",
Namespace: GreenNamespace,
Name: "notGonnaFindIt",
},
}
......@@ -217,7 +229,7 @@ var _ = Describe("getProject", func() {
sut.Client = &failingClient
request := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "default",
Namespace: GreenNamespace,
Name: "notGonnaFindIt",
},
}
......@@ -231,28 +243,28 @@ var _ = Describe("getProject", func() {
})
})
var _ = Describe("getGroup", func() {
var _ = Describe("getGitlabGroup", func() {
Context("green state", func() {
var fakeGroupID = 1
sut, _, _, _ := getControllerWithMocksInGreenTestState()
group, err := sut.getGroup(fakeGroupID)
project := getGreenProject()
group, err := sut.getGitlabGroup(context.TODO(), &project, getGreenRequest())
It("should return a group, and no error.", func() {
Expect(group).To(BeAssignableToTypeOf(&gitlab.Group{}))
Expect(err).To(BeNil())
})
})
Context("gitlab client returns error", func() {
var fakeGroupID = 1
project := getGreenProject()
failingGitlabClient := MockGitlabClient{
getGroupFunction: func(groupID int) (*gitlab.Group, int, error) {
return nil, 500, &MockError{
message: "failure in getGroup",
message: "failure in getGitlabGroup",
}
},
}
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut.gitlabClient = &failingGitlabClient
group, err := sut.getGroup(fakeGroupID)
group, err := sut.getGitlabGroup(context.TODO(), &project, getGreenRequest())
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
......@@ -264,3 +276,36 @@ var _ = Describe("getGroup", func() {
})
})
})
var _ = Describe("updateStatus", func() {
Context("green state", func() {
project := getGreenProject()
sut, _, _, statusWriter := getControllerWithMocksInGreenTestState()
result := sut.updateStatus(context.TODO(), &project, groupDoesntExist)
It("should update the status of the object", func() {
Expect(statusWriter.updateFunctionCalled).To(BeTrue())
})
It("should not return an error", func() {
Expect(result).To(BeNil())
})
})
Context("updateStatus fails", func() {
project := getGreenProject()
sut, _, log, statusWriter := getControllerWithMocksInGreenTestState()
statusWriter.updateFunction = func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
return &MockError{
message: "updateStatus fails",
}
}
result := sut.updateStatus(context.TODO(), &project, groupDoesntExist)
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorUpdatingStatusOfProject))
})
It("should return the error", func() {
Expect(result).ToNot(BeNil())
})
})
})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment