Skip to content

Commit da03b23

Browse files
authored
feat: support formatted output in oras attach and push (#1237)
Signed-off-by: Billy Zha <jinzha1@microsoft.com>
1 parent 22c59ef commit da03b23

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1240
-120
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package display
17+
18+
import (
19+
"os"
20+
21+
"oras.land/oras/cmd/oras/internal/display/metadata"
22+
"oras.land/oras/cmd/oras/internal/display/metadata/json"
23+
"oras.land/oras/cmd/oras/internal/display/metadata/template"
24+
"oras.land/oras/cmd/oras/internal/display/metadata/text"
25+
"oras.land/oras/cmd/oras/internal/display/status"
26+
)
27+
28+
// NewPushHandler returns status and metadata handlers for push command.
29+
func NewPushHandler(format string, tty *os.File, verbose bool) (status.PushHandler, metadata.PushHandler) {
30+
var statusHandler status.PushHandler
31+
if tty != nil {
32+
statusHandler = status.NewTTYPushHandler(tty)
33+
} else if format == "" {
34+
statusHandler = status.NewTextPushHandler(verbose)
35+
} else {
36+
statusHandler = status.NewDiscardHandler()
37+
}
38+
39+
var metadataHandler metadata.PushHandler
40+
switch format {
41+
case "":
42+
metadataHandler = text.NewPushHandler()
43+
case "json":
44+
metadataHandler = json.NewPushHandler()
45+
default:
46+
metadataHandler = template.NewPushHandler(format)
47+
}
48+
49+
return statusHandler, metadataHandler
50+
}
51+
52+
// NewAttachHandler returns status and metadata handlers for attach command.
53+
func NewAttachHandler(format string, tty *os.File, verbose bool) (status.AttachHandler, metadata.AttachHandler) {
54+
var statusHandler status.AttachHandler
55+
if tty != nil {
56+
statusHandler = status.NewTTYAttachHandler(tty)
57+
} else if format == "" {
58+
statusHandler = status.NewTextAttachHandler(verbose)
59+
} else {
60+
statusHandler = status.NewDiscardHandler()
61+
}
62+
63+
var metadataHandler metadata.AttachHandler
64+
switch format {
65+
case "":
66+
metadataHandler = text.NewAttachHandler()
67+
case "json":
68+
metadataHandler = json.NewAttachHandler()
69+
default:
70+
metadataHandler = template.NewAttachHandler(format)
71+
}
72+
73+
return statusHandler, metadataHandler
74+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package metadata
17+
18+
import (
19+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
20+
"oras.land/oras/cmd/oras/internal/option"
21+
)
22+
23+
// PushHandler handles metadata output for push events.
24+
type PushHandler interface {
25+
OnCopied(opts *option.Target) error
26+
OnCompleted(root ocispec.Descriptor) error
27+
}
28+
29+
// AttachHandler handles metadata output for attach events.
30+
type AttachHandler interface {
31+
OnCompleted(opts *option.Target, root, subject ocispec.Descriptor) error
32+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package json
17+
18+
import (
19+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
20+
"oras.land/oras/cmd/oras/internal/display/metadata"
21+
"oras.land/oras/cmd/oras/internal/display/metadata/model"
22+
"oras.land/oras/cmd/oras/internal/option"
23+
)
24+
25+
// AttachHandler handles json metadata output for attach events.
26+
type AttachHandler struct{}
27+
28+
// NewAttachHandler creates a new handler for attach events.
29+
func NewAttachHandler() metadata.AttachHandler {
30+
return AttachHandler{}
31+
}
32+
33+
// OnCompleted is called when the attach command is completed.
34+
func (AttachHandler) OnCompleted(opts *option.Target, root, subject ocispec.Descriptor) error {
35+
return printJSON(model.NewPush(root, opts.Path))
36+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package json
17+
18+
import (
19+
"encoding/json"
20+
"os"
21+
)
22+
23+
func printJSON(object any) error {
24+
encoder := json.NewEncoder(os.Stdout)
25+
encoder.SetIndent("", " ")
26+
return encoder.Encode(object)
27+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package json
17+
18+
import (
19+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
20+
"oras.land/oras/cmd/oras/internal/display/metadata"
21+
"oras.land/oras/cmd/oras/internal/display/metadata/model"
22+
"oras.land/oras/cmd/oras/internal/option"
23+
)
24+
25+
// PushHandler handles JSON metadata output for push events.
26+
type PushHandler struct {
27+
path string
28+
}
29+
30+
// NewPushHandler creates a new handler for push events.
31+
func NewPushHandler() metadata.PushHandler {
32+
return &PushHandler{}
33+
}
34+
35+
// OnCopied is called after files are copied.
36+
func (ph *PushHandler) OnCopied(opts *option.Target) error {
37+
ph.path = opts.Path
38+
return nil
39+
}
40+
41+
// OnCompleted is called after the push is completed.
42+
func (ph *PushHandler) OnCompleted(root ocispec.Descriptor) error {
43+
return printJSON(model.NewPush(root, ph.path))
44+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package model
17+
18+
import (
19+
"github.com/opencontainers/go-digest"
20+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
21+
)
22+
23+
// DigestReference is a reference to an artifact with digest.
24+
type DigestReference struct {
25+
Ref string
26+
}
27+
28+
// NewDigestReference creates a new digest reference.
29+
func NewDigestReference(name string, digest string) DigestReference {
30+
return DigestReference{
31+
Ref: name + "@" + digest,
32+
}
33+
}
34+
35+
// Descriptor is a descriptor with digest reference.
36+
// We cannot use ocispec.Descriptor here since the first letter of the json
37+
// annotation key is not uppercase.
38+
type Descriptor struct {
39+
DigestReference
40+
41+
// MediaType is the media type of the object this schema refers to.
42+
MediaType string
43+
44+
// Digest is the digest of the targeted content.
45+
Digest digest.Digest
46+
47+
// Size specifies the size in bytes of the blob.
48+
Size int64
49+
50+
// URLs specifies a list of URLs from which this object MAY be downloaded
51+
URLs []string `json:",omitempty"`
52+
53+
// Annotations contains arbitrary metadata relating to the targeted content.
54+
Annotations map[string]string `json:",omitempty"`
55+
56+
// ArtifactType is the IANA media type of this artifact.
57+
ArtifactType string
58+
}
59+
60+
// FromDescriptor converts a OCI descriptor to a descriptor with digest reference.
61+
func FromDescriptor(name string, desc ocispec.Descriptor) Descriptor {
62+
ret := Descriptor{
63+
DigestReference: NewDigestReference(name, desc.Digest.String()),
64+
MediaType: desc.MediaType,
65+
Digest: desc.Digest,
66+
Size: desc.Size,
67+
URLs: desc.URLs,
68+
Annotations: desc.Annotations,
69+
ArtifactType: desc.ArtifactType,
70+
}
71+
return ret
72+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package model
17+
18+
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
19+
20+
// push contains metadata formatted by oras push.
21+
type push struct {
22+
Descriptor
23+
}
24+
25+
// NewPush returns a metadata getter for push command.
26+
func NewPush(desc ocispec.Descriptor, path string) any {
27+
return push{FromDescriptor(path, desc)}
28+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright The ORAS Authors.
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+
16+
package template
17+
18+
import (
19+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
20+
"oras.land/oras/cmd/oras/internal/display/metadata"
21+
"oras.land/oras/cmd/oras/internal/display/metadata/model"
22+
"oras.land/oras/cmd/oras/internal/option"
23+
)
24+
25+
// AttachHandler handles go-template metadata output for attach events.
26+
type AttachHandler struct {
27+
template string
28+
}
29+
30+
// NewAttachHandler returns a new handler for attach metadata events.
31+
func NewAttachHandler(template string) metadata.AttachHandler {
32+
return &AttachHandler{template: template}
33+
}
34+
35+
// OnCompleted formats the metadata of attach command.
36+
func (ah *AttachHandler) OnCompleted(opts *option.Target, root, subject ocispec.Descriptor) error {
37+
return parseAndWrite(model.NewPush(root, opts.Path), ah.template)
38+
}

0 commit comments

Comments
 (0)