UNCLASSIFIED

Commit 672610cd authored by Jason van Brackel's avatar Jason van Brackel Committed by abrichards
Browse files

feat: Add group and project variables support for the gitlab client.

parent 35e05dfd
......@@ -38,6 +38,18 @@ type GroupSpec struct {
// information for logging into the
GitlabCredentialsName string `json:"gitlabCredentialsName"`
// KustomizeProductionPath is the relative path for the kustomization manifest for production use
// +kubebuilder:validation:required
KustomizeProductionPath string `json:"kustomizeProductionPath"`
// KustomizeStagingPath is the relative path for the kustomization manifest for staging use
// +kubebuilder:validation:required
KustomizeStagingPath string `json:"kustomizeStagingPath"`
// ManifestRepositoryURL is the path to the git repository for the deployment manifests
// +kubebuilder:validation:required
ManifestRepositoryURL string `json:"manifestRepositoryUrl"`
// ProjectSpecs are for the GitLab projects managed by this Group. It's expected that Projects
// will be managed at the group level rather than be adjusted themselves.
// +kubebuilder:validation:Required
......
......@@ -67,12 +67,12 @@ func initVarsPipeline() testVarsPipeline {
testVars.objectList2 = PipelineList{TypeMeta: objectList2MetaType, Items: objectItems2}
// leave scaffold Foo value for testing?
testVars.testObjectSpec1 = PipelineSpec{Dummy: testVars.testSpec}
testVars.testObjectSpec2 = PipelineSpec{Dummy: "other value"}
testVars.testObjectSpec1 = PipelineSpec{ProjectName: testVars.testSpec}
testVars.testObjectSpec2 = PipelineSpec{ProjectName: "other value"}
// leave scaffold Foo value for testing?
testVars.testObjectStatus1 = PipelineStatus{Dummy: testVars.testStatus}
testVars.testObjectStatus2 = PipelineStatus{Dummy: "other value"}
testVars.testObjectStatus1 = PipelineStatus{State: testVars.testStatus}
testVars.testObjectStatus2 = PipelineStatus{State: "other value"}
return testVars
}
......@@ -221,7 +221,7 @@ func TestDeepCopy_DeepCopySpec_Pipeline(t *testing.T) {
newObjectList := lTestVars.testObjectSpec1.DeepCopy()
got := newObjectList.Dummy
got := newObjectList.ProjectName
want := lTestVars.expectedSpec
if got != want {
......@@ -241,7 +241,7 @@ func TestDeepCopy_DeepCopySpecInto_Pipeline(t *testing.T) {
lTestVars.testObjectSpec1.DeepCopyInto(&lTestVars.testObjectSpec2)
got := lTestVars.testObjectSpec2.Dummy
got := lTestVars.testObjectSpec2.ProjectName
want := lTestVars.expectedSpec
if got != want {
......@@ -255,7 +255,7 @@ func TestDeepCopy_DeepCopyStatus_Pipeline(t *testing.T) {
newObjectStatus := lTestVars.testObjectStatus1.DeepCopy()
got := newObjectStatus.Dummy
got := newObjectStatus.State
want := lTestVars.expectedStatus
if got != want {
......@@ -277,7 +277,7 @@ func TestDeepCopy_DeepCopyStatusInto_Pipeline(t *testing.T) {
lTestVars.testObjectStatus1.DeepCopyInto(&lTestVars.testObjectStatus2)
got := lTestVars.testObjectStatus2.Dummy
got := lTestVars.testObjectStatus2.State
want := lTestVars.expectedStatus
if got != want {
......
......@@ -27,15 +27,14 @@ import (
type PipelineSpec struct {
// test dummy - do not remove
Dummy string `json:"dummy,omitempty"`
ProjectName string `json:"projectName,omitempty"`
}
// PipelineStatus defines the observed state of Pipeline
type PipelineStatus struct {
// We can use Pipeline and Job data to update this on a loop
// test dummy - do not remove
Dummy string `json:"dummy,omitempty"`
// State is the current state of the Pipeline, used mostly for error states.
State string `json:"state,omitempty"`
}
//+kubebuilder:object:root=true
......
......@@ -58,6 +58,9 @@ type ProjectSpec struct {
// +kubebuilder:validation:required
Language string `json:"language"` //TODO: Consider a custom type / annotation
// ManifestImage is the name of the DockerImage for the Project application
// +kubebuilder:validation:required
ManifestImage string `json:"manifestImage"`
}
// ProjectStatus defines the observed state of Project
......
......@@ -43,6 +43,9 @@ import (
gogitlab "github.com/xanzy/go-gitlab"
)
// TODO(jvb) Update CI/CD Variables in Groups
// TODO(jvb) Update CI/CD Variables in Projects
const itemsPerPage = 50
// Client -
......@@ -57,16 +60,26 @@ type Client interface {
GetGroup(groupID int) (*gogitlab.Group, int, error)
GetGroupByFullPath(fullPath *string) (*gogitlab.Group, int, error)
GetGroups(search *string) ([]*gogitlab.Group, error)
GetGroupVariable(groupID int, key string) (*gogitlab.GroupVariable, int, error)
GetGroupVariables(groupID int) ([]*gogitlab.GroupVariable, int, error)
AddGroup(createGroupOptions gogitlab.CreateGroupOptions) (*gogitlab.Group, int, error)
AddGroupMember(groupID *int, userID *int, accessLevel gogitlab.AccessLevelValue) (*gogitlab.GroupMember, int, error)
AddGroupVariable(groupID int, options gogitlab.CreateGroupVariableOptions) (*gogitlab.GroupVariable, int, error)
UpdateGroup(groupID int, updateGroupOptions *gogitlab.UpdateGroupOptions) (*gogitlab.Group, int, error)
UpdateGroupVariable(groupID int, key string, options gogitlab.UpdateGroupVariableOptions) (*gogitlab.GroupVariable, int, error)
DeleteGroup(groupID int, waitInterval int, waitCount int) (int, error)
DeleteGroupVariable(groupID int, key string, waitInterval int, waitCount int) (int, error)
GetProject(projectID int) (*gogitlab.Project, int, error)
GetProjectByFullPath(fullPath *string) (*gogitlab.Project, int, error)
GetProjects(search *string) ([]*gogitlab.Project, error)
GetProjectVariable(projectID int, key string) (*gogitlab.ProjectVariable, int, error)
GetProjectVariables(projectID int) ([]*gogitlab.ProjectVariable, int, error)
AddProject(createProjectOptions gogitlab.CreateProjectOptions) (*gogitlab.Project, int, error)
AddProjectVariable(projectID int, options gogitlab.CreateProjectVariableOptions) (*gogitlab.ProjectVariable, int, error)
UpdateProject(projectID int, editProjectOptions gogitlab.EditProjectOptions) (*gogitlab.Project, int, error)
UpdateProjectVariable(projectID int, key string, options gogitlab.UpdateProjectVariableOptions) (*gogitlab.ProjectVariable, int, error)
DeleteProject(projectID int, waitInterval int, waitCount int) (int, error)
DeleteProjectVariable(projectID int, key string, waitInterval int, waitCount int) (int, error)
GetMergeRequestByID(projectID int, mergeRequestID int) (*gogitlab.MergeRequest, error)
GetMergeRequests(projectID int, sourceBranch string, targetBranch string) ([]*gogitlab.MergeRequest, error)
CreateMergeRequest(projectID int, mrOptions *gogitlab.CreateMergeRequestOptions) (*gogitlab.MergeRequest, error)
......@@ -80,6 +93,220 @@ type ClientImpl struct {
apiURL string
}
// GetGroupVariables -
func (r ClientImpl) GetGroupVariables(groupID int) ([]*gogitlab.GroupVariable, int, error) {
logPrefix := "GetGroupVariables"
options := gogitlab.ListGroupVariablesOptions{
Page: 1,
PerPage: itemsPerPage,
}
groupVariables, res, err := r.client.GroupVariables.ListVariables(groupID, &options)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return groupVariables, res.StatusCode, nil
}
// GetProjectVariables -
func (r ClientImpl) GetProjectVariables(projectID int) ([]*gogitlab.ProjectVariable, int, error) {
logPrefix := "GetProjectVariables"
options := gogitlab.ListProjectVariablesOptions{
Page: 1,
PerPage: itemsPerPage,
}
projectVariable, res, err := r.client.ProjectVariables.ListVariables(projectID, &options)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return projectVariable, res.StatusCode, nil
}
// GetGroupVariable -
func (r ClientImpl) GetGroupVariable(groupID int, key string) (*gogitlab.GroupVariable, int, error) {
logPrefix := "GetGroupVariable"
groupVariable, res, err := r.client.GroupVariables.GetVariable(groupID, key)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return groupVariable, res.StatusCode, nil
}
// GetProjectVariable -
func (r ClientImpl) GetProjectVariable(projectID int, key string) (*gogitlab.ProjectVariable, int, error) {
logPrefix := "GetProjectVariable"
projectVariable, res, err := r.client.ProjectVariables.GetVariable(projectID, key)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return projectVariable, res.StatusCode, nil
}
// AddGroupVariable -
func (r ClientImpl) AddGroupVariable(groupID int, options gogitlab.CreateGroupVariableOptions) (*gogitlab.GroupVariable, int, error) {
logPrefix := "AddGroupVariable"
groupVariable, res, err := r.client.GroupVariables.CreateVariable(groupID, &options)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return groupVariable, res.StatusCode, nil
}
// UpdateGroupVariable -
func (r ClientImpl) UpdateGroupVariable(groupID int, key string, options gogitlab.UpdateGroupVariableOptions) (*gogitlab.GroupVariable, int, error) {
logPrefix := "UpdateGroupVariable"
groupVariable, res, err := r.client.GroupVariables.UpdateVariable(groupID, key, &options)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return groupVariable, res.StatusCode, nil
}
// DeleteGroupVariable -
func (r ClientImpl) DeleteGroupVariable(groupID int, key string, waitInterval int, waitCount int) (int, error) {
// waiting will be skipped if waitCount is 0
// setting wait will use a loop to wait until resource has completed deletion
logPrefix := "DeleteGroupVariable"
res, err := r.client.GroupVariables.RemoveVariable(groupID, key)
if err != nil {
processError(logPrefix, err)
return 0, err
}
returnStatusCode := res.StatusCode
// wait for resource to be deleted
if waitCount > 0 {
done := false
retryCount := waitCount
for !done && retryCount > 0 {
_, res, err = r.client.GroupVariables.GetVariable(groupID, key)
if err != nil && res == nil {
processError(logPrefix, err)
return 0, err
}
if res.StatusCode == http.StatusNotFound {
done = true
}
retryCount = retryCount - 1
rlog.Debugf("DeleteGroupVariable wait status: %d", res.StatusCode)
time.Sleep((time.Duration(waitInterval) * time.Millisecond))
}
if done {
returnStatusCode = http.StatusOK
} else {
returnStatusCode = http.StatusRequestTimeout
}
}
processComplete(logPrefix, returnStatusCode)
return returnStatusCode, nil
}
// AddProjectVariable -
func (r ClientImpl) AddProjectVariable(projectID int, options gogitlab.CreateProjectVariableOptions) (*gogitlab.ProjectVariable, int, error) {
logPrefix := "AddProjectVariable"
projectVariable, res, err := r.client.ProjectVariables.CreateVariable(projectID, &options)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return projectVariable, res.StatusCode, nil
}
// UpdateProjectVariable -
func (r ClientImpl) UpdateProjectVariable(projectID int, key string, options gogitlab.UpdateProjectVariableOptions) (*gogitlab.ProjectVariable, int, error) {
logPrefix := "UpdateProjectVariable"
projectVariable, res, err := r.client.ProjectVariables.UpdateVariable(projectID, key, &options)
if err != nil {
processError(logPrefix, err)
return nil, 0, err
}
processComplete(logPrefix, res.StatusCode)
return projectVariable, res.StatusCode, nil
}
// DeleteProjectVariable -
func (r ClientImpl) DeleteProjectVariable(projectID int, key string, waitInterval int, waitCount int) (int, error) {
logPrefix := "DeleteProjectVariable"
res, err := r.client.ProjectVariables.RemoveVariable(projectID, key)
if err != nil {
processError(logPrefix, err)
return 0, err
}
returnStatusCode := res.StatusCode
// wait for resource to be deleted
if waitCount > 0 {
done := false
retryCount := waitCount
for !done && retryCount > 0 {
_, res, err = r.client.ProjectVariables.GetVariable(projectID, key)
if err != nil && res == nil {
processError(logPrefix, err)
return 0, err
}
if res.StatusCode == http.StatusNotFound {
done = true
}
retryCount = retryCount - 1
rlog.Debugf("DeleteGroupVariable wait status: %d", res.StatusCode)
time.Sleep((time.Duration(waitInterval) * time.Millisecond))
}
if done {
returnStatusCode = http.StatusOK
} else {
returnStatusCode = http.StatusRequestTimeout
}
}
processComplete(logPrefix, returnStatusCode)
return returnStatusCode, nil
}
// processError - helper
func processError(logPrefix string, err error) {
rlog.Warnf("%s error: %v", logPrefix, err)
......
This diff is collapsed.
......@@ -44,6 +44,18 @@ spec:
namespace that contains authentication information for logging into
the
type: string
kustomizeProductionPath:
description: KustomizeProductionPath is the relative path for the
kustomization manifest for production use
type: string
kustomizeStagingPath:
description: KustomizeStagingPath is the relative path for the kustomization
manifest for staging use
type: string
manifestRepositoryUrl:
description: ManifestRepositoryURL is the path to the git repository
for the deployment manifests
type: string
name:
description: Name is the name of the Group and will be used as part
of the URL in Gitlab
......@@ -79,6 +91,10 @@ spec:
description: Language is the programming language of the Project
for this application.
type: string
manifestImage:
description: ManifestImage is the name of the DockerImage for
the Project application
type: string
name:
description: Name is the name of this Project
type: string
......@@ -106,6 +122,7 @@ spec:
- groupId
- impactLevel
- language
- manifestImage
- name
- path
- virtualService
......@@ -114,6 +131,9 @@ spec:
required:
- description
- gitlabCredentialsName
- kustomizeProductionPath
- kustomizeStagingPath
- manifestRepositoryUrl
- name
- path
type: object
......
......@@ -36,15 +36,16 @@ spec:
spec:
description: PipelineSpec defines the desired state of Pipeline
properties:
dummy:
projectName:
description: test dummy - do not remove
type: string
type: object
status:
description: PipelineStatus defines the observed state of Pipeline
properties:
dummy:
description: test dummy - do not remove
state:
description: State is the current state of the Pipeline, used mostly
for error states.
type: string
type: object
type: object
......
......@@ -57,6 +57,10 @@ spec:
description: Language is the programming language of the Project for
this application.
type: string
manifestImage:
description: ManifestImage is the name of the DockerImage for the
Project application
type: string
name:
description: Name is the name of this Project
type: string
......@@ -84,6 +88,7 @@ spec:
- groupId
- impactLevel
- language
- manifestImage
- name
- path
- virtualService
......
......@@ -431,6 +431,46 @@ type MockGitlabClient struct {
updateProjectFunction func(projectID int, editProjectOptions gitlab.EditProjectOptions) (*gitlab.Project, int, error)
}
func (m *MockGitlabClient) GetGroupVariable(groupID int, key string) (*gitlab.GroupVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) GetGroupVariables(groupID int) ([]*gitlab.GroupVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) GetProjectVariables(projectID int) ([]*gitlab.ProjectVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) GetProjectVariable(projectID int, key string) (*gitlab.ProjectVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) AddGroupVariable(groupID int, options gitlab.CreateGroupVariableOptions) (*gitlab.GroupVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) UpdateGroupVariable(groupID int, key string, options gitlab.UpdateGroupVariableOptions) (*gitlab.GroupVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) DeleteGroupVariable(groupID int, key string, waitInterval int, waitCount int) (int, error) {
panic("implement me")
}
func (m *MockGitlabClient) AddProjectVariable(projectID int, options gitlab.CreateProjectVariableOptions) (*gitlab.ProjectVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) UpdateProjectVariable(projectID int, key string, options gitlab.UpdateProjectVariableOptions) (*gitlab.ProjectVariable, int, error) {
panic("implement me")
}
func (m *MockGitlabClient) DeleteProjectVariable(projectID int, key string, waitInterval int, waitCount int) (int, error) {
panic("implement me")
}
func (m *MockGitlabClient) GetUser(userID int) (*gitlab.User, int, error) {
panic("implement me")
}
......
......@@ -18,20 +18,30 @@ package gitlab
import (
"context"
"github.com/go-logr/logr"
gogitlab "github.com/xanzy/go-gitlab"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"strconv"
apisGitlab "valkyrie.dso.mil/valkyrie-api/apis/gitlab"
"valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
gitlabv1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
const (
errorWhileLookingUpPipeline = "error while looking up pipeline."
errorUpdatingStatusOfPipeline = "Error updating status of pipeline."
)
// PipelineReconciler reconciles a Pipeline object
type PipelineReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
Log logr.Logger
Scheme *runtime.Scheme
gitlabClient gitlabClient.Client
gitlabClientConfiguration apisGitlab.ClientConfiguration
}
//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=pipelines,verbs=get;list;watch;create;update;patch;delete
......@@ -39,25 +49,118 @@ type PipelineReconciler struct {
//+kubebuilder:rbac:groups=gitlab.valkyrie.dso.mil,resources=pipelines/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Pipeline object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile
// move the current state of the cluster closer to the desired state. This loop will
// create an maintain the CI/CD Pipeline settings for a Gitlab Project
func (r *PipelineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = r.Log.WithValues("pipeline", req.NamespacedName)
r.Log = r.Log.WithValues("pipeline", req.NamespacedName)
var err error
var pipeline *v1alpha1.Pipeline
var project *v1alpha1.Project
// Grab the pipeline from the K8s API
if pipeline, err = r.getPipeline(ctx, req); err != nil {
r.Log.Error(err, errorWhileLookingUpPipeline, "request", req)
return ctrl.Result{Requeue: client.IgnoreNotFound(err) != nil}, client.IgnoreNotFound(err)
}
// Grab the project from the K8s API for the credentials
if project, err = r.getProject(ctx, req, pipeline.Spec.ProjectName); err != nil {
r.Log.Error(err, errorWhileLookingUpProject)
_ = r.updateStatus(ctx, pipeline, errorWhileLookingUpProject)
return ctrl.Result{Requeue: true}, err
}
// Do a nil check here so we can use mock gitlabClients
if r.gitlabClient == nil {
if r.gitlabClientConfiguration == nil {
r.gitlabClientConfiguration = apisGitlab.ClientConfigurationImpl{}
}
// Setup Gitlab Client
if r.gitlabClient, err = r.gitlabClientConfiguration.SetupClient(r.Client, project.Spec.GitlabCredentialsName); err != nil {
r.Log.Error(err, errorUnableToSetupGitlabClient)
_ = r.updateStatus(ctx, pipeline, errorUnableToSetupGitlabClient)
return ctrl.Result{Requeue: true}, err
}
}
// your logic here
// Grab the Gitlab Project
//var gitlabProject *gogitlab.Project
if _, err = r.getGitlabProject(project); err != nil {
r.Log.Error(err, errorGettingProjectFromGitlab)
_ = r.updateStatus(ctx, pipeline, errorGettingProjectFromGitlab)
return ctrl.Result{Requeue: true}, err
}
// Setup General Pipelines
// Enable Public Pipelines
// Enable Auto-cancel redundant pipelines
// Enable skip outdated deployment jobs
// Set CI/CD Configuration file to products/{group}/{project-path}-ci.yml@platform-one/devops/pipeline-products
// Setup Artifacts
// Enable keep artifacts from most recent successful jobs
// TODO Setup Variables (Group Controller & Group Object needs updated here)
// KUSTOMIZE_PRODUCTION_PATH
// KUSTOMIZE_STAGING_PATH
// MAINIFEST_REPO_PATH
return ctrl.Result{}, nil
}
func (r *PipelineReconciler) getProject(ctx context.Context, request ctrl.Request, projectName string) (*v1alpha1.Project, error) {
objectKey := types.NamespacedName{
Namespace: request.Namespace,
Name: projectName,
}
project := v1alpha1.Project{}
err := r.Client.Get(ctx, objectKey, &project)
return &project, err
}
func (r *PipelineReconciler) getPipeline(ctx context.Context, request ctrl.Request) (*v1alpha1.Pipeline, error) {
pipeline := v1alpha1.Pipeline{}
err := r.Client.Get(ctx, request.NamespacedName, &pipeline)
return &pipeline, err
}
func (r *PipelineReconciler) updateStatus(ctx context.Context, pipeline *v1alpha1.Pipeline, status string) error {
pipeline.Status.State = status
if err := r.Status().Update(ctx, pipeline); err != nil {
r.Log.Error(err, errorUpdatingStatusOfPipeline, "pipeline", pipeline, "status", status)
return err
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *PipelineReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&gitlabv1alpha1.Pipeline{}).
For(&v1alpha1.Pipeline{}).
Complete(r)
}
func (r *PipelineReconciler) getGitlabProject(project *v1alpha1.Project) (*gogitlab.Project, error) {
var id int
var err error
var statusCode int
var gitlabProject *gogitlab.Project
id, err = strconv.Atoi(project.Annotations[annotationKeyID])
if err != nil {
return nil, err
}
if gitlabProject, statusCode, err = r.gitlabClient.GetProject(id); err != nil {
r.Log.Error(err, errorGettingProjectFromGitlab, "statusCode", statusCode)
return nil, err
}
return gitlabProject, nil
}
package gitlab
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/xanzy/go-gitlab"
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"
"valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
const GreenPipelineName = "greenPipeline"
func getGreenPipeline() v1alpha1.Pipeline {
return v1alpha1.Pipeline{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Annotations: make(map[string]string),
},
Spec: v1alpha1.PipelineSpec{
ProjectName: GreenProjectName,
},
Status: v1alpha1.PipelineStatus{
State: "",
},
}
}
func getGreenGitlabProject() *gitlab.Project {
project := gitlab.Project{
ID: 1,
Description: "Green Project",
DefaultBranch: "",
Public: false,
Visibility: "",
SSHURLToRepo: "",
HTTPURLToRepo: "",
WebURL: "",
ReadmeURL: "",
TagList: nil,
Owner: nil,
Name: "Green Project",
NameWithNamespace: "",
Path: "/greenProject",
PathWithNamespace: "/greenGroup/greenProject",
IssuesEnabled: false,
OpenIssuesCount: 0,
MergeRequestsEnabled: false,
ApprovalsBeforeMerge: 0,
JobsEnabled: false,
WikiEnabled: false,
SnippetsEnabled: false,
ResolveOutdatedDiffDiscussions: false,
ContainerExpirationPolicy: nil,
ContainerRegistryEnabled: false,
CreatedAt: nil,
LastActivityAt: nil,
CreatorID: 0,
Namespace: nil,
ImportStatus: "",
ImportError: "",
Permissions: nil,
MarkedForDeletionAt: nil,
EmptyRepo: false,
Archived: false,
AvatarURL: "",
LicenseURL: "",
License: nil,
SharedRunnersEnabled: false,
ForksCount: 0,
StarCount: 0,
RunnersToken: "",
PublicBuilds: false,
AllowMergeOnSkippedPipeline: false,
OnlyAllowMergeIfPipelineSucceeds: false,
OnlyAllowMergeIfAllDiscussionsAreResolved: false,
RemoveSourceBranchAfterMerge: false,
LFSEnabled: false,
RequestAccessEnabled: false,
MergeMethod: "",
ForkedFromProject: nil,
Mirror: false,
MirrorUserID: 0,
MirrorTriggerBuilds: false,
OnlyMirrorProtectedBranches: false,
MirrorOverwritesDivergedBranches: false,
PackagesEnabled: false,
ServiceDeskEnabled: false,
ServiceDeskAddress: "",
IssuesAccessLevel: "",
RepositoryAccessLevel: "",
MergeRequestsAccessLevel: "",
ForkingAccessLevel: "",
WikiAccessLevel: "",
BuildsAccessLevel: "",
SnippetsAccessLevel: "",
PagesAccessLevel: "",
OperationsAccessLevel: "",
AutocloseReferencedIssues: false,
SuggestionCommitMessage: "",
CIForwardDeploymentEnabled: false,
SharedWithGroups: nil,
Statistics: nil,
Links: nil,
CIConfigPath: "",
CIDefaultGitDepth: 0,
CustomAttributes: nil,
ComplianceFrameworks: nil,
BuildCoverageRegex: "",
IssuesTemplate: "",
MergeRequestsTemplate: "",
}
return &project
}
func getRequestWithDefaultNamespacedTestPipeline() ctrl.Request {
return ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: GreenNamespace,
Name: GreenPipelineName,
},
}
}
func getPipelineControllerWithMocksInGreenTestState() (PipelineReconciler, *MockManager, *MockLogger, *MockStatusWriter) {
builder := v1alpha1.SchemeBuilder.Register(&v1alpha1.Pipeline{}, &v1alpha1.PipelineList{})
scheme, _ := builder.Build()
mockStatusWriter := MockStatusWriter{}
pipeline := getGreenPipeline()
project := getGreenProject()
loggerMock := MockLogger{
WithValuesKeysAndValues: nil,
WithValuesCalled: false,
WithNameValue: "",
WithNameCalled: false,
}
clientMock := MockClient{
statusWriter: &mockStatusWriter,
expectedObjects: make(map[client.ObjectKey]client.Object),
}
clientMock.expectedObjects[getRequestWithDefaultNamespacedTestPipeline().NamespacedName] = &pipeline
clientMock.expectedObjects[getRequestWithDefaultNamespacedTestProject().NamespacedName] = &project
mockManager := &MockManager{
Log: &loggerMock,
builder: builder,
}
gitlabProject := getGreenGitlabProject()
gitlabClient := MockGitlabClient{
expectedProjects: make(map[int]*gitlab.Project, 0),
}
gitlabClient.expectedProjects[gitlabProject.ID] = gitlabProject
sut := PipelineReconciler{
Client: &clientMock,
Log: &loggerMock,
Scheme: scheme,
}
sut.gitlabClient = &gitlabClient
return sut, mockManager, &loggerMock, &mockStatusWriter
}
var _ = Describe("SetupWithManager", func() {
var _ = Describe("SetupWithManager", func() {
sut, mgr, _, _ := getPipelineControllerWithMocksInGreenTestState()
err := sut.SetupWithManager(mgr)
It("should return no error", func() {
Expect(err).To(BeNil())
})
})
})
var _ = Describe("reconcile", func() {
Context("green state", func() {
ctx := context.TODO()
request := getRequestWithDefaultNamespacedTestPipeline()
sut, _, _, _ := getPipelineControllerWithMocksInGreenTestState()
result, err := sut.Reconcile(ctx, request)
It("should not return an error", func() {
Expect(err).To(BeNil())
})
It("should not requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: false}))
})
})
Context("pipeline isn't found in the K8s API", func() {
sut, _, log, _ := getPipelineControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestPipeline()
request.NamespacedName.Name = "not going to find it"
result, err := sut.Reconcile(context.TODO(), request)
It("should return no error", func() {
Expect(err).To(BeNil())
})
It("should not requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: false}))
})
It("should log that there was an error looking for the project", func() {
Expect(log.loggedMessage).To(Equal(errorWhileLookingUpPipeline))
})
})
Context("project isn't found in the K8s API or returns an error", func() {
sut, _, log, status := getPipelineControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestPipeline()
delete(sut.Client.(*MockClient).expectedObjects, getRequestWithDefaultNamespacedTestProject().NamespacedName)
result, err := sut.Reconcile(context.TODO(), request)
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
It("should log the error", func() {
Expect(log.loggedMessage).To(Equal(errorWhileLookingUpProject))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should update the pipeline status", func() {
Expect(status.updatedObject.(*v1alpha1.Pipeline).Status.State).To(Equal(errorWhileLookingUpProject))
})
})
Context("SetupClient fails", func() {
sut, _, log, status := getPipelineControllerWithMocksInGreenTestState()
request := getRequestWithDefaultNamespacedTestPipeline()
sut.gitlabClient = nil
sut.gitlabClientConfiguration = &MockClientConfiguration{
SetupClientFunction: func(client client.Client, credentialsName string) (gitlabClient.Client, error) {
return nil, &MockError{message: "SetupClientFunction fails."}
},
}
result, err := sut.Reconcile(context.TODO(), request)
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorUnableToSetupGitlabClient))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should return an error", func() {
Expect(err).To(Not(BeNil()))
})
It("should update the pipeline status", func() {
Expect(status.updatedObject.(*v1alpha1.Pipeline).Status.State).To(Equal(errorUnableToSetupGitlabClient))
})
})
Context("getGitlabProject fails to get the ID annotation", func() {
sut, _, log, status := getPipelineControllerWithMocksInGreenTestState()
sut.Client.(*MockClient).expectedObjects[getRequestWithDefaultNamespacedTestProject().NamespacedName].(*v1alpha1.Project).Annotations[annotationKeyID] = "BAD"
request := getRequestWithDefaultNamespacedTestPipeline()
result, err := sut.Reconcile(context.TODO(), request)
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
It("should log the error", func() {
Expect(log.loggedMessage).To(Equal(errorGettingProjectFromGitlab))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should update the pipeline status", func() {
Expect(status.updatedObject.(*v1alpha1.Pipeline).Status.State).To(Equal(errorGettingProjectFromGitlab))
})
})
Context("getGitlabProject returns an error", func() {
sut, _, log, status := getPipelineControllerWithMocksInGreenTestState()
sut.gitlabClient.(*MockGitlabClient).getProjectFunction = func(groupID int) (*gitlab.Project, int, error) {
return nil, 500, &MockError{
message: "failure in getGitlabProject",
}
}
request := getRequestWithDefaultNamespacedTestPipeline()
result, err := sut.Reconcile(context.TODO(), request)
It("should return an error", func() {
Expect(err).ToNot(BeNil())
})
It("should log the error", func() {
Expect(log.loggedMessage).To(Equal(errorGettingProjectFromGitlab))
})
It("should requeue the object", func() {
Expect(result).To(Equal(ctrl.Result{Requeue: true}))
})
It("should update the pipeline status", func() {
Expect(status.updatedObject.(*v1alpha1.Pipeline).Status.State).To(Equal(errorGettingProjectFromGitlab))
})
})
})
var _ = Describe("updateStatus fails", func() {
sut, _, log, status := getPipelineControllerWithMocksInGreenTestState()
status.updateFunction = func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
return &MockError{
message: "update status fails.",
}
}
pipeline := getGreenPipeline()
err := sut.updateStatus(context.TODO(), &pipeline, errorGettingProjectFromGitlab)
It("should log the error", func() {
Expect(log.logLevelCalled).To(Equal("Error"))
Expect(log.loggedMessage).To(Equal(errorUpdatingStatusOfPipeline))
})
It("should return the error", func() {
Expect(err).ToNot(BeNil())
})
})
......@@ -25,7 +25,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"strconv"
apisGitlab "valkyrie.dso.mil/valkyrie-api/apis/gitlab"
v1alpha1 "valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
"valkyrie.dso.mil/valkyrie-api/apis/gitlab/v1alpha1"
gitlabClient "valkyrie.dso.mil/valkyrie-api/clients/gitlab"
)
......
......@@ -41,6 +41,8 @@ func getGreenProject() gitlabv1alpha1.Project {
},
}
project.Annotations[annotationKeyID] = "1"
return project
}
......
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