Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
28 changes: 27 additions & 1 deletion pkg/cli/cmd/bicep/publish/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,37 @@ func (r *Runner) prepareDestination() (*remote.Repository, error) {
return dst, nil
}

// enhanceOCIError enhances the OCI registry error with detailed information about naming rules.
// The 'br:' prefix indicates this is a Bicep OCI registry reference.
// The target parameter should be the OCI reference without the 'br:' prefix (as stored in Runner.Target).
func enhanceOCIError(target string, err error) error {
errMsg := err.Error()

const helpMessage = "The target must be a valid Bicep OCI registry reference in the form 'br:<OCI-registry-hostname>/<module-path>:<tag>'."

// Display the target with the 'br:' prefix to match what the user provided
displayTarget := "br:" + target

// Check if this is a known OCI validation error
if strings.Contains(errMsg, "invalid repository") ||
strings.Contains(errMsg, "invalid registry") ||
strings.Contains(errMsg, "invalid tag") ||
strings.Contains(errMsg, "missing registry or repository") ||
strings.Contains(errMsg, "invalid reference") {
return clierrors.MessageWithCause(err,
"Invalid OCI reference in target %q.\n\n"+helpMessage,
displayTarget)
}

// Return the original error if we don't recognize it
return err
}

// extractDestination extracts the host, repo, and tag from the target
func (r *Runner) extractDestination() (*destination, error) {
ref, err := registry.ParseReference(r.Target)
if err != nil {
return nil, err
return nil, enhanceOCIError(r.Target, err)
}

host := ref.Host()
Expand Down
78 changes: 78 additions & 0 deletions pkg/cli/cmd/bicep/publish/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net/http"
"net/url"
"reflect"
"strings"
"testing"

"github.com/opencontainers/go-digest"
Expand Down Expand Up @@ -98,6 +99,83 @@ func TestRunner_extractDestination(t *testing.T) {
}
}

func TestRunner_extractDestination_EnhancedErrors(t *testing.T) {
tests := []struct {
name string
target string
wantErr bool
expectedErrContains []string
}{
{
name: "uppercase in repository name",
target: "localhost:5000/myregistry/Data/mySqlDatabases/kubernetes/kubernetesmysql:latest",
wantErr: true,
expectedErrContains: []string{
"Invalid OCI reference",
"br:",
},
},
{
name: "uppercase at start of repository",
target: "localhost:5000/MyRegistry/data:latest",
wantErr: true,
expectedErrContains: []string{
"Invalid OCI reference",
"br:",
},
},
{
name: "invalid tag starting with hyphen",
target: "localhost:5000/myregistry/data:-invalid",
wantErr: true,
expectedErrContains: []string{
"Invalid OCI reference",
"br:",
},
},
{
name: "missing repository",
target: "localhost:5000",
wantErr: true,
expectedErrContains: []string{
"Invalid OCI reference",
"br:",
},
},
{
name: "valid lowercase repository",
target: "localhost:5000/myregistry/data/mysqldatabases/kubernetes/kubernetesmysql:latest",
wantErr: false,
},
{
name: "valid with hyphens and underscores",
target: "localhost:5000/my-registry/my_data:v1.0.0",
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Runner{
Target: tt.target,
}
_, err := r.extractDestination()
if (err != nil) != tt.wantErr {
t.Errorf("Runner.extractDestination() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.wantErr {
errStr := err.Error()
for _, expectedStr := range tt.expectedErrContains {
if !strings.Contains(errStr, expectedStr) {
t.Errorf("Runner.extractDestination() error = %q, expected to contain %q", errStr, expectedStr)
}
}
}
})
}
}

func Test_pushBlob(t *testing.T) {
tests := []struct {
name string
Expand Down