Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PoC for multi arch image create/update #1450

Closed
wants to merge 14 commits into from
Closed
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
2 changes: 2 additions & 0 deletions cmd/oras/root/manifest/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package manifest

import (
"github.com/spf13/cobra"
"oras.land/oras/cmd/oras/root/manifest/index"
)

func Cmd() *cobra.Command {
Expand All @@ -30,6 +31,7 @@ func Cmd() *cobra.Command {
fetchCmd(),
fetchConfigCmd(),
pushCmd(),
index.Cmd(),
)
return cmd
}
33 changes: 33 additions & 0 deletions cmd/oras/root/manifest/index/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright The ORAS Authors.
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 index

import (
"github.com/spf13/cobra"
)

func Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "index [command]",
Short: "Index operations",
}

cmd.AddCommand(
createCmd(),
updateCmd(),
)
return cmd
}
174 changes: 174 additions & 0 deletions cmd/oras/root/manifest/index/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
Copyright The ORAS Authors.
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 index

import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"

"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras/cmd/oras/internal/command"
oerrors "oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/descriptor"
)

type createOptions struct {
option.Common
option.Target

sources []string
extraRefs []string
}

func createCmd() *cobra.Command {
var opts createOptions
cmd := &cobra.Command{
Use: "create [flags] <name>[:<tag[,<tag>][...]] [{<tag>|<digest>}...]",
Short: "Create and push an index from provided manifests",
Long: `Create and push an index to a repository or an OCI image layout

Example - create an index from source manifests tagged amd64, arm64, darwin in the repository
localhost:5000/hello, and push the index without tagging it:
oras manifest index create localhost:5000/hello amd64 arm64 darwin

Example - create an index from source manifests tagged amd64, arm64, darwin in the repository
localhost:5000/hello, and push the index with tag 'latest':
oras manifest index create localhost:5000/hello:latest amd64 arm64 darwin

Example - create an index from source manifests using both tags and digests,
and push the index with tag 'latest':
oras manifest index create localhost:5000/hello latest amd64 sha256:xxx darwin

Example - create an index and push it with multiple tags:
oras manifest index create localhost:5000/tag1, tag2, tag3 amd64 arm64 sha256:xxx
`,
Args: cobra.MinimumNArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
refs := strings.Split(args[0], ",")
opts.RawReference = refs[0]
opts.extraRefs = refs[1:]
opts.sources = args[1:]
return option.Parse(cmd, &opts)

Check warning on line 72 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L68-L72

Added lines #L68 - L72 were not covered by tests
// todo: add EnsureReferenceNotEmpty somewhere
},
RunE: func(cmd *cobra.Command, args []string) error {
return createIndex(cmd, opts)
},

Check warning on line 77 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L74-L77

Added lines #L74 - L77 were not covered by tests
}

option.ApplyFlags(&opts, cmd.Flags())
return oerrors.Command(cmd, &opts.Target)
}

func createIndex(cmd *cobra.Command, opts createOptions) error {
ctx, logger := command.GetLogger(cmd, &opts.Common)
target, err := opts.NewTarget(opts.Common, logger)
if err != nil {
return err

Check warning on line 88 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L84-L88

Added lines #L84 - L88 were not covered by tests
}
// we assume that the sources and the to be created index are all in the same
// repository, so no copy is needed
manifests, err := resolveSourceManifests(ctx, target, opts.sources)
if err != nil {
return err

Check warning on line 94 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L92-L94

Added lines #L92 - L94 were not covered by tests
}
desc, content, err := packIndex(&ocispec.Index{}, manifests)
if err != nil {
return err

Check warning on line 98 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L96-L98

Added lines #L96 - L98 were not covered by tests
}
return pushIndex(ctx, target, desc, content, opts.Reference, opts.extraRefs)

Check warning on line 100 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L100

Added line #L100 was not covered by tests
}

func resolveSourceManifests(ctx context.Context, target oras.ReadOnlyTarget, sources []string) ([]ocispec.Descriptor, error) {
var resolved []ocispec.Descriptor
for _, source := range sources {
desc, content, err := oras.FetchBytes(ctx, target, source, oras.DefaultFetchBytesOptions)
if err != nil {
return nil, err

Check warning on line 108 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L103-L108

Added lines #L103 - L108 were not covered by tests
}
if descriptor.IsImageManifest(desc) {
desc.Platform, err = getPlatform(ctx, target, content)
if err != nil {
return nil, err

Check warning on line 113 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L110-L113

Added lines #L110 - L113 were not covered by tests
}
}
resolved = append(resolved, desc)

Check warning on line 116 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L116

Added line #L116 was not covered by tests
}
return resolved, nil

Check warning on line 118 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L118

Added line #L118 was not covered by tests
}

func getPlatform(ctx context.Context, target oras.ReadOnlyTarget, manifestBytes []byte) (*ocispec.Platform, error) {

Check warning on line 121 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L121

Added line #L121 was not covered by tests
// extract config descriptor
var manifest ocispec.Manifest
if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
return nil, err

Check warning on line 125 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L123-L125

Added lines #L123 - L125 were not covered by tests
}
// fetch config content
contentBytes, err := content.FetchAll(ctx, target, manifest.Config)
if err != nil {
return nil, err

Check warning on line 130 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L128-L130

Added lines #L128 - L130 were not covered by tests
}
var platform ocispec.Platform
if err := json.Unmarshal(contentBytes, &platform); err != nil {
return nil, err

Check warning on line 134 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L132-L134

Added lines #L132 - L134 were not covered by tests
}
return &platform, nil

Check warning on line 136 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L136

Added line #L136 was not covered by tests
}

func packIndex(oldIndex *ocispec.Index, manifests []ocispec.Descriptor) (ocispec.Descriptor, []byte, error) {
index := ocispec.Index{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
MediaType: ocispec.MediaTypeImageIndex,
ArtifactType: oldIndex.ArtifactType,
Manifests: manifests,
Subject: oldIndex.Subject,
Annotations: oldIndex.Annotations,

Check warning on line 148 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L139-L148

Added lines #L139 - L148 were not covered by tests
}
indexBytes, err := json.Marshal(index)
if err != nil {
return ocispec.Descriptor{}, nil, err

Check warning on line 152 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L150-L152

Added lines #L150 - L152 were not covered by tests
}
desc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageIndex, indexBytes)
return desc, indexBytes, nil

Check warning on line 155 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L154-L155

Added lines #L154 - L155 were not covered by tests
}

func pushIndex(ctx context.Context, target oras.Target, desc ocispec.Descriptor, content []byte, ref string, extraRefs []string) error {
var err error

Check warning on line 159 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L158-L159

Added lines #L158 - L159 were not covered by tests
// need to refine the variable names of ref, extra ref
if ref != "" {
extraRefs = append(extraRefs, ref)

Check warning on line 162 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L161-L162

Added lines #L161 - L162 were not covered by tests
}
if len(extraRefs) == 0 {
err = target.Push(ctx, desc, bytes.NewReader(content))
} else {
desc, err = oras.TagBytesN(ctx, target, desc.MediaType, content, extraRefs, oras.DefaultTagBytesNOptions)

Check warning on line 167 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L164-L167

Added lines #L164 - L167 were not covered by tests
}
if err != nil {
return err

Check warning on line 170 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L169-L170

Added lines #L169 - L170 were not covered by tests
}
fmt.Println("Created and pushed index:", desc.Digest)
return nil

Check warning on line 173 in cmd/oras/root/manifest/index/create.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/manifest/index/create.go#L172-L173

Added lines #L172 - L173 were not covered by tests
}
Loading
Loading