Skip to content

Commit

Permalink
blueprint downloader (#375)
Browse files Browse the repository at this point in the history
* initial implementation of a blueprint downloader

* add tests for download and registration

* utilize printer within downloader implementation

* fix linter issues

* fix linter issues

---------

Co-authored-by: Gerald Morrison <67469729+morri-son@users.noreply.github.com>
  • Loading branch information
2 people authored and robertwolu committed Sep 25, 2023
1 parent df856eb commit 663c00f
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 2 deletions.
8 changes: 6 additions & 2 deletions pkg/contexts/oci/artdesc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func ToDescriptorMediaType(media string) string {
return ToContentMediaType(media) + "+json"
}

func ToArchiveMediaTypes(media string) []string {
base := ToContentMediaType(media)
return []string{base + "+tar", base + "+tar+gzip"}
}

func IsOCIMediaType(media string) bool {
c := ToContentMediaType(media)
for _, t := range ContentTypes() {
Expand Down Expand Up @@ -90,8 +95,7 @@ func DescriptorTypes() []string {
func ArchiveBlobTypes() []string {
r := []string{}
for _, t := range ContentTypes() {
t = ToContentMediaType(t)
r = append(r, t+"+tar", t+"+tar+gzip")
r = append(r, ToArchiveMediaTypes(t)...)
}
return r
}
Expand Down
106 changes: 106 additions & 0 deletions pkg/contexts/ocm/download/handlers/blueprint/blueprint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package blueprint_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/open-component-model/ocm/pkg/env/builder"
. "github.com/open-component-model/ocm/pkg/testutils"

"github.com/mandelsoft/vfs/pkg/projectionfs"

"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/common/accessio"
"github.com/open-component-model/ocm/pkg/common/accessobj"
"github.com/open-component-model/ocm/pkg/contexts/oci/testhelper"
"github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact"
v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
"github.com/open-component-model/ocm/pkg/contexts/ocm/download"
"github.com/open-component-model/ocm/pkg/contexts/ocm/download/handlers/blueprint"
ctfocm "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf"
tenv "github.com/open-component-model/ocm/pkg/env"
"github.com/open-component-model/ocm/pkg/utils/tarutils"
)

const (
COMPONENT = "github.com/compa"
VERSION = "1.0.0"
CTF = "ctf"
OCI = "oci"

OCIHOST = "source"
OCINAMESPACE = "ocm/value"
OCIVERSION = "v2.0"

MIMETYPE = "testmimetype"
ARTIFACT_TYPE = "testartifacttype"
OCI_ARTIFACT_NAME = "ociblueprint"
LOCAL_ARTIFACT_NAME = "localblueprint"
ARTIFACT_VERSION = "v1.0.0"

TESTDATA_PATH = "testdata/blueprint"
ARCHIVE_PATH = "archive"
DOWNLOAD_PATH = "download"
)

var _ = Describe("download blueprint", func() {
var env *Builder

BeforeEach(func() {
env = NewBuilder(tenv.NewEnvironment(tenv.TestData()))

MustBeSuccessful(tarutils.CreateTarFromFs(Must(projectionfs.New(env, TESTDATA_PATH)), ARCHIVE_PATH, tarutils.Gzip, env))

env.OCICommonTransport(OCI, accessio.FormatDirectory, func() {
env.Namespace(OCINAMESPACE, func() {
env.Manifest(OCIVERSION, func() {
env.Config(func() {
env.BlobStringData(blueprint.CONFIG_MIME_TYPE, "{}")
})
env.Layer(func() {
env.BlobFromFile(blueprint.BLUEPRINT_MIMETYPE, ARCHIVE_PATH)
})
})
})
})

testhelper.FakeOCIRepo(env, OCI, OCIHOST)
env.OCMCommonTransport(CTF, accessio.FormatDirectory, func() {
env.ComponentVersion(COMPONENT, VERSION, func() {
env.Resource(OCI_ARTIFACT_NAME, ARTIFACT_VERSION, blueprint.TYPE, v1.ExternalRelation, func() {
env.Access(ociartifact.New(OCIHOST + ".alias/" + OCINAMESPACE + ":" + OCIVERSION))
})
env.Resource(LOCAL_ARTIFACT_NAME, ARTIFACT_VERSION, blueprint.TYPE, v1.LocalRelation, func() {
env.BlobFromFile(blueprint.BLUEPRINT_MIMETYPE, ARCHIVE_PATH)
})
})
})
})

AfterEach(func() {
env.Cleanup()
})
DescribeTable("download blueprints", func(index int) {
src := Must(ctfocm.Open(env.OCMContext(), accessobj.ACC_READONLY, CTF, 0, env))
defer Close(src, "source ctf")

cv := Must(src.LookupComponentVersion(COMPONENT, VERSION))
defer Close(cv)

racc := Must(cv.GetResourceByIndex(index))

p, buf := common.NewBufferedPrinter()
ok, path := Must2(download.For(env).Download(p, racc, DOWNLOAD_PATH, env))
Expect(ok).To(BeTrue())
Expect(path).To(Equal(DOWNLOAD_PATH))
Expect(env.FileExists(DOWNLOAD_PATH + "/blueprint.yaml")).To(BeTrue())
Expect(env.FileExists(DOWNLOAD_PATH + "/test/README.md")).To(BeTrue())
Expect(buf.String()).To(StringEqualTrimmedWithContext(DOWNLOAD_PATH + ": 2 file(s) with 390 byte(s) written"))
},
Entry("oci artifact", 0),
Entry("local resource", 1),
)
})
92 changes: 92 additions & 0 deletions pkg/contexts/ocm/download/handlers/blueprint/extrator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package blueprint

import (
"github.com/mandelsoft/vfs/pkg/projectionfs"
"github.com/mandelsoft/vfs/pkg/vfs"

"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/common/accessio"
"github.com/open-component-model/ocm/pkg/common/accessobj"
"github.com/open-component-model/ocm/pkg/common/compression"
"github.com/open-component-model/ocm/pkg/contexts/oci/repositories/artifactset"
"github.com/open-component-model/ocm/pkg/finalizer"
"github.com/open-component-model/ocm/pkg/utils/tarutils"
)

const (
BLUEPRINT_MIMETYPE_LEGACY = "application/vnd.gardener.landscaper.blueprint.layer.v1.tar"
BLUEPRINT_MIMETYPE = "application/vnd.gardener.landscaper.blueprint.v1+tar+gzip"
)

func ExtractArchive(pr common.Printer, _ *Handler, access accessio.DataAccess, path string, fs vfs.FileSystem) (_ bool, rerr error) {
var finalize finalizer.Finalizer
defer finalize.FinalizeWithErrorPropagationf(&rerr, "extracting archived (and compressed) blueprint")

rawReader, err := access.Reader()
if err != nil {
return true, err
}
finalize.Close(rawReader)

reader, _, err := compression.AutoDecompress(rawReader)
if err != nil {
return true, err
}
finalize.Close(reader)

err = fs.MkdirAll(path, 0o700)
if err != nil {
return true, err
}

pfs, err := projectionfs.New(fs, path)
if err != nil {
return true, err
}
fcnt, bcnt, err := tarutils.ExtractTarToFsWithInfo(pfs, reader)
if err != nil {
return true, err
}
pr.Printf("%s: %d file(s) with %d byte(s) written\n", path, fcnt, bcnt)
return true, nil
}

func ExtractArtifact(pr common.Printer, handler *Handler, access accessio.DataAccess, path string, fs vfs.FileSystem) (_ bool, rerr error) {
var finalize finalizer.Finalizer
defer finalize.FinalizeWithErrorPropagationf(&rerr, "extracting oci artifact containing a blueprint")

rd, err := access.Reader()
if err != nil {
return true, err
}
finalize.Close(rd)

set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(rd))
if err != nil {
return true, err
}
finalize.Close(set)

art, err := set.GetArtifact(set.GetMain().String())
if err != nil {
return true, err
}
finalize.Close(art)

desc := art.ManifestAccess().GetDescriptor().Layers[0]
if !handler.ociConfigMimeTypes.Contains(art.ManifestAccess().GetDescriptor().Config.MediaType) {
if desc.MediaType != BLUEPRINT_MIMETYPE && desc.MediaType != BLUEPRINT_MIMETYPE_LEGACY {
return false, nil
}
}

blob, err := art.GetBlob(desc.Digest)
if err != nil {
return true, err
}
return ExtractArchive(pr, handler, blob, path, fs)
}
86 changes: 86 additions & 0 deletions pkg/contexts/ocm/download/handlers/blueprint/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors.
//
// SPDX-License-Identifier: Apache-2.0

package blueprint

import (
"github.com/mandelsoft/vfs/pkg/vfs"

"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/common/accessio"
"github.com/open-component-model/ocm/pkg/contexts/oci/artdesc"
"github.com/open-component-model/ocm/pkg/contexts/ocm/cpi"
registry "github.com/open-component-model/ocm/pkg/contexts/ocm/download"
"github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes"
"github.com/open-component-model/ocm/pkg/finalizer"
"github.com/open-component-model/ocm/pkg/generics"
"github.com/open-component-model/ocm/pkg/mime"
"github.com/open-component-model/ocm/pkg/utils"
)

const (
TYPE = resourcetypes.BLUEPRINT
LEGACY_TYPE = resourcetypes.BLUEPRINT_LEGACY
CONFIG_MIME_TYPE = "application/vnd.gardener.landscaper.blueprint.config.v1"
)

type Extractor func(pr common.Printer, handler *Handler, access accessio.DataAccess, path string, fs vfs.FileSystem) (bool, error)

var (
supportedArtifactTypes []string
mimeTypeExtractorRegistry map[string]Extractor
)

type Handler struct {
ociConfigMimeTypes generics.Set[string]
}

func init() {
supportedArtifactTypes = []string{TYPE, LEGACY_TYPE}
mimeTypeExtractorRegistry = map[string]Extractor{
mime.MIME_TAR: ExtractArchive,
mime.MIME_TGZ: ExtractArchive,
mime.MIME_TGZ_ALT: ExtractArchive,
BLUEPRINT_MIMETYPE: ExtractArchive,
}
for _, t := range append(artdesc.ToArchiveMediaTypes(artdesc.MediaTypeImageManifest), artdesc.ToArchiveMediaTypes(artdesc.MediaTypeDockerSchema2Manifest)...) {
mimeTypeExtractorRegistry[t] = ExtractArtifact
}

h := New()

registry.Register(h, registry.ForArtifactType(TYPE))
registry.Register(h, registry.ForArtifactType(LEGACY_TYPE))
}

func New(configmimetypes ...string) *Handler {
if len(configmimetypes) == 0 || utils.Optional(configmimetypes...) == "" {
configmimetypes = []string{CONFIG_MIME_TYPE}
}
return &Handler{
ociConfigMimeTypes: generics.NewSet[string](configmimetypes...),
}
}

func (h *Handler) Download(pr common.Printer, racc cpi.ResourceAccess, path string, fs vfs.FileSystem) (_ bool, _ string, err error) {
var finalize finalizer.Finalizer
defer finalize.FinalizeWithErrorPropagationf(&err, "downloading blueprint")

meth, err := racc.AccessMethod()
if err != nil {
return false, "", err
}
finalize.Close(meth)

ex := mimeTypeExtractorRegistry[meth.MimeType()]
if ex == nil {
return false, "", nil
}

ok, err := ex(pr, h, meth, path, fs)
if err != nil || !ok {
return ok, "", err
}
return true, path, nil
}
Loading

0 comments on commit 663c00f

Please sign in to comment.