Skip to content

Commit 59b4a45

Browse files
authored
feat(operator): adds events and retries (#158)
1 parent 3ab8eff commit 59b4a45

File tree

11 files changed

+725
-469
lines changed

11 files changed

+725
-469
lines changed

foundry/operator/blueprint.cue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: "1.0"
2+
project: {
3+
name: "foundry-operator"
4+
release: {
5+
docker: {
6+
on: {
7+
merge: {}
8+
tag: {}
9+
}
10+
11+
config: {
12+
tag: _ @forge(name="GIT_HASH_OR_TAG")
13+
}
14+
}
15+
}
16+
}

foundry/operator/cmd/main.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"context"
2021
"crypto/tls"
2122
"flag"
2223
"log/slog"
@@ -44,7 +45,9 @@ import (
4445
foundryv1alpha1 "github.com/input-output-hk/catalyst-forge/foundry/operator/api/v1alpha1"
4546
"github.com/input-output-hk/catalyst-forge/foundry/operator/internal/controller"
4647
"github.com/input-output-hk/catalyst-forge/foundry/operator/pkg/config"
48+
"github.com/input-output-hk/catalyst-forge/foundry/operator/pkg/handlers"
4749
"github.com/input-output-hk/catalyst-forge/lib/project/deployment"
50+
"github.com/input-output-hk/catalyst-forge/lib/project/providers"
4851
"github.com/input-output-hk/catalyst-forge/lib/project/secrets"
4952
"github.com/input-output-hk/catalyst-forge/lib/tools/fs/billy"
5053
"github.com/input-output-hk/catalyst-forge/lib/tools/git/repo/remote"
@@ -223,17 +226,32 @@ func main() {
223226
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
224227
Level: slog.LevelDebug,
225228
}))
229+
230+
setupLog.Info("Fetching git auth token")
231+
secretStore := secrets.NewDefaultSecretStore()
232+
creds, err := providers.GetGitProviderCreds(&cfg.Deployer.Git.Creds, &secretStore, logger)
233+
if err != nil {
234+
setupLog.Error(err, "unable to get auth token")
235+
os.Exit(1)
236+
}
237+
238+
apiClient := api.NewClient(cfg.ApiUrl, api.WithTimeout(10*time.Second))
226239
if err = (&controller.ReleaseDeploymentReconciler{
227-
ApiClient: api.NewClient(cfg.ApiUrl, api.WithTimeout(10*time.Second)),
228-
Client: mgr.GetClient(),
229-
Config: cfg,
230-
FsDeploy: billy.NewBaseOsFS(),
231-
FsSource: billy.NewBaseOsFS(),
232-
Logger: logger,
233-
ManifestStore: deployment.NewDefaultManifestGeneratorStore(),
234-
Remote: remote.GoGitRemoteInteractor{},
235-
Scheme: mgr.GetScheme(),
236-
SecretStore: secrets.NewDefaultSecretStore(),
240+
Client: mgr.GetClient(),
241+
Config: cfg,
242+
DeploymentHandler: handlers.NewReleaseDeploymentHandler(context.Background(), apiClient),
243+
Logger: logger,
244+
ManifestStore: deployment.NewDefaultManifestGeneratorStore(),
245+
Remote: remote.GoGitRemoteInteractor{},
246+
RepoHandler: handlers.NewRepoHandler(
247+
billy.NewBaseOsFS(),
248+
billy.NewBaseOsFS(),
249+
logger,
250+
remote.GoGitRemoteInteractor{},
251+
creds.Token,
252+
),
253+
Scheme: mgr.GetScheme(),
254+
SecretStore: secrets.NewDefaultSecretStore(),
237255
}).SetupWithManager(mgr); err != nil {
238256
setupLog.Error(err, "unable to create controller", "controller", "Release")
239257
os.Exit(1)

foundry/operator/internal/controller/release_controller.go

Lines changed: 41 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package controller
1818

1919
import (
2020
"context"
21-
"fmt"
2221
"log/slog"
2322

2423
"cuelang.org/go/cue/cuecontext"
@@ -27,31 +26,26 @@ import (
2726
"sigs.k8s.io/controller-runtime/pkg/client"
2827
"sigs.k8s.io/controller-runtime/pkg/log"
2928

30-
api "github.com/input-output-hk/catalyst-forge/foundry/api/client"
31-
3229
foundryv1alpha1 "github.com/input-output-hk/catalyst-forge/foundry/operator/api/v1alpha1"
3330
"github.com/input-output-hk/catalyst-forge/foundry/operator/pkg/config"
31+
"github.com/input-output-hk/catalyst-forge/foundry/operator/pkg/handlers"
3432
"github.com/input-output-hk/catalyst-forge/lib/project/deployment"
3533
depl "github.com/input-output-hk/catalyst-forge/lib/project/deployment/deployer"
36-
"github.com/input-output-hk/catalyst-forge/lib/project/providers"
3734
"github.com/input-output-hk/catalyst-forge/lib/project/secrets"
38-
"github.com/input-output-hk/catalyst-forge/lib/tools/fs/billy"
39-
"github.com/input-output-hk/catalyst-forge/lib/tools/git/repo"
4035
"github.com/input-output-hk/catalyst-forge/lib/tools/git/repo/remote"
4136
)
4237

4338
// ReleaseDeploymentReconciler reconciles a Release object
4439
type ReleaseDeploymentReconciler struct {
4540
client.Client
46-
ApiClient api.Client
47-
Config config.OperatorConfig
48-
FsDeploy *billy.BillyFs
49-
FsSource *billy.BillyFs
50-
Logger *slog.Logger
51-
ManifestStore deployment.ManifestGeneratorStore
52-
Remote remote.GitRemoteInteractor
53-
Scheme *runtime.Scheme
54-
SecretStore secrets.SecretStore
41+
Config config.OperatorConfig
42+
DeploymentHandler *handlers.ReleaseDeploymentHandler
43+
Logger *slog.Logger
44+
ManifestStore deployment.ManifestGeneratorStore
45+
Remote remote.GitRemoteInteractor
46+
RepoHandler *handlers.RepoHandler
47+
Scheme *runtime.Scheme
48+
SecretStore secrets.SecretStore
5549
}
5650

5751
// +kubebuilder:rbac:groups=foundry.projectcatalyst.io,resources=releases,verbs=get;list;watch;create;update;patch;delete
@@ -70,35 +64,34 @@ func (r *ReleaseDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Re
7064

7165
// 2. Fetch the associated ReleaseDeployment from the API
7266
log.Info("Fetching release deployment from API", "releaseID", resource.Spec.ReleaseID, "deploymentID", resource.Spec.ID)
73-
releaseDeployment, err := r.ApiClient.GetDeployment(ctx, resource.Spec.ReleaseID, resource.Spec.ID)
74-
if err != nil {
75-
log.Error(err, "unable to fetch deployment")
67+
if err := r.DeploymentHandler.Load(&resource); err != nil {
68+
log.Error(err, "unable to load deployment")
7669
return ctrl.Result{}, err
77-
} else if releaseDeployment == nil {
78-
log.Error(err, "unable to find deployment")
79-
return ctrl.Result{}, fmt.Errorf("unable to find deployment")
8070
}
81-
release := releaseDeployment.Release
71+
release := r.DeploymentHandler.Release()
8272

8373
// 3. Check if the deployment has already been completed
84-
if isDeploymentComplete(releaseDeployment.Status) {
85-
log.Info("Deployment already succeeded")
74+
if r.DeploymentHandler.IsCompleted() {
75+
log.Info("Deployment already finished")
8676
return ctrl.Result{}, nil
8777
}
8878

89-
// 4. Set deployment status to running if not already set
90-
log.Info("Checking deployment status", "status", releaseDeployment.Status)
91-
if releaseDeployment.Status != api.DeploymentStatusRunning {
92-
if err := r.ApiClient.UpdateDeploymentStatus(
93-
ctx,
94-
release.ID,
95-
releaseDeployment.ID,
96-
api.DeploymentStatusRunning,
97-
"Deployment in progress",
98-
); err != nil {
99-
log.Error(err, "unable to update deployment")
79+
// 4. Check if max attempts have been reached
80+
if r.DeploymentHandler.MaxAttemptsReached(r.Config.MaxAttempts - 1) {
81+
log.Info("Max attempts reached, setting deployment to failed")
82+
if err := r.DeploymentHandler.SetFailed("Max attempts reached"); err != nil {
83+
log.Error(err, "unable to set deployment status to failed")
84+
r.DeploymentHandler.AddErrorEvent(nil, "Max attempts reached")
10085
return ctrl.Result{}, err
10186
}
87+
88+
return ctrl.Result{}, nil
89+
}
90+
91+
// 5. Set deployment status to running if not already set
92+
if err := r.DeploymentHandler.SetRunning(); err != nil {
93+
log.Error(err, "unable to set deployment status to running")
94+
return ctrl.Result{}, err
10295
}
10396

10497
// resource.Status.State = "Deploying"
@@ -107,35 +100,26 @@ func (r *ReleaseDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Re
107100
// return ctrl.Result{}, err
108101
// }
109102

110-
// 5. Get the git auth token for repos
111-
log.Info("Fetching git auth token")
112-
token, err := r.getAuth()
113-
if err != nil {
114-
log.Error(err, "unable to get auth token")
115-
return ctrl.Result{}, err
116-
}
117-
fmt.Println("token", token)
118-
119103
// 6. Open repos
120104
log.Info("Opening deployment repo", "url", r.Config.Deployer.Git.Url)
121-
deployRepo, err := r.getDeployRepo(token)
122-
if err != nil {
123-
log.Error(err, "unable to get deployment repo")
105+
if err := r.RepoHandler.LoadDeploymentRepo(r.Config.Deployer.Git.Url, r.Config.Deployer.Git.Ref); err != nil {
106+
log.Error(err, "unable to load deployment repo")
124107
return ctrl.Result{}, err
125108
}
126109

127110
log.Info("Opening source repo", "url", release.SourceRepo)
128-
sourceRepo, err := r.getSourceRepo(token, release)
129-
if err != nil {
130-
log.Error(err, "unable to create repo")
111+
if err := r.RepoHandler.LoadSourceRepo(release.SourceRepo, release.SourceCommit); err != nil {
112+
log.Error(err, "unable to load source repo")
113+
r.DeploymentHandler.AddErrorEvent(err, "Unable to load source repo")
131114
return ctrl.Result{}, err
132115
}
133116

134117
// 7. Fetch the bundle from the source repo
135118
log.Info("Fetching bundle from source repo", "url", release.SourceRepo, "commit", release.SourceCommit)
136-
bundle, err := deployment.FetchBundle(sourceRepo, release.ProjectPath, r.SecretStore, r.Logger)
119+
bundle, err := deployment.FetchBundle(*r.RepoHandler.SourceRepo(), release.ProjectPath, r.SecretStore, r.Logger)
137120
if err != nil {
138121
log.Error(err, "unable to fetch bundle")
122+
r.DeploymentHandler.AddErrorEvent(err, "Unable to fetch deployment bundle")
139123
return ctrl.Result{}, err
140124
}
141125

@@ -153,111 +137,32 @@ func (r *ReleaseDeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Re
153137
resource.Spec.ID,
154138
release.Project,
155139
bundle,
156-
depl.WithRepo(&deployRepo),
140+
depl.WithRepo(r.RepoHandler.DeploymentRepo()),
157141
)
158142
if err != nil {
159143
log.Error(err, "unable to create deployment")
144+
r.DeploymentHandler.AddErrorEvent(err, "Unable to create deployment")
160145
return ctrl.Result{}, err
161146
}
162147

163148
// 9. Commit and push the deployment
164149
log.Info("Committing and pushing deployment")
165150
if err := deployment.Commit(); err != nil {
166151
log.Error(err, "unable to commit deployment")
152+
r.DeploymentHandler.AddErrorEvent(err, "Unable to commit deployment")
167153
return ctrl.Result{}, err
168154
}
169155

170156
// 10. Update the deployment status to succeeded
171-
log.Info("Updating deployment status to succeeded")
172-
if err := r.ApiClient.UpdateDeploymentStatus(
173-
ctx,
174-
release.ID,
175-
releaseDeployment.ID,
176-
api.DeploymentStatusSucceeded,
177-
"Deployment succeeded",
178-
); err != nil {
179-
log.Error(err, "unable to update deployment")
157+
log.Info("Deployment succeeded")
158+
if err := r.DeploymentHandler.SetSucceeded(); err != nil {
159+
log.Error(err, "unable to set deployment status to succeeded")
180160
return ctrl.Result{}, err
181161
}
182162

183163
return ctrl.Result{}, nil
184164
}
185165

186-
func (r *ReleaseDeploymentReconciler) getAuth() (string, error) {
187-
creds, err := providers.GetGitProviderCreds(&r.Config.Deployer.Git.Creds, &r.SecretStore, r.Logger)
188-
if err != nil {
189-
return "", err
190-
}
191-
192-
return creds.Token, nil
193-
}
194-
195-
func (r *ReleaseDeploymentReconciler) getDeployRepo(token string) (repo.GitRepo, error) {
196-
var dfs *billy.BillyFs
197-
if r.FsDeploy == nil {
198-
dfs = billy.NewInMemoryFs()
199-
} else {
200-
dfs = r.FsDeploy
201-
}
202-
203-
rp, err := repo.NewCachedRepo(
204-
r.Config.Deployer.Git.Url,
205-
r.Logger,
206-
repo.WithFS(dfs),
207-
repo.WithGitRemoteInteractor(r.Remote),
208-
repo.WithAuth("forge", token),
209-
)
210-
if err != nil {
211-
return repo.GitRepo{}, err
212-
}
213-
214-
if err := rp.CheckoutBranch(r.Config.Deployer.Git.Ref); err != nil {
215-
return repo.GitRepo{}, fmt.Errorf("failed to checkout branch %s: %w", r.Config.Deployer.Git.Ref, err)
216-
}
217-
218-
if err := rp.Pull(); err != nil && !repo.IsErrNoUpdates(err) {
219-
return repo.GitRepo{}, fmt.Errorf("failed to pull latest changes from branch %s: %w", r.Config.Deployer.Git.Ref, err)
220-
}
221-
222-
return rp, nil
223-
}
224-
225-
func (r *ReleaseDeploymentReconciler) getSourceRepo(token string, release *api.Release) (repo.GitRepo, error) {
226-
var sfs *billy.BillyFs
227-
if r.FsSource == nil {
228-
sfs = billy.NewBaseOsFS()
229-
} else {
230-
sfs = r.FsSource
231-
}
232-
233-
rp, err := repo.NewCachedRepo(
234-
release.SourceRepo,
235-
r.Logger,
236-
repo.WithFS(sfs),
237-
repo.WithGitRemoteInteractor(r.Remote),
238-
repo.WithAuth("forge", token),
239-
)
240-
if err != nil {
241-
return repo.GitRepo{}, err
242-
}
243-
244-
if err := rp.Fetch(); err != nil && !repo.IsErrNoUpdates(err) {
245-
return repo.GitRepo{}, fmt.Errorf("failed to fetch latest changes: %w", err)
246-
}
247-
248-
if err := rp.CheckoutCommit(release.SourceCommit); err != nil {
249-
return repo.GitRepo{}, fmt.Errorf("failed to checkout commit %s: %w", release.SourceCommit, err)
250-
}
251-
252-
return rp, nil
253-
}
254-
255-
// isDeploymentComplete checks if the deployment is complete
256-
// by checking if the status is either Succeeded or Failed.
257-
func isDeploymentComplete(status api.DeploymentStatus) bool {
258-
return status == api.DeploymentStatusSucceeded || status == api.DeploymentStatusFailed
259-
}
260-
261166
// SetupWithManager sets up the controller with the Manager.
262167
func (r *ReleaseDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
263168
return ctrl.NewControllerManagedBy(mgr).

0 commit comments

Comments
 (0)