mirror of
https://github.com/hauler-dev/hauler.git
synced 2026-08-19 04:16:27 +00:00
save
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rancherfederal/hauler/pkg/apis/driver"
|
||||
"github.com/rancherfederal/hauler/pkg/apis/haul"
|
||||
"github.com/rancherfederal/hauler/pkg/create"
|
||||
"github.com/rancherfederal/hauler/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type createOpts struct {
|
||||
}
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
opts := &createOpts{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "create a haul",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.Run()
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o createOpts) Run() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
logger := log.NewPrettyLogger()
|
||||
|
||||
// TODO: Load this from config if provided
|
||||
h := haul.Haul{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Haul",
|
||||
APIVersion: "v1alpha1",
|
||||
},
|
||||
Metadata: metav1.ObjectMeta{
|
||||
Name: "haul",
|
||||
},
|
||||
Spec: haul.HaulSpec{
|
||||
Driver: driver.K3sDriver{
|
||||
Version: driver.K3sDefaultVersion,
|
||||
},
|
||||
PreloadImages: []string{
|
||||
"plndr/kube-vip:0.3.4",
|
||||
"registry:2.7.1",
|
||||
"gitea/gitea:1.14.1-rootless",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d, err := yaml.Marshal(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(d))
|
||||
|
||||
c, err := create.NewCreator(logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = c
|
||||
_ = ctx
|
||||
//if err := c.Create(ctx, h); err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,7 +3,11 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"github.com/rancherfederal/hauler/pkg/deployer"
|
||||
"github.com/rancherfederal/hauler/pkg/kube"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"time"
|
||||
)
|
||||
|
||||
type deployOpts struct {
|
||||
@@ -40,5 +44,44 @@ func (o *deployOpts) Run(haul string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err := waitForReady()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForReady will wait for the cluster components to be ready
|
||||
// TODO: Make components dynamic based on what is auto-loaded
|
||||
func waitForReady() error {
|
||||
cfg, err := kube.NewKubeClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checker, err := kube.NewStatusChecker(cfg, 5*time.Second, 30*time.Minute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objs, err := buildComponentRefs()
|
||||
|
||||
if err := checker.WaitForCondition(objs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildComponentRefs() ([]object.ObjMetadata, error) {
|
||||
var objRefs []object.ObjMetadata
|
||||
for _, deployment := range []string{"coredns", "local-path-provisioner"} {
|
||||
objMeta, err := object.CreateObjMetadata("kube-system", deployment, schema.GroupKind{Group: "apps", Kind: "Deployment"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objRefs = append(objRefs, objMeta)
|
||||
}
|
||||
return objRefs, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"github.com/containerd/containerd/remotes/docker"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ociOpts struct {
|
||||
insecure bool
|
||||
plainHTTP bool
|
||||
}
|
||||
|
||||
const (
|
||||
haulerMediaType = "application/vnd.oci.image"
|
||||
)
|
||||
|
||||
func NewOCICommand() *cobra.Command {
|
||||
opts := ociOpts{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "oci",
|
||||
Short: "oci stuff",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(NewOCIPushCommand())
|
||||
cmd.AddCommand(NewOCIPullCommand())
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&opts.insecure, "insecure", "", false, "Connect to registry without certs")
|
||||
f.BoolVarP(&opts.plainHTTP, "plain-http", "", false, "Connect to registry over plain http")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ociOpts) resolver() (remotes.Resolver, error) {
|
||||
resolver := docker.NewResolver(docker.ResolverOptions{PlainHTTP: true})
|
||||
return resolver, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/oras-project/oras-go/pkg/content"
|
||||
"github.com/oras-project/oras-go/pkg/oras"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ociPullOpts struct {
|
||||
ociOpts
|
||||
|
||||
sourceRef string
|
||||
outDir string
|
||||
}
|
||||
|
||||
func NewOCIPullCommand() *cobra.Command {
|
||||
opts := ociPullOpts{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull",
|
||||
Short: "oci pull",
|
||||
Aliases: []string{"p"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.PreRun()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.sourceRef = args[0]
|
||||
return opts.Run()
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVarP(&opts.outDir, "out-dir", "o", ".", "output directory")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ociPullOpts) PreRun() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ociPullOpts) Run() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := content.NewFileStore(o.outDir)
|
||||
defer store.Close()
|
||||
|
||||
allowedMediaTypes := []string{
|
||||
haulerMediaType,
|
||||
}
|
||||
|
||||
resolver, err := o.resolver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
desc, _, err := oras.Pull(ctx, resolver, o.sourceRef, store, oras.WithAllowedMediaTypes(allowedMediaTypes))
|
||||
|
||||
logrus.Infof("pulled %s with digest: %s", o.sourceRef, desc.Digest)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/oras-project/oras-go/pkg/content"
|
||||
"github.com/oras-project/oras-go/pkg/oras"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ociPushOpts struct {
|
||||
ociOpts
|
||||
|
||||
targetRef string
|
||||
pathRef string
|
||||
}
|
||||
|
||||
func NewOCIPushCommand() *cobra.Command {
|
||||
opts := ociPushOpts{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "push",
|
||||
Short: "oci push",
|
||||
Aliases: []string{"p"},
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return opts.PreRun()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.pathRef = args[0]
|
||||
opts.targetRef = args[1]
|
||||
return opts.Run()
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ociPushOpts) PreRun() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ociPushOpts) Run() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
data, err := os.ReadFile(o.pathRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resolver, err := o.resolver()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
store := content.NewMemoryStore()
|
||||
|
||||
contents := []ocispec.Descriptor{
|
||||
store.Add(o.pathRef, haulerMediaType, data),
|
||||
}
|
||||
|
||||
desc, err := oras.Push(ctx, resolver, o.targetRef, store, contents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("pushed %s to %s with digest: %s", o.pathRef, o.targetRef, desc.Digest)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func NewPackageCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "package",
|
||||
Short: "package all dependencies into a compressed archive",
|
||||
Long: `package all dependencies into a compresed archive used by deploy.
|
||||
Long: `package all dependencies into a compressed archive used by deploy.
|
||||
|
||||
Container images, git repositories, and more, packaged and ready to be served within an air gap.`,
|
||||
Aliases: []string{"p", "pkg"},
|
||||
|
||||
@@ -39,6 +39,8 @@ then deploy the package into your air-gapped environment.`,
|
||||
cmd.AddCommand(NewPackageCommand())
|
||||
cmd.AddCommand(NewDeployCommand())
|
||||
cmd.AddCommand(NewBundleCommand())
|
||||
cmd.AddCommand(NewOCICommand())
|
||||
cmd.AddCommand(NewCreateCommand())
|
||||
|
||||
f := cmd.PersistentFlags()
|
||||
f.StringVarP(&loglevel, "loglevel", "l", "info",
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/rancherfederal/hauler
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/docker/cli v20.10.6+incompatible // indirect
|
||||
github.com/containerd/containerd v1.5.0-rc.3
|
||||
github.com/docker/docker v20.10.6+incompatible // indirect
|
||||
github.com/go-git/go-git/v5 v5.4.0
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
@@ -11,16 +11,23 @@ require (
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/klauspost/compress v1.12.2 // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.0
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
github.com/oras-project/oras-go v0.1.0
|
||||
github.com/rs/zerolog v1.22.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.16.0
|
||||
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
k8s.io/apimachinery v0.20.6
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.21.1
|
||||
k8s.io/apimachinery v0.21.1
|
||||
k8s.io/client-go v0.21.1
|
||||
rsc.io/letsencrypt v0.0.3 // indirect
|
||||
sigs.k8s.io/cli-utils v0.25.0
|
||||
sigs.k8s.io/controller-runtime v0.8.3
|
||||
)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
EtcPath = "/etc/rancher"
|
||||
ExecutableBin = "hauler/bin"
|
||||
)
|
||||
|
||||
type Driver interface {
|
||||
Name() string
|
||||
Images() []string
|
||||
|
||||
ReleaseArtifactsURL() string
|
||||
AutodeployManifestsPath() string
|
||||
PreloadImagesPath() string
|
||||
AnonymousStaticPath() string
|
||||
}
|
||||
|
||||
type DriverConfig struct {
|
||||
NodeName string `json:"node-name" yaml:"node-name"`
|
||||
KubeConfigMode string `json:"write-kubeconfig-mode" yaml:"write-kubeconfig-mode"`
|
||||
NodeLabels []string `json:"node-label" yaml:"node-label"`
|
||||
}
|
||||
|
||||
func linesToSlice(r io.ReadCloser) ([]string, error) {
|
||||
var lines []string
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
K3sDefaultReleasesURL = "https://github.com/k3s-io/k3s/releases/download"
|
||||
K3sDefaultVersion = "v1.21.1+k3s1"
|
||||
K3sExecutable = "k3s"
|
||||
)
|
||||
|
||||
const (
|
||||
k3sDriverName = "k3s"
|
||||
)
|
||||
|
||||
type K3sDriver struct {
|
||||
Version string `json:"version" yaml:"version"`
|
||||
|
||||
Config K3sConfig `json:"config"`
|
||||
}
|
||||
|
||||
type K3sConfig struct {
|
||||
DriverConfig
|
||||
}
|
||||
|
||||
func (k K3sDriver) Name() string { return k3sDriverName }
|
||||
func (k K3sDriver) Images() []string {
|
||||
//TODO: Don't panic!!
|
||||
resp, err := http.Get(fmt.Sprintf("%s/%s/%s-images.txt", K3sDefaultReleasesURL, k.Version, k.Name()))
|
||||
if err != nil {
|
||||
panic("failed getting images")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
images, err := linesToSlice(resp.Body)
|
||||
if err != nil {
|
||||
panic("failed getting images")
|
||||
}
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
func (k K3sDriver) ReleaseArtifactsURL() string { return fmt.Sprintf("%s/%s", K3sDefaultReleasesURL, k.Version) }
|
||||
func (k K3sDriver) PreloadImagesPath() string { return filepath.Join(k3sDriverName, "agent/images") }
|
||||
func (k K3sDriver) AutodeployManifestsPath() string { return filepath.Join(k3sDriverName, "server/manifests") }
|
||||
func (k K3sDriver) AnonymousStaticPath() string { return filepath.Join(k3sDriverName, "server/static") }
|
||||
@@ -0,0 +1 @@
|
||||
package driver
|
||||
@@ -0,0 +1,19 @@
|
||||
package haul
|
||||
|
||||
import (
|
||||
"github.com/rancherfederal/hauler/pkg/apis/driver"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type Haul struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
Metadata metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
|
||||
Spec HaulSpec `json:"spec" yaml:"spec"`
|
||||
}
|
||||
|
||||
type HaulSpec struct {
|
||||
Driver driver.Driver
|
||||
|
||||
PreloadImages []string `json:"preloadedImages" yaml:"preloadedImages"`
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mholt/archiver/v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Archiver interface {
|
||||
// TODO: This isn't the greatest interface...
|
||||
Archive([]string, string) error
|
||||
String() string
|
||||
}
|
||||
|
||||
type zstdArchiver archiver.TarZstd
|
||||
|
||||
func NewArchiver() *archiver.TarZstd {
|
||||
z := &archiver.TarZstd{
|
||||
Tar: &archiver.Tar{
|
||||
OverwriteExisting: true,
|
||||
MkdirAll: true,
|
||||
ImplicitTopLevelFolder: false,
|
||||
StripComponents: 0,
|
||||
ContinueOnError: false,
|
||||
},
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func CompressAndArchive(a Archiver, source string, dest string) error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chdir(source)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Chdir(cwd)
|
||||
|
||||
archivePath := filepath.Join(cwd, fmt.Sprintf("%s.%s", dest, a.String()))
|
||||
if err := a.Archive([]string{"."}, archivePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/rancherfederal/hauler/pkg/apis/haul"
|
||||
"github.com/rancherfederal/hauler/pkg/archive"
|
||||
"os"
|
||||
)
|
||||
|
||||
type bootstrapper struct {
|
||||
haul haul.Haul
|
||||
}
|
||||
|
||||
func NewBootstrapper(haulPath string) *bootstrapper {
|
||||
tmpdir, err := os.MkdirTemp("", "hauler")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer os.Remove(tmpdir)
|
||||
|
||||
a := archive.NewArchiver()
|
||||
err = a.Unarchive(haulPath, tmpdir)
|
||||
|
||||
var h haul.Haul
|
||||
|
||||
return &bootstrapper{
|
||||
haul: h,
|
||||
}
|
||||
}
|
||||
|
||||
func (b bootstrapper) Bootstrap(ctx context.Context) error {
|
||||
a := archive.NewArchiver()
|
||||
_ = a
|
||||
|
||||
tmpdir, err := os.MkdirTemp("", "hauler")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpdir)
|
||||
|
||||
err = a.Unarchive(".", tmpdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+23
-7
@@ -2,7 +2,6 @@ package bundle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
@@ -77,13 +76,30 @@ func (l *LayoutStore) Add(ctx context.Context, imageName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LayoutStore) Push(ctx context.Context, imageName string) error {
|
||||
ii, err := l.layout.ImageIndex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (l *LayoutStore) Find(ctx context.Context, imageName string) error {
|
||||
//ii, err := l.layout.ImageIndex()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//
|
||||
//im, err := ii.IndexManifest()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//
|
||||
//for _, m := range im.Manifests {
|
||||
// if ref, ok := m.Annotations[refNameAnnotation]; ok {
|
||||
// r, err := l.layout.Image(m.Digest)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if r == imageName {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
fmt.Println(ii)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LayoutStore) appendImage(img v1.Image, ref name.Reference, options ...layout.Option) error {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rancherfederal/hauler/pkg/apis/driver"
|
||||
"github.com/rancherfederal/hauler/pkg/apis/haul"
|
||||
"github.com/rancherfederal/hauler/pkg/archive"
|
||||
"github.com/rancherfederal/hauler/pkg/fetcher"
|
||||
"github.com/rancherfederal/hauler/pkg/log"
|
||||
"github.com/rs/zerolog"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Creator struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func NewCreator(logger zerolog.Logger) (*Creator, error) {
|
||||
return &Creator{
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c Creator) Create(ctx context.Context, h haul.Haul) error {
|
||||
tmpdir, err := os.MkdirTemp("", "hauler")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpdir)
|
||||
|
||||
if err := c.buildHaulLayout(h.Spec.Driver, tmpdir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.stamp(h.Spec.Driver, tmpdir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = saveDriverExecutable(ctx, h.Spec.Driver, tmpdir, &c.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
archivePath := filepath.Join(tmpdir, h.Spec.Driver.PreloadImagesPath(), fmt.Sprintf("%s.tar", h.Metadata.Name))
|
||||
err = SavePreloadImages(ctx, h, archivePath, &c.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := archive.NewArchiver()
|
||||
err = archive.CompressAndArchive(a, tmpdir, h.Metadata.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Creator) stamp(d driver.Driver, dir string) error {
|
||||
data, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
haulerCfgFile := filepath.Join(dir, "hauler.json")
|
||||
err = os.WriteFile(haulerCfgFile, data, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Creator) buildHaulLayout(d driver.Driver, dir string) error {
|
||||
c.logger.Info().Msgf("building package layout in %s", dir)
|
||||
|
||||
preloadImagesPath := filepath.Join(dir, d.PreloadImagesPath())
|
||||
c.logger.Debug().Msgf("Creating directory for preloaded images: %s", preloadImagesPath)
|
||||
if err := os.MkdirAll(preloadImagesPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
autodeployManifestsPath := filepath.Join(dir, d.AutodeployManifestsPath())
|
||||
c.logger.Debug().Msgf("Creating directory for autodeployed resources: %s", autodeployManifestsPath)
|
||||
if err := os.MkdirAll(autodeployManifestsPath, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
anonymousStaticPath := filepath.Join(dir, d.AnonymousStaticPath())
|
||||
c.logger.Debug().Msgf("Creating directory for content to host anonymously: %s", anonymousStaticPath)
|
||||
if err := os.MkdirAll(anonymousStaticPath, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
driverExecutablePath := filepath.Join(dir, driver.ExecutableBin)
|
||||
c.logger.Debug().Msgf("Creating directory for driver executable: %s", driverExecutablePath)
|
||||
if err := os.MkdirAll(driverExecutablePath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveDriverExecutable(ctx context.Context, d driver.Driver, dir string, logger log.Logger) error {
|
||||
logger.Info().Msgf("Fetching %s executable", d.Name())
|
||||
rawUrl := fmt.Sprintf("%s/%s", d.ReleaseArtifactsURL(), driver.K3sExecutable)
|
||||
|
||||
f := fetcher.FileFetcher{}
|
||||
dst := filepath.Join(dir, driver.ExecutableBin, driver.K3sExecutable)
|
||||
|
||||
err := f.Get(ctx, rawUrl, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/rancherfederal/hauler/pkg/apis/haul"
|
||||
"github.com/rancherfederal/hauler/pkg/log"
|
||||
)
|
||||
|
||||
func SavePreloadImages(ctx context.Context, h haul.Haul, archivePath string, logger log.Logger) error {
|
||||
imageRefs := make(map[name.Reference]v1.Image)
|
||||
images := listImages(h)
|
||||
|
||||
for _, image := range images {
|
||||
ref, err := name.ParseReference(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info().Msgf("identified %s", ref.Name())
|
||||
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageRefs[ref] = img
|
||||
}
|
||||
|
||||
logger.Info().Msgf("saving %d images to preload as %s", len(images), archivePath)
|
||||
if err := tarball.MultiRefWriteToFile(archivePath, imageRefs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listImages(h haul.Haul) []string {
|
||||
images := h.Spec.Driver.Images()
|
||||
images = append(images, h.Spec.PreloadImages...)
|
||||
return images
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (d *Deployer) explode(pkg string) error {
|
||||
// config will create a driver config if not exists, and merge if one exists
|
||||
func (d *Deployer) config() error {
|
||||
cfgFilePath := filepath.Join(v1alpha1.DriverEtcPath, d.Cluster.Driver.String(), "config.yaml")
|
||||
err := os.MkdirAll(filepath.Dir(cfgFilePath), 0666)
|
||||
err := os.MkdirAll(filepath.Dir(cfgFilePath), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,6 +125,7 @@ func (d *Deployer) config() error {
|
||||
func (d *Deployer) start() error {
|
||||
cmd := exec.Command("/bin/sh", "/opt/hauler/bin/k3s-init.sh")
|
||||
cmd.Env = append(os.Environ(), []string{
|
||||
"INSTALL_K3S_EXEC=--write-kubeconfig-mode '0644'",
|
||||
"INSTALL_K3S_SKIP_DOWNLOAD=true",
|
||||
"INSTALL_K3S_SELINUX_WARN=true",
|
||||
"INSTALL_K3S_SKIP_SELINUX_RPM=true",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package kube
|
||||
|
||||
import (
|
||||
"github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"path/filepath"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func NewKubeClient() (client.Client, error) {
|
||||
cfg, err := NewKubeClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme := apiruntime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
kubeClient, err := client.New(cfg, client.Options{
|
||||
Scheme: scheme,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kubeClient, nil
|
||||
}
|
||||
|
||||
func NewKubeClientConfig() (*rest.Config, error) {
|
||||
loadingRules := loadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
cfg, err := kubeConfig.ClientConfig()
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func loadingRules() *clientcmd.ClientConfigLoadingRules {
|
||||
return &clientcmd.ClientConfigLoadingRules{
|
||||
Precedence: []string{
|
||||
filepath.Join(v1alpha1.DriverEtcPath, v1alpha1.DriverK3S, "k3s.yaml"),
|
||||
filepath.Join(v1alpha1.DriverEtcPath, v1alpha1.DriverRKE2, "rke2.yaml"),
|
||||
},
|
||||
WarnIfAllMissing: true,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package kube
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StatusChecker struct {
|
||||
poller *polling.StatusPoller
|
||||
client client.Client
|
||||
logger *logrus.Entry
|
||||
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewStatusChecker(kubeConfig *rest.Config, interval time.Duration, timeout time.Duration) (*StatusChecker, error) {
|
||||
restMapper, err := apiutil.NewDynamicRESTMapper(kubeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := client.New(kubeConfig, client.Options{Mapper: restMapper})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StatusChecker{
|
||||
poller: polling.NewStatusPoller(c, restMapper),
|
||||
client: c,
|
||||
logger: logrus.WithFields(logrus.Fields{}),
|
||||
interval: interval,
|
||||
timeout: timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sc *StatusChecker) WaitForCondition(objs ...object.ObjMetadata) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sc.timeout)
|
||||
defer cancel()
|
||||
|
||||
eventsChan := sc.poller.Poll(ctx, objs, polling.Options{
|
||||
PollInterval: sc.interval,
|
||||
UseCache: true,
|
||||
})
|
||||
coll := collector.NewResourceStatusCollector(objs)
|
||||
|
||||
done := coll.ListenWithObserver(eventsChan, desiredStatusNotifierFunc(cancel, status.CurrentStatus))
|
||||
<-done
|
||||
|
||||
for _, rs := range coll.ResourceStatuses {
|
||||
switch rs.Status {
|
||||
case status.CurrentStatus:
|
||||
fmt.Printf("%s: %s ready\n", rs.Identifier.Name, strings.ToLower(rs.Identifier.GroupKind.Kind))
|
||||
case status.NotFoundStatus:
|
||||
fmt.Println(fmt.Errorf("%s: %s not found", rs.Identifier.Name, strings.ToLower(rs.Identifier.GroupKind.Kind)))
|
||||
default:
|
||||
fmt.Println(fmt.Errorf("%s: %s not ready", rs.Identifier.Name, strings.ToLower(rs.Identifier.GroupKind.Kind)))
|
||||
}
|
||||
}
|
||||
|
||||
if coll.Error != nil || ctx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Errorf("timed out waiting for condition")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// desiredStatusNotifierFunc returns an Observer function for the
|
||||
// ResourceStatusCollector that will cancel the context (using the cancelFunc)
|
||||
// when all resources have reached the desired status.
|
||||
func desiredStatusNotifierFunc(cancelFunc context.CancelFunc, desired status.Status) collector.ObserverFunc {
|
||||
return func(rsc *collector.ResourceStatusCollector, _ event.Event) {
|
||||
var rss []*event.ResourceStatus
|
||||
for _, rs := range rsc.ResourceStatuses {
|
||||
rss = append(rss, rs)
|
||||
}
|
||||
aggStatus := aggregator.AggregateStatus(rss, desired)
|
||||
if aggStatus == desired {
|
||||
cancelFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Info() *zerolog.Event
|
||||
Debug() *zerolog.Event
|
||||
Error() *zerolog.Event
|
||||
}
|
||||
|
||||
func NewPrettyLogger() zerolog.Logger {
|
||||
output := zerolog.ConsoleWriter{
|
||||
Out: os.Stdout,
|
||||
}
|
||||
|
||||
logger := zerolog.New(output).With().Timestamp().Logger()
|
||||
return logger
|
||||
}
|
||||
@@ -140,7 +140,6 @@ func (p *Packager) pkgPreloadImages(ctx context.Context, dir string, images []st
|
||||
if err := tarball.MultiRefWriteToFile(dir, imageRefs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user