Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions pkg/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd
import (
"context"
"fmt"
"net/url"
"os"

"github.com/kernel/hypeman-cli/internal/apiquery"
Expand Down Expand Up @@ -145,7 +146,9 @@ func handleImagesDelete(ctx context.Context, cmd *cli.Command) error {
return err
}

return client.Images.Delete(ctx, requestflag.CommandRequestValue[string](cmd, "name"), options...)
// URL-encode the name to handle slashes in image references (e.g., docker.io/library/nginx:latest)
name := url.PathEscape(requestflag.CommandRequestValue[string](cmd, "name"))
return client.Images.Delete(ctx, name, options...)
}

func handleImagesGet(ctx context.Context, cmd *cli.Command) error {
Expand All @@ -170,7 +173,9 @@ func handleImagesGet(ctx context.Context, cmd *cli.Command) error {

var res []byte
options = append(options, option.WithResponseBodyInto(&res))
_, err = client.Images.Get(ctx, requestflag.CommandRequestValue[string](cmd, "name"), options...)
// URL-encode the name to handle slashes in image references (e.g., docker.io/library/nginx:latest)
name := url.PathEscape(requestflag.CommandRequestValue[string](cmd, "name"))
_, err = client.Images.Get(ctx, name, options...)
if err != nil {
return err
}
Expand Down
34 changes: 23 additions & 11 deletions pkg/cmd/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"strings"
Expand Down Expand Up @@ -70,11 +71,21 @@ func handlePush(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("invalid target: %w", err)
}

auth := &hypemanAuth{}
token := os.Getenv("HYPEMAN_BEARER_TOKEN")
if token == "" {
token = os.Getenv("HYPEMAN_API_KEY")
}

// Use custom transport that always sends Basic auth header
transport := &authTransport{
base: http.DefaultTransport,
token: token,
}

err = remote.Write(dstRef, img,
remote.WithContext(ctx),
remote.WithAuth(auth),
remote.WithAuth(authn.Anonymous),
remote.WithTransport(transport),
)
if err != nil {
return fmt.Errorf("push failed: %w", err)
Expand All @@ -84,15 +95,16 @@ func handlePush(ctx context.Context, cmd *cli.Command) error {
return nil
}

type hypemanAuth struct{}
// authTransport adds Basic auth header to all requests
type authTransport struct {
base http.RoundTripper
token string
}

func (a *hypemanAuth) Authorization() (*authn.AuthConfig, error) {
token := os.Getenv("HYPEMAN_BEARER_TOKEN")
if token == "" {
token = os.Getenv("HYPEMAN_API_KEY")
}
if token == "" {
return &authn.AuthConfig{}, nil
func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.token != "" {
// Use Bearer auth directly
req.Header.Set("Authorization", "Bearer "+t.token)
}
return &authn.AuthConfig{RegistryToken: token}, nil
return t.base.RoundTrip(req)
}
6 changes: 4 additions & 2 deletions pkg/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"net/url"
"os"
"strings"
"time"
Expand Down Expand Up @@ -114,7 +115,8 @@ func handleRun(ctx context.Context, cmd *cli.Command) error {
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)

// Check if image exists and is ready
imgInfo, err := client.Images.Get(ctx, image)
// URL-encode the image name to handle slashes (e.g., docker.io/library/nginx:latest)
imgInfo, err := client.Images.Get(ctx, url.PathEscape(image))
if err != nil {
// Image not found, try to pull it
var apiErr *hypeman.Error
Expand Down Expand Up @@ -272,7 +274,7 @@ func waitForImageReady(ctx context.Context, client *hypeman.Client, img *hypeman
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
updated, err := client.Images.Get(ctx, img.Name)
updated, err := client.Images.Get(ctx, url.PathEscape(img.Name))
if err != nil {
return fmt.Errorf("failed to check image status: %w", err)
}
Expand Down