Skip to content

Commit eb4c592

Browse files
authored
feat(worker): show running worker hooks in job spawn info (#6174)
1 parent 70e3ef9 commit eb4c592

File tree

4 files changed

+94
-49
lines changed

4 files changed

+94
-49
lines changed

engine/worker/internal/run.go

+67-37
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"context"
77
"crypto/md5"
88
"fmt"
9-
"io/fs"
109
"os"
1110
"os/exec"
1211
"os/user"
@@ -738,64 +737,82 @@ func (w *CurrentWorker) setupHooks(ctx context.Context, jobInfo sdk.WorkflowNode
738737
// The error contains 'Executable file not found', the capa is not on the worker
739738
continue
740739
}
740+
741741
hookFilename := fmt.Sprintf("%d-%s-%s", hookConfig.Priority, integrationName, slug.Convert(hookConfig.Label))
742-
hookFilePath := path.Join(workingDir, "setup", hookFilename)
743-
log.Info(ctx, "setting up hook %q", hookFilePath)
744742

745-
hookFile, err := fs.Create(hookFilePath)
743+
w.hooks = append(w.hooks, workerHook{
744+
Config: hookConfig,
745+
SetupPath: path.Join(workingDir, "setup", hookFilename),
746+
TeardownPath: path.Join(workingDir, "teardown", hookFilename),
747+
})
748+
}
749+
750+
for _, h := range w.hooks {
751+
infos := []sdk.SpawnInfo{{
752+
RemoteTime: time.Now(),
753+
Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoWorkerHookSetup.ID, Args: []interface{}{h.Config.Label}},
754+
}}
755+
if err := w.Client().QueueJobSendSpawnInfo(ctx, w.currentJob.wJob.ID, infos); err != nil {
756+
return sdk.WrapError(err, "cannot record QueueJobSendSpawnInfo for job (err spawn): %d", w.currentJob.wJob.ID)
757+
}
758+
759+
log.Info(ctx, "setting up hook at %q", h.SetupPath)
760+
761+
hookFile, err := fs.Create(h.SetupPath)
746762
if err != nil {
747-
return errors.Errorf("unable to open hook file %q in %q: %v", hookFilePath, w.basedir.Name(), err)
763+
return errors.Errorf("unable to open hook file %q in %q: %v", h.SetupPath, w.basedir.Name(), err)
748764
}
749-
if _, err := hookFile.WriteString(hookConfig.Setup); err != nil {
765+
if _, err := hookFile.WriteString(h.Config.Setup); err != nil {
750766
_ = hookFile.Close
751-
return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
767+
return errors.Errorf("unable to setup hook %q: %v", h.SetupPath, err)
752768
}
753769
if err := hookFile.Close(); err != nil {
754-
return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
770+
return errors.Errorf("unable to setup hook %q: %v", h.SetupPath, err)
755771
}
756772

757-
hookFilePath = path.Join(workingDir, "teardown", hookFilename)
758-
hookFile, err = fs.Create(hookFilePath)
773+
hookFile, err = fs.Create(h.TeardownPath)
759774
if err != nil {
760-
return errors.Errorf("unable to open hook file %q: %v", hookFilePath, err)
775+
return errors.Errorf("unable to open hook file %q: %v", h.TeardownPath, err)
761776
}
762-
if _, err := hookFile.WriteString(hookConfig.Teardown); err != nil {
777+
if _, err := hookFile.WriteString(h.Config.Teardown); err != nil {
763778
_ = hookFile.Close
764-
return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
779+
return errors.Errorf("unable to setup hook %q: %v", h.TeardownPath, err)
765780
}
766781
if err := hookFile.Close(); err != nil {
767-
return errors.Errorf("unable to setup hook %q: %v", hookFilePath, err)
782+
return errors.Errorf("unable to setup hook %q: %v", h.TeardownPath, err)
768783
}
769784
}
770785
}
771786
return nil
772787
}
773788

774-
func (w *CurrentWorker) executeHooksSetup(ctx context.Context, basedir afero.Fs, workingDir string) error {
789+
func (w *CurrentWorker) executeHooksSetup(ctx context.Context, fs afero.Fs, workingDir string) error {
775790
if strings.EqualFold(runtime.GOOS, "windows") {
776791
log.Warn(ctx, "hooks are not supported on windows")
777792
return nil
778793
}
779794

780795
var result = make(map[string]string)
781-
var setupDir = path.Join(workingDir, "setup")
782796

783-
var absPath string
784-
if x, ok := basedir.(*afero.BasePathFs); ok {
785-
absPath, _ = x.RealPath(setupDir)
786-
absPath, _ = filepath.Abs(path.Dir(absPath))
797+
basedir, ok := fs.(*afero.BasePathFs)
798+
if !ok {
799+
return sdk.WithStack(fmt.Errorf("invalid given basedir"))
787800
}
788801

789-
setupDir = filepath.Join(absPath, filepath.Base(setupDir))
790-
791802
workerEnv := w.Environ()
792803

793-
err := filepath.Walk(setupDir, func(filepath string, info os.FileInfo, err error) error {
804+
for _, h := range w.hooks {
805+
filepath, err := basedir.RealPath(h.SetupPath)
794806
if err != nil {
795-
return err
807+
return sdk.WrapError(err, "cannot get real path for: %s", h.SetupPath)
796808
}
797-
if info.IsDir() {
798-
return nil
809+
810+
infos := []sdk.SpawnInfo{{
811+
RemoteTime: time.Now(),
812+
Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoWorkerHookRun.ID, Args: []interface{}{h.Config.Label}},
813+
}}
814+
if err := w.Client().QueueJobSendSpawnInfo(ctx, w.currentJob.wJob.ID, infos); err != nil {
815+
return sdk.WrapError(err, "cannot record QueueJobSendSpawnInfo for job (err spawn): %d", w.currentJob.wJob.ID)
799816
}
800817

801818
str := fmt.Sprintf("source %s ; echo '<<<ENVIRONMENT>>>' ; env", filepath)
@@ -821,22 +838,35 @@ func (w *CurrentWorker) executeHooksSetup(ctx context.Context, basedir afero.Fs,
821838
}
822839
}
823840
}
824-
return nil
825-
})
841+
}
826842
w.currentJob.envFromHooks = result
827-
return errors.WithStack(err)
843+
return nil
828844
}
829845

830-
func (w *CurrentWorker) executeHooksTeardown(_ context.Context, basedir afero.Fs, workingDir string) error {
831-
err := afero.Walk(basedir, path.Join(workingDir, "setup"), func(path string, info fs.FileInfo, err error) error {
832-
if info.IsDir() {
833-
return nil
846+
func (w *CurrentWorker) executeHooksTeardown(ctx context.Context, fs afero.Fs, workingDir string) error {
847+
basedir, ok := fs.(*afero.BasePathFs)
848+
if !ok {
849+
return sdk.WithStack(fmt.Errorf("invalid given basedir"))
850+
}
851+
852+
for _, h := range w.hooks {
853+
filepath, err := basedir.RealPath(h.SetupPath)
854+
if err != nil {
855+
return sdk.WrapError(err, "cannot get real path for: %s", h.SetupPath)
856+
}
857+
858+
infos := []sdk.SpawnInfo{{
859+
RemoteTime: time.Now(),
860+
Message: sdk.SpawnMsg{ID: sdk.MsgSpawnInfoWorkerHookRunTeardown.ID, Args: []interface{}{h.Config.Label}},
861+
}}
862+
if err := w.Client().QueueJobSendSpawnInfo(ctx, w.currentJob.wJob.ID, infos); err != nil {
863+
return sdk.WrapError(err, "cannot record QueueJobSendSpawnInfo for job (err spawn): %d", w.currentJob.wJob.ID)
834864
}
835-
cmd := exec.Command("bash", "-c", path)
865+
866+
cmd := exec.Command("bash", "-c", filepath)
836867
if output, err := cmd.CombinedOutput(); err != nil {
837868
return errors.WithMessage(err, w.blur.String(string(output)))
838869
}
839-
return nil
840-
})
841-
return err
870+
}
871+
return nil
842872
}

engine/worker/internal/start_test.go

+14-12
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ func TestStartWorkerWithABookedJob(t *testing.T) {
8686
Status: sdk.StatusBuilding,
8787
})
8888

89+
gock.New("http://cds-api.local").Post("/queue/workflows/42/spawn/infos").Times(3).
90+
HeaderPresent("Authorization").
91+
Reply(200).
92+
JSON(nil)
93+
8994
gock.New("http://cds-api.local").Get("project/proj_key/workflows/workflow_name/runs/0").Times(1).
9095
HeaderPresent("Authorization").
9196
Reply(200).
@@ -284,14 +289,14 @@ export FOO_FROM_HOOK=BAR`,
284289

285290
var checkRequest gock.ObserverFunc = func(request *http.Request, mock gock.Mock) {
286291
bodyContent, err := io.ReadAll(request.Body)
287-
assert.NoError(t, err)
292+
require.NoError(t, err)
288293
request.Body = io.NopCloser(bytes.NewReader(bodyContent))
289294
if mock != nil {
290295
switch mock.Request().URLStruct.String() {
291296
case "http://cds-api.local/queue/workflows/42/step":
292297
var result sdk.StepStatus
293298
err := json.Unmarshal(bodyContent, &result)
294-
assert.NoError(t, err)
299+
require.NoError(t, err)
295300

296301
switch result.StepOrder {
297302
case 0:
@@ -320,15 +325,14 @@ export FOO_FROM_HOOK=BAR`,
320325
}
321326
case "http://cds-api.local/queue/workflows/42/result":
322327
var result sdk.Result
323-
err := json.Unmarshal(bodyContent, &result)
324-
assert.NoError(t, err)
325-
assert.Equal(t, int64(42), result.BuildID)
326-
assert.Equal(t, sdk.StatusFail, result.Status)
328+
require.NoError(t, json.Unmarshal(bodyContent, &result))
329+
require.Equal(t, int64(42), result.BuildID)
330+
require.Equal(t, sdk.StatusFail, result.Status)
327331
if len(result.NewVariables) > 0 {
328-
assert.Equal(t, "cds.build.newvar", result.NewVariables[0].Name)
332+
require.Equal(t, "cds.build.newvar", result.NewVariables[0].Name)
329333
// assert.Equal(t, "cds.semver", result.NewVariables[0].Name)
330334
// assert.Equal(t, "git.describe", result.NewVariables[0].Name)
331-
assert.Equal(t, "newval", result.NewVariables[0].Value)
335+
require.Equal(t, "newval", result.NewVariables[0].Value)
332336
} else {
333337
t.Error("missing new variables")
334338
}
@@ -363,8 +367,7 @@ export FOO_FROM_HOOK=BAR`,
363367
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
364368
defer cancel()
365369

366-
err = internal.StartWorker(ctx, w, 42)
367-
assert.NoError(t, err)
370+
require.NoError(t, internal.StartWorker(ctx, w, 42))
368371

369372
var isDone bool
370373
if gock.IsDone() {
@@ -380,7 +383,7 @@ export FOO_FROM_HOOK=BAR`,
380383
}
381384
}
382385
}
383-
assert.True(t, isDone)
386+
require.True(t, isDone)
384387
if gock.HasUnmatchedRequest() {
385388
reqs := gock.GetUnmatchedRequests()
386389
for _, req := range reqs {
@@ -426,5 +429,4 @@ export FOO_FROM_HOOK=BAR`,
426429
assert.Equal(t, 1, strings.Count(logBuffer.String(), "HATCHERY_REGION=local-test"))
427430
assert.Equal(t, 1, strings.Count(logBuffer.String(), "BASEDIR="))
428431
assert.Equal(t, 1, strings.Count(logBuffer.String(), "FOO_FROM_HOOK=BAR"))
429-
430432
}

engine/worker/internal/types.go

+7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ type CurrentWorker struct {
6969
}
7070
client cdsclient.WorkerInterface
7171
blur *sdk.Blur
72+
hooks []workerHook
73+
}
74+
75+
type workerHook struct {
76+
Config sdk.WorkerHookSetupTeardownScripts
77+
SetupPath string
78+
TeardownPath string
7279
}
7380

7481
// BuiltInAction defines builtin action signature

sdk/messages.go

+6
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ var (
9797
MsgWorkflowV3Preview = &Message{"MsgWorkflowV3Preview", trad{FR: "Le workflow a été généré en version 3 à partir d'une ancienne version", EN: "The workflow was generated in version 3 from an old version"}, nil, RunInfoTypeWarning}
9898
MsgSpawnInfoDisableSecretInjection = &Message{"MsgSpawnInfoDisableSecretInjection", trad{EN: "⚠ Project's secrets were not automatically injected for this job because of a region prerequisite: %s"}, nil, RunInfoTypInfo}
9999
MsgSpawnInfoManualSecretInjection = &Message{"MsgSpawnInfoManualSecretInjection", trad{EN: "Prerequisites of type secret matched %s secret(s)"}, nil, RunInfoTypInfo}
100+
MsgSpawnInfoWorkerHookSetup = &Message{"MsgSpawnInfoWorkerHookSetup", trad{EN: "Setting up worker hook %q"}, nil, RunInfoTypInfo}
101+
MsgSpawnInfoWorkerHookRun = &Message{"MsgSpawnInfoWorkerHookRun", trad{EN: "Running worker hook %q"}, nil, RunInfoTypInfo}
102+
MsgSpawnInfoWorkerHookRunTeardown = &Message{"MsgSpawnInfoWorkerHookRunTeardown", trad{EN: "Running worker hook %q teardown"}, nil, RunInfoTypInfo}
100103
)
101104

102105
// Messages contains all sdk Messages
@@ -178,6 +181,9 @@ var Messages = map[string]*Message{
178181
MsgWorkflowV3Preview.ID: MsgWorkflowV3Preview,
179182
MsgSpawnInfoDisableSecretInjection.ID: MsgSpawnInfoDisableSecretInjection,
180183
MsgSpawnInfoManualSecretInjection.ID: MsgSpawnInfoManualSecretInjection,
184+
MsgSpawnInfoWorkerHookSetup.ID: MsgSpawnInfoWorkerHookSetup,
185+
MsgSpawnInfoWorkerHookRun.ID: MsgSpawnInfoWorkerHookRun,
186+
MsgSpawnInfoWorkerHookRunTeardown.ID: MsgSpawnInfoWorkerHookRunTeardown,
181187
}
182188

183189
//Message represent a struc format translated messages

0 commit comments

Comments
 (0)