Skip to content

Commit 23b4bf6

Browse files
committed
v1: Integrate compliance policy snapshots into blueprint handlers
Integrate compliance policy snapshot creation into blueprint lifecycle handlers. When blueprints use compliance policies, we now capture and store the generated TOML to enable policy change detection. This allows detecting when compliance customizations become obsolete: User creates blueprint with policy A → snapshot saved User updates to policy B → compare snapshots to identify removed customizations User can be informed about customizations that may no longer be needed Changes: Update CreateBlueprint, UpdateBlueprint, and FixupBlueprint handlers Add buildServiceSnapshots() helper function Add null-byte validation for service snapshots Update tests for snapshot integration
1 parent 7c70a30 commit 23b4bf6

3 files changed

Lines changed: 331 additions & 47 deletions

File tree

internal/v1/handler_blueprints.go

Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"log/slog"
89
"net/http"
910
"net/url"
1011
"regexp"
@@ -144,6 +145,51 @@ func (u *User) MergeForUpdate(userData []User) error {
144145
return nil
145146
}
146147

148+
// Helper function to build service snapshots if compliance policy exists
149+
func (h *Handlers) buildServiceSnapshots(ctx echo.Context, customizations *Customizations, distribution Distributions) (*db.ServiceSnapshots, error) {
150+
if customizations.Openscap == nil {
151+
return nil, nil
152+
}
153+
154+
var compl OpenSCAPCompliance
155+
compl, err := customizations.Openscap.AsOpenSCAPCompliance()
156+
if err != nil {
157+
slog.ErrorContext(ctx.Request().Context(), "error in AsOpenSCAPCompliance", "error", err.Error())
158+
return nil, err
159+
}
160+
if compl.PolicyId == uuid.Nil {
161+
return nil, nil
162+
}
163+
164+
var cust Customizations
165+
_, err = h.lintOpenscap(ctx, &cust, true, distribution, compl.PolicyId.String())
166+
if err != nil {
167+
slog.ErrorContext(ctx.Request().Context(), "error getting policy customizations via lintOpenscap",
168+
"error", err.Error(), "distribution", distribution, "policy_id", compl.PolicyId.String())
169+
return nil, err
170+
}
171+
172+
policyCustomizationsJSON, err := json.Marshal(cust)
173+
if err != nil {
174+
slog.ErrorContext(ctx.Request().Context(), "error marshaling policy customizations to JSON",
175+
"error", err.Error(), "policy_id", compl.PolicyId.String())
176+
return nil, err
177+
}
178+
179+
serviceSnapshots := &db.ServiceSnapshots{
180+
Compliance: &db.ComplianceSnapshot{
181+
PolicyId: compl.PolicyId,
182+
PolicyCustomizations: policyCustomizationsJSON,
183+
},
184+
}
185+
186+
slog.DebugContext(ctx.Request().Context(), "built compliance snapshot",
187+
"policy_id", compl.PolicyId,
188+
"distribution", distribution)
189+
190+
return serviceSnapshots, nil
191+
}
192+
147193
// Util function used to create and update Blueprint from API request (WRITE)
148194
func BlueprintFromAPI(cbr CreateBlueprintRequest) (BlueprintBody, error) {
149195
bb := BlueprintBody{
@@ -237,7 +283,12 @@ func (h *Handlers) CreateBlueprint(ctx echo.Context) error {
237283

238284
id := uuid.New()
239285
versionId := uuid.New()
240-
ctx.Logger().Infof("Inserting blueprint: %s (%s), for orgID: %s and account: %s", blueprintRequest.Name, id, userID.OrgID(), userID.AccountNumber())
286+
slog.DebugContext(ctx.Request().Context(), "inserting blueprint",
287+
"name", blueprintRequest.Name,
288+
"id", id,
289+
"org_id", userID.OrgID(),
290+
"account", userID.AccountNumber())
291+
241292
desc := ""
242293
if blueprintRequest.Description != nil {
243294
desc = *blueprintRequest.Description
@@ -268,9 +319,26 @@ func (h *Handlers) CreateBlueprint(ctx echo.Context) error {
268319
return err
269320
}
270321

271-
err = h.server.db.InsertBlueprint(ctx.Request().Context(), id, versionId, userID.OrgID(), userID.AccountNumber(), blueprintRequest.Name, desc, body, metadata)
322+
serviceSnapshots, err := h.buildServiceSnapshots(ctx, &blueprintRequest.Customizations, blueprintRequest.Distribution)
272323
if err != nil {
273-
ctx.Logger().Errorf("Error inserting id into db: %s", err.Error())
324+
slog.ErrorContext(ctx.Request().Context(), "error building service snapshots",
325+
"blueprint_id", id,
326+
"error", err.Error())
327+
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to build compliance snapshots")
328+
}
329+
330+
var serviceSnapshotsJSON json.RawMessage
331+
if serviceSnapshots != nil {
332+
serviceSnapshotsJSON, err = json.Marshal(serviceSnapshots)
333+
if err != nil {
334+
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to marshal service snapshots")
335+
}
336+
}
337+
338+
err = h.server.db.InsertBlueprint(ctx.Request().Context(), id, versionId, userID.OrgID(), userID.AccountNumber(), blueprintRequest.Name, desc, body, metadata, serviceSnapshotsJSON)
339+
if err != nil {
340+
slog.ErrorContext(ctx.Request().Context(), "error inserting blueprint with service snapshots into db",
341+
"error", err.Error())
274342

275343
var e *pgconn.PgError
276344
if errors.As(err, &e) && e.Code == pgerrcode.UniqueViolation {
@@ -283,7 +351,6 @@ func (h *Handlers) CreateBlueprint(ctx echo.Context) error {
283351
}
284352
return err
285353
}
286-
ctx.Logger().Infof("Inserted blueprint %s", id)
287354
return ctx.JSON(http.StatusCreated, ComposeResponse{
288355
Id: id,
289356
})
@@ -294,8 +361,7 @@ func (h *Handlers) GetBlueprint(ctx echo.Context, id openapi_types.UUID, params
294361
if err != nil {
295362
return err
296363
}
297-
298-
ctx.Logger().Infof("Fetching blueprint %s", id)
364+
slog.DebugContext(ctx.Request().Context(), "fetching blueprint", "id", id)
299365
if params.Version != nil && *params.Version <= 0 {
300366
if *params.Version != -1 {
301367
return echo.NewHTTPError(http.StatusBadRequest, "Invalid version number")
@@ -366,8 +432,6 @@ func (h *Handlers) ExportBlueprint(ctx echo.Context, id openapi_types.UUID) erro
366432
if err != nil {
367433
return err
368434
}
369-
370-
ctx.Logger().Infof("Fetching blueprint %s", id)
371435
blueprintEntry, err := h.server.db.GetBlueprint(ctx.Request().Context(), id, userID.OrgID(), nil)
372436
if err != nil {
373437
if errors.Is(err, db.ErrBlueprintNotFound) {
@@ -428,7 +492,7 @@ func (h *Handlers) ExportBlueprint(ctx echo.Context, id openapi_types.UUID) erro
428492
}
429493

430494
if exportedRepositoriesResp.Body == nil {
431-
ctx.Logger().Warnf("Unable to export custom repositories, empty body")
495+
slog.WarnContext(ctx.Request().Context(), "unable to export custom repositories, empty body")
432496
return ctx.JSON(http.StatusOK, blueprintExportResponse)
433497
}
434498

@@ -519,15 +583,32 @@ func (h *Handlers) UpdateBlueprint(ctx echo.Context, blueprintId uuid.UUID) erro
519583
if blueprintRequest.Description != nil {
520584
desc = *blueprintRequest.Description
521585
}
522-
err = h.server.db.UpdateBlueprint(ctx.Request().Context(), versionId, blueprintId, userID.OrgID(), blueprintRequest.Name, desc, body)
586+
587+
serviceSnapshots, err := h.buildServiceSnapshots(ctx, &blueprintRequest.Customizations, blueprintRequest.Distribution)
588+
if err != nil {
589+
slog.ErrorContext(ctx.Request().Context(), "error building service snapshots",
590+
"blueprint_id", blueprintId,
591+
"error", err.Error())
592+
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to build compliance snapshots")
593+
}
594+
595+
var serviceSnapshotsJSON json.RawMessage
596+
if serviceSnapshots != nil {
597+
serviceSnapshotsJSON, err = json.Marshal(serviceSnapshots)
598+
if err != nil {
599+
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to marshal service snapshots")
600+
}
601+
}
602+
603+
err = h.server.db.UpdateBlueprint(ctx.Request().Context(), versionId, blueprintId, userID.OrgID(), blueprintRequest.Name, desc, body, serviceSnapshotsJSON)
523604
if err != nil {
524-
ctx.Logger().Errorf("Error updating blueprint in db: %v", err)
605+
slog.ErrorContext(ctx.Request().Context(), "error updating blueprint with service snapshots in db",
606+
"error", err)
525607
if errors.Is(err, db.ErrBlueprintNotFound) {
526608
return echo.NewHTTPError(http.StatusNotFound, err)
527609
}
528610
return err
529611
}
530-
ctx.Logger().Infof("Updated blueprint %s", blueprintId)
531612
return ctx.JSON(http.StatusCreated, ComposeResponse{
532613
Id: blueprintId,
533614
})
@@ -681,7 +762,7 @@ func (h *Handlers) GetBlueprintComposes(ctx echo.Context, blueprintId openapi_ty
681762
composes, err := h.server.db.GetBlueprintComposes(ctx.Request().Context(), userID.OrgID(), blueprintId, params.BlueprintVersion, since, limit, offset, ignoreImageTypeStrings)
682763
if err != nil {
683764
if errors.Is(err, db.ErrBlueprintNotFound) {
684-
return echo.NewHTTPError(http.StatusNotFound)
765+
return echo.NewHTTPError(http.StatusNotFound, err)
685766
}
686767
return err
687768
}
@@ -787,14 +868,37 @@ func (h *Handlers) FixupBlueprint(ctx echo.Context, id openapi_types.UUID) error
787868
return err
788869
}
789870
desc := common.FromPtr(blueprintRequest.Description)
871+
slog.DebugContext(ctx.Request().Context(), "starting buildServiceSnapshots during fixup",
872+
"blueprint_id", blueprintEntry.Id,
873+
"distribution", blueprintRequest.Distribution,
874+
"has_openscap", blueprintRequest.Customizations.Openscap != nil)
790875

791-
err = h.server.db.UpdateBlueprint(ctx.Request().Context(), uuid.New(), blueprintEntry.Id, userID.OrgID(), blueprintRequest.Name, desc, body)
876+
serviceSnapshots, err := h.buildServiceSnapshots(ctx, &blueprintRequest.Customizations, blueprintRequest.Distribution)
792877
if err != nil {
793-
ctx.Logger().Errorf("Error updating blueprint in db: %v", err)
878+
slog.ErrorContext(ctx.Request().Context(), "error building service snapshots during fixup",
879+
"blueprint_id", blueprintEntry.Id,
880+
"error", err.Error())
881+
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to build compliance snapshots during fixup")
882+
}
883+
884+
var serviceSnapshotsJSON json.RawMessage
885+
if serviceSnapshots != nil {
886+
serviceSnapshotsJSON, err = json.Marshal(serviceSnapshots)
887+
if err != nil {
888+
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to marshal service snapshots")
889+
}
890+
}
891+
892+
versionId := uuid.New()
893+
err = h.server.db.UpdateBlueprint(ctx.Request().Context(), versionId, blueprintEntry.Id, userID.OrgID(), blueprintRequest.Name, desc, body, serviceSnapshotsJSON)
894+
if err != nil {
895+
slog.ErrorContext(ctx.Request().Context(), "error updating blueprint in db during fixup",
896+
"error", err)
794897
if errors.Is(err, db.ErrBlueprintNotFound) {
795898
return echo.NewHTTPError(http.StatusNotFound, err)
796899
}
797900
return err
798901
}
902+
799903
return ctx.NoContent(http.StatusCreated)
800904
}

0 commit comments

Comments
 (0)