Add bundle lifecycle status tracking (build, push)#315
Add bundle lifecycle status tracking (build, push)#315ashutosh-narkar merged 1 commit intoopen-policy-agent:mainfrom
Conversation
srenatus
left a comment
There was a problem hiding this comment.
Couple of early comments! 👍
| return svc.Run(ctx) | ||
| }) | ||
|
|
||
| time.Sleep(time.Millisecond * 500) |
There was a problem hiding this comment.
Can you please use testing/synctest? It should let us run the same test without an actual sleep. (Same as other test.)
There was a problem hiding this comment.
I'd appreciate some help. I'm getting the error panic: deadlock: main bubble goroutine has exited but blocked goroutines remain [recovered, repanicked] when I use synctest.Test and synctest.Wait. I'm probably doing something wrong here.
There was a problem hiding this comment.
Ah, let's leave it as-is then. We can clean this up later.
pkg/service/service_test.go
Outdated
| WithLogger(log) | ||
|
|
||
| var g errgroup.Group | ||
| ctx, cancel := context.WithCancel(context.Background()) |
There was a problem hiding this comment.
[nit] Let's be consistent wth ctx := t.Context(). Also we can then use this below with GetLatestBundleStatus.
pkg/service/worker.go
Outdated
| return worker | ||
| } | ||
|
|
||
| func (worker *BundleWorker) WithPrincipal(principal string) *BundleWorker { |
There was a problem hiding this comment.
Why does the bundle worker need a principal..? 🤔 I think it would make sense if UpsertBundleStatus didn't require authz. It's not exposed through any http handler.
There was a problem hiding this comment.
Makes sense. Removed authz for UpsertBundleStatus
internal/database/database.go
Outdated
| // For sqlite, postgres, and cockroach, it behaves identically to upsertReturning with returning=true. | ||
| func (d *Database) upsertReturningLastInsertID(ctx context.Context, tx *sql.Tx, tenant, table string, columns []string, primaryKey []string, values ...any) (int64, error) { | ||
| valueArgs := d.args(len(columns)) | ||
| if tenant != "" { // not a relation-only table |
There was a problem hiding this comment.
This is taken from the generic upsert helper, I think? We don't need to deal with it like that here, I suppose.
There was a problem hiding this comment.
removed upsertReturningLastInsertID and rolled this into upsertReturning
internal/database/database.go
Outdated
| // For MySQL, it adds `id = LAST_INSERT_ID(id)` to the ON DUPLICATE KEY UPDATE clause so that | ||
| // LastInsertId() returns the correct ID for both inserts and updates. |
There was a problem hiding this comment.
Would it perhaps make sense to adjust upsertReturning? Sounds like this twist would be applicable across the tables.
There was a problem hiding this comment.
removed upsertReturningLastInsertID and rolled this into upsertReturning
ac4bc49 to
9681af7
Compare
srenatus
left a comment
There was a problem hiding this comment.
Had a quick look, it looks good, some nitpicks only!
internal/authz/authz_test.rego
Outdated
| "secrets.view", | ||
| "tokens.view", | ||
| "sources.data.read", | ||
| "bundles_statuses.view", |
There was a problem hiding this comment.
Given that we have sources.data.read, wouldn't bundles.status.view fit in slightly better?
There was a problem hiding this comment.
Updated to bundles.status.view
| } | ||
|
|
||
| func (d *Database) upsertReturning(ctx context.Context, returning bool, tx *sql.Tx, tenant, table string, columns []string, primaryKey []string, values ...any) (int, error) { | ||
| func (d *Database) upsertReturning(ctx context.Context, returning, returningLastInsertID bool, tx *sql.Tx, tenant, table string, columns []string, primaryKey []string, values ...any) (int, error) { |
There was a problem hiding this comment.
Hm! I'm surprised there's a need to differentiate "returning" and "returningLastInsertID". I took the former to mean the same when it was introduced. When do we only want one of them?
There was a problem hiding this comment.
IIUC the special handling is only needed for MySQL. In our case, we want the ID for both inserts and updates. Since the other databases support the RETURNING clause, they return the correct ID. MySQL will return 0 or something for the update case. Hence this is a workaround to get the correct ID on update.
| return b | ||
| } | ||
|
|
||
| func (b *Builder) Revision() string { |
There was a problem hiding this comment.
Is this always safe to call? I think b.revision might only get populated at bundle build time...? 🤔
There was a problem hiding this comment.
We're only tracking build and upload state as we need the revision to be non-empty.
There was a problem hiding this comment.
We can't track sync state due to this.
| assert.Empty(t, resp.Result) | ||
| }) | ||
|
|
||
| t.Run("unauthorized", func(t *testing.T) { |
There was a problem hiding this comment.
Should we check some cross-tenant request, perhaps?
There was a problem hiding this comment.
Added a test case for this.
| setup("PUT", "/v1/bundles/{bundle}", s.v1BundlesPut) | ||
| setup("DELETE", "/v1/bundles/{bundle}", s.v1BundlesDelete) | ||
| setup("GET", "/v1/bundles/{bundle}/status/latest", s.v1BundleStatusLatestGet) | ||
| setup("GET", "/v1/bundles/{bundle}/status", s.v1BundleStatusList) |
There was a problem hiding this comment.
Will there be a follow up docs update for these new endpoints, or is that still too early?
There was a problem hiding this comment.
Once we merge this, I will create a PR to update the docs.
| } | ||
|
|
||
| const ownerKey = "test-owner-key" | ||
| if err := db.UpsertToken(ctx, "internal", "default", &config.Token{ |
There was a problem hiding this comment.
[nit] to have less "magic" strings to track down
| if err := db.UpsertToken(ctx, "internal", "default", &config.Token{ | |
| if err := db.UpsertToken(ctx, principal.Id, principal.Tenant, &config.Token{ |
Same in other places.
|
|
||
| if _, err := db.UpsertBundleStatus(ctx, "default", "bundle1", "rev1", "build", "in_progress", ""); err != nil { | ||
| t.Fatal(err) | ||
| } |
There was a problem hiding this comment.
Are there missing assertions following this status update, or this is here to show that we only get the immediately following status update when querying? Maybe there is value in asserting both statuses(?)
There was a problem hiding this comment.
We are testing that we get the last inserted status for a tenant+bundle combo. So we expect to get only one status which we assert later in the test.
| setup("PUT", "/v1/bundles/{bundle}", s.v1BundlesPut) | ||
| setup("DELETE", "/v1/bundles/{bundle}", s.v1BundlesDelete) | ||
| setup("GET", "/v1/bundles/{bundle}/status/latest", s.v1BundleStatusLatestGet) | ||
| setup("GET", "/v1/bundles/{bundle}/status", s.v1BundleStatusList) |
There was a problem hiding this comment.
Given that we're returning a list of statuses I suppose this could also be .../statuses if we want to be sticklers for naming consistency; but I'm guessing we're seeing this as a general "status" endpoint for the bundle, and the actual return type is irrelevant(?)
(also, status kinda reads better 😄)
There was a problem hiding this comment.
also,
statuskinda reads better
👍
| return | ||
| } | ||
|
|
||
| resp := types.BundleStatusGetResponseV1{Result: status} |
There was a problem hiding this comment.
If there are no status records, we're gonna return an empty dictionary ({})? Maybe we should add a test for this to not have future regressions on this behavior.
There was a problem hiding this comment.
Scratch that, I was misled by the func doc 😄. But we should still assert this behavior in testing.
There was a problem hiding this comment.
This behavior is consistent with v1BundlesGet. We have a test for this in internal/database/bundle_status_test.go (see TestGetLatestBundleStatus)
| if err == nil { | ||
| _, err = tx.ExecContext(ctx, | ||
| fmt.Sprintf( | ||
| `DELETE FROM bundles_statuses WHERE bundle_id = %s AND id < %s`, |
There was a problem hiding this comment.
I forget, are bundle ID:s unique server-wide, across tenants? If not, might we be deleting records for another tenant with here?
There was a problem hiding this comment.
I think so. This is the same pattern used for deleting other resources.
| if b.Revision() != "" { | ||
| _, err := w.database.UpsertBundleStatus(ctx, w.tenant, w.bundleConfig.Name, b.Revision(), BuildPhaseBuild.String(), BuildStateBuildFailed.String(), err.Error()) | ||
| if err != nil { | ||
| w.log.Warnf("failed to track bundle build state %q: %v", w.bundleConfig.Name, err) |
There was a problem hiding this comment.
Ok, so we don't abort on status error 👍.
| w.log.Warnf("failed to upload bundle %q: %v", w.bundleConfig.Name, err) | ||
|
|
||
| if b.Revision() != "" { | ||
| _, err := w.database.UpsertBundleStatus(ctx, w.tenant, w.bundleConfig.Name, b.Revision(), BuildPhasePush.String(), BuildStatePushFailed.String(), err.Error()) |
There was a problem hiding this comment.
We only need to report on the ultimate success/failure of each phase, not that they're in progress?
There was a problem hiding this comment.
Only success/failure atm
| } | ||
| } | ||
|
|
||
| if w.storage != nil { |
There was a problem hiding this comment.
It's up to the client to know that storage has been configured, and therefore when to expect a PUSH phase?
Would there be value in tracking each phase separately, or that would just be unwarranted complexity?
There was a problem hiding this comment.
It's up to the client to know that storage has been configured, and therefore when to expect a PUSH phase?
Yes. So clients can plugin their own storage implementation.
Would there be value in tracking each phase separately, or that would just be unwarranted complexity?
We currently track build and push phases. One phase for a tenant+bundle+revision combo.
| t.Fatalf("expected bundle status %v but got %v", service.BuildStatePushFailed.String(), status.Status) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Should we also add a test case for when there is no storage, and we only expect BUILD-phase status?
There was a problem hiding this comment.
The service will create a storage if the user does not provide one.
This change adds support for tracking the status of a bundle through the build and push phases. The sync phase is not tracked as we need a bundle revision to keep track of a tenant+bundle+revision combo. Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
64420c7 to
39bb353
Compare
This change adds support for tracking the status of a bundle
through the build and push phases. The sync phase
is not tracked as we need a bundle revision to keep
track of a tenant+bundle+revision combo.