mirror of
https://github.com/hauler-dev/hauler.git
synced 2026-02-14 18:09:51 +00:00
212 lines
5.0 KiB
Go
212 lines
5.0 KiB
Go
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/rancherfederal/hauler/pkg/packager/images"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/empty"
|
|
"github.com/google/go-containerregistry/pkg/v1/layout"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
fleetapi "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
|
|
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
|
|
"github.com/spf13/afero"
|
|
"helm.sh/helm/v3/pkg/cli"
|
|
"helm.sh/helm/v3/pkg/downloader"
|
|
"helm.sh/helm/v3/pkg/getter"
|
|
"k8s.io/apimachinery/pkg/util/json"
|
|
)
|
|
|
|
type PkgFs struct {
|
|
FS *afero.BasePathFs
|
|
root string
|
|
}
|
|
|
|
func NewPkgFS(dir string) PkgFs {
|
|
var p PkgFs
|
|
p.FS = afero.NewBasePathFs(afero.NewOsFs(), dir).(*afero.BasePathFs)
|
|
|
|
// TODO: absolutely no way this'll bite us in the butt later...
|
|
abs, _ := filepath.Abs(dir)
|
|
p.root = abs
|
|
return p
|
|
}
|
|
|
|
func (p PkgFs) Path(elem ...string) string {
|
|
complete := []string{p.root}
|
|
return filepath.Join(append(complete, elem...)...)
|
|
}
|
|
|
|
func (p PkgFs) Bundle() PkgFs {
|
|
return PkgFs{
|
|
FS: afero.NewBasePathFs(p.FS, v1alpha1.BundlesDir).(*afero.BasePathFs),
|
|
root: p.Path(v1alpha1.BundlesDir),
|
|
}
|
|
}
|
|
|
|
func (p PkgFs) Image() PkgFs {
|
|
return PkgFs{
|
|
FS: afero.NewBasePathFs(p.FS, v1alpha1.LayoutDir).(*afero.BasePathFs),
|
|
root: p.Path(v1alpha1.LayoutDir),
|
|
}
|
|
}
|
|
|
|
func (p PkgFs) Bin() PkgFs {
|
|
return PkgFs{
|
|
FS: afero.NewBasePathFs(p.FS, v1alpha1.BinDir).(*afero.BasePathFs),
|
|
root: p.Path(v1alpha1.BinDir),
|
|
}
|
|
}
|
|
|
|
func (p PkgFs) Chart() PkgFs {
|
|
return PkgFs{
|
|
FS: afero.NewBasePathFs(p.FS, v1alpha1.ChartDir).(*afero.BasePathFs),
|
|
root: p.Path(v1alpha1.ChartDir),
|
|
}
|
|
}
|
|
|
|
//AddBundle will add a bundle to a package and all images that are autodetected from it
|
|
func (p PkgFs) AddBundle(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) {
|
|
if err := p.mkdirIfNotExists(v1alpha1.BundlesDir, os.ModePerm); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := json.Marshal(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := p.Bundle().WriteFile(fmt.Sprintf("%s.json", b.Name), data, 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
imgs, err := images.ImageMapFromBundle(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return imgs, nil
|
|
}
|
|
|
|
func (p PkgFs) AddBin(r io.Reader, name string) error {
|
|
if err := p.mkdirIfNotExists(v1alpha1.BinDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := p.Bin().FS.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(f, r)
|
|
return err
|
|
}
|
|
|
|
//AddImage will add an image to the pkgfs in OCI layout fmt
|
|
//TODO: Extra work is done to ensure this is unique within the index.json
|
|
func (p PkgFs) AddImage(ref name.Reference, img v1.Image) error {
|
|
if err := p.mkdirIfNotExists(v1alpha1.LayoutDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
annotations := make(map[string]string)
|
|
annotations[ocispec.AnnotationRefName] = ref.Name()
|
|
|
|
lp, err := p.layout()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
//TODO: Change to ReplaceImage
|
|
return lp.AppendImage(img, layout.WithAnnotations(annotations))
|
|
}
|
|
|
|
//TODO: Not very robust
|
|
//For ref: https://github.com/helm/helm/blob/bf486a25cdc12017c7dac74d1582a8a16acd37ea/pkg/action/pull.go#L75
|
|
func (p PkgFs) AddChart(ref string, version string) error {
|
|
if err := p.mkdirIfNotExists(v1alpha1.ChartDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
d := downloader.ChartDownloader{
|
|
Out: nil,
|
|
Verify: downloader.VerifyNever,
|
|
Getters: getter.All(cli.New()), // TODO: Probably shouldn't do this...
|
|
Options: []getter.Option{
|
|
getter.WithInsecureSkipVerifyTLS(true),
|
|
},
|
|
}
|
|
|
|
_, _, err := d.DownloadTo(ref, version, p.Chart().Path())
|
|
return err
|
|
}
|
|
|
|
func (p PkgFs) layout() (layout.Path, error) {
|
|
path := p.Image().Path(".")
|
|
lp, err := layout.FromPath(path)
|
|
if os.IsNotExist(err) {
|
|
lp, err = layout.Write(path, empty.Index)
|
|
}
|
|
|
|
return lp, err
|
|
}
|
|
|
|
//WriteFile is a helper method to write a file within the PkgFs
|
|
func (p PkgFs) WriteFile(name string, data []byte, perm os.FileMode) error {
|
|
f, err := p.FS.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.Write(data)
|
|
if err1 := f.Close(); err1 != nil && err == nil {
|
|
err = err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (p PkgFs) MapLayout() (map[name.Reference]v1.Image, error) {
|
|
imgRefs := make(map[name.Reference]v1.Image)
|
|
|
|
//TODO: Factor this out to a Store interface
|
|
lp, err := p.layout()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ii, _ := lp.ImageIndex()
|
|
im, _ := ii.IndexManifest()
|
|
|
|
for _, m := range im.Manifests {
|
|
ref, err := name.ParseReference(m.Annotations[ocispec.AnnotationRefName])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
img, err := lp.Image(m.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
imgRefs[ref] = img
|
|
}
|
|
|
|
return imgRefs, err
|
|
}
|
|
|
|
//TODO: Is this actually faster than just os.MkdirAll?
|
|
func (p PkgFs) mkdirIfNotExists(dir string, perm os.FileMode) error {
|
|
_, err := os.Stat(p.Path(dir))
|
|
if os.IsNotExist(err) {
|
|
mkdirErr := p.FS.MkdirAll(dir, perm)
|
|
if mkdirErr != nil {
|
|
return mkdirErr
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|