Skip to content

Commit

Permalink
feat: Add infrastructure stack run poller (#170)
Browse files Browse the repository at this point in the history
* infrastructure stack poller

* infrastructure stack poller

* change names

* register stack Reconciler

* bump go cient

* handle job

* reconcile Job

* create job

* merge job spec

* remove job check

* add label selector

* add label selector

* update job reconciler

* use new client to simplify job generation

* default job spec

* do not update run status

* add run job generation

* update variable name

* add stack run job controller

* check job status

* move job logic

* make defaultJobContainer private

* update logs

* update default job

* fetch job pods

* use pointer

* update log

* change error handling

* add label selector

* default image and version

* add label selector

* fix client

* refactor

* refactor

* fetch job pod status

* update exit codes

* start adding tests for stack run job controller

* fix tests

* add more tests

* fix job creation

* add container envs

* rebase

* add job controller

* fix unit tests

* fix unmarshal job spec

* sync step status

* update step statuses

* update mocks

* ignore stack run

* add default container annotation

* initialize agent tests

* add tests for stack agent

* fix approval logic

* add nil checks

* add more tests

* add tests to cover job creation

* add missing mapping for sa

* fix annotations mapping

* cover job spec mapping

* cover raw job spec mapping

* add pipeline gate controller tests

* fix make test

* simplify test suite

* cleanup other suites

* simplify test suite

* fix kustomize files

* add image names

* add image versions

* change image

* bump console client

* fix linter

---------

Co-authored-by: Marcin Maciaszczyk <[email protected]>
Co-authored-by: michaeljguarino <[email protected]>
  • Loading branch information
3 people authored May 17, 2024
1 parent d765f7b commit 9d3fb48
Show file tree
Hide file tree
Showing 25 changed files with 1,780 additions and 105 deletions.
15 changes: 15 additions & 0 deletions cmd/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"os"
"time"

"github.com/pluralsh/deployment-operator/internal/utils"
"github.com/pluralsh/deployment-operator/pkg/controller/stacks"

"github.com/pluralsh/deployment-operator/pkg/controller"
"github.com/pluralsh/deployment-operator/pkg/controller/namespaces"
"github.com/pluralsh/deployment-operator/pkg/controller/pipelinegates"
Expand Down Expand Up @@ -69,6 +72,18 @@ func runAgent(opt *options, config *rest.Config, ctx context.Context, k8sClient
Queue: ns.NamespaceQueue,
})

namespace, err := utils.GetOperatorNamespace()
if err != nil {
setupLog.Error(err, "unable to get operator namespace")
os.Exit(1)
}

s := stacks.NewStackReconciler(mgr.GetClient(), k8sClient, r, namespace, opt.consoleUrl, opt.deployToken)
mgr.AddController(&controller.Controller{
Name: "Stack Controller",
Do: s,
Queue: s.StackQueue,
})
if err := mgr.Start(); err != nil {
setupLog.Error(err, "unable to start controller manager")
os.Exit(1)
Expand Down
8 changes: 8 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ func main() {
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "HealthConvert")
}
if err = (&controller.StackRunJobReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
ConsoleClient: ctrlMgr.GetClient(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "StackRun")
}

//+kubebuilder:scaffold:builder

if err = (&controller.PipelineGateReconciler{
Expand Down
18 changes: 2 additions & 16 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,5 @@ resources:
- bases/deployments.plural.sh_luascripts.yaml
#+kubebuilder:scaffold:crdkustomizeresource

patches:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
#- path: patches/webhook_in_luascripts.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch

# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- path: patches/cainjection_in_luascripts.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch

# [WEBHOOK] To enable webhook, uncomment the following section
# the following config is for teaching kustomize how to do kustomization for CRDs.

#configurations:
#- kustomizeconfig.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ require (
github.com/open-policy-agent/gatekeeper/v3 v3.15.1
github.com/orcaman/concurrent-map/v2 v2.0.1
github.com/pkg/errors v0.9.1
github.com/pluralsh/console-client-go v0.5.6
github.com/pluralsh/console-client-go v0.5.8
github.com/pluralsh/controller-reconcile-helper v0.0.4
github.com/pluralsh/gophoenix v0.1.3-0.20231201014135-dff1b4309e34
github.com/pluralsh/polly v0.1.10
github.com/samber/lo v1.39.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/vektah/gqlparser/v2 v2.5.11
github.com/vektra/mockery/v2 v2.39.0
github.com/vmware-tanzu/velero v1.13.0
github.com/yuin/gopher-lua v1.1.1
Expand Down Expand Up @@ -192,7 +193,6 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
github.com/vektah/gqlparser/v2 v2.5.11 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pluralsh/console-client-go v0.5.6 h1:8CUQco0vJehtKabVVNHAkFE4V9UI9MaMKvYNgQRrJdo=
github.com/pluralsh/console-client-go v0.5.6/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo=
github.com/pluralsh/console-client-go v0.5.8 h1:Qm7vS+gCbmWqy5i4saLPc5/SUZaW6RCzxWF+uxyPA+Y=
github.com/pluralsh/console-client-go v0.5.8/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo=
github.com/pluralsh/controller-reconcile-helper v0.0.4 h1:1o+7qYSyoeqKFjx+WgQTxDz4Q2VMpzprJIIKShxqG0E=
github.com/pluralsh/controller-reconcile-helper v0.0.4/go.mod h1:AfY0gtteD6veBjmB6jiRx/aR4yevEf6K0M13/pGan/s=
github.com/pluralsh/gophoenix v0.1.3-0.20231201014135-dff1b4309e34 h1:ab2PN+6if/Aq3/sJM0AVdy1SYuMAnq4g20VaKhTm/Bw=
Expand Down
4 changes: 2 additions & 2 deletions internal/controller/backup_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ func (r *BackupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
// Read resource from Kubernetes cluster.
backup := &velerov1.Backup{}
if err := r.Get(ctx, req.NamespacedName, backup); err != nil {
logger.Error(err, "Unable to fetch backup")
logger.Error(err, "unable to fetch backup")
return ctrl.Result{}, k8sClient.IgnoreNotFound(err)
}

// Upsert backup data to the Console.
logger.Info("Cluster backup saved", "name", backup.Name, "namespace", backup.Namespace)
logger.Info("cluster backup saved", "name", backup.Name, "namespace", backup.Namespace)
_, err := r.ConsoleClient.SaveClusterBackup(console.BackupAttributes{
Name: backup.Name,
Namespace: backup.Namespace,
Expand Down
160 changes: 160 additions & 0 deletions internal/controller/pipelinegate_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package controller

import (
"context"
"time"

"github.com/go-logr/logr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
console "github.com/pluralsh/console-client-go"
"github.com/pluralsh/deployment-operator/api/v1alpha1"
"github.com/pluralsh/deployment-operator/pkg/client"
"github.com/pluralsh/deployment-operator/pkg/test/common"
"github.com/pluralsh/deployment-operator/pkg/test/mocks"
"github.com/samber/lo"
"github.com/stretchr/testify/mock"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var _ = Describe("PipelineGate Controller", Ordered, func() {
Context("When reconciling a resource", func() {
const (
gateName = "gate-test"
namespace = "default"
id = "123"
raw = `{"backoffLimit":4,"template":{"metadata":{"namespace":"default","creationTimestamp":null},"spec":{"containers":[{"name":"pi","image":"perl:5.34.0","command":["perl","-Mbignum=bpi","-wle","print bpi(2000)"],"resources":{}}],"restartPolicy":"Never"}}}`
)

gateCache := client.NewCache[console.PipelineGateFragment](time.Second, func(id string) (*console.PipelineGateFragment, error) {
return &console.PipelineGateFragment{
ID: id,
Name: "test",
Spec: &console.GateSpecFragment{
Job: &console.JobSpecFragment{
Namespace: namespace,
Raw: lo.ToPtr(raw),
},
},
Status: nil,
}, nil
})

ctx := context.Background()
gateNamespacedName := types.NamespacedName{Name: gateName, Namespace: namespace}
pipelineGate := &v1alpha1.PipelineGate{}

BeforeAll(func() {
By("Creating pipeline gate")
err := kClient.Get(ctx, gateNamespacedName, pipelineGate)
if err != nil && errors.IsNotFound(err) {
resource := &v1alpha1.PipelineGate{
ObjectMeta: metav1.ObjectMeta{
Name: gateName,
Namespace: namespace,
},
Spec: v1alpha1.PipelineGateSpec{
ID: id,
Name: "test",
Type: v1alpha1.GateType(console.GateTypeJob),
GateSpec: &v1alpha1.GateSpec{
JobSpec: &batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "image1",
},
},
},
},
},
},
},
}
Expect(kClient.Create(ctx, resource)).To(Succeed())
}

})

It("should set state pending", func() {
fakeConsoleClient := mocks.NewClientMock(mocks.TestingT)
fakeConsoleClient.On("UpdateGate", mock.Anything, mock.Anything).Return(nil)
reconciler := &PipelineGateReconciler{
Client: kClient,
ConsoleClient: fakeConsoleClient,
GateCache: gateCache,
Scheme: kClient.Scheme(),
Log: logr.Logger{},
}
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: gateNamespacedName})
Expect(err).NotTo(HaveOccurred())

existingGate := &v1alpha1.PipelineGate{}
Expect(kClient.Get(ctx, gateNamespacedName, existingGate)).NotTo(HaveOccurred())
Expect(*existingGate.Status.State).Should(Equal(v1alpha1.GateState(console.GateStatePending)))

})

It("should reconcile Pending Gate", func() {
fakeConsoleClient := mocks.NewClientMock(mocks.TestingT)
fakeConsoleClient.On("UpdateGate", mock.Anything, mock.Anything).Return(nil)
reconciler := &PipelineGateReconciler{
Client: kClient,
ConsoleClient: fakeConsoleClient,
GateCache: gateCache,
Scheme: kClient.Scheme(),
Log: logr.Logger{},
}
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: gateNamespacedName})
Expect(err).NotTo(HaveOccurred())

existingGate := &v1alpha1.PipelineGate{}
Expect(kClient.Get(ctx, gateNamespacedName, existingGate)).NotTo(HaveOccurred())
Expect(*existingGate.Status.State).Should(Equal(v1alpha1.GateState(console.GateStateRunning)))
existingJob := &batchv1.Job{}
Expect(kClient.Get(ctx, gateNamespacedName, existingJob)).NotTo(HaveOccurred())
})

It("should open Gate", func() {
fakeConsoleClient := mocks.NewClientMock(mocks.TestingT)
fakeConsoleClient.On("UpdateGate", mock.Anything, mock.Anything).Return(nil)
reconciler := &PipelineGateReconciler{
Client: kClient,
ConsoleClient: fakeConsoleClient,
GateCache: gateCache,
Scheme: kClient.Scheme(),
Log: logr.Logger{},
}

existingJob := &batchv1.Job{}
Expect(kClient.Get(ctx, gateNamespacedName, existingJob)).NotTo(HaveOccurred())

Expect(common.MaybePatch(kClient, existingJob,
func(p *batchv1.Job) {
p.Status.Conditions = []batchv1.JobCondition{
{
Type: batchv1.JobComplete,
Status: corev1.ConditionTrue,
},
}
})).To(Succeed())

_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: gateNamespacedName})
Expect(err).NotTo(HaveOccurred())

existingGate := &v1alpha1.PipelineGate{}
Expect(kClient.Get(ctx, gateNamespacedName, existingGate)).NotTo(HaveOccurred())
Expect(*existingGate.Status.State).Should(Equal(v1alpha1.GateState(console.GateStateOpen)))

Expect(kClient.Delete(ctx, existingGate)).To(Succeed())
Expect(kClient.Delete(ctx, existingJob)).To(Succeed())
})
})
})
Loading

0 comments on commit 9d3fb48

Please sign in to comment.