Skip to content

Commit 1f22489

Browse files
committed
image/refs: Add a generic name-based reference interface
And implement that interface for tarballs based on the specs image-layout. I plan on adding other backends later, but this is enough for a proof of concept. Also add a new oci-refs command so folks can access the new read functionality from the command line. The Engine.List interface uses a callback instead of returning channels or a slice. Benefits vs. returning a slice of names: * There's no need to allocate a slice for the results, so calls with large (or negative) 'size' values can be made without consuming large amounts of memory. * The name collection and processing can happen concurrently, so: * We don't waste cycles collecting names we won't use. * Slow collection can happen in the background if/when the consumer is blocked on something else. The benefit of using callbacks vs. returning name and error channels (as discussed in [1]) is more of a trade-off. Stephen Day [2] and JT Olds [3] don't like channel's internal locks. Dave Cheney doesn't have a problem with them [4]. Which approach is more efficient for a given situation depends on how expensive it is for the engine to find the next key and how expensive it is to act on a returned name. If both are expensive, you want goroutines in there somewhere to get concurrent execution, and channels will help those goroutines communicate. When either action is fast (or both are fast), channels are unnecessary overhead. By using a callback in the interface, we avoid baking in the overhead. Folks who want concurrent execution can initialize their own channel, launch List in a goroutine, and use the callback to inject names into their channel. In a subsequent commit, I'll replace the image/walker.go functionality with this new API. I'd prefer casLayout for the imported package, but Stephen doesn't want camelCase for package names [5]. [1]: https://blog.golang.org/pipelines [2]: opencontainers/image-spec#159 (comment) [3]: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-and-you-should-feel-bad/ [4]: https://groups.google.com/d/msg/golang-nuts/LM648yrPpck/idyupwodAwAJ Subject: Re: [go-nuts] Re: "Go channels are bad and you should feel bad" Date: Wed, 2 Mar 2016 16:04:13 -0800 (PST) Message-Id: <[email protected]> [5]: opencontainers/image-spec#159 (comment) Signed-off-by: W. Trevor King <[email protected]>
1 parent 8e8fcf9 commit 1f22489

File tree

8 files changed

+449
-0
lines changed

8 files changed

+449
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/oci-cas
22
/oci-create-runtime-bundle
33
/oci-image-validate
4+
/oci-refs
45
/oci-unpack

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ TOOLS := \
66
oci-cas \
77
oci-create-runtime-bundle \
88
oci-image-validate \
9+
oci-refs \
910
oci-unpack
1011

1112
default: help

cmd/oci-refs/get.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"os"
21+
22+
"github.com/opencontainers/image-tools/image/refs/layout"
23+
"github.com/spf13/cobra"
24+
"golang.org/x/net/context"
25+
)
26+
27+
type getCmd struct {
28+
path string
29+
name string
30+
}
31+
32+
func newGetCmd() *cobra.Command {
33+
state := &getCmd{}
34+
35+
return &cobra.Command{
36+
Use: "get PATH NAME",
37+
Short: "Retrieve a reference from the store",
38+
Run: state.Run,
39+
}
40+
}
41+
42+
func (state *getCmd) Run(cmd *cobra.Command, args []string) {
43+
if len(args) != 2 {
44+
fmt.Fprintln(os.Stderr, "both PATH and NAME must be provided")
45+
if err := cmd.Usage(); err != nil {
46+
fmt.Fprintln(os.Stderr, err)
47+
}
48+
os.Exit(1)
49+
}
50+
51+
state.path = args[0]
52+
state.name = args[1]
53+
54+
err := state.run()
55+
if err != nil {
56+
fmt.Fprintln(os.Stderr, err)
57+
os.Exit(1)
58+
}
59+
60+
os.Exit(0)
61+
}
62+
63+
func (state *getCmd) run() (err error) {
64+
ctx := context.Background()
65+
66+
engine, err := layout.NewEngine(state.path)
67+
if err != nil {
68+
return err
69+
}
70+
defer engine.Close()
71+
72+
descriptor, err := engine.Get(ctx, state.name)
73+
if err != nil {
74+
return err
75+
}
76+
77+
return json.NewEncoder(os.Stdout).Encode(&descriptor)
78+
}

cmd/oci-refs/list.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"github.com/opencontainers/image-tools/image/refs/layout"
22+
"github.com/spf13/cobra"
23+
"golang.org/x/net/context"
24+
)
25+
26+
type listCmd struct {
27+
path string
28+
}
29+
30+
func newListCmd() *cobra.Command {
31+
state := &listCmd{}
32+
33+
return &cobra.Command{
34+
Use: "list PATH",
35+
Short: "Return available names from the store.",
36+
Run: state.Run,
37+
}
38+
}
39+
40+
func (state *listCmd) Run(cmd *cobra.Command, args []string) {
41+
if len(args) != 1 {
42+
fmt.Fprintln(os.Stderr, "PATH must be provided")
43+
if err := cmd.Usage(); err != nil {
44+
fmt.Fprintln(os.Stderr, err)
45+
}
46+
os.Exit(1)
47+
}
48+
49+
state.path = args[0]
50+
51+
err := state.run()
52+
if err != nil {
53+
fmt.Fprintln(os.Stderr, err)
54+
os.Exit(1)
55+
}
56+
57+
os.Exit(0)
58+
}
59+
60+
func (state *listCmd) run() (err error) {
61+
ctx := context.Background()
62+
63+
engine, err := layout.NewEngine(state.path)
64+
if err != nil {
65+
return err
66+
}
67+
defer engine.Close()
68+
69+
return engine.List(ctx, "", -1, 0, state.printName)
70+
}
71+
72+
func (state *listCmd) printName(ctx context.Context, name string) (err error) {
73+
n, err = fmt.Fprintln(os.Stdout, name)
74+
if n < len(name) {
75+
return fmt.Errorf("wrote %d of %d name", n, len(name))
76+
}
77+
return err
78+
}

cmd/oci-refs/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"github.com/spf13/cobra"
22+
)
23+
24+
func main() {
25+
cmd := &cobra.Command{
26+
Use: "oci-refs",
27+
Short: "Name-based reference manipulation",
28+
}
29+
30+
cmd.AddCommand(newGetCmd())
31+
cmd.AddCommand(newListCmd())
32+
33+
err := cmd.Execute()
34+
if err != nil {
35+
fmt.Fprintln(os.Stderr, err)
36+
os.Exit(1)
37+
}
38+
}

image/refs/interface.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package refs implements generic name-based reference access.
16+
package refs
17+
18+
import (
19+
"github.com/opencontainers/image-spec/specs-go"
20+
"golang.org/x/net/context"
21+
)
22+
23+
// ListNameCallback templates an Engine.List callback used for
24+
// processing names. See Engine.List for more details.
25+
type ListNameCallback func(ctx context.Context, name string) (err error)
26+
27+
// Engine represents a name-based reference storage engine.
28+
type Engine interface {
29+
30+
// Put adds a new reference to the store. The action is idempotent;
31+
// a nil return means "that descriptor is stored at NAME" without
32+
// implying "because of your Put()".
33+
Put(ctx context.Context, name string, descriptor *specs.Descriptor) (err error)
34+
35+
// Get returns a reference from the store. Returns os.ErrNotExist
36+
// if the name is not found.
37+
Get(ctx context.Context, name string) (descriptor *specs.Descriptor, err error)
38+
39+
// List returns available names from the store.
40+
//
41+
// Results are sorted alphabetically.
42+
//
43+
// Arguments:
44+
//
45+
// * ctx: gives callers a way to gracefully cancel a long-running
46+
// list.
47+
// * prefix: limits the result set to names starting with that
48+
// value.
49+
// * size: limits the length of the result set to the first 'size'
50+
// matches. A value of -1 means "all results".
51+
// * from: shifts the result set to start from the 'from'th match.
52+
// * nameCallback: called for every matching name. List returns any
53+
// errors returned by nameCallback and aborts further listing.
54+
//
55+
// For example, a store with names like:
56+
//
57+
// * 123
58+
// * abcd
59+
// * abce
60+
// * abcf
61+
// * abcg
62+
//
63+
// will have the following call/result pairs:
64+
//
65+
// * List(ctx, "", -1, 0, printName) -> "123", "abcd", "abce", "abcf", "abcg"
66+
// * List(ctx, "", 2, 0, printName) -> "123", "abcd"
67+
// * List(ctx, "", 2, 1, printName) -> "abcd", "abce"
68+
// * List(ctx,"abc", 2, 1, printName) -> "abce", "abcf"
69+
List(ctx context.Context, prefix string, size int, from int, nameCallback ListNameCallback) (err error)
70+
71+
// Delete removes a reference from the store. Returns
72+
// os.ErrNotExist if the name is not found.
73+
Delete(ctx context.Context, name string) (err error)
74+
75+
// Close releases resources held by the engine. Subsequent engine
76+
// method calls will fail.
77+
Close() (err error)
78+
}

image/refs/layout/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2016 The Linux Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package layout implements the refs interface using the image-spec's
16+
// image-layout [1].
17+
//
18+
// [1]: https://github.com/opencontainers/image-spec/blob/master/image-layout.md
19+
package layout
20+
21+
import (
22+
"os"
23+
24+
"github.com/opencontainers/image-tools/image/refs"
25+
)
26+
27+
// NewEngine instantiates an engine with the appropriate backend (tar,
28+
// HTTP, ...).
29+
func NewEngine(path string) (engine refs.Engine, err error) {
30+
file, err := os.Open(path)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
return NewTarEngine(file)
36+
}

0 commit comments

Comments
 (0)