Skip to content

Commit 84e8cf7

Browse files
committed
atc: static and dynamic airway modes support resource deletions
1 parent 5fddef9 commit 84e8cf7

File tree

4 files changed

+83
-34
lines changed

4 files changed

+83
-34
lines changed

cmd/atc-installer/installer/run.go

+1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ func Run(cfg Config) (flight.Stages, error) {
375375
{
376376
Operations: []admissionregistrationv1.OperationType{
377377
admissionregistrationv1.Update,
378+
admissionregistrationv1.Delete,
378379
},
379380
Rule: admissionregistrationv1.Rule{
380381
APIGroups: []string{"*"},

cmd/atc/handler.go

+54-31
Original file line numberDiff line numberDiff line change
@@ -249,38 +249,26 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, controllers *atc.Contr
249249

250250
addRequestAttrs(r.Context(), slog.String("user", review.Request.UserInfo.Username))
251251

252-
review.Response = &admissionv1.AdmissionResponse{
253-
UID: review.Request.UID,
254-
Allowed: true,
255-
Result: &metav1.Status{
256-
Status: metav1.StatusSuccess,
257-
Message: "validation passed",
258-
},
259-
}
260-
261-
var prev unstructured.Unstructured
262-
if err := json.Unmarshal(review.Request.OldObject.Raw, &prev); err != nil {
252+
prev, err := UnstructuredFromRawExt(review.Request.OldObject)
253+
if err != nil {
263254
http.Error(w, err.Error(), http.StatusBadRequest)
264255
return
265256
}
266257

267-
var next unstructured.Unstructured
268-
if err := json.Unmarshal(review.Request.Object.Raw, &next); err != nil {
258+
next, err := UnstructuredFromRawExt(review.Request.Object)
259+
if err != nil {
269260
http.Error(w, err.Error(), http.StatusBadRequest)
270261
return
271262
}
272263

273-
addRequestAttrs(r.Context(), slog.String("resourceId", internal.Canonical(&next)))
274-
275-
if internal.GetOwner(&prev) != internal.GetOwner(&next) {
276-
review.Response.Allowed = false
277-
review.Response.Result = &metav1.Status{
278-
Message: "cannot modify yoke labels",
279-
Status: metav1.StatusFailure,
280-
Reason: metav1.StatusReasonBadRequest,
281-
}
264+
review.Response = &admissionv1.AdmissionResponse{
265+
UID: review.Request.UID,
266+
Allowed: true,
267+
Result: &metav1.Status{
268+
Status: metav1.StatusSuccess,
269+
Message: "validation passed",
270+
},
282271
}
283-
284272
defer func() {
285273
review.Request = nil
286274
addRequestAttrs(r.Context(), slog.Group(
@@ -291,16 +279,24 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, controllers *atc.Contr
291279
json.NewEncoder(w).Encode(review)
292280
}()
293281

294-
if !review.Response.Allowed {
282+
addRequestAttrs(r.Context(), slog.String("resourceId", internal.Canonical(prev)))
283+
284+
if next != nil && internal.GetOwner(prev) != internal.GetOwner(next) {
285+
review.Response.Allowed = false
286+
review.Response.Result = &metav1.Status{
287+
Message: "cannot modify yoke labels",
288+
Status: metav1.StatusFailure,
289+
Reason: metav1.StatusReasonBadRequest,
290+
}
295291
return
296292
}
297293

298-
if ctrl.ResourcesAreEqual(&prev, &next) {
294+
if next != nil && ctrl.ResourcesAreEqual(prev, next) {
299295
addRequestAttrs(r.Context(), slog.String("skipReason", "resources are equal"))
300296
return
301297
}
302298

303-
owners := next.GetOwnerReferences()
299+
owners := prev.GetOwnerReferences()
304300
if len(owners) == 0 {
305301
addRequestAttrs(r.Context(), slog.String("skipReason", "no owner references"))
306302
return
@@ -328,30 +324,43 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, controllers *atc.Contr
328324
)
329325

330326
if !ok {
327+
addRequestAttrs(r.Context(), slog.String("skipReason", "no registered flight controller"))
331328
return
332329
}
333330

334-
labels := next.GetLabels()
331+
labels := prev.GetLabels()
335332
release := labels[internal.LabelYokeRelease]
336333
namespace := labels[internal.LabelYokeReleaseNS]
337334

338-
mode := controller.FlightMode(next.GetName(), namespace)
335+
mode := controller.FlightMode(prev.GetName(), namespace)
339336

340337
addRequestAttrs(r.Context(), slog.String("airwayMode", string(mode)))
341338

342339
switch mode {
343340
case v1alpha1.AirwayModeStatic:
341+
if next == nil || !next.GetDeletionTimestamp().IsZero() {
342+
review.Response.Allowed = false
343+
review.Response.Result = &metav1.Status{
344+
Message: "cannot delete resources managed by Air-Traffic-Controller",
345+
Status: metav1.StatusFailure,
346+
Reason: metav1.StatusReasonBadRequest,
347+
}
348+
return
349+
}
350+
344351
release, err := client.GetRelease(r.Context(), release, namespace)
345352
if err != nil {
346-
// Handle?
353+
addRequestAttrs(r.Context(), slog.String("skipReason", fmt.Sprintf("failed to get release: %v", err)))
347354
return
348355
}
349356
if len(release.History) == 0 {
357+
addRequestAttrs(r.Context(), slog.String("skipReason", "no release history found"))
350358
return
351359
}
352360

353361
stages, err := client.GetRevisionResources(r.Context(), release.ActiveRevision())
354362
if err != nil {
363+
addRequestAttrs(r.Context(), slog.String("skipReason", fmt.Sprintf("failed to get release resources: %v", err)))
355364
return
356365
}
357366

@@ -363,12 +372,13 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, controllers *atc.Contr
363372
resource.GetName() == next.GetName()
364373
})
365374
if !ok {
375+
addRequestAttrs(r.Context(), slog.String("skipReason", "could not find desired resource in release"))
366376
return
367377
}
368378

369-
internal.RemoveAdditions(desired, &next)
379+
internal.RemoveAdditions(desired, next)
370380

371-
if !ctrl.ResourcesAreEqual(desired, &next) {
381+
if !ctrl.ResourcesAreEqual(desired, next) {
372382
review.Response.Allowed = false
373383
review.Response.Result = &metav1.Status{
374384
Message: "cannot modify flight sub-resources",
@@ -511,3 +521,16 @@ func addRequestAttrs(ctx context.Context, attrs ...slog.Attr) {
511521
}
512522
*reqAttrs = append(*reqAttrs, attrs...)
513523
}
524+
525+
func UnstructuredFromRawExt(ext runtime.RawExtension) (*unstructured.Unstructured, error) {
526+
if len(ext.Raw) == 0 {
527+
return nil, nil
528+
}
529+
530+
var resource unstructured.Unstructured
531+
if err := json.Unmarshal(ext.Raw, &resource); err != nil {
532+
return nil, err
533+
}
534+
535+
return &resource, nil
536+
}

cmd/atc/main_test.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,12 @@ func TestAirwayModes(t *testing.T) {
12441244
deployment, err = deploymentIntf.Update(ctx, deployment, metav1.UpdateOptions{FieldManager: "test"})
12451245
require.EqualError(t, err, `admission webhook "resources.yoke.cd" denied the request: cannot modify flight sub-resources`)
12461246

1247+
require.EqualError(
1248+
t,
1249+
deploymentIntf.Delete(ctx, "test", metav1.DeleteOptions{}),
1250+
`admission webhook "resources.yoke.cd" denied the request: cannot delete resources managed by Air-Traffic-Controller`,
1251+
)
1252+
12471253
backendIntf := client.Dynamic.
12481254
Resource(schema.GroupVersionResource{
12491255
Group: "examples.com",
@@ -1303,7 +1309,26 @@ func TestAirwayModes(t *testing.T) {
13031309
},
13041310
time.Second,
13051311
30*time.Second,
1306-
"deployment failed to self-heal",
1312+
"deployment failed to self-heal from bad value",
1313+
)
1314+
1315+
require.NoError(t, deploymentIntf.Delete(ctx, "test", metav1.DeleteOptions{}))
1316+
1317+
testutils.EventuallyNoErrorf(
1318+
t,
1319+
func() error {
1320+
deployment, err = deploymentIntf.Get(ctx, "test", metav1.GetOptions{})
1321+
if err != nil {
1322+
return err
1323+
}
1324+
if actual := *deployment.Spec.Replicas; actual != 2 {
1325+
return fmt.Errorf("expected replicas to be 2 but got %d", actual)
1326+
}
1327+
return nil
1328+
},
1329+
time.Second,
1330+
30*time.Second,
1331+
"deployment failed to self-heal after deletion",
13071332
)
13081333

13091334
configmapIntf := client.Clientset.CoreV1().ConfigMaps("default")

internal/k8s/k8s.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,13 @@ func (client Client) RemoveOrphans(ctx context.Context, previous, current intern
248248

249249
defer internal.DebugTimer(ctx, "delete resource "+name)()
250250

251-
resourceInterface, err := client.GetDynamicResourceInterface(resource)
251+
intf, err := client.GetDynamicResourceInterface(resource)
252252
if err != nil {
253253
errs = append(errs, fmt.Errorf("failed to resolve resource %s: %w", name, err))
254254
return
255255
}
256256

257-
if err := resourceInterface.Delete(ctx, resource.GetName(), metav1.DeleteOptions{}); err != nil {
257+
if err := intf.Delete(ctx, resource.GetName(), metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) {
258258
errs = append(errs, fmt.Errorf("failed to delete %s: %w", name, err))
259259
return
260260
}

0 commit comments

Comments
 (0)