Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin_src/main' into resolve-port
Browse files Browse the repository at this point in the history
  • Loading branch information
qweeah committed Feb 16, 2023
2 parents 2076499 + fd179a3 commit f71d164
Show file tree
Hide file tree
Showing 36 changed files with 719 additions and 210 deletions.
1 change: 1 addition & 0 deletions .github/licenserc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ header:
paths-ignore:
- '**/*.md'
- 'CODEOWNERS'
- 'LICENSE'
- 'KEYS'
- 'go.mod'
Expand Down
19 changes: 1 addition & 18 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,9 @@ jobs:
run: make test
- name: Run E2E Tests
run: |
cd $GITHUB_WORKSPACE/test/e2e
go install github.com/onsi/ginkgo/v2/ginkgo
mnt_root="$GITHUB_WORKSPACE/test/e2e/testdata/distribution/mount"
rm -rf $mnt_root/docker
for layer in $(ls $mnt_root/*.tar.gz); do
tar -xvzf $layer -C $mnt_root
done
trap 'docker kill oras-e2e || true' ERR
docker run --pull always -d -p 5000:5000 --rm --name oras-e2e \
--env REGISTRY_STORAGE_DELETE_ENABLED=true \
--env REGISTRY_AUTH_HTPASSWD_REALM=test-basic \
--env REGISTRY_AUTH_HTPASSWD_PATH=/etc/docker/registry/passwd \
--mount type=bind,source=$mnt_root/docker,target=/var/lib/registry/docker \
--mount type=bind,source=$mnt_root/passwd_bcrypt,target=/etc/docker/registry/passwd \
ghcr.io/oras-project/registry:v1.0.0-rc.3
ginkgo -r -p --succinct suite
docker kill oras-e2e || true
sh $GITHUB_WORKSPACE/test/e2e/scripts/e2e.sh $GITHUB_WORKSPACE --clean
env:
ORAS_PATH: bin/linux/amd64/oras
ORAS_REGISTRY_HOST: localhost:5000
- name: Check Version
run: bin/linux/amd64/oras version
- name: Upload coverage to codecov.io
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ _dist/
.DS_Store

# Distribution storage files for local E2E testing
test/e2e/testdata/distribution/mount/docker/
test/e2e/testdata/distribution/mount/docker/
test/e2e/testdata/distribution/mount_fallback/docker/
6 changes: 6 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Derived from OWNERS.md
* @deitch
* @jdolitsky
* @sajayantony
* @shizhMSFT
* @stevelasker
26 changes: 25 additions & 1 deletion cmd/oras/internal/option/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Remote struct {
resolveDialContext func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error)
applyDistributionSpec bool
distributionSpec distributionSpec
headerFlags []string
headers http.Header
}

// EnableDistributionSpecFlag set distribution specification flag as applicable.
Expand All @@ -62,6 +64,7 @@ func (opts *Remote) EnableDistributionSpecFlag() {
func (opts *Remote) ApplyFlags(fs *pflag.FlagSet) {
opts.ApplyFlagsWithPrefix(fs, "", "")
fs.BoolVarP(&opts.PasswordFromStdin, "password-stdin", "", false, "read password or identity token from stdin")
fs.StringArrayVarP(&opts.headerFlags, "header", "H", nil, "add custom headers to requests")
}

func applyPrefix(prefix, description string) (flagPrefix, notePrefix string) {
Expand Down Expand Up @@ -105,6 +108,9 @@ func (opts *Remote) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description

// Parse tries to read password with optional cmd prompt.
func (opts *Remote) Parse() error {
if err := opts.parseCustomHeaders(); err != nil {
return err
}
if err := opts.readPassword(); err != nil {
return err
}
Expand Down Expand Up @@ -217,7 +223,8 @@ func (opts *Remote) authClient(registry string, debug bool) (client *auth.Client
TLSClientConfig: config,
},
},
Cache: auth.NewCache(),
Cache: auth.NewCache(),
Header: opts.headers,
}
client.SetUserAgent("oras/" + version.GetVersion())
if debug {
Expand Down Expand Up @@ -251,6 +258,23 @@ func (opts *Remote) authClient(registry string, debug bool) (client *auth.Client
return
}

func (opts *Remote) parseCustomHeaders() error {
if len(opts.headerFlags) != 0 {
headers := map[string][]string{}
for _, h := range opts.headerFlags {
name, value, found := strings.Cut(h, ":")
if !found || strings.TrimSpace(name) == "" {
// In conformance to the RFC 2616 specification
// Reference: https://www.rfc-editor.org/rfc/rfc2616#section-4.2
return fmt.Errorf("invalid header: %q", h)
}
headers[name] = append(headers[name], value)
}
opts.headers = headers
}
return nil
}

// Credential returns a credential based on the remote options.
func (opts *Remote) Credential() auth.Credential {
return credential.Credential(opts.Username, opts.Password)
Expand Down
101 changes: 101 additions & 0 deletions cmd/oras/internal/option/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,104 @@ func TestRemote_parseResolve(t *testing.T) {
})
}
}

func TestRemote_parseCustomHeaders(t *testing.T) {
tests := []struct {
name string
headerFlags []string
want nhttp.Header
wantErr bool
}{
{
name: "no custom header is provided",
headerFlags: []string{},
want: nil,
wantErr: false,
},
{
name: "one name-value pair",
headerFlags: []string{"key:value"},
want: map[string][]string{"key": {"value"}},
wantErr: false,
},
{
name: "multiple name-value pairs",
headerFlags: []string{"key:value", "k:v"},
want: map[string][]string{"key": {"value"}, "k": {"v"}},
wantErr: false,
},
{
name: "multiple name-value pairs with commas",
headerFlags: []string{"key:value,value2,value3", "k:v,v2,v3"},
want: map[string][]string{"key": {"value,value2,value3"}, "k": {"v,v2,v3"}},
wantErr: false,
},
{
name: "empty string is a valid value",
headerFlags: []string{"k:", "key:value,value2,value3"},
want: map[string][]string{"k": {""}, "key": {"value,value2,value3"}},
wantErr: false,
},
{
name: "multiple colons are allowed",
headerFlags: []string{"k::::v,v2,v3", "key:value,value2,value3"},
want: map[string][]string{"k": {":::v,v2,v3"}, "key": {"value,value2,value3"}},
wantErr: false,
},
{
name: "name with spaces",
headerFlags: []string{"bar :b"},
want: map[string][]string{"bar ": {"b"}},
wantErr: false,
},
{
name: "value with spaces",
headerFlags: []string{"foo: a"},
want: map[string][]string{"foo": {" a"}},
wantErr: false,
},
{
name: "repeated pairs",
headerFlags: []string{"key:value", "key:value"},
want: map[string][]string{"key": {"value", "value"}},
wantErr: false,
},
{
name: "repeated name with different values",
headerFlags: []string{"key:value", "key:value2"},
want: map[string][]string{"key": {"value", "value2"}},
wantErr: false,
},
{
name: "one valid header and one invalid header(no pair)",
headerFlags: []string{"key:value,value2,value3", "vk"},
want: nil,
wantErr: true,
},
{
name: "one valid header and one invalid header(empty name)",
headerFlags: []string{":v", "key:value,value2,value3"},
want: nil,
wantErr: true,
},
{
name: "pure-space name is invalid",
headerFlags: []string{" : foo "},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opts := &Remote{
headerFlags: tt.headerFlags,
}
if err := opts.parseCustomHeaders(); (err != nil) != tt.wantErr {
t.Errorf("Remote.parseCustomHeaders() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(tt.want, opts.headers) {
t.Errorf("Remote.parseCustomHeaders() = %v, want %v", opts.headers, tt.want)
}
})
}
}
Loading

0 comments on commit f71d164

Please sign in to comment.