UNCLASSIFIED

Commit 576ebc79 authored by abrichards's avatar abrichards
Browse files

Merge branch 'dev-jvb-714' into 'master'

feat: Add variables to group and project CICD settings

See merge request !52
parents 50015b0d 544a0756
......@@ -35,16 +35,20 @@ import (
// Errors
const (
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"
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"
errorWhileUpdatingGitlabGroupCiVariables = "Error updating the Gitlab group CI variables"
errorGettingGroupVariablesFromGitlab = "Error getting group variables from Gitlab"
errorCreatingGroupVariableInGitlab = "Error creating group variable in Gitlab"
errorUpdatingGroupVariableInGitLab = "Error updating group variable in Gitlab"
)
// Group Status
......@@ -53,6 +57,13 @@ const (
GroupOK = "OK"
)
// Group CI/CD Variables to Manage
const (
KustomizeStagingPathVariableName = "KUSTOMIZE_STAGING_PATH"
KustomizeProductionPathVariableName = "KUSTOMIZE_PRODUCTION_PATH"
ManifestRepoURLVariableName = "MANIFEST_REPO_URL"
)
const valkyrie = "valkyrie"
// GroupReconciler reconciles the state of a Gitlab Group, for each reconciliation loop it will log in to Gitlab
......@@ -94,7 +105,11 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
// Do a nil check here so we can use mock gitlabClients
if r.gitlabClient == nil {
if r.gitlabClientConfiguration == nil {
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{}
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{
Log: r.Log,
Ctx: ctx,
Req: req,
}
}
if r.gitlabClient, err = r.gitlabClientConfiguration.SetupClient(r.Client, group.Spec.GitlabCredentialsName); err != nil {
r.Log.Error(err, errorUnableToSetupGitlabClient)
......@@ -112,7 +127,6 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if !found {
if gitlabGroup, _, err = r.createGroup(group); err != nil {
r.Log.Error(err, errorWhileCreatingGitlabGroup, "group", group)
return ctrl.Result{Requeue: true}, err
}
......@@ -123,12 +137,17 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
}
}
if err := r.updateStatus(ctx, gitlabGroup.ID, group); err != nil {
if err = r.updateCiVariables(gitlabGroup.ID, group); err != nil {
r.Log.Error(err, errorWhileUpdatingGitlabGroupCiVariables, "group", group)
return ctrl.Result{Requeue: true}, err
}
if err = r.updateStatus(ctx, gitlabGroup.ID, group); err != nil {
r.Log.Error(err, errorUnableToUpdateStatus, "group", group)
return ctrl.Result{Requeue: true}, err
}
if err := r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil {
if err = r.processProjects(ctx, group, group.Spec.ProjectSpecs); err != nil {
r.Log.Error(err, errorUnableToProcessProjects, "group", group)
return ctrl.Result{Requeue: true}, err
}
......@@ -198,12 +217,12 @@ func (r *GroupReconciler) processProjects(ctx context.Context, group *gitlabv1al
r.Log.Error(err, errorLookingUpProject, "name", projectSpec.Name)
return err
} else if foundProject {
if _, err := r.updateProject(ctx, 0, projectSpec, project); err != nil {
if _, err = r.updateProject(ctx, 0, projectSpec, project); err != nil {
r.Log.Error(err, errorUpdatingProject, "project", project)
}
} else {
if err := r.createProject(ctx, group, projectSpec); err != nil {
if err = r.createProject(ctx, group, projectSpec); err != nil {
r.Log.Error(err, errorTryingToCreateProject, "project", projectSpec)
return err
......@@ -287,3 +306,75 @@ func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&gitlabv1alpha1.Project{}).
Complete(r)
}
func (r *GroupReconciler) updateCiVariables(ID int, group *gitlabv1alpha1.Group) error {
var variablesToUpdate []string = []string{KustomizeStagingPathVariableName, KustomizeProductionPathVariableName, ManifestRepoURLVariableName}
var err error
var groupVariable *gitlab.GroupVariable
var statusCode int
for _, variableToUpdate := range variablesToUpdate {
if groupVariable, statusCode, err = r.gitlabClient.GetGroupVariable(ID, variableToUpdate); err != nil {
if statusCode != 404 {
r.Log.Error(err, errorGettingGroupVariablesFromGitlab, "statusCode", statusCode, "variable", variablesToUpdate)
return err
}
if groupVariable, statusCode, err = r.CreateCiVariable(ID, variableToUpdate, group); err != nil {
r.Log.Error(err, errorCreatingGroupVariableInGitlab, "statusCode", statusCode, "variable", variablesToUpdate)
return err
}
} else {
value := r.GetGroupVariableValueByName(variableToUpdate, group)
option := gitlab.UpdateGroupVariableOptions{
Value: &value,
VariableType: &groupVariable.VariableType,
Protected: &groupVariable.Protected,
Masked: &groupVariable.Masked,
EnvironmentScope: &groupVariable.EnvironmentScope,
}
if groupVariable, statusCode, err = r.gitlabClient.UpdateGroupVariable(ID, groupVariable.Key, option); err != nil {
r.Log.Error(err, errorUpdatingGroupVariableInGitLab, "statusCode", statusCode, "variable", variablesToUpdate)
return err
}
}
}
return nil
}
func (r *GroupReconciler) CreateCiVariable(ID int, variableToCreate string, group *gitlabv1alpha1.Group) (*gitlab.GroupVariable, int, error) {
envType := gitlab.EnvVariableType
protected := true
masked := false
scope := "*"
var value string
if value = r.GetGroupVariableValueByName(variableToCreate, group); value == "" {
return nil, 405, nil // 405 HTTP Not Supported
}
options := gitlab.CreateGroupVariableOptions{
Key: &variableToCreate,
Value: &value,
VariableType: &envType,
Protected: &protected,
Masked: &masked,
EnvironmentScope: &scope,
}
return r.gitlabClient.AddGroupVariable(ID, options)
}
func (r *GroupReconciler) GetGroupVariableValueByName(variableToCreate string, group *gitlabv1alpha1.Group) string {
switch variableToCreate {
case KustomizeStagingPathVariableName:
return group.Spec.KustomizeStagingPath
case KustomizeProductionPathVariableName:
return group.Spec.KustomizeProductionPath
case ManifestRepoURLVariableName:
return group.Spec.ManifestRepositoryURL
}
return ""
}
......@@ -10,7 +10,9 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"strconv"
"testing"
"time"
gitlab2 "valkyrie.dso.mil/valkyrie-api/apis/gitlab"
gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
......@@ -37,11 +39,14 @@ func getGreenGroup() *gitlabv1alpha1.Group {
Annotations: make(map[string]string),
},
Spec: gitlabv1alpha1.GroupSpec{
FullPath: "/greenGroup",
Name: "greenGroup",
Description: "a green Group",
GitlabCredentialsName: "greenCredentialsName",
ProjectSpecs: make([]gitlabv1alpha1.ProjectSpec, 0),
FullPath: "/greenGroup",
Name: "greenGroup",
Description: "a green Group",
GitlabCredentialsName: "greenCredentialsName",
KustomizeProductionPath: "/kustomize/prod/path",
KustomizeStagingPath: "/kustomize/staging/path",
ManifestRepositoryURL: "https://gitserver/manifest/repo/url",
ProjectSpecs: make([]gitlabv1alpha1.ProjectSpec, 0),
},
Status: gitlabv1alpha1.GroupStatus{
CreatedTime: metav1.Time{
......@@ -80,10 +85,11 @@ func getGroupControllerWithMocksInGreenTestState() (*GroupReconciler, *MockLogge
}
clientMock := MockClient{statusWriter: &MockStatusWriter{}}
clientMock.expectedObjects = make(map[client.ObjectKey]client.Object)
greenGroup := getGreenGroup()
clientMock.expectedObjects[types.NamespacedName{
Namespace: greenGroupNamespace,
Name: greenGroupName,
}] = getGreenGroup()
}] = greenGroup
expectedGitlabGroups := make(map[int]*gitlab.Group)
expectedGitlabGroups[greenGroupID] = getGreenGitlabGroup()
......@@ -91,6 +97,22 @@ func getGroupControllerWithMocksInGreenTestState() (*GroupReconciler, *MockLogge
expectedGroups: expectedGitlabGroups,
}
key := ManifestRepoURLVariableName
protected := true
masked := false
variableType := gitlab.EnvVariableType
environmentScope := "*"
options := gitlab.CreateGroupVariableOptions{
Key: &key,
Value: &greenGroup.Spec.ManifestRepositoryURL,
VariableType: &variableType,
Protected: &protected,
Masked: &masked,
EnvironmentScope: &environmentScope,
}
gitlabClientMock.AddGroupVariable(greenGroupID, options)
configurationMock := MockClientConfiguration{
GitlabClient: &gitlabClientMock,
SetupClientFunction: nil,
......@@ -127,6 +149,16 @@ var _ = Describe("Reconcile", func() {
Expect(err).To(BeNil())
})
})
Context("supports client configuration mocking", func() {
sut, _, _, _, _ := getGroupControllerWithMocksInGreenTestState()
// Remove the gitlab client mock
sut.gitlabClient = nil
sut.gitlabClientConfiguration = nil
sut.Reconcile(context.TODO(), getGreenGroupControllerRequest())
It("should use the default client configuration implementation", func() {
Expect(sut.gitlabClientConfiguration).To(BeAssignableToTypeOf(gitlab2.ClientConfigurationImpl{}))
})
})
Context("the API Object isn't found", func() {
sut, logger, _, _, _ := getGroupControllerWithMocksInGreenTestState()
result, err := sut.Reconcile(context.TODO(), ctrl.Request{
......@@ -236,6 +268,57 @@ var _ = Describe("Reconcile", func() {
Expect(err).NotTo(BeNil())
})
})
Context("getGroupVariable fails", func() {
sut, log, _, _, gitlabClient := getGroupControllerWithMocksInGreenTestState()
gitlabClient.getGroupVariableFunction = func(groupID int, key string) (*gitlab.GroupVariable, int, error) {
return nil, 500, &MockError{"getGroupVariable mocked error"}
}
result, err := sut.Reconcile(context.TODO(), getGreenGroupControllerRequest())
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroupCiVariables))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
})
Context("createCiVariable fails", func() {
sut, log, _, _, gitlabClient := getGroupControllerWithMocksInGreenTestState()
gitlabClient.addGroupVariableFunction = func(groupID int, options gitlab.CreateGroupVariableOptions) (*gitlab.GroupVariable, int, error) {
return nil, 500, &MockError{ "addGroupVariable mocked error"}
}
result, err := sut.Reconcile(context.TODO(), getGreenGroupControllerRequest())
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroupCiVariables))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
})
Context("updateGroupVariable fails", func() {
sut, log, _, _, gitlabClient := getGroupControllerWithMocksInGreenTestState()
gitlabClient.updateGroupVariableFunction = func(groupID int, key string, options gitlab.UpdateGroupVariableOptions) (*gitlab.GroupVariable, int, error) {
return nil, 500, &MockError{ "updateGroupVariable mocked error"}
}
result, err := sut.Reconcile(context.TODO(), getGreenGroupControllerRequest())
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorWhileUpdatingGitlabGroupCiVariables))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
})
Context("updateStatus fails", func() {
sut, logger, _, kubernetesClient, _ := getGroupControllerWithMocksInGreenTestState()
......@@ -499,3 +582,76 @@ var _ = Describe("updateProject", func() {
Expect(project).To(Equal(*returnedProject))
})
})
func TestGroupReconciler_GetGroupVariableValueByName(t *testing.T) {
type args struct {
variableToCreate string
group *gitlabv1alpha1.Group
}
group := getGreenGroup()
tests := []struct {
name string
args args
want string
}{
{
name: "should return value of " + KustomizeProductionPathVariableName,
args: args{
variableToCreate: KustomizeProductionPathVariableName,
group: group,
},
want: group.Spec.KustomizeProductionPath,
},
{
name: "should return value of " + KustomizeStagingPathVariableName,
args: args{
variableToCreate: KustomizeStagingPathVariableName,
group: group,
},
want: group.Spec.KustomizeStagingPath,
},
{
name: "should return value of " + ManifestRepoURLVariableName,
args: args{
variableToCreate: ManifestRepoURLVariableName,
group: group,
},
want: group.Spec.ManifestRepositoryURL,
},
{
name: "should return no value",
args: args{
variableToCreate: "not supported",
group: group,
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r, _, _, _, _ := getGroupControllerWithMocksInGreenTestState()
if got := r.GetGroupVariableValueByName(tt.args.variableToCreate, tt.args.group); got != tt.want {
t.Errorf("GetGroupVariableValueByName() = %v, want %v", got, tt.want)
}
})
}
}
var _ = Describe("CreateCiVariable", func() {
Context("an unsupported variable key is called", func() {
sut, _, _, _, _ := getGroupControllerWithMocksInGreenTestState()
result, statusCode, err := sut.CreateCiVariable(1, "unsupported variable", getGreenGroup())
It("should return an empty value", func() {
Expect(result).To(BeNil())
})
It("should return a 405 (not supported) statusCode", func() {
Expect(statusCode).To(Equal(405))
})
It("should not return an error", func() {
Expect(err).To(BeNil())
})
})
})
\ No newline at end of file
......@@ -415,60 +415,208 @@ func (m *MockError) Error() string {
}
type MockGitlabClient struct {
newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error)
getGroupsFunction func(name *string) ([]*gitlab.Group, int, error)
addGroupFunction func(options gitlab.CreateGroupOptions) (*gitlab.Group, int, error)
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
getMRByIDFunc func(projectID int, mrID int) (*gitlab.MergeRequest, error)
closeMRFunc func(projectID int, mrID int) error
getMRsFunc func(projectID int, sourceBranch string, targetBranch string) ([]*gitlab.MergeRequest, error)
createMRFunc func(projectID int, mrOptions *gitlab.CreateMergeRequestOptions) (*gitlab.MergeRequest, error)
getProjectFunction func(projectID int) (*gitlab.Project, int, error)
addProjectFunction func(options gitlab.CreateProjectOptions) (*gitlab.Project, int, error)
updateProjectFunction func(projectID int, editProjectOptions gitlab.EditProjectOptions) (*gitlab.Project, int, error)
newClientFunction func(token string, options ...gitlab.ClientOptionFunc) (*gitlab.Client, error)
getGroupsFunction func(name *string) ([]*gitlab.Group, int, error)
getGroupVariableFunction func(groupID int, key string) (*gitlab.GroupVariable, int, error)
addGroupVariableFunction func(groupID int, options gitlab.CreateGroupVariableOptions) (*gitlab.GroupVariable, int, error)
addProjectVariableFunction func(groupID int, options gitlab.CreateProjectVariableOptions) (*gitlab.ProjectVariable, int, error)
addGroupFunction func(options gitlab.CreateGroupOptions) (*gitlab.Group, int, error)
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
expectedGroupVariables map[string]*gitlab.GroupVariable
expectedProjectVariables map[string]*gitlab.ProjectVariable
getMRByIDFunc func(projectID int, mrID int) (*gitlab.MergeRequest, error)
closeMRFunc func(projectID int, mrID int) error
getMRsFunc func(projectID int, sourceBranch string, targetBranch string) ([]*gitlab.MergeRequest, error)
createMRFunc func(projectID int, mrOptions *gitlab.CreateMergeRequestOptions) (*gitlab.MergeRequest, error)
getProjectFunction func(projectID int) (*gitlab.Project, int, error)
addProjectFunction func(options gitlab.CreateProjectOptions) (*gitlab.Project, int, error)
updateProjectFunction func(projectID int, editProjectOptions gitlab.EditProjectOptions) (*gitlab.Project, int, error)
updateGroupVariableFunction func(groupID int, key string, options gitlab.UpdateGroupVariableOptions) (*gitlab.GroupVariable, int, error)
updateProjectVariableFunction func(groupID int, key string, options gitlab.UpdateProjectVariableOptions) (*gitlab.ProjectVariable, int, error)
getProjectVariableFunction func(projectID int, key string) (*gitlab.ProjectVariable, int, error)
}
func (m *MockGitlabClient) GetGroupVariable(groupID int, key string) (*gitlab.GroupVariable, int, error) {
panic("implement me")
if m.getGroupVariableFunction != nil {
return m.getGroupVariableFunction(groupID, key)
}
if m.expectedGroupVariables == nil {
m.expectedGroupVariables = make(map[string]*gitlab.GroupVariable)
}
returnVariable := m.expectedGroupVariables[key]
if returnVariable == nil {
return returnVariable, 404, &MockError{message: "not found"}
}
return m.expectedGroupVariables[key], 200, nil
}
func (m *MockGitlabClient) GetGroupVariables(groupID int) ([]*gitlab.GroupVariable, int, error) {
panic("implement me")
if m.expectedGroupVariables == nil {
m.expectedGroupVariables = make(map[string]*gitlab.GroupVariable)
}
returnVariables := make([]*gitlab.GroupVariable, 0)
for _, value := range m.expectedGroupVariables {
returnVariables = append(returnVariables, value)
}
return returnVariables, 200, nil
}
func (m *MockGitlabClient) GetProjectVariables(projectID int) ([]*gitlab.ProjectVariable, int, error) {
panic("implement me")
if m.expectedProjectVariables == nil {
m.expectedProjectVariables = make(map[string]*gitlab.ProjectVariable)
}
returnVariables := make([]*gitlab.ProjectVariable, 0)
for _, value := range m.expectedProjectVariables {
returnVariables = append(returnVariables, value)
}
return returnVariables, 200, nil
}
func (m *MockGitlabClient) GetProjectVariable(projectID int, key string) (*gitlab.ProjectVariable, int, error) {
panic("implement me")
if m.getProjectVariableFunction != nil {
return m.getProjectVariableFunction(projectID, key)
}
if m.expectedProjectVariables == nil {
m.expectedProjectVariables = make(map[string]*gitlab.ProjectVariable)
}
returnVariable := m.expectedProjectVariables[key]
if returnVariable == nil {
return returnVariable, 404, &MockError{message: "not found"}
}
return m.expectedProjectVariables[key], 200, nil
}
func (m *MockGitlabClient) AddGroupVariable(groupID int, options gitlab.CreateGroupVariableOptions) (*gitlab.GroupVariable, int, error) {
panic("implement me")
if m.addGroupVariableFunction != nil {
return m.addGroupVariableFunction(groupID, options)
}
if m.expectedGroupVariables == nil {
m.expectedGroupVariables = make(map[string]*gitlab.GroupVariable)
}
returnVariable := gitlab.GroupVariable{
Key: *options.Key,
Value: *options.Value,
VariableType: *options.VariableType,
Protected: *options.Protected,
Masked: *options.Masked,
EnvironmentScope: *options.EnvironmentScope,
}
m.expectedGroupVariables[*options.Key] = &returnVariable
return &returnVariable, 200, nil
}
func (m *MockGitlabClient) UpdateGroupVariable(groupID int, key string, options gitlab.UpdateGroupVariableOptions) (*gitlab.GroupVariable, int, error) {
panic("implement me")
if m.updateGroupVariableFunction != nil {
return m.updateGroupVariableFunction(groupID, key, options)
}
if m.expectedGroupVariables == nil {
m.expectedGroupVariables = make(map[string]*gitlab.GroupVariable)
}
returnVariable := gitlab.GroupVariable{
Key: key,
Value: *options.Value,
VariableType: *options.VariableType,
Protected: *options.Protected,
Masked: *options.Masked,
EnvironmentScope: *options.EnvironmentScope,
}
m.expectedGroupVariables[key] = &returnVariable
return &returnVariable, 200, nil
}
func (m *MockGitlabClient) DeleteGroupVariable(groupID int, key string, waitInterval int, waitCount int) (int, error) {
panic("implement me")
if m.expectedGroupVariables == nil {
m.expectedGroupVariables = make(map[string]*gitlab.GroupVariable)
}
foundVariable := m.expectedGroupVariables[key]
if foundVariable != nil {
delete(m.expectedGroupVariables, key)
}
return 200, nil
}
func (m *MockGitlabClient) AddProjectVariable(projectID int, options gitlab.CreateProjectVariableOptions) (*gitlab.ProjectVariable, int, error) {
panic("implement me")
if m.addProjectVariableFunction != nil {
return m.addProjectVariableFunction(projectID, options)
}
if m.expectedProjectVariables == nil {
m.expectedProjectVariables = make(map[string]*gitlab.ProjectVariable)
}
returnVariable := gitlab.ProjectVariable{
Key: *options.Key,
Value: *options.Value,
VariableType: *options.VariableType,
Protected: *options.Protected,
Masked: *options.Masked,
EnvironmentScope: *options.EnvironmentScope,
}
m.expectedProjectVariables[*options.Key] = &returnVariable
return &returnVariable, 200, nil
}
func (m *MockGitlabClient) UpdateProjectVariable(projectID int, key string, options gitlab.UpdateProjectVariableOptions) (*gitlab.ProjectVariable, int, error) {
panic("implement me")
if m.updateProjectVariableFunction != nil {
return m.updateProjectVariableFunction(projectID, key, options)
}
if m.expectedProjectVariables == nil {
m.expectedProjectVariables = make(map[string]*gitlab.ProjectVariable)
}
returnVariable := gitlab.ProjectVariable{
Key: key,
Value: *options.Value,
VariableType: *options.VariableType,
Protected: *options.Protected,
Masked: *options.Masked,
EnvironmentScope: *options.EnvironmentScope,
}
m.expectedProjectVariables[key] = &returnVariable
return &returnVariable, 200, nil
}
func (m *MockGitlabClient) DeleteProjectVariable(projectID int, key string, waitInterval int, waitCount int) (int, error) {
panic("implement me")
if m.expectedProjectVariables == nil {
m.expectedProjectVariables = make(map[string]*gitlab.ProjectVariable)
}
foundVariable := m.expectedProjectVariables[key]
if foundVariable != nil {
delete(m.expectedProjectVariables, key)
}
return 200, nil
}
func (m *MockGitlabClient) GetUser(userID int) (*gitlab.User, int, error) {
......
......@@ -51,13 +51,19 @@ const (
errorCreatingGitlabProject = "Error creating Gitlab project."
errorUpdatingGitlabProject = "Error updating Gitlab project."
errorUpdatingProjectIDAnnotation = "Error updating project ID annotation."
)
errorUpdatingProjectVariableInGitlab = "Error updating project variable in Gitlab"
errorGettingProjectVariableFromGitlab = "Error getting project variable from Gitlab"
errorCreatingProjectVariableInGitlab = "Error creating project variable in Gitlab"
errorWhileUpdatingProjectVariableInGitlab = "Error while updating project variable in gitlab"
)
// statuses
const (
groupDoesntExist string = "GitlabGroupDoesNotExist"
)
const ManifestImageVariableName = "MANIFEST_IMAGE"
const annotationKeyID = "ID"
// Reconcile is the main reconciliation loop that will create/edit/delete Gitlab Projects.
......@@ -77,10 +83,15 @@ func (r *ProjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
// Do a nil check here so we can use mock gitlabClients
if r.gitlabClient == nil {
if r.gitlabClientConfiguration == nil {
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{}
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{
Log: r.Log,
Ctx: ctx,
Req: req,
}
}
if r.gitlabClient, err = r.gitlabClientConfiguration.SetupClient(r.Client, project.Spec.GitlabCredentialsName); err != nil {
r.Log.Error(err, errorUnableToSetupGitlabClient)
_ = r.updateStatus(ctx, project, errorUnableToSetupGitlabClient)
return ctrl.Result{Requeue: true}, err
}
}
......@@ -115,6 +126,12 @@ func (r *ProjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{Requeue: true}, err
}
if _, err = r.updateManifestImageSetting(id, project.Spec.ManifestImage); err != nil {
r.Log.Error(err, errorUpdatingProjectVariableInGitlab)
_ = r.updateStatus(ctx, project, errorUpdatingProjectVariableInGitlab)
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)
......@@ -156,18 +173,6 @@ func (r *ProjectReconciler) getGitlabGroup(ctx context.Context, project *v1alpha
var gitlabGroup *gitlab.Group
var response int
var err error
// 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)
}
if gitlabGroup, response, err = r.gitlabClient.GetGroup(project.Spec.GroupID); err != nil {
r.Log.Error(err, errorGettingGroupFromGitlab, "response", response, "groupID", project)
......@@ -222,3 +227,52 @@ func (r *ProjectReconciler) updateProjectIDAnnotation(ctx context.Context, proje
project.ObjectMeta.Annotations[annotationKeyID] = strconv.Itoa(gitlabProject.ID)
return r.Client.Update(ctx, project)
}
func (r *ProjectReconciler) updateManifestImageSetting(id int, manifestPath string) (*gitlab.ProjectVariable, error) {
var projectVariable *gitlab.ProjectVariable
var statusCode int
var err error
key := ManifestImageVariableName
value := manifestPath
variableType := gitlab.EnvVariableType
protected := true
masked := false
environmentScope := "*"
if projectVariable, statusCode, err = r.gitlabClient.GetProjectVariable(id, ManifestImageVariableName); err != nil{
if statusCode == 404 {
if projectVariable, statusCode, err = r.gitlabClient.AddProjectVariable(id, gitlab.CreateProjectVariableOptions{
Key: &key,
Value: &value,
VariableType: &variableType,
Protected: &protected,
Masked: &masked,
EnvironmentScope: &environmentScope,
}); err != nil {
r.Log.Error(err, errorCreatingProjectVariableInGitlab, "statusCode", statusCode)
return projectVariable, err
}
return projectVariable, err
}
r.Log.Error(err, errorGettingProjectVariableFromGitlab, "statusCode", statusCode)
return projectVariable, err
}
projectVariable, statusCode, err = r.gitlabClient.UpdateProjectVariable(id, ManifestImageVariableName, gitlab.UpdateProjectVariableOptions{
Value: &manifestPath,
VariableType: &projectVariable.VariableType,
Protected: &projectVariable.Protected,
Masked: &projectVariable.Masked,
EnvironmentScope: &projectVariable.EnvironmentScope,
})
if err != nil{
r.Log.Error(err, errorWhileUpdatingProjectVariableInGitlab, "statusCode", statusCode)
return projectVariable, err
}
return projectVariable, err
}
......@@ -9,7 +9,8 @@ import (
"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"
apisGitlab "valkyrie.dso.mil/valkyrie-api/apis/gitlab"
"valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
......@@ -18,15 +19,15 @@ const (
GreenNamespace = "default"
)
func getGreenProject() gitlabv1alpha1.Project {
project := gitlabv1alpha1.Project{
func getGreenProject() v1alpha1.Project {
project := v1alpha1.Project{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: GreenProjectName,
Namespace: GreenNamespace,
Annotations: make(map[string]string, 0),
},
Spec: gitlabv1alpha1.ProjectSpec{
Spec: v1alpha1.ProjectSpec{
Name: GreenProjectName,
FullPath: "https://example.com/TestProject",
ImpactLevel: "2",
......@@ -34,8 +35,9 @@ func getGreenProject() gitlabv1alpha1.Project {
VirtualService: GreenNamespace,
ServicePort: 8081,
Language: "golang",
ManifestImage: "manifestImage",
},
Status: gitlabv1alpha1.ProjectStatus{
Status: v1alpha1.ProjectStatus{
URL: "https://example.com/TestProject",
State: "Created",
},
......@@ -46,7 +48,7 @@ func getGreenProject() gitlabv1alpha1.Project {
return project
}
func getGreenRequest() ctrl.Request {
func getGreenProjectRequest() ctrl.Request {
request := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: GreenNamespace,
......@@ -56,8 +58,8 @@ func getGreenRequest() ctrl.Request {
return request
}
func getControllerWithMocksInGreenTestState() (ProjectReconciler, *MockManager, *MockLogger, *MockStatusWriter) {
builder := gitlabv1alpha1.SchemeBuilder.Register(&gitlabv1alpha1.Project{}, &gitlabv1alpha1.ProjectList{})
func getProjectControllerWithMocksInGreenTestState() (ProjectReconciler, *MockManager, *MockLogger, *MockStatusWriter) {
builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.Project{}, &v1alpha1.ProjectList{})
scheme, _ := builder.Build()
mockStatusWriter := MockStatusWriter{}
......@@ -99,7 +101,7 @@ func getRequestWithDefaultNamespacedTestProject() ctrl.Request {
}
var _ = Describe("SetupWithManager", func() {
sut, mgr, _, _ := getControllerWithMocksInGreenTestState()
sut, mgr, _, _ := getProjectControllerWithMocksInGreenTestState()
err := sut.SetupWithManager(mgr)
......@@ -110,7 +112,7 @@ var _ = Describe("SetupWithManager", func() {
var _ = Describe("reconcile", func() {
Context("green state", func() {
sut, _, _, _ := getControllerWithMocksInGreenTestState()
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestProject()
result, err := sut.Reconcile(context.TODO(), request)
It("should return an empty result", func() {
......@@ -120,8 +122,18 @@ var _ = Describe("reconcile", func() {
Expect(err).To(BeNil())
})
})
Context("supports client configuration mocking", func() {
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
// Remove the gitlab client mock
sut.gitlabClient = nil
sut.gitlabClientConfiguration = nil
sut.Reconcile(context.TODO(), getGreenProjectRequest())
It("should use the default client configuration implementation", func() {
Expect(sut.gitlabClientConfiguration).To(BeAssignableToTypeOf(apisGitlab.ClientConfigurationImpl{}))
})
})
Context("SetupClient fails", func() {
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestProject()
sut.gitlabClient = nil
sut.gitlabClientConfiguration = &MockClientConfiguration{
......@@ -142,7 +154,7 @@ var _ = Describe("reconcile", func() {
})
})
Context("project isn't found", func() {
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestProject()
request.NamespacedName.Name = "not going to find it"
result, err := sut.Reconcile(context.TODO(), request)
......@@ -157,7 +169,7 @@ var _ = Describe("reconcile", func() {
})
})
Context("getProject returns an error", func() {
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
failingClient := MockClient{
GetFunction: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
return &MockError{
......@@ -191,7 +203,7 @@ var _ = Describe("reconcile", func() {
}
},
}
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
sut.gitlabClient = &failingGitlabClient
request := getRequestWithDefaultNamespacedTestProject()
result, err := sut.Reconcile(context.TODO(), request)
......@@ -214,7 +226,7 @@ var _ = Describe("reconcile", func() {
}
},
}
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
sut.gitlabClient = &failingGitlabClient
request := getRequestWithDefaultNamespacedTestProject()
result, err := sut.Reconcile(context.TODO(), request)
......@@ -240,7 +252,7 @@ var _ = Describe("reconcile", func() {
}
},
}
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
sut.gitlabClient = &failingGitlabClient
request := getRequestWithDefaultNamespacedTestProject()
result, err := sut.Reconcile(context.TODO(), request)
......@@ -263,10 +275,10 @@ var _ = Describe("reconcile", func() {
}
},
}
sut, _, log, status := getControllerWithMocksInGreenTestState()
sut, _, log, status := getProjectControllerWithMocksInGreenTestState()
sut.gitlabClient = &failingGitlabClient
request := getGreenRequest()
request := getGreenProjectRequest()
result, err := sut.Reconcile(context.TODO(), request)
It("should return an error", func() {
......@@ -282,14 +294,113 @@ var _ = Describe("reconcile", func() {
Expect(status.updateFunctionCalled).To(BeTrue())
})
})
Context("getProjectVariable fails", func() {
sut, _, log, status := getProjectControllerWithMocksInGreenTestState()
ctx := context.TODO()
req := getGreenProjectRequest()
sut.gitlabClient.(*MockGitlabClient).getProjectVariableFunction = func(projectID int, key string) (*gitlab.ProjectVariable, int, error) {
return nil, 500, &MockError{message: "mocked getProjectVariable failure."}
}
result, err := sut.Reconcile(ctx, req)
It("should return and error", func() {
Expect(err).ToNot(BeNil())
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorUpdatingProjectVariableInGitlab))
})
It("should update the status of the project", func() {
Expect(status.updatedObject.(*v1alpha1.Project).Status.State).To(Equal(errorUpdatingProjectVariableInGitlab))
})
})
Context("addProjectVariable fails", func() {
sut, _, log, status := getProjectControllerWithMocksInGreenTestState()
ctx := context.TODO()
req := getGreenProjectRequest()
sut.gitlabClient.(*MockGitlabClient).addProjectVariableFunction = func(groupID int, options gitlab.CreateProjectVariableOptions) (*gitlab.ProjectVariable, int, error) {
return nil, 500, &MockError{message: "mocked addProjectVariable failure."}
}
result, err := sut.Reconcile(ctx, req)
It("should return and error", func() {
Expect(err).ToNot(BeNil())
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorUpdatingProjectVariableInGitlab))
})
It("should update the status of the project", func() {
Expect(status.updatedObject.(*v1alpha1.Project).Status.State).To(Equal(errorUpdatingProjectVariableInGitlab))
})
})
Context("updateProjectVariable fails", func() {
sut, _, log, status := getProjectControllerWithMocksInGreenTestState()
ctx := context.TODO()
req := getGreenProjectRequest()
sut.gitlabClient.(*MockGitlabClient).expectedProjectVariables = make(map[string]*gitlab.ProjectVariable,0)
sut.gitlabClient.(*MockGitlabClient).expectedProjectVariables[ManifestImageVariableName] = &gitlab.ProjectVariable{
Key: ManifestImageVariableName,
Value: "testValue",
VariableType: gitlab.EnvVariableType,
Protected: true,
Masked: false,
EnvironmentScope: "*",
}
sut.gitlabClient.(*MockGitlabClient).updateProjectVariableFunction = func(groupID int, key string, options gitlab.UpdateProjectVariableOptions) (*gitlab.ProjectVariable, int, error) {
return nil, 500, &MockError{message: "updateProjectVariable Failure"}
}
result, err := sut.Reconcile(ctx, req)
It("should return and error", func() {
Expect(err).ToNot(BeNil())
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorUpdatingProjectVariableInGitlab))
})
It("should update the status of the project", func() {
Expect(status.updatedObject.(*v1alpha1.Project).Status.State).To(Equal(errorUpdatingProjectVariableInGitlab))
})
})
Context("updateProjectVariable green state", func() {
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
ctx := context.TODO()
req := getGreenProjectRequest()
sut.gitlabClient.(*MockGitlabClient).expectedProjectVariables = make(map[string]*gitlab.ProjectVariable,0)
sut.gitlabClient.(*MockGitlabClient).expectedProjectVariables[ManifestImageVariableName] = &gitlab.ProjectVariable{
Key: ManifestImageVariableName,
Value: "testValue",
VariableType: gitlab.EnvVariableType,
Protected: true,
Masked: false,
EnvironmentScope: "*",
}
result, err := sut.Reconcile(ctx, req)
It("should return an empty result", func() {
Expect(result).To(Equal(ctrl.Result{}))
})
It("should return no error", func() {
Expect(err).To(BeNil())
})
})
Context("updateProjectIDAnnotation returns an error", func() {
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
sut.Client.(*MockClient).UpdateFunction = func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
return &MockError{message: errorUpdatingProjectIDAnnotation}
}
request := getGreenRequest()
request := getGreenProjectRequest()
result, err := sut.Reconcile(context.TODO(), request)
......@@ -308,7 +419,7 @@ var _ = Describe("reconcile", func() {
var _ = Describe("getProject", func() {
Context("green state", func() {
sut, _, _, _ := getControllerWithMocksInGreenTestState()
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestProject()
project, err := sut.getProject(context.TODO(), request)
It("should return a project from the client", func() {
......@@ -320,7 +431,7 @@ var _ = Describe("getProject", func() {
})
})
Context("object is not found.", func() {
sut, _, _, _ := getControllerWithMocksInGreenTestState()
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
request := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: GreenNamespace,
......@@ -337,7 +448,7 @@ var _ = Describe("getProject", func() {
})
})
Context("an error occurs", func() {
sut, _, _, _ := getControllerWithMocksInGreenTestState()
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
failingClient := MockClient{
GetFunction: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
return &MockError{
......@@ -364,9 +475,9 @@ var _ = Describe("getProject", func() {
var _ = Describe("getGitlabGroup", func() {
Context("green state", func() {
sut, _, _, _ := getControllerWithMocksInGreenTestState()
sut, _, _, _ := getProjectControllerWithMocksInGreenTestState()
project := getGreenProject()
group, err := sut.getGitlabGroup(context.TODO(), &project, getGreenRequest())
group, err := sut.getGitlabGroup(context.TODO(), &project, getGreenProjectRequest())
It("should return a group, and no error.", func() {
Expect(group).To(BeAssignableToTypeOf(&gitlab.Group{}))
Expect(err).To(BeNil())
......@@ -374,16 +485,13 @@ var _ = Describe("getGitlabGroup", func() {
})
Context("gitlab client returns error", func() {
project := getGreenProject()
failingGitlabClient := MockGitlabClient{
getGroupFunction: func(groupID int) (*gitlab.Group, int, error) {
return nil, 500, &MockError{
message: "failure in getGitlabGroup",
}
},
sut, _, log, _ := getProjectControllerWithMocksInGreenTestState()
sut.gitlabClient.(*MockGitlabClient).getGroupFunction = func(groupID int) (*gitlab.Group, int, error){
return nil, 500, &MockError{
message: "failure in getGitlabGroup",
}
}
sut, _, log, _ := getControllerWithMocksInGreenTestState()
sut.gitlabClient = &failingGitlabClient
group, err := sut.getGitlabGroup(context.TODO(), &project, getGreenRequest())
group, err := sut.getGitlabGroup(context.TODO(), &project, getGreenProjectRequest())
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
......@@ -399,7 +507,7 @@ var _ = Describe("getGitlabGroup", func() {
var _ = Describe("updateStatus", func() {
Context("green state", func() {
project := getGreenProject()
sut, _, _, statusWriter := getControllerWithMocksInGreenTestState()
sut, _, _, statusWriter := getProjectControllerWithMocksInGreenTestState()
result := sut.updateStatus(context.TODO(), &project, groupDoesntExist)
It("should update the status of the object", func() {
......@@ -411,7 +519,7 @@ var _ = Describe("updateStatus", func() {
})
Context("updateStatus fails", func() {
project := getGreenProject()
sut, _, log, statusWriter := getControllerWithMocksInGreenTestState()
sut, _, log, statusWriter := getProjectControllerWithMocksInGreenTestState()
statusWriter.updateFunction = func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
return &MockError{
message: "updateStatus fails",
......
......@@ -74,16 +74,6 @@ var _ = Describe("instantiateControllers", func() {
})
})
var _ = Describe("newManager", func() {
When("Creating a new manager", func() {
program := &driverImpl{}
mgr, _ := program.newManager(":8086", ":8087", false)
It("Should return a new manager", func() {
Expect(mgr).NotTo(BeNil())
})
})
})
var _ = Describe("parseCommmandLine", func() {
Describe("Supported Command Lines", func() {
When("parsing the commmand lines", func() {
......
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