Skip to content

Commit

Permalink
feat: add argo external link (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
eliecharra authored Mar 6, 2024
1 parent 11a452c commit b9378de
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 46 deletions.
5 changes: 5 additions & 0 deletions api/v1beta1/annotation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package v1beta1

const (
ArgoExternalLink = "link.argocd.argoproj.io/external-link"
)
19 changes: 16 additions & 3 deletions api/v1beta1/run_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package v1beta1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/spacelift-io/spacelift-operator/internal/spacelift/models"
)

// RunSpec defines the desired state of Run
Expand Down Expand Up @@ -85,9 +87,20 @@ func (r *Run) IsTerminated() bool {
return found
}

// SetState set status.state and also update the argo health
func (r *Run) SetState(state RunState) {
r.Status.State = state
type RunCreated struct {
Id, Url string
State RunState
}

// SetRun is used to sync the k8s CRD with a spacelift run model.
// It basically takes care of updating all status fields
func (r *Run) SetRun(run *models.Run) {
if run.Id != "" {
r.Status.Id = run.Id
}
if run.State != "" {
r.Status.State = RunState(run.State)
}
argoHealth := &ArgoStatus{
Health: ArgoHealthProgressing,
}
Expand Down
15 changes: 15 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions internal/controller/run_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,25 @@ func (r *RunReconciler) handleNewRun(ctx context.Context, run *v1beta1.Run) (ctr
// TODO(eliecharra): Implement better error handling and retry errors that could be retried
return ctrl.Result{}, nil
}
run.SetState(v1beta1.RunState(spaceliftRun.State))
run.Status.Id = spaceliftRun.RunID
logger.WithValues(
logging.RunState, run.Status.State,
logging.RunId, run.Status.Id,
).Info("New run created")

// Set initial annotations when a run is created
if run.Annotations == nil {
run.Annotations = make(map[string]string, 1)
}
run.Annotations[v1beta1.ArgoExternalLink] = spaceliftRun.Url
// Updating annotations will not trigger another reconciliation loop
if err := r.RunRepository.Update(ctx, run); err != nil {
if k8sErrors.IsConflict(err) {
logger.Info("Conflict on Run update, let's try again.")
return ctrl.Result{RequeueAfter: time.Second * 3}, nil
}
return ctrl.Result{}, err
}
run.SetRun(spaceliftRun)
if err := r.RunRepository.UpdateStatus(ctx, run); err != nil {
if k8sErrors.IsConflict(err) {
logger.Info("Conflict on Run status update, let's try again.")
Expand Down
12 changes: 7 additions & 5 deletions internal/controller/run_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"k8s.io/apimachinery/pkg/types"

"github.com/spacelift-io/spacelift-operator/api/v1beta1"
"github.com/spacelift-io/spacelift-operator/internal/spacelift/repository"
"github.com/spacelift-io/spacelift-operator/internal/spacelift/models"
"github.com/spacelift-io/spacelift-operator/tests/integration"
)

Expand Down Expand Up @@ -95,23 +95,23 @@ func (s *RunControllerSuite) TestRunCreation_OK() {
return run.Status.State == v1beta1.RunStateQueued
})).
Once().
Return(&repository.GetRunOutput{
Return(&models.Run{
State: "READY",
}, nil)
s.FakeSpaceliftRunRepo.EXPECT().
Get(mock.Anything, mock.MatchedBy(func(run *v1beta1.Run) bool {
return run.Status.State == "READY"
})).
Once().
Return(&repository.GetRunOutput{
Return(&models.Run{
State: "APPLYING",
}, nil)
s.FakeSpaceliftRunRepo.EXPECT().
Get(mock.Anything, mock.MatchedBy(func(run *v1beta1.Run) bool {
return run.Status.State == "APPLYING"
})).
Once().
Return(&repository.GetRunOutput{
Return(&models.Run{
State: string(v1beta1.RunStateFinished),
}, nil)

Expand All @@ -120,6 +120,8 @@ func (s *RunControllerSuite) TestRunCreation_OK() {

// Assert that the Queued state has been applied
run = s.AssertRunState(run, "READY")
s.Require().NotNil(run.Annotations)
s.Assert().Equal("http://example.com/test", run.Annotations[v1beta1.ArgoExternalLink])
s.Require().NotNil(run.Status.Argo)
s.Assert().Equal(v1beta1.ArgoHealthProgressing, run.Status.Argo.Health)

Expand Down Expand Up @@ -147,7 +149,7 @@ func (s *RunControllerSuite) TestRunCreation_OK_WithErrorDuringWatch() {
return run.Status.State == v1beta1.RunStateQueued
})).
Once().
Return(&repository.GetRunOutput{
Return(&models.Run{
State: string(v1beta1.RunStateFinished),
}, nil).NotBefore(errCall)

Expand Down
4 changes: 4 additions & 0 deletions internal/k8s/repository/run_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func (r *RunRepository) Get(ctx context.Context, name types.NamespacedName) (*v1
return &run, nil
}

func (r *RunRepository) Update(ctx context.Context, run *v1beta1.Run) error {
return r.client.Update(ctx, run)
}

func (r *RunRepository) UpdateStatus(ctx context.Context, run *v1beta1.Run) error {
return r.client.Status().Update(ctx, run)
}
4 changes: 4 additions & 0 deletions internal/spacelift/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ func (c *client) Mutate(ctx context.Context, mutation interface{}, variables map
func (c *client) Query(ctx context.Context, query interface{}, variables map[string]interface{}, opts ...graphql.RequestOption) error {
return c.wraps.Query(ctx, query, variables, opts...)
}

func (c *client) URL(format string, a ...interface{}) string {
return c.wraps.URL(format, a...)
}
3 changes: 3 additions & 0 deletions internal/spacelift/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ type Client interface {

// Mutate executes a single GraphQL mutation request.
Mutate(context.Context, interface{}, map[string]interface{}, ...graphql.RequestOption) error

// URL returns a full URL given a formatted path.
URL(string, ...interface{}) string
}
7 changes: 7 additions & 0 deletions internal/spacelift/models/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package models

type Run struct {
Id string
Url string
State string
}
33 changes: 17 additions & 16 deletions internal/spacelift/repository/mocks/RunRepository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 10 additions & 16 deletions internal/spacelift/repository/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (

"github.com/spacelift-io/spacelift-operator/api/v1beta1"
spaceliftclient "github.com/spacelift-io/spacelift-operator/internal/spacelift/client"
"github.com/spacelift-io/spacelift-operator/internal/spacelift/models"
)

//go:generate mockery --with-expecter --name RunRepository
type RunRepository interface {
Create(context.Context, *v1beta1.Run) (*CreateRunOutput, error)
Get(context.Context, *v1beta1.Run) (*GetRunOutput, error)
Create(context.Context, *v1beta1.Run) (*models.Run, error)
Get(context.Context, *v1beta1.Run) (*models.Run, error)
}

type runRepository struct {
Expand All @@ -28,12 +29,7 @@ func NewRunRepository(client client.Client) *runRepository {
type CreateRunQuery struct {
}

type CreateRunOutput struct {
RunID string
State string
}

func (r *runRepository) Create(ctx context.Context, run *v1beta1.Run) (*CreateRunOutput, error) {
func (r *runRepository) Create(ctx context.Context, run *v1beta1.Run) (*models.Run, error) {
c, err := spaceliftclient.GetSpaceliftClient(ctx, r.client, run.Namespace)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch spacelift client while creating run")
Expand All @@ -50,17 +46,15 @@ func (r *runRepository) Create(ctx context.Context, run *v1beta1.Run) (*CreateRu
if err := c.Mutate(ctx, &mutation, vars); err != nil {
return nil, errors.Wrap(err, "unable to create run")
}
return &CreateRunOutput{
RunID: mutation.RunTrigger.ID,
url := c.URL("/stack/%s/run/%s", run.Spec.StackName, mutation.RunTrigger.ID)
return &models.Run{
Id: mutation.RunTrigger.ID,
State: mutation.RunTrigger.State,
Url: url,
}, nil
}

type GetRunOutput struct {
State string
}

func (r *runRepository) Get(ctx context.Context, run *v1beta1.Run) (*GetRunOutput, error) {
func (r *runRepository) Get(ctx context.Context, run *v1beta1.Run) (*models.Run, error) {
c, err := spaceliftclient.GetSpaceliftClient(ctx, r.client, run.Namespace)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch spacelift client while creating run")
Expand All @@ -79,7 +73,7 @@ func (r *runRepository) Get(ctx context.Context, run *v1beta1.Run) (*GetRunOutpu
if err := c.Query(ctx, &query, vars); err != nil {
return nil, errors.Wrap(err, "unable to get run")
}
return &GetRunOutput{
return &models.Run{
State: query.Stack.Run.State,
}, nil
}
2 changes: 1 addition & 1 deletion internal/spacelift/watcher/run_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (w *RunWatcher) Start(ctx context.Context, run *v1beta1.Run) error {
continue
}

run.SetState(v1beta1.RunState(spaceliftRun.State))
run.SetRun(spaceliftRun)
if err := w.k8sRunRepo.UpdateStatus(ctxWithTimeout, run); err != nil {
if k8sErrors.IsConflict(err) {
logger.Info("Conflict updating run status, retrying immediately")
Expand Down
7 changes: 4 additions & 3 deletions tests/integration/run_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"k8s.io/apimachinery/pkg/types"

"github.com/spacelift-io/spacelift-operator/api/v1beta1"
"github.com/spacelift-io/spacelift-operator/internal/spacelift/repository"
"github.com/spacelift-io/spacelift-operator/internal/spacelift/models"
)

var DefaultValidRun = v1beta1.Run{
Expand Down Expand Up @@ -42,9 +42,10 @@ func (s *WithRunSuiteHelper) CreateTestRun() (*v1beta1.Run, error) {
return r.ObjectMeta.Annotations["test.id"] == fakeRunULID
})).
Once().
Return(&repository.CreateRunOutput{
RunID: fakeRunULID,
Return(&models.Run{
Id: fakeRunULID,
State: string(v1beta1.RunStateQueued),
Url: "http://example.com/test",
}, nil)
if err := s.Client().Create(s.Context(), &run); err != nil {
return nil, err
Expand Down

0 comments on commit b9378de

Please sign in to comment.