Skip to content

Commit 56baa24

Browse files
committed
Delete obsolete bundles before writing new ones
When a GitRepo is updated such that a path to a bundle changes, but resources overlap between the old and new paths, there was a risk of obsolete bundles being cleaned up after newer bundles had been created. This would happen intermittently due to a race condition, and cause deletion of resources from the newer bundle, leading to the GitRepo status appearing as _Modified_. To remedy this, `fleet apply` now deletes obsolete bundles synchronously before writing new ones, that last step happening asynchronously as it previously did.
1 parent 58d7521 commit 56baa24

1 file changed

Lines changed: 37 additions & 10 deletions

File tree

internal/cmd/cli/apply/apply.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ type Options struct {
9090
JobNameEnvVar string
9191
}
9292

93+
type bundleWithOpts struct {
94+
bundle *fleet.Bundle
95+
scans []*fleet.ImageScan
96+
opts *Options
97+
}
98+
9399
func globDirs(baseDir string) (result []string, err error) {
94100
for strings.HasPrefix(baseDir, "/") {
95101
baseDir = baseDir[1:]
@@ -120,7 +126,8 @@ func CreateBundles(pctx context.Context, client client.Client, r record.EventRec
120126
// 1. Goroutines will be launched, honouring the concurrency limit, and eventually block trying to write to `bundlesChan`.
121127
// 2. The main function will read from `bundlesChan`, hence unblocking the goroutines. This will continue to read from `bundlesChan` until it is closed.
122128
// 3. We use another goroutine to wait for all goroutines to finish, then close `bundlesChan`, finally unblocking the main function.
123-
bundlesChan := make(chan *fleet.Bundle)
129+
130+
bundlesChan := make(chan *bundleWithOpts)
124131
eg, ctx := errgroup.WithContext(pctx)
125132
eg.SetLimit(bundleCreationMaxConcurrency + 1) // extra goroutine for WalkDir loop
126133
eg.Go(func() error {
@@ -163,9 +170,9 @@ func CreateBundles(pctx context.Context, client client.Client, r record.EventRec
163170
select {
164171
case <-ctx.Done():
165172
return ctx.Err()
166-
case bundlesChan <- bundle:
173+
case bundlesChan <- &bundleWithOpts{bundle: bundle, scans: scans, opts: &opts}:
167174
}
168-
return writeBundle(ctx, client, r, bundle, scans, opts)
175+
return nil
169176
})
170177
return nil
171178
}); err != nil {
@@ -181,8 +188,10 @@ func CreateBundles(pctx context.Context, client client.Client, r record.EventRec
181188
}()
182189

183190
gitRepoBundlesMap := make(map[string]bool)
191+
var bundlesToWrite []*bundleWithOpts
184192
for b := range bundlesChan {
185-
gitRepoBundlesMap[b.Name] = true
193+
gitRepoBundlesMap[b.bundle.Name] = true
194+
bundlesToWrite = append(bundlesToWrite, b)
186195
}
187196
// Recovers any error that could happen in the errgroup, won't actually wait
188197
if err := eg.Wait(); err != nil {
@@ -201,7 +210,15 @@ func CreateBundles(pctx context.Context, client client.Client, r record.EventRec
201210
return fmt.Errorf("no resource found at the following paths to deploy: %v", baseDirs)
202211
}
203212

204-
return nil
213+
egWrite, ctx := errgroup.WithContext(pctx)
214+
egWrite.SetLimit(bundleCreationMaxConcurrency)
215+
for _, b := range bundlesToWrite {
216+
egWrite.Go(func() error {
217+
return writeBundle(ctx, client, r, b.bundle, b.scans, *b.opts)
218+
})
219+
}
220+
221+
return egWrite.Wait()
205222
}
206223

207224
// CreateBundlesDriven creates bundles from the given baseDirs. Those bundles' names will be prefixed with
@@ -222,7 +239,7 @@ func CreateBundlesDriven(pctx context.Context, client client.Client, r record.Ev
222239
// 1. Goroutines will be launched, honouring the concurrency limit, and eventually block trying to write to `bundlesChan`.
223240
// 2. The main function will read from `bundlesChan`, hence unblocking the goroutines. This will continue to read from `bundlesChan` until it is closed.
224241
// 3. We use another goroutine to wait for all goroutines to finish, then close `bundlesChan`, finally unblocking the main function.
225-
bundlesChan := make(chan *fleet.Bundle)
242+
bundlesChan := make(chan *bundleWithOpts)
226243
eg, ctx := errgroup.WithContext(pctx)
227244
eg.SetLimit(bundleCreationMaxConcurrency + 1) // extra goroutine for WalkDir loop
228245
eg.Go(func() error {
@@ -251,9 +268,9 @@ func CreateBundlesDriven(pctx context.Context, client client.Client, r record.Ev
251268
select {
252269
case <-ctx.Done():
253270
return ctx.Err()
254-
case bundlesChan <- bundle:
271+
case bundlesChan <- &bundleWithOpts{bundle: bundle, scans: scans, opts: &opts}:
255272
}
256-
return writeBundle(ctx, client, r, bundle, scans, opts)
273+
return nil
257274
})
258275
}
259276
return nil
@@ -264,8 +281,10 @@ func CreateBundlesDriven(pctx context.Context, client client.Client, r record.Ev
264281
}()
265282

266283
gitRepoBundlesMap := make(map[string]bool)
284+
var bundlesToWrite []*bundleWithOpts
267285
for b := range bundlesChan {
268-
gitRepoBundlesMap[b.Name] = true
286+
gitRepoBundlesMap[b.bundle.Name] = true
287+
bundlesToWrite = append(bundlesToWrite, b)
269288
}
270289
// Recovers any error that could happen in the errgroup, won't actually wait
271290
if err := eg.Wait(); err != nil {
@@ -284,7 +303,15 @@ func CreateBundlesDriven(pctx context.Context, client client.Client, r record.Ev
284303
return fmt.Errorf("no resource found at the following paths to deploy: %v", baseDirs)
285304
}
286305

287-
return nil
306+
egWrite, ctx := errgroup.WithContext(pctx)
307+
egWrite.SetLimit(bundleCreationMaxConcurrency)
308+
for _, b := range bundlesToWrite {
309+
egWrite.Go(func() error {
310+
return writeBundle(ctx, client, r, b.bundle, b.scans, *b.opts)
311+
})
312+
}
313+
314+
return egWrite.Wait()
288315
}
289316

290317
// getPathAndFleetYaml returns the path and options file from a given path.

0 commit comments

Comments
 (0)