-
Notifications
You must be signed in to change notification settings - Fork 21
Add pushes API for exporting images to remote registries #353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
+3,957
−457
Draft
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
f3ebd6f
Add imagepush manager for outbound image push jobs
chruffins b6ac5cc
Support borrowed per-push registry credentials
chruffins c4a65c4
Fix push job lifecycle races and orphan handling
chruffins 954aaee
Address review: panic-safe queue completion, credentialed orphans, pe…
chruffins 3a74966
Notify subscribers when a recovered push is closed
chruffins 8f4fdd0
Fix remaining review findings on push lifecycle
chruffins 107efc1
imagepush: fix credential-blind dedup and drop dead orphan adoption
chruffins 5f7038f
imagepush: share a generic queue and slim the test harness
chruffins 888c9f3
imagepush: dedupe test wait/assert scaffolding
chruffins d98dec5
imagepush: address parallel-review findings
chruffins da865ae
imagepush: deflake queue completion-hook ordering test
chruffins 2c7f833
images: fix build-phase metrics test after build-queue consolidation
chruffins 68b750e
imagepush: address review findings on dedup, recovery, and tests
chruffins f461185
imagepush: merge dedup waiters into successor instead of re-pushing
chruffins 6edf954
Add pushes API for exporting images to remote registries
chruffins 2076b55
Fix gofmt alignment for push manager wiring
chruffins b38c3b6
Fix push status enum varnames and empty-credentials fallback
chruffins a9a4500
Add pushes resource to the Stainless SDK config
chruffins 13a1a28
Address pushes API review: push timeout, drop dead wait surface, simp…
chruffins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
|
|
||
| "github.com/google/go-containerregistry/pkg/authn" | ||
| "github.com/kernel/hypeman/lib/imagepush" | ||
| "github.com/kernel/hypeman/lib/images" | ||
| "github.com/kernel/hypeman/lib/logger" | ||
| "github.com/kernel/hypeman/lib/oapi" | ||
| ) | ||
|
|
||
| func (s *ApiService) CreatePush(ctx context.Context, request oapi.CreatePushRequestObject) (oapi.CreatePushResponseObject, error) { | ||
| log := logger.FromContext(ctx) | ||
|
|
||
| domainReq := imagepush.PushRequest{ | ||
| Image: request.Body.Image, | ||
| Target: request.Body.Target, | ||
| Credentials: pushCredentialsToAuthn(request.Body.Credentials), | ||
| } | ||
| if request.Body.Insecure != nil { | ||
| domainReq.Insecure = *request.Body.Insecure | ||
| } | ||
|
|
||
| push, err := s.PushManager.CreatePush(ctx, domainReq) | ||
| if err != nil { | ||
| switch { | ||
| case errors.Is(err, images.ErrInvalidName): | ||
| return oapi.CreatePush400JSONResponse{ | ||
| Code: "invalid_name", | ||
| Message: err.Error(), | ||
| }, nil | ||
| case errors.Is(err, imagepush.ErrInvalidTarget): | ||
| return oapi.CreatePush400JSONResponse{ | ||
| Code: "invalid_target", | ||
| Message: err.Error(), | ||
| }, nil | ||
| case errors.Is(err, images.ErrNotFound): | ||
| return oapi.CreatePush404JSONResponse{ | ||
| Code: "not_found", | ||
| Message: "image not found", | ||
| }, nil | ||
| case errors.Is(err, imagepush.ErrImageNotReady): | ||
| return oapi.CreatePush409JSONResponse{ | ||
| Code: "image_not_ready", | ||
| Message: err.Error(), | ||
| }, nil | ||
| default: | ||
| log.ErrorContext(ctx, "failed to create push", "error", err) | ||
| return oapi.CreatePush500JSONResponse{ | ||
| Code: "internal_error", | ||
| Message: "failed to create push", | ||
| }, nil | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return oapi.CreatePush202JSONResponse(pushToOAPI(*push)), nil | ||
| } | ||
|
|
||
| func (s *ApiService) GetPush(ctx context.Context, request oapi.GetPushRequestObject) (oapi.GetPushResponseObject, error) { | ||
| log := logger.FromContext(ctx) | ||
|
|
||
| push, err := s.PushManager.GetPush(ctx, request.Id) | ||
| if err != nil { | ||
| if errors.Is(err, imagepush.ErrNotFound) { | ||
| return oapi.GetPush404JSONResponse{ | ||
| Code: "not_found", | ||
| Message: "push not found", | ||
| }, nil | ||
| } | ||
| log.ErrorContext(ctx, "failed to get push", "error", err) | ||
| return oapi.GetPush500JSONResponse{ | ||
| Code: "internal_error", | ||
| Message: "failed to get push", | ||
| }, nil | ||
| } | ||
|
|
||
| return oapi.GetPush200JSONResponse(pushToOAPI(*push)), nil | ||
| } | ||
|
|
||
| func (s *ApiService) ListPushes(ctx context.Context, request oapi.ListPushesRequestObject) (oapi.ListPushesResponseObject, error) { | ||
| log := logger.FromContext(ctx) | ||
|
|
||
| pushes, err := s.PushManager.ListPushes(ctx) | ||
| if err != nil { | ||
| log.ErrorContext(ctx, "failed to list pushes", "error", err) | ||
| return oapi.ListPushes500JSONResponse{ | ||
| Code: "internal_error", | ||
| Message: "failed to list pushes", | ||
| }, nil | ||
| } | ||
|
|
||
| out := make([]oapi.Push, 0, len(pushes)) | ||
| for _, push := range pushes { | ||
| out = append(out, pushToOAPI(push)) | ||
| } | ||
| return oapi.ListPushes200JSONResponse(out), nil | ||
| } | ||
|
|
||
| // pushCredentialsToAuthn maps API credentials to the go-containerregistry | ||
| // auth config. Returns nil when absent or empty so the push falls back to | ||
| // the server's default credential resolution — an empty credentials object | ||
| // must not mask the keychain. | ||
| func pushCredentialsToAuthn(creds *oapi.PushCredentials) *authn.AuthConfig { | ||
| if creds == nil { | ||
| return nil | ||
| } | ||
| cfg := &authn.AuthConfig{} | ||
| if creds.Username != nil { | ||
| cfg.Username = *creds.Username | ||
| } | ||
| if creds.Password != nil { | ||
| cfg.Password = *creds.Password | ||
| } | ||
| if creds.RegistryToken != nil { | ||
| cfg.RegistryToken = *creds.RegistryToken | ||
| } | ||
| if cfg.Username == "" && cfg.Password == "" && cfg.RegistryToken == "" { | ||
| return nil | ||
| } | ||
| return cfg | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| func pushToOAPI(push imagepush.Push) oapi.Push { | ||
| out := oapi.Push{ | ||
| Id: push.ID, | ||
| Image: push.Image, | ||
| Digest: push.Digest, | ||
| Target: push.Target, | ||
| Status: oapi.PushStatus(push.Status), | ||
| QueuePosition: push.QueuePosition, | ||
| Error: push.Error, | ||
| CreatedAt: push.CreatedAt, | ||
| CompletedAt: push.CompletedAt, | ||
| } | ||
| if push.Status == oapi.PushStatus(imagepush.StatusPushed) { | ||
|
Check failure on line 137 in cmd/api/api/pushes.go
|
||
| layers := push.Layers | ||
| out.Layers = &layers | ||
| bytes := push.Bytes | ||
| out.Bytes = &bytes | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| return out | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.