Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 19 additions & 4 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,33 @@ $ oci-discovery --debug resolve example.com/app#1.0 2>/tmp/log
{
"example.com/app#1.0": [
{
"casEngines": [
{
"config": {
"protocol": "oci-cas-template-v1",
"uri": "/cas/{algorithm}/{encoded}"
},
"uri": "https://example.com/.well-known/oci-host-ref-engines"
}
],
"mediaType": "application/vnd.oci.descriptor.v1+json",
"root": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:e9770a03fbdccdd4632895151a93f9af58bbe2c91fdfaaf73160648d250e6ec3"
"size": 799,
"digest": "sha256:e9770a03fbdccdd4632895151a93f9af58bbe2c91fdfaaf73160648d250e6ec3",
"annotations": {
"org.opencontainers.image.ref.name": "1.0"
},
"casEngines": [
{
"protocol": "oci-cas-template-v1",
"uri": "https://a.example.com/cas/{algorithm}/{encoded:2}/{encoded}"
}
],
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
"size": 799
},
"uri": "https://example.com/oci-index/app"
}
Expand Down
47 changes: 41 additions & 6 deletions tools/cmd/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"github.com/xiekeyang/oci-discovery/tools/engine"
"github.com/xiekeyang/oci-discovery/tools/hostbasedimagenames"
"github.com/xiekeyang/oci-discovery/tools/refengine"
"github.com/xiekeyang/oci-discovery/tools/refenginediscovery"
Expand All @@ -30,6 +31,33 @@ import (
// resolved is a flag for breaking discovery iteration.
var resolved = fmt.Errorf("satisfactory resolution")

type resolvedName struct {
refengine.MerkleRoot

// CASEngines holds the ref-engines object's CAS-engine suggestions,
// if any.
CASEngines []engine.Reference
}

// Add casEngines as a sibling of the MerkleRoot properties.
func (resolved resolvedName) MarshalJSON() ([]byte, error) {
b, err := json.Marshal(resolved.MerkleRoot)
if err != nil {
return nil, err
}

var data map[string]interface{}
err = json.Unmarshal(b, &data)
if err != nil {
return nil, err
}

if resolved.CASEngines != nil {
data["casEngines"] = resolved.CASEngines
}
return json.Marshal(data)
}

var resolveCommand = cli.Command{
Name: "resolve",
Usage: "Resolve image names via OCI Ref-Engine Discovery.",
Expand All @@ -45,7 +73,7 @@ var resolveCommand = cli.Command{
},
Action: func(c *cli.Context) error {
ctx := context.Background()
allRoots := map[string][]refengine.MerkleRoot{}
resolvedNames := map[string][]resolvedName{}

protocols := []string{}
if c.IsSet("protocol") {
Expand All @@ -61,8 +89,8 @@ var resolveCommand = cli.Command{

err = refenginediscovery.Discover(
ctx, protocols, parsedName["host"],
func(ctx context.Context, refEngine refengine.Engine, casEngines []refenginediscovery.ResolvedCASEngine) error {
return resolveCallback(ctx, allRoots, refEngine, casEngines, name)
func(ctx context.Context, refEngine refengine.Engine, casEngines []engine.Reference) error {
return resolveCallback(ctx, resolvedNames, refEngine, casEngines, name)
})
if err == resolved {
continue
Expand All @@ -73,16 +101,23 @@ var resolveCommand = cli.Command{

encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", "\t")
return encoder.Encode(allRoots)
return encoder.Encode(resolvedNames)
},
}

func resolveCallback(ctx context.Context, allRoots map[string][]refengine.MerkleRoot, refEngine refengine.Engine, casEngines []refenginediscovery.ResolvedCASEngine, name string) (err error) {
func resolveCallback(ctx context.Context, resolvedNames map[string][]resolvedName, refEngine refengine.Engine, casEngines []engine.Reference, name string) (err error) {
roots, err := refEngine.Get(ctx, name)
if err != nil {
logrus.Warn(err)
return nil
}
allRoots[name] = roots
if len(roots) == 0 {
return nil
}
resolvedNames[name] = make([]resolvedName, len(roots))
for i, root := range roots {
resolvedNames[name][i].MerkleRoot = root
resolvedNames[name][i].CASEngines = casEngines
}
return resolved
}
8 changes: 6 additions & 2 deletions tools/engine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ func (c *Config) UnmarshalJSON(b []byte) (err error) {
return err
}

data, ok := dataInterface.(map[string]interface{})
return c.unmarshalInterface(dataInterface)
}

func (c *Config) unmarshalInterface(d interface{}) (err error) {
data, ok := d.(map[string]interface{})
if !ok {
return fmt.Errorf("engine config is not a JSON object: %v", dataInterface)
return fmt.Errorf("engine config is not a JSON object: %v", d)
}

protocolInterface, ok := data["protocol"]
Expand Down
89 changes: 89 additions & 0 deletions tools/engine/reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2017 oci-discovery contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package engine

import (
"encoding/json"
"fmt"
"net/url"
)

// Reference holds a single resolved engine config.
type Reference struct {

// Config is the engine configuration.
Config Config

// URI is the source, if any, from which Config was retrieved. It
// can be used to expand any relative reference contained within
// Config.
URI *url.URL
}

// UnmarshalJSON reads 'config' and 'uri' properties into Config and
// URI respectively. The main difference from the stock
// json.Unmarshal implementation is that the 'uri' value is read from
// a string instead of from an object with Scheme, Host,
// etc. properties.
func (reference *Reference) UnmarshalJSON(b []byte) (err error) {
var dataInterface interface{}
if err := json.Unmarshal(b, &dataInterface); err != nil {
return err
}

data, ok := dataInterface.(map[string]interface{})
if !ok {
return fmt.Errorf("reference is not a JSON object: %v", dataInterface)
}

configInterface, ok := data["config"]
if !ok {
return fmt.Errorf("reference missing required 'config' entry: %v", data)
}

err = reference.Config.unmarshalInterface(configInterface)
if err != nil {
return err
}

uriInterface, ok := data["uri"]
if !ok {
reference.URI = nil
} else {
uriString, ok := uriInterface.(string)
if !ok {
return fmt.Errorf("reference uri is not a string: %v", uriInterface)
}
reference.URI, err = url.Parse(uriString)
if err != nil {
return err
}
}

return nil
}

// MarshalJSON writes 'config' and 'uri' properties to the output
// object. The main difference from the stock json.Marshal
// implementation is that the 'uri' value is written as a string instead
// of an object with Scheme, Host, etc. properties.
func (reference Reference) MarshalJSON() ([]byte, error) {
data := map[string]interface{}{}
data["config"] = reference.Config
if reference.URI != nil {
data["uri"] = reference.URI.String()
}
return json.Marshal(data)
}
80 changes: 80 additions & 0 deletions tools/engine/reference_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2017 oci-discovery contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package engine

import (
"encoding/json"
"net/url"
"testing"

"github.com/stretchr/testify/assert"
)

func TestReferenceGood(t *testing.T) {
for _, testcase := range []struct {
JSON string
Expected Reference
}{
{
JSON: `{"config":{"protocol":"oci-image-template-v1","uri":"index.json"}}`,
Expected: Reference{
Config: Config{
Protocol: "oci-image-template-v1",
Data: map[string]interface{}{
"uri": "index.json",
},
},
URI: nil,
},
},
{
JSON: `{"config":{"protocol":"oci-image-template-v1","uri":"index.json"},"uri":"https://example.com"}`,
Expected: Reference{
Config: Config{
Protocol: "oci-image-template-v1",
Data: map[string]interface{}{
"uri": "index.json",
},
},
URI: &url.URL{
Scheme: "https",
Host: "example.com",
},
},
},
} {
t.Run(testcase.JSON, func(t *testing.T) {
reference := Reference{
Config: Config{
Protocol: "initial value",
Data: map[string]interface{}{
"initial": "value",
},
},
URI: &url.URL{
Scheme: "https",
Host: "initial.value.example.com",
},
}
json.Unmarshal([]byte(testcase.JSON), &reference)
assert.Equal(t, testcase.Expected, reference)
marshaled, err := json.Marshal(reference)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, testcase.JSON, string(marshaled))
})
}
}
2 changes: 2 additions & 0 deletions tools/refengine/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
type Engine interface {

// Get returns an array of potential Merkle roots from the store.
// When no results are found, roots will be an empty array and err
// will be nil.
Get(ctx context.Context, name string) (roots []MerkleRoot, err error)

// Close releases resources held by the engine. Subsequent engine
Expand Down
11 changes: 10 additions & 1 deletion tools/refengine/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
// MerkleRoot holds a single resolved Merkle root.
type MerkleRoot struct {
// MediaType is the media type of Root.
MediaType string `json:"mediaType,omitempty"`
MediaType string

// Root is the Merkle root object. While this may be of any type.
// OCI tools will generally use image-spec Descriptors.
Expand All @@ -34,6 +34,11 @@ type MerkleRoot struct {
URI *url.URL
}

// UnmarshalJSON reads 'mediaType', 'root', and 'uri' properties into
// MediaType, Root, and URI respectively. The main difference from
// the stock json.Unmarshal implementation is that the 'uri' value is
// read from a string instead of from an object with Scheme, Host,
// etc. properties.
func (root *MerkleRoot) UnmarshalJSON(b []byte) (err error) {
var dataInterface interface{}
if err := json.Unmarshal(b, &dataInterface); err != nil {
Expand Down Expand Up @@ -73,6 +78,10 @@ func (root *MerkleRoot) UnmarshalJSON(b []byte) (err error) {
return nil
}

// MarshalJSON writes 'mediaType', 'root', and 'uri' properties to the
// output object. The main difference from the stock json.Marshal
// implementation is that the 'uri' value is written as a string
// instead of an object with Scheme, Host, etc. properties.
func (root MerkleRoot) MarshalJSON() ([]byte, error) {
data := map[string]interface{}{}
if root.MediaType != "" {
Expand Down
11 changes: 6 additions & 5 deletions tools/refenginediscovery/refenginediscovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"net/url"

"github.com/sirupsen/logrus"
"github.com/xiekeyang/oci-discovery/tools/engine"
"github.com/xiekeyang/oci-discovery/tools/refengine"
"golang.org/x/net/context"
)
Expand Down Expand Up @@ -113,17 +114,17 @@ func (base *Base) RefEngines(ctx context.Context, refEngineCallback RefEngineCal
logrus.Debugf("unsupported ref-engine protocol %q (%v)", config.Protocol, refengine.Constructors)
continue
}
engine, err := constructor(ctx, base.URI, config.Data)
eng, err := constructor(ctx, base.URI, config.Data)
if err != nil {
logrus.Warnf("failed to initialize %s ref engine with %v: %s", config.Protocol, config.Data, err)
continue
}
resolvedCASEngines := make([]ResolvedCASEngine, len(base.Config.CASEngines))
CASEngines := make([]engine.Reference, len(base.Config.CASEngines))
for i, config := range base.Config.CASEngines {
resolvedCASEngines[i].Config = config
resolvedCASEngines[i].URI = base.URI
CASEngines[i].Config = config
CASEngines[i].URI = base.URI
}
err = refEngineCallback(ctx, engine, resolvedCASEngines)
err = refEngineCallback(ctx, eng, CASEngines)
if err != nil {
return err
}
Expand Down
Loading