From e374314cae25c6bf8d06107e5ab92bd6703b8a8e Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Tue, 15 Jun 2021 15:13:18 -0600 Subject: [PATCH 01/11] refactor Driver interface --- README.md | 58 +- cmd/haulerctl/app/bootstrap.go | 37 +- cmd/haulerctl/app/create.go | 15 +- cmd/haulerctl/app/create_test.go | 2 +- cmd/haulerctl/app/pkg.go | 26 + cmd/haulerctl/app/pkg_boot.go | 34 + cmd/haulerctl/app/pkg_create.go | 106 +++ cmd/haulerctl/app/root.go | 4 +- go.mod | 3 +- go.sum | 5 +- pkg/apis/hauler.cattle.io/v1alpha1/driver.go | 147 ++-- pkg/bootstrap/booter.go | 106 +-- pkg/bootstrap/kube.go | 12 +- pkg/driver/driver.go | 49 ++ pkg/driver/embed/k3s-init.sh | 872 +++++++++++++++++++ pkg/driver/embed/rke2-init.sh | 507 +++++++++++ pkg/driver/k3s.go | 173 ++++ pkg/packager/images/images.go | 28 +- pkg/packager/packager.go | 93 +- pkg/packager/packager_test.go | 36 + 20 files changed, 2006 insertions(+), 307 deletions(-) create mode 100644 cmd/haulerctl/app/pkg.go create mode 100644 cmd/haulerctl/app/pkg_boot.go create mode 100644 cmd/haulerctl/app/pkg_create.go create mode 100644 pkg/driver/driver.go create mode 100644 pkg/driver/embed/k3s-init.sh create mode 100644 pkg/driver/embed/rke2-init.sh create mode 100644 pkg/driver/k3s.go create mode 100644 pkg/packager/packager_test.go diff --git a/README.md b/README.md index fa05295..fd842fc 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,4 @@ # Hauler - Kubernetes Air Gap Migration -```bash -# _ _ -# | |__ __ _ _ _| | ___ _ __ -# | '_ \ / _` | | | | |/ _ \ '__| -# | | | | (_| | |_| | | __/ | -# |_| |_|\__,_|\__,_|_|\___|_| -# -# , , _______________________________ -# ,-----------|'------'| | | -# /. '-' |-' |_____________________________| -# |/| | | -# | .________.'----' _______________________________ -# | || | || | | -# \__|' \__|' |_____________________________| -# -# __________________________________________________________ -# | | -# |________________________________________________________| -# -# __________________________________________________________ -# | | -# |________________________________________________________| - -``` ## WARNING - Work In Progress @@ -37,15 +13,41 @@ Kubernetes-focused software usually relies on executables, archives, container i Hauler aims to fill this gap by standardizing low-level components of this stack and automating the collection and transfer of artifacts. +## Usage + +Package a self contained deployable cluster + +```bash +# CLI +# bare k3s cluster +hauler create + +# k3s cluster with autodeployed manifests on boot +hauler create -p path/to/rawmanifests -p path/to/kustomizebase -p path/to/helmchart -i image + +# Config File +hauler create [-c ./package.yaml] +``` + +Bootstrap a cluster from a packaged archive + +```bash +hauler boot package.tar.zst +``` + +Relocate a set of images + +```bash +hauler save -p path/to/manifests -i image:tag -i image@sha256:... + +hauler relocate bundle.tar.zst airgap-registry:5000 +``` + ## Additional Details - [Roadmap](./ROADMAP.md) - [Vagrant](./VAGRANT.md) -## Go CLI - -The initial MVP for a hauler CLI used to streamline the packaging and deploying processes is in the `cmd/` and `pkg/` folders, along with `go.mod` and `go.sum`. Currently only a `package` subcommand is supported, which generates a `.tar.gz` archive used in the future `deploy` subcommand. - ### Build To build hauler, the Go CLI v1.14 or higher is required. See for downloads and see for installation instructions. diff --git a/cmd/haulerctl/app/bootstrap.go b/cmd/haulerctl/app/bootstrap.go index ca0ed79..9bc95dc 100644 --- a/cmd/haulerctl/app/bootstrap.go +++ b/cmd/haulerctl/app/bootstrap.go @@ -2,14 +2,11 @@ package app import ( "context" - "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" "github.com/rancherfederal/hauler/pkg/bootstrap" + "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/packager" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "os" - "path/filepath" - "sigs.k8s.io/yaml" ) type deployOpts struct { @@ -62,44 +59,28 @@ func (o *deployOpts) Run(packagePath string) error { return err } - bundleData, err := os.ReadFile(filepath.Join(tmpdir, "package.json")) - if err != nil { - return err - } - - var p v1alpha1.Package - if err := yaml.Unmarshal(bundleData, &p); err != nil { - return err - } - - d := v1alpha1.NewDriver(p.Spec.Driver.Kind) - - bootLogger := o.logger.WithFields(logrus.Fields{ - "driver": p.Spec.Driver.Kind, - }) - b, err := bootstrap.NewBooter(tmpdir) if err != nil { return err } - o.logger.Infof("Initializing package for driver: %s", p.Spec.Driver.Kind) - if err := b.Init(); err != nil { + d := driver.NewDriver(b.Package.Spec.Driver) + if err != nil { return err } - o.logger.Infof("Performing pre %s boot steps", p.Spec.Driver.Kind) - if err := b.PreBoot(ctx, d, bootLogger); err != nil { + o.logger.Infof("Performing pre %s boot steps", b.Package.Spec.Driver.Type) + if err := b.PreBoot(ctx, d, o.logger); err != nil { return err } - o.logger.Infof("Booting %s", p.Spec.Driver.Kind) - if err := b.Boot(ctx, d, bootLogger); err != nil { + o.logger.Infof("Booting %s", b.Package.Spec.Driver.Type) + if err := b.Boot(ctx, d, o.logger); err != nil { return err } - o.logger.Infof("Performing post %s boot steps", p.Spec.Driver.Kind) - if err := b.PostBoot(ctx, d, bootLogger); err != nil { + o.logger.Infof("Performing post %s boot steps", b.Package.Spec.Driver.Type) + if err := b.PostBoot(ctx, d, o.logger); err != nil { return err } diff --git a/cmd/haulerctl/app/create.go b/cmd/haulerctl/app/create.go index cd7736a..5cb0f09 100644 --- a/cmd/haulerctl/app/create.go +++ b/cmd/haulerctl/app/create.go @@ -3,6 +3,7 @@ package app import ( "context" "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/packager" "github.com/spf13/cobra" "os" @@ -41,7 +42,7 @@ Container images, git repositories, and more, packaged and ready to be served wi f := cmd.Flags() f.StringVarP(&opts.driver, "driver", "d", "k3s", - "Driver type to use for package (k3s or rke2)") + "IDriver type to use for package (k3s or rke2)") f.StringVarP(&opts.outputFile, "output", "o", "haul.tar.zst", "package output location relative to the current directory (haul.tar.zst)") f.StringVarP(&opts.configFile, "config", "c", "./package.yaml", @@ -82,21 +83,21 @@ func (o *createOpts) Run() error { } defer os.RemoveAll(tmpdir) - pkgr := packager.NewPackager(tmpdir) + pkgr := packager.NewPackager(tmpdir, o.logger) - o.logger.Infof("Packaging driver (%s %s) artifacts...", p.Spec.Driver.Version, p.Spec.Driver.Kind) - d := v1alpha1.NewDriver(p.Spec.Driver.Kind) - if err = pkgr.Driver(ctx, d); err != nil { + o.logger.Infof("Packaging driver (%s %s) artifacts...", p.Spec.Driver.Version, p.Spec.Driver.Type) + d := driver.NewDriver(p.Spec.Driver) + if err = pkgr.PackageDriver(ctx, d); err != nil { return err } o.logger.Infof("Packaging fleet artifacts...") - if err = pkgr.Fleet(ctx, p.Spec.Fleet); err != nil { + if err = pkgr.PackageFleet(ctx, p.Spec.Fleet); err != nil { return err } o.logger.Infof("Packaging images and manifests defined in specified paths...") - if _, err = pkgr.Bundles(ctx, p.Spec.Paths...); err != nil { + if _, err = pkgr.PackageBundles(ctx, p.Spec.Paths...); err != nil { return err } diff --git a/cmd/haulerctl/app/create_test.go b/cmd/haulerctl/app/create_test.go index f851532..7be7c69 100644 --- a/cmd/haulerctl/app/create_test.go +++ b/cmd/haulerctl/app/create_test.go @@ -24,7 +24,7 @@ func Test_createOpts_Run(t *testing.T) { Spec: v1alpha1.PackageSpec{ Fleet: v1alpha1.Fleet{Version: "0.3.5"}, Driver: v1alpha1.Driver{ - Kind: "k3s", + Type: "k3s", Version: "v1.21.1+k3s1", }, Paths: []string{ diff --git a/cmd/haulerctl/app/pkg.go b/cmd/haulerctl/app/pkg.go new file mode 100644 index 0000000..daf4487 --- /dev/null +++ b/cmd/haulerctl/app/pkg.go @@ -0,0 +1,26 @@ +package app + +import "github.com/spf13/cobra" + +type pkgOpts struct{} + +func NewPkgCommand() *cobra.Command { + opts := &pkgOpts{} + //TODO + _ = opts + + cmd := &cobra.Command{ + Use: "pkg", + Short: "", + Long: "", + Aliases: []string{"p", "package"}, + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + cmd.AddCommand(NewPkgCreateCommand()) + cmd.AddCommand(NewPkgBootCommand()) + + return cmd +} diff --git a/cmd/haulerctl/app/pkg_boot.go b/cmd/haulerctl/app/pkg_boot.go new file mode 100644 index 0000000..d5c00b6 --- /dev/null +++ b/cmd/haulerctl/app/pkg_boot.go @@ -0,0 +1,34 @@ +package app + +import "github.com/spf13/cobra" + +type pkgBootOpts struct { + cfgFile string +} + +func NewPkgBootCommand() *cobra.Command { + opts := pkgBootOpts{} + + cmd := &cobra.Command{ + Use: "boot", + Short: "", + Long: "", + Aliases: []string{"b", "bootstrap"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + return opts.PreRun() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return opts.Run() + }, + } + + return cmd +} + +func (o *pkgBootOpts) PreRun() error { + return nil +} + +func (o *pkgBootOpts) Run() error { + return nil +} diff --git a/cmd/haulerctl/app/pkg_create.go b/cmd/haulerctl/app/pkg_create.go new file mode 100644 index 0000000..32b972f --- /dev/null +++ b/cmd/haulerctl/app/pkg_create.go @@ -0,0 +1,106 @@ +package app + +import ( + "context" + "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "os" + "sigs.k8s.io/yaml" +) + +type pkgCreateOpts struct { + cfgFile string + + driver string + driverVersion string + + fleetVersion string + + images []string + paths []string +} + +func NewPkgCreateCommand() *cobra.Command { + opts := pkgCreateOpts{} + + cmd := &cobra.Command{ + Use: "create", + Short: "", + Long: "", + Aliases: []string{"c"}, + PreRunE: func(cmd *cobra.Command, args []string) error { + return opts.PreRun() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return opts.Run() + }, + } + + f := cmd.PersistentFlags() + f.StringVarP(&opts.cfgFile, "config", "c", "./pkg.yaml", + "path to config file") + f.StringVarP(&opts.driver, "driver", "d", "k3s", + "") + f.StringVar(&opts.driverVersion, "driver-version", "v1.21.1+k3s1", + "") + f.StringVar(&opts.fleetVersion, "fleet-version", "v0.3.5", + "") + f.StringSliceVarP(&opts.images, "image", "i", []string{}, + "") + f.StringSliceVarP(&opts.paths, "path", "p", []string{}, + "") + + return cmd +} + +func (o *pkgCreateOpts) PreRun() error { + _, err := os.Stat(o.cfgFile) + if os.IsNotExist(err) { + logrus.Infof("Could not find %s, creating one", o.cfgFile) + p := v1alpha1.Package{ + TypeMeta: metav1.TypeMeta{ + Kind: "", + APIVersion: "", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + }, + Spec: v1alpha1.PackageSpec{ + Fleet: v1alpha1.Fleet{ + Version: o.fleetVersion, + }, + Driver: v1alpha1.Driver{ + Type: o.driver, + Version: o.driverVersion, + }, + Paths: o.paths, + Images: o.images, + }, + } + + data, err := yaml.Marshal(p) + if err != nil { + return err + } + + if err := os.WriteFile(o.cfgFile, data, 0644); err != nil { + return err + } + } + + if err != nil { + return err + } + return nil +} + +func (o *pkgCreateOpts) Run() error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _ = ctx + + return nil +} diff --git a/cmd/haulerctl/app/root.go b/cmd/haulerctl/app/root.go index 3dded55..8add381 100644 --- a/cmd/haulerctl/app/root.go +++ b/cmd/haulerctl/app/root.go @@ -67,9 +67,11 @@ func NewRootCommand() *cobra.Command { cobra.OnInitialize(initConfig) cmd.AddCommand(NewRelocateCommand()) - cmd.AddCommand(NewCreateCommand()) cmd.AddCommand(NewBundleCommand()) cmd.AddCommand(NewCopyCommand()) + + cmd.AddCommand(NewPkgCommand()) + cmd.AddCommand(NewCreateCommand()) cmd.AddCommand(NewBootstrapCommand()) f := cmd.PersistentFlags() diff --git a/go.mod b/go.mod index f94c27b..b599b05 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/imdario/mergo v0.3.12 github.com/klauspost/compress v1.13.0 // indirect github.com/klauspost/pgzip v1.2.5 // indirect - github.com/mattn/go-runewidth v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mholt/archiver/v3 v3.5.0 github.com/mitchellh/go-homedir v1.1.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 @@ -26,7 +26,6 @@ require ( github.com/pelletier/go-toml v1.8.1 // indirect github.com/rancher/fleet v0.3.5 github.com/rancher/fleet/pkg/apis v0.0.0 - github.com/rivo/uniseg v0.2.0 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.1.3 diff --git a/go.sum b/go.sum index dfed8a9..9c92ca1 100644 --- a/go.sum +++ b/go.sum @@ -926,8 +926,8 @@ github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mN github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= @@ -1192,7 +1192,6 @@ github.com/rancher/wrangler v0.7.3-0.20210224225730-5ed69efb6ab9/go.mod h1:9du9G github.com/rancher/wrangler-cli v0.0.0-20200815040857-81c48cf8ab43/go.mod h1:KxpGNhk/oVL6LCfyxESTD1sb8eXRlUxtkbNm06+7dZU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY= diff --git a/pkg/apis/hauler.cattle.io/v1alpha1/driver.go b/pkg/apis/hauler.cattle.io/v1alpha1/driver.go index 5058d14..5794d40 100644 --- a/pkg/apis/hauler.cattle.io/v1alpha1/driver.go +++ b/pkg/apis/hauler.cattle.io/v1alpha1/driver.go @@ -1,7 +1,6 @@ package v1alpha1 import ( - "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/cli-utils/pkg/object" ) @@ -15,82 +14,78 @@ type Drive interface { SystemObjects() (objs []object.ObjMetadata) } +//Driver type Driver struct { - Kind string `json:"kind"` + Type string `json:"type"` Version string `json:"version"` } -type k3s struct { - dataDir string - etcDir string -} - -//TODO: Don't hardcode this -func (k k3s) BinURL() string { - return "https://github.com/k3s-io/k3s/releases/download/v1.21.1%2Bk3s1/k3s" -} - -func (k k3s) Images() ([]string, error) { - //TODO: Replace this with a query to images.txt on release page - return []string{ - "docker.io/rancher/coredns-coredns:1.8.3", - "docker.io/rancher/klipper-helm:v0.5.0-build20210505", - "docker.io/rancher/klipper-lb:v0.2.0", - "docker.io/rancher/library-busybox:1.32.1", - "docker.io/rancher/library-traefik:2.4.8", - "docker.io/rancher/local-path-provisioner:v0.0.19", - "docker.io/rancher/metrics-server:v0.3.6", - "docker.io/rancher/pause:3.1", - }, nil -} - -func (k k3s) Config() (*map[string]interface{}, error) { - // TODO: This should be typed - c := make(map[string]interface{}) - c["write-kubeconfig-mode"] = "0644" - - //TODO: Add uid or something to ensure this works for multi-node setups - c["node-name"] = "hauler" - - return &c, nil -} - -func (k k3s) SystemObjects() (objs []object.ObjMetadata) { - //TODO: Make sure this matches up with specified config disables - for _, dep := range []string{"coredns", "local-path-provisioner", "metrics-server"} { - objMeta, _ := object.CreateObjMetadata("kube-system", dep, schema.GroupKind{Kind: "Deployment", Group: "apps"}) - objs = append(objs, objMeta) - } - return objs -} - -func (k k3s) LibPath() string { return "/var/lib/rancher/k3s" } -func (k k3s) EtcPath() string { return "/etc/rancher/k3s" } - -//TODO: Implement rke2 as a driver -type rke2 struct{} - -func (r rke2) Images() ([]string, error) { return []string{}, nil } -func (r rke2) BinURL() string { return "" } -func (r rke2) LibPath() string { return "" } -func (r rke2) EtcPath() string { return "" } -func (r rke2) Config() (*map[string]interface{}, error) { return nil, nil } -func (r rke2) SystemObjects() (objs []object.ObjMetadata) { return objs } - -//NewDriver will return the appropriate driver given a kind, defaults to k3s -func NewDriver(kind string) Drive { - var d Drive - switch kind { - case "rke2": - //TODO - d = rke2{} - - default: - d = k3s{ - dataDir: "/var/lib/rancher/k3s", - etcDir: "/etc/rancher/k3s", - } - } - - return d -} +////TODO: Don't hardcode this +//func (k k3s) BinURL() string { +// return "https://github.com/k3s-io/k3s/releases/download/v1.21.1%2Bk3s1/k3s" +//} +// +//func (k k3s) PackageImages() ([]string, error) { +// //TODO: Replace this with a query to images.txt on release page +// return []string{ +// "docker.io/rancher/coredns-coredns:1.8.3", +// "docker.io/rancher/klipper-helm:v0.5.0-build20210505", +// "docker.io/rancher/klipper-lb:v0.2.0", +// "docker.io/rancher/library-busybox:1.32.1", +// "docker.io/rancher/library-traefik:2.4.8", +// "docker.io/rancher/local-path-provisioner:v0.0.19", +// "docker.io/rancher/metrics-server:v0.3.6", +// "docker.io/rancher/pause:3.1", +// }, nil +//} +// +//func (k k3s) Config() (*map[string]interface{}, error) { +// // TODO: This should be typed +// c := make(map[string]interface{}) +// c["write-kubeconfig-mode"] = "0644" +// +// //TODO: Add uid or something to ensure this works for multi-node setups +// c["node-name"] = "hauler" +// +// return &c, nil +//} +// +//func (k k3s) SystemObjects() (objs []object.ObjMetadata) { +// //TODO: Make sure this matches up with specified config disables +// for _, dep := range []string{"coredns", "local-path-provisioner", "metrics-server"} { +// objMeta, _ := object.CreateObjMetadata("kube-system", dep, schema.GroupKind{Kind: "Deployment", Group: "apps"}) +// objs = append(objs, objMeta) +// } +// return objs +//} +// +//func (k k3s) LibPath() string { return "/var/lib/rancher/k3s" } +//func (k k3s) EtcPath() string { return "/etc/rancher/k3s" } +// +////TODO: Implement rke2 as a driver +//type rke2 struct{} +// +//func (r rke2) PackageImages() ([]string, error) { return []string{}, nil } +//func (r rke2) BinURL() string { return "" } +//func (r rke2) LibPath() string { return "" } +//func (r rke2) EtcPath() string { return "" } +//func (r rke2) Config() (*map[string]interface{}, error) { return nil, nil } +//func (r rke2) SystemObjects() (objs []object.ObjMetadata) { return objs } +// +////NewDriver will return the appropriate driver given a kind, defaults to k3s +//func NewDriver(kind string) Drive { +// var d Drive +// switch kind { +// case "rke2": +// //TODO +// d = rke2{} +// +// default: +// d = k3s{ +// dataDir: "/var/lib/rancher/k3s", +// etcDir: "/etc/rancher/k3s", +// } +// } +// +// return d +//} diff --git a/pkg/bootstrap/booter.go b/pkg/bootstrap/booter.go index b4a7aca..37aafde 100644 --- a/pkg/bootstrap/booter.go +++ b/pkg/bootstrap/booter.go @@ -5,9 +5,9 @@ import ( "context" "fmt" "github.com/google/go-containerregistry/pkg/v1/tarball" - "github.com/imdario/mergo" "github.com/otiai10/copy" "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/fs" "github.com/rancherfederal/hauler/pkg/log" "github.com/sirupsen/logrus" @@ -15,16 +15,14 @@ import ( "io" "k8s.io/cli-runtime/pkg/genericclioptions" "os" - "os/exec" "path/filepath" - "sigs.k8s.io/yaml" ) type Booter interface { Init() error PreBoot(context.Context) error - Boot(context.Context, v1alpha1.Drive) error - PostBoot(context.Context, v1alpha1.Drive) error + Boot(context.Context, driver.Driver) error + PostBoot(context.Context, driver.Driver) error } type booter struct { @@ -47,68 +45,49 @@ func NewBooter(pkgPath string) (*booter, error) { }, nil } -func (b booter) Init() error { - d := v1alpha1.NewDriver(b.Package.Spec.Driver.Kind) +func (b booter) PreBoot(ctx context.Context, d driver.Driver, logger log.Logger) error { + l := logger.WithFields(logrus.Fields{ + "phase": "preboot", + }) //TODO: Feel like there's a better way to do this if err := b.moveBin(); err != nil { return err } + if err := b.moveImages(d); err != nil { return err } + if err := b.moveBundles(d); err != nil { return err } + if err := b.moveCharts(d); err != nil { return err } - return nil -} - -func (b booter) PreBoot(ctx context.Context, d v1alpha1.Drive, logger log.Logger) error { - l := logger.WithFields(logrus.Fields{ - "phase": "preboot", - }) - l.Infof("Creating driver configuration") - if err := b.writeConfig(d); err != nil { + if err := d.WriteConfig(); err != nil { return err } return nil } -func (b booter) Boot(ctx context.Context, d v1alpha1.Drive, logger log.Logger) error { +func (b booter) Boot(ctx context.Context, d driver.Driver, logger log.Logger) error { l := logger.WithFields(logrus.Fields{ "phase": "boot", }) - //TODO: Generic - cmd := exec.Command("/bin/sh", "/opt/hauler/bin/k3s-init.sh") - - cmd.Env = append(os.Environ(), []string{ - "INSTALL_K3S_SKIP_DOWNLOAD=true", - "INSTALL_K3S_SELINUX_WARN=true", - "INSTALL_K3S_SKIP_SELINUX_RPM=true", - "INSTALL_K3S_BIN_DIR=/opt/hauler/bin", - - //TODO: Provide a real dryrun option - //"INSTALL_K3S_SKIP_START=true", - }...) - var stdoutBuf, stderrBuf bytes.Buffer - cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) - cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf) + out := io.MultiWriter(os.Stdout, &stdoutBuf, &stderrBuf) - err := cmd.Run() + err := d.Start(out) if err != nil { return err } - l.Infof("Driver successfully started!") - l.Infof("Waiting for driver core components to provision...") waitErr := waitForDriver(ctx, d) if waitErr != nil { @@ -118,13 +97,13 @@ func (b booter) Boot(ctx context.Context, d v1alpha1.Drive, logger log.Logger) e return nil } -func (b booter) PostBoot(ctx context.Context, d v1alpha1.Drive, logger log.Logger) error { +func (b booter) PostBoot(ctx context.Context, d driver.Driver, logger log.Logger) error { l := logger.WithFields(logrus.Fields{ "phase": "postboot", }) cf := genericclioptions.NewConfigFlags(true) - cf.KubeConfig = stringptr(fmt.Sprintf("%s/k3s.yaml", d.EtcPath())) + cf.KubeConfig = stringptr(d.KubeConfigPath()) fleetCrdChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-crd-%s.tgz", b.Package.Spec.Fleet.Version)) fleetCrdChart, err := loader.Load(fleetCrdChartPath) @@ -167,9 +146,9 @@ func (b booter) moveBin() error { return copy.Copy(b.fs.Bin().Path(), path) } -func (b booter) moveImages(d v1alpha1.Drive) error { +func (b booter) moveImages(d driver.Driver) error { //NOTE: archives are not recursively searched, this _must_ be at the images dir - path := filepath.Join(d.LibPath(), "agent/images") + path := d.DataPath("agent/images") if err := os.MkdirAll(path, 0700); err != nil { return err } @@ -182,49 +161,18 @@ func (b booter) moveImages(d v1alpha1.Drive) error { return tarball.MultiRefWriteToFile(filepath.Join(path, "hauler.tar"), refs) } -func (b booter) moveBundles(d v1alpha1.Drive) error { - path := filepath.Join(d.LibPath(), "server/manifests/hauler") - if err := os.MkdirAll(d.LibPath(), 0700); err != nil { - return err - } - - return copy.Copy(b.fs.Bundle().Path(), path) -} - -func (b booter) moveCharts(d v1alpha1.Drive) error { - path := filepath.Join(d.LibPath(), "server/static/charts/hauler") +func (b booter) moveBundles(d driver.Driver) error { + path := d.DataPath("server/manifests/hauler") if err := os.MkdirAll(path, 0700); err != nil { return err } + return copy.Copy(b.fs.Bundle().Path(), path) +} +func (b booter) moveCharts(d driver.Driver) error { + path := d.DataPath("server/static/charts/hauler") + if err := os.MkdirAll(path, 0700); err != nil { + return err + } return copy.Copy(b.fs.Chart().Path(), path) } - -func (b booter) writeConfig(d v1alpha1.Drive) error { - if err := os.MkdirAll(d.EtcPath(), os.ModePerm); err != nil { - return err - } - - c, err := d.Config() - if err != nil { - return err - } - - var uc map[string]interface{} - - path := filepath.Join(d.EtcPath(), "config.yaml") - if data, err := os.ReadFile(path); err != nil { - err := yaml.Unmarshal(data, &uc) - if err != nil { - return err - } - } - - //Merge with user defined configs taking precedence - if err := mergo.Merge(c, uc); err != nil { - return err - } - - data, err := yaml.Marshal(c) - return os.WriteFile(path, data, 0644) -} diff --git a/pkg/bootstrap/kube.go b/pkg/bootstrap/kube.go index 9c07c20..27d3e6c 100644 --- a/pkg/bootstrap/kube.go +++ b/pkg/bootstrap/kube.go @@ -3,7 +3,7 @@ package bootstrap import ( "context" "errors" - "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/kube" log "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/action" @@ -11,18 +11,16 @@ import ( "helm.sh/helm/v3/pkg/release" "k8s.io/cli-runtime/pkg/genericclioptions" "os" - "path/filepath" "time" ) -func waitForDriver(ctx context.Context, d v1alpha1.Drive) error { +func waitForDriver(ctx context.Context, d driver.Driver) error { ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() //TODO: This is a janky way of waiting for file to exist - path := filepath.Join(d.EtcPath(), "k3s.yaml") for { - _, err := os.Stat(path) + _, err := os.Stat(d.KubeConfigPath()) if err == nil { break } @@ -56,10 +54,12 @@ func installChart(cf *genericclioptions.ConfigFlags, chart *chart.Chart, release client := action.NewInstall(actionConfig) client.ReleaseName = releaseName - client.Namespace, cf.Namespace = namespace, stringptr(namespace) // TODO: Not sure why this needs to be set twice client.CreateNamespace = true client.Wait = true + //TODO: Do this better + client.Namespace, cf.Namespace = namespace, stringptr(namespace) + return client.Run(chart, vals) } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go new file mode 100644 index 0000000..86a882f --- /dev/null +++ b/pkg/driver/driver.go @@ -0,0 +1,49 @@ +package driver + +import ( + "context" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "io" + "sigs.k8s.io/cli-utils/pkg/object" +) + +type Driver interface { + Name() string + + //TODO: Really want this to just return a usable client + KubeConfigPath() string + + Images(ctx context.Context) (map[name.Reference]v1.Image, error) + + Binary() (io.ReadCloser, error) + + SystemObjects() []object.ObjMetadata + + Start(io.Writer) error + + DataPath(...string) string + + WriteConfig() error +} + +//NewDriver will return a new concrete Driver type given a kind +func NewDriver(driver v1alpha1.Driver) (d Driver) { + switch driver.Type { + case "rke2": + // TODO + default: + d = K3s{ + Version: driver.Version, + Config: K3sConfig{ + DataDir: "/var/lib/rancher/k3s", + KubeConfig: "/etc/rancher/k3s/k3s.yaml", + KubeConfigMode: "0644", + Disable: nil, + }, + } + } + + return +} diff --git a/pkg/driver/embed/k3s-init.sh b/pkg/driver/embed/k3s-init.sh new file mode 100644 index 0000000..2c33166 --- /dev/null +++ b/pkg/driver/embed/k3s-init.sh @@ -0,0 +1,872 @@ +#!/bin/sh +set -e +set -o noglob + +# Usage: +# curl ... | ENV_VAR=... sh - +# or +# ENV_VAR=... ./install.sh +# +# Example: +# Installing a server without traefik: +# curl ... | INSTALL_K3S_EXEC="--disable=traefik" sh - +# Installing an agent to point at a server: +# curl ... | K3S_TOKEN=xxx K3S_URL=https://server-url:6443 sh - +# +# Environment variables: +# - K3S_* +# Environment variables which begin with K3S_ will be preserved for the +# systemd service to use. Setting K3S_URL without explicitly setting +# a systemd exec command will default the command to "agent", and we +# enforce that K3S_TOKEN or K3S_CLUSTER_SECRET is also set. +# +# - INSTALL_K3S_SKIP_DOWNLOAD +# If set to true will not download k3s hash or binary. +# +# - INSTALL_K3S_FORCE_RESTART +# If set to true will always restart the K3s service +# +# - INSTALL_K3S_SYMLINK +# If set to 'skip' will not create symlinks, 'force' will overwrite, +# default will symlink if command does not exist in path. +# +# - INSTALL_K3S_SKIP_ENABLE +# If set to true will not enable or start k3s service. +# +# - INSTALL_K3S_SKIP_START +# If set to true will not start k3s service. +# +# - INSTALL_K3S_VERSION +# Version of k3s to download from github. Will attempt to download from the +# stable channel if not specified. +# +# - INSTALL_K3S_COMMIT +# Commit of k3s to download from temporary cloud storage. +# * (for developer & QA use) +# +# - INSTALL_K3S_BIN_DIR +# Directory to install k3s binary, links, and uninstall script to, or use +# /usr/local/bin as the default +# +# - INSTALL_K3S_BIN_DIR_READ_ONLY +# If set to true will not write files to INSTALL_K3S_BIN_DIR, forces +# setting INSTALL_K3S_SKIP_DOWNLOAD=true +# +# - INSTALL_K3S_SYSTEMD_DIR +# Directory to install systemd service and environment files to, or use +# /etc/systemd/system as the default +# +# - INSTALL_K3S_EXEC or script arguments +# Command with flags to use for launching k3s in the systemd service, if +# the command is not specified will default to "agent" if K3S_URL is set +# or "server" if not. The final systemd command resolves to a combination +# of EXEC and script args ($@). +# +# The following commands result in the same behavior: +# curl ... | INSTALL_K3S_EXEC="--disable=traefik" sh -s - +# curl ... | INSTALL_K3S_EXEC="server --disable=traefik" sh -s - +# curl ... | INSTALL_K3S_EXEC="server" sh -s - --disable=traefik +# curl ... | sh -s - server --disable=traefik +# curl ... | sh -s - --disable=traefik +# +# - INSTALL_K3S_NAME +# Name of systemd service to create, will default from the k3s exec command +# if not specified. If specified the name will be prefixed with 'k3s-'. +# +# - INSTALL_K3S_TYPE +# Type of systemd service to create, will default from the k3s exec command +# if not specified. +# +# - INSTALL_K3S_SELINUX_WARN +# If set to true will continue if k3s-selinux policy is not found. +# +# - INSTALL_K3S_SKIP_SELINUX_RPM +# If set to true will skip automatic installation of the k3s RPM. +# +# - INSTALL_K3S_CHANNEL_URL +# Channel URL for fetching k3s download URL. +# Defaults to 'https://update.k3s.io/v1-release/channels'. +# +# - INSTALL_K3S_CHANNEL +# Channel to use for fetching k3s download URL. +# Defaults to 'stable'. + +GITHUB_URL=https://github.com/k3s-io/k3s/releases +STORAGE_URL=https://storage.googleapis.com/k3s-ci-builds +DOWNLOADER= + +# --- helper functions for logs --- +info() +{ + echo '[INFO] ' "$@" +} +warn() +{ + echo '[WARN] ' "$@" >&2 +} +fatal() +{ + echo '[ERROR] ' "$@" >&2 + exit 1 +} + +# --- fatal if no systemd or openrc --- +verify_system() { + if [ -x /sbin/openrc-run ]; then + HAS_OPENRC=true + return + fi + if [ -d /run/systemd ]; then + HAS_SYSTEMD=true + return + fi + fatal 'Can not find systemd or openrc to use as a process supervisor for k3s' +} + +# --- add quotes to command arguments --- +quote() { + for arg in "$@"; do + printf '%s\n' "$arg" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" + done +} + +# --- add indentation and trailing slash to quoted args --- +quote_indent() { + printf ' \\\n' + for arg in "$@"; do + printf '\t%s \\\n' "$(quote "$arg")" + done +} + +# --- escape most punctuation characters, except quotes, forward slash, and space --- +escape() { + printf '%s' "$@" | sed -e 's/\([][!#$%&()*;<=>?\_`{|}]\)/\\\1/g;' +} + +# --- escape double quotes --- +escape_dq() { + printf '%s' "$@" | sed -e 's/"/\\"/g' +} + +# --- ensures $K3S_URL is empty or begins with https://, exiting fatally otherwise --- +verify_k3s_url() { + case "${K3S_URL}" in + "") + ;; + https://*) + ;; + *) + fatal "Only https:// URLs are supported for K3S_URL (have ${K3S_URL})" + ;; + esac +} + +# --- define needed environment variables --- +setup_env() { + # --- use command args if passed or create default --- + case "$1" in + # --- if we only have flags discover if command should be server or agent --- + (-*|"") + if [ -z "${K3S_URL}" ]; then + CMD_K3S=server + else + if [ -z "${K3S_TOKEN}" ] && [ -z "${K3S_TOKEN_FILE}" ] && [ -z "${K3S_CLUSTER_SECRET}" ]; then + fatal "Defaulted k3s exec command to 'agent' because K3S_URL is defined, but K3S_TOKEN, K3S_TOKEN_FILE or K3S_CLUSTER_SECRET is not defined." + fi + CMD_K3S=agent + fi + ;; + # --- command is provided --- + (*) + CMD_K3S=$1 + shift + ;; + esac + + verify_k3s_url + + CMD_K3S_EXEC="${CMD_K3S}$(quote_indent "$@")" + + # --- use systemd name if defined or create default --- + if [ -n "${INSTALL_K3S_NAME}" ]; then + SYSTEM_NAME=k3s-${INSTALL_K3S_NAME} + else + if [ "${CMD_K3S}" = server ]; then + SYSTEM_NAME=k3s + else + SYSTEM_NAME=k3s-${CMD_K3S} + fi + fi + + # --- check for invalid characters in system name --- + valid_chars=$(printf '%s' "${SYSTEM_NAME}" | sed -e 's/[][!#$%&()*;<=>?\_`{|}/[:space:]]/^/g;' ) + if [ "${SYSTEM_NAME}" != "${valid_chars}" ]; then + invalid_chars=$(printf '%s' "${valid_chars}" | sed -e 's/[^^]/ /g') + fatal "Invalid characters for system name: + ${SYSTEM_NAME} + ${invalid_chars}" + fi + + # --- use sudo if we are not already root --- + SUDO=sudo + if [ $(id -u) -eq 0 ]; then + SUDO= + fi + + # --- use systemd type if defined or create default --- + if [ -n "${INSTALL_K3S_TYPE}" ]; then + SYSTEMD_TYPE=${INSTALL_K3S_TYPE} + else + if [ "${CMD_K3S}" = server ]; then + SYSTEMD_TYPE=notify + else + SYSTEMD_TYPE=exec + fi + fi + + # --- use binary install directory if defined or create default --- + if [ -n "${INSTALL_K3S_BIN_DIR}" ]; then + BIN_DIR=${INSTALL_K3S_BIN_DIR} + else + # --- use /usr/local/bin if root can write to it, otherwise use /opt/bin if it exists + BIN_DIR=/usr/local/bin + if ! $SUDO sh -c "touch ${BIN_DIR}/k3s-ro-test && rm -rf ${BIN_DIR}/k3s-ro-test"; then + if [ -d /opt/bin ]; then + BIN_DIR=/opt/bin + fi + fi + fi + + # --- use systemd directory if defined or create default --- + if [ -n "${INSTALL_K3S_SYSTEMD_DIR}" ]; then + SYSTEMD_DIR="${INSTALL_K3S_SYSTEMD_DIR}" + else + SYSTEMD_DIR=/etc/systemd/system + fi + + # --- set related files from system name --- + SERVICE_K3S=${SYSTEM_NAME}.service + UNINSTALL_K3S_SH=${UNINSTALL_K3S_SH:-${BIN_DIR}/${SYSTEM_NAME}-uninstall.sh} + KILLALL_K3S_SH=${KILLALL_K3S_SH:-${BIN_DIR}/k3s-killall.sh} + + # --- use service or environment location depending on systemd/openrc --- + if [ "${HAS_SYSTEMD}" = true ]; then + FILE_K3S_SERVICE=${SYSTEMD_DIR}/${SERVICE_K3S} + FILE_K3S_ENV=${SYSTEMD_DIR}/${SERVICE_K3S}.env + elif [ "${HAS_OPENRC}" = true ]; then + $SUDO mkdir -p /etc/rancher/k3s + FILE_K3S_SERVICE=/etc/init.d/${SYSTEM_NAME} + FILE_K3S_ENV=/etc/rancher/k3s/${SYSTEM_NAME}.env + fi + + # --- get hash of config & exec for currently installed k3s --- + PRE_INSTALL_HASHES=$(get_installed_hashes) + + # --- if bin directory is read only skip download --- + if [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ]; then + INSTALL_K3S_SKIP_DOWNLOAD=true + fi + + # --- setup channel values + INSTALL_K3S_CHANNEL_URL=${INSTALL_K3S_CHANNEL_URL:-'https://update.k3s.io/v1-release/channels'} + INSTALL_K3S_CHANNEL=${INSTALL_K3S_CHANNEL:-'stable'} +} + +# --- check if skip download environment variable set --- +can_skip_download() { + if [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != true ]; then + return 1 + fi +} + +# --- verify an executable k3s binary is installed --- +verify_k3s_is_executable() { + if [ ! -x ${BIN_DIR}/k3s ]; then + fatal "Executable k3s binary not found at ${BIN_DIR}/k3s" + fi +} + +# --- set arch and suffix, fatal if architecture not supported --- +setup_verify_arch() { + if [ -z "$ARCH" ]; then + ARCH=$(uname -m) + fi + case $ARCH in + amd64) + ARCH=amd64 + SUFFIX= + ;; + x86_64) + ARCH=amd64 + SUFFIX= + ;; + arm64) + ARCH=arm64 + SUFFIX=-${ARCH} + ;; + aarch64) + ARCH=arm64 + SUFFIX=-${ARCH} + ;; + arm*) + ARCH=arm + SUFFIX=-${ARCH}hf + ;; + *) + fatal "Unsupported architecture $ARCH" + esac +} + +# --- verify existence of network downloader executable --- +verify_downloader() { + # Return failure if it doesn't exist or is no executable + [ -x "$(command -v $1)" ] || return 1 + + # Set verified executable as our downloader program and return success + DOWNLOADER=$1 + return 0 +} + +# --- create temporary directory and cleanup when done --- +setup_tmp() { + TMP_DIR=$(mktemp -d -t k3s-install.XXXXXXXXXX) + TMP_HASH=${TMP_DIR}/k3s.hash + TMP_BIN=${TMP_DIR}/k3s.bin + cleanup() { + code=$? + set +e + trap - EXIT + rm -rf ${TMP_DIR} + exit $code + } + trap cleanup INT EXIT +} + +# --- use desired k3s version if defined or find version from channel --- +get_release_version() { + if [ -n "${INSTALL_K3S_COMMIT}" ]; then + VERSION_K3S="commit ${INSTALL_K3S_COMMIT}" + elif [ -n "${INSTALL_K3S_VERSION}" ]; then + VERSION_K3S=${INSTALL_K3S_VERSION} + else + info "Finding release for channel ${INSTALL_K3S_CHANNEL}" + version_url="${INSTALL_K3S_CHANNEL_URL}/${INSTALL_K3S_CHANNEL}" + case $DOWNLOADER in + curl) + VERSION_K3S=$(curl -w '%{url_effective}' -L -s -S ${version_url} -o /dev/null | sed -e 's|.*/||') + ;; + wget) + VERSION_K3S=$(wget -SqO /dev/null ${version_url} 2>&1 | grep -i Location | sed -e 's|.*/||') + ;; + *) + fatal "Incorrect downloader executable '$DOWNLOADER'" + ;; + esac + fi + info "Using ${VERSION_K3S} as release" +} + +# --- download from github url --- +download() { + [ $# -eq 2 ] || fatal 'download needs exactly 2 arguments' + + case $DOWNLOADER in + curl) + curl -o $1 -sfL $2 + ;; + wget) + wget -qO $1 $2 + ;; + *) + fatal "Incorrect executable '$DOWNLOADER'" + ;; + esac + + # Abort if download command failed + [ $? -eq 0 ] || fatal 'Download failed' +} + +# --- download hash from github url --- +download_hash() { + if [ -n "${INSTALL_K3S_COMMIT}" ]; then + HASH_URL=${STORAGE_URL}/k3s${SUFFIX}-${INSTALL_K3S_COMMIT}.sha256sum + else + HASH_URL=${GITHUB_URL}/download/${VERSION_K3S}/sha256sum-${ARCH}.txt + fi + info "Downloading hash ${HASH_URL}" + download ${TMP_HASH} ${HASH_URL} + HASH_EXPECTED=$(grep " k3s${SUFFIX}$" ${TMP_HASH}) + HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*} +} + +# --- check hash against installed version --- +installed_hash_matches() { + if [ -x ${BIN_DIR}/k3s ]; then + HASH_INSTALLED=$(sha256sum ${BIN_DIR}/k3s) + HASH_INSTALLED=${HASH_INSTALLED%%[[:blank:]]*} + if [ "${HASH_EXPECTED}" = "${HASH_INSTALLED}" ]; then + return + fi + fi + return 1 +} + +# --- download binary from github url --- +download_binary() { + if [ -n "${INSTALL_K3S_COMMIT}" ]; then + BIN_URL=${STORAGE_URL}/k3s${SUFFIX}-${INSTALL_K3S_COMMIT} + else + BIN_URL=${GITHUB_URL}/download/${VERSION_K3S}/k3s${SUFFIX} + fi + info "Downloading binary ${BIN_URL}" + download ${TMP_BIN} ${BIN_URL} +} + +# --- verify downloaded binary hash --- +verify_binary() { + info "Verifying binary download" + HASH_BIN=$(sha256sum ${TMP_BIN}) + HASH_BIN=${HASH_BIN%%[[:blank:]]*} + if [ "${HASH_EXPECTED}" != "${HASH_BIN}" ]; then + fatal "Download sha256 does not match ${HASH_EXPECTED}, got ${HASH_BIN}" + fi +} + +# --- setup permissions and move binary to system directory --- +setup_binary() { + chmod 755 ${TMP_BIN} + info "Installing k3s to ${BIN_DIR}/k3s" + $SUDO chown root:root ${TMP_BIN} + $SUDO mv -f ${TMP_BIN} ${BIN_DIR}/k3s +} + +# --- setup selinux policy --- +setup_selinux() { + case ${INSTALL_K3S_CHANNEL} in + *testing) + rpm_channel=testing + ;; + *latest) + rpm_channel=latest + ;; + *) + rpm_channel=stable + ;; + esac + + rpm_site="rpm.rancher.io" + if [ "${rpm_channel}" = "testing" ]; then + rpm_site="rpm-testing.rancher.io" + fi + + policy_hint="please install: + yum install -y container-selinux selinux-policy-base + yum install -y https://${rpm_site}/k3s/${rpm_channel}/common/centos/7/noarch/k3s-selinux-0.2-1.el7_8.noarch.rpm +" + policy_error=fatal + if [ "$INSTALL_K3S_SELINUX_WARN" = true ] || grep -q 'ID=flatcar' /etc/os-release; then + policy_error=warn + fi + + if [ "$INSTALL_K3S_SKIP_SELINUX_RPM" = true ] || can_skip_download; then + info "Skipping installation of SELinux RPM" + else + install_selinux_rpm ${rpm_site} ${rpm_channel} + fi + + if ! $SUDO chcon -u system_u -r object_r -t container_runtime_exec_t ${BIN_DIR}/k3s >/dev/null 2>&1; then + if $SUDO grep '^\s*SELINUX=enforcing' /etc/selinux/config >/dev/null 2>&1; then + $policy_error "Failed to apply container_runtime_exec_t to ${BIN_DIR}/k3s, ${policy_hint}" + fi + else + if [ ! -f /usr/share/selinux/packages/k3s.pp ]; then + $policy_error "Failed to find the k3s-selinux policy, ${policy_hint}" + fi + fi +} + +# --- if on an el7/el8 system, install k3s-selinux +install_selinux_rpm() { + if [ -r /etc/redhat-release ] || [ -r /etc/centos-release ] || [ -r /etc/oracle-release ]; then + dist_version="$(. /etc/os-release && echo "$VERSION_ID")" + maj_ver=$(echo "$dist_version" | sed -E -e "s/^([0-9]+)\.?[0-9]*$/\1/") + set +o noglob + $SUDO rm -f /etc/yum.repos.d/rancher-k3s-common*.repo + set -o noglob + if [ -r /etc/redhat-release ]; then + case ${maj_ver} in + 7) + $SUDO yum -y install yum-utils + $SUDO yum-config-manager --enable rhel-7-server-extras-rpms + ;; + 8) + : + ;; + *) + return + ;; + esac + fi + $SUDO tee /etc/yum.repos.d/rancher-k3s-common.repo >/dev/null << EOF +[rancher-k3s-common-${2}] +name=Rancher K3s Common (${2}) +baseurl=https://${1}/k3s/${2}/common/centos/${maj_ver}/noarch +enabled=1 +gpgcheck=1 +gpgkey=https://${1}/public.key +EOF + $SUDO yum -y install "k3s-selinux" + fi + return +} + +# --- download and verify k3s --- +download_and_verify() { + if can_skip_download; then + info 'Skipping k3s download and verify' + verify_k3s_is_executable + return + fi + + setup_verify_arch + verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files' + setup_tmp + get_release_version + download_hash + + if installed_hash_matches; then + info 'Skipping binary downloaded, installed k3s matches hash' + return + fi + + download_binary + verify_binary + setup_binary +} + +# --- add additional utility links --- +create_symlinks() { + [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return + [ "${INSTALL_K3S_SYMLINK}" = skip ] && return + + for cmd in kubectl crictl ctr; do + if [ ! -e ${BIN_DIR}/${cmd} ] || [ "${INSTALL_K3S_SYMLINK}" = force ]; then + which_cmd=$(command -v ${cmd} 2>/dev/null || true) + if [ -z "${which_cmd}" ] || [ "${INSTALL_K3S_SYMLINK}" = force ]; then + info "Creating ${BIN_DIR}/${cmd} symlink to k3s" + $SUDO ln -sf k3s ${BIN_DIR}/${cmd} + else + info "Skipping ${BIN_DIR}/${cmd} symlink to k3s, command exists in PATH at ${which_cmd}" + fi + else + info "Skipping ${BIN_DIR}/${cmd} symlink to k3s, already exists" + fi + done +} + +# --- create killall script --- +create_killall() { + [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return + info "Creating killall script ${KILLALL_K3S_SH}" + $SUDO tee ${KILLALL_K3S_SH} >/dev/null << \EOF +#!/bin/sh +[ $(id -u) -eq 0 ] || exec sudo $0 $@ + +for bin in /var/lib/rancher/k3s/data/**/bin/; do + [ -d $bin ] && export PATH=$PATH:$bin:$bin/aux +done + +set -x + +for service in /etc/systemd/system/k3s*.service; do + [ -s $service ] && systemctl stop $(basename $service) +done + +for service in /etc/init.d/k3s*; do + [ -x $service ] && $service stop +done + +pschildren() { + ps -e -o ppid= -o pid= | \ + sed -e 's/^\s*//g; s/\s\s*/\t/g;' | \ + grep -w "^$1" | \ + cut -f2 +} + +pstree() { + for pid in $@; do + echo $pid + for child in $(pschildren $pid); do + pstree $child + done + done +} + +killtree() { + kill -9 $( + { set +x; } 2>/dev/null; + pstree $@; + set -x; + ) 2>/dev/null +} + +getshims() { + ps -e -o pid= -o args= | sed -e 's/^ *//; s/\s\s*/\t/;' | grep -w 'k3s/data/[^/]*/bin/containerd-shim' | cut -f1 +} + +killtree $({ set +x; } 2>/dev/null; getshims; set -x) + +do_unmount_and_remove() { + awk -v path="$1" '$2 ~ ("^" path) { print $2 }' /proc/self/mounts | sort -r | xargs -r -t -n 1 sh -c 'umount "$0" && rm -rf "$0"' +} + +do_unmount_and_remove '/run/k3s' +do_unmount_and_remove '/var/lib/rancher/k3s' +do_unmount_and_remove '/var/lib/kubelet/pods' +do_unmount_and_remove '/var/lib/kubelet/plugins' +do_unmount_and_remove '/run/netns/cni-' + +# Remove CNI namespaces +ip netns show 2>/dev/null | grep cni- | xargs -r -t -n 1 ip netns delete + +# Delete network interface(s) that match 'master cni0' +ip link show 2>/dev/null | grep 'master cni0' | while read ignore iface ignore; do + iface=${iface%%@*} + [ -z "$iface" ] || ip link delete $iface +done +ip link delete cni0 +ip link delete flannel.1 +rm -rf /var/lib/cni/ +iptables-save | grep -v KUBE- | grep -v CNI- | iptables-restore +EOF + $SUDO chmod 755 ${KILLALL_K3S_SH} + $SUDO chown root:root ${KILLALL_K3S_SH} +} + +# --- create uninstall script --- +create_uninstall() { + [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return + info "Creating uninstall script ${UNINSTALL_K3S_SH}" + $SUDO tee ${UNINSTALL_K3S_SH} >/dev/null << EOF +#!/bin/sh +set -x +[ \$(id -u) -eq 0 ] || exec sudo \$0 \$@ + +${KILLALL_K3S_SH} + +if command -v systemctl; then + systemctl disable ${SYSTEM_NAME} + systemctl reset-failed ${SYSTEM_NAME} + systemctl daemon-reload +fi +if command -v rc-update; then + rc-update delete ${SYSTEM_NAME} default +fi + +rm -f ${FILE_K3S_SERVICE} +rm -f ${FILE_K3S_ENV} + +remove_uninstall() { + rm -f ${UNINSTALL_K3S_SH} +} +trap remove_uninstall EXIT + +if (ls ${SYSTEMD_DIR}/k3s*.service || ls /etc/init.d/k3s*) >/dev/null 2>&1; then + set +x; echo 'Additional k3s services installed, skipping uninstall of k3s'; set -x + exit +fi + +for cmd in kubectl crictl ctr; do + if [ -L ${BIN_DIR}/\$cmd ]; then + rm -f ${BIN_DIR}/\$cmd + fi +done + +rm -rf /etc/rancher/k3s +rm -rf /run/k3s +rm -rf /run/flannel +rm -rf /var/lib/rancher/k3s +rm -rf /var/lib/kubelet +rm -f ${BIN_DIR}/k3s +rm -f ${KILLALL_K3S_SH} + +if type yum >/dev/null 2>&1; then + yum remove -y k3s-selinux + rm -f /etc/yum.repos.d/rancher-k3s-common*.repo +fi +EOF + $SUDO chmod 755 ${UNINSTALL_K3S_SH} + $SUDO chown root:root ${UNINSTALL_K3S_SH} +} + +# --- disable current service if loaded -- +systemd_disable() { + $SUDO systemctl disable ${SYSTEM_NAME} >/dev/null 2>&1 || true + $SUDO rm -f /etc/systemd/system/${SERVICE_K3S} || true + $SUDO rm -f /etc/systemd/system/${SERVICE_K3S}.env || true +} + +# --- capture current env and create file containing k3s_ variables --- +create_env_file() { + info "env: Creating environment file ${FILE_K3S_ENV}" + $SUDO touch ${FILE_K3S_ENV} + $SUDO chmod 0600 ${FILE_K3S_ENV} + env | grep '^K3S_' | $SUDO tee ${FILE_K3S_ENV} >/dev/null + env | grep -Ei '^(NO|HTTP|HTTPS)_PROXY' | $SUDO tee -a ${FILE_K3S_ENV} >/dev/null +} + +# --- write systemd service file --- +create_systemd_service_file() { + info "systemd: Creating service file ${FILE_K3S_SERVICE}" + $SUDO tee ${FILE_K3S_SERVICE} >/dev/null << EOF +[Unit] +Description=Lightweight Kubernetes +Documentation=https://k3s.io +Wants=network-online.target +After=network-online.target + +[Install] +WantedBy=multi-user.target + +[Service] +Type=${SYSTEMD_TYPE} +EnvironmentFile=-/etc/default/%N +EnvironmentFile=-/etc/sysconfig/%N +EnvironmentFile=-${FILE_K3S_ENV} +KillMode=process +Delegate=yes +# Having non-zero Limit*s causes performance problems due to accounting overhead +# in the kernel. We recommend using cgroups to do container-local accounting. +LimitNOFILE=1048576 +LimitNPROC=infinity +LimitCORE=infinity +TasksMax=infinity +TimeoutStartSec=0 +Restart=always +RestartSec=5s +ExecStartPre=-/sbin/modprobe br_netfilter +ExecStartPre=-/sbin/modprobe overlay +ExecStart=${BIN_DIR}/k3s \\ + ${CMD_K3S_EXEC} + +EOF +} + +# --- write openrc service file --- +create_openrc_service_file() { + LOG_FILE=/var/log/${SYSTEM_NAME}.log + + info "openrc: Creating service file ${FILE_K3S_SERVICE}" + $SUDO tee ${FILE_K3S_SERVICE} >/dev/null << EOF +#!/sbin/openrc-run + +depend() { + after network-online + want cgroups +} + +start_pre() { + rm -f /tmp/k3s.* +} + +supervisor=supervise-daemon +name=${SYSTEM_NAME} +command="${BIN_DIR}/k3s" +command_args="$(escape_dq "${CMD_K3S_EXEC}") + >>${LOG_FILE} 2>&1" + +output_log=${LOG_FILE} +error_log=${LOG_FILE} + +pidfile="/var/run/${SYSTEM_NAME}.pid" +respawn_delay=5 +respawn_max=0 + +set -o allexport +if [ -f /etc/environment ]; then source /etc/environment; fi +if [ -f ${FILE_K3S_ENV} ]; then source ${FILE_K3S_ENV}; fi +set +o allexport +EOF + $SUDO chmod 0755 ${FILE_K3S_SERVICE} + + $SUDO tee /etc/logrotate.d/${SYSTEM_NAME} >/dev/null << EOF +${LOG_FILE} { + missingok + notifempty + copytruncate +} +EOF +} + +# --- write systemd or openrc service file --- +create_service_file() { + [ "${HAS_SYSTEMD}" = true ] && create_systemd_service_file + [ "${HAS_OPENRC}" = true ] && create_openrc_service_file + return 0 +} + +# --- get hashes of the current k3s bin and service files +get_installed_hashes() { + $SUDO sha256sum ${BIN_DIR}/k3s ${FILE_K3S_SERVICE} ${FILE_K3S_ENV} 2>&1 || true +} + +# --- enable and start systemd service --- +systemd_enable() { + info "systemd: Enabling ${SYSTEM_NAME} unit" + $SUDO systemctl enable ${FILE_K3S_SERVICE} >/dev/null + $SUDO systemctl daemon-reload >/dev/null +} + +systemd_start() { + info "systemd: Starting ${SYSTEM_NAME}" + $SUDO systemctl restart ${SYSTEM_NAME} +} + +# --- enable and start openrc service --- +openrc_enable() { + info "openrc: Enabling ${SYSTEM_NAME} service for default runlevel" + $SUDO rc-update add ${SYSTEM_NAME} default >/dev/null +} + +openrc_start() { + info "openrc: Starting ${SYSTEM_NAME}" + $SUDO ${FILE_K3S_SERVICE} restart +} + +# --- startup systemd or openrc service --- +service_enable_and_start() { + [ "${INSTALL_K3S_SKIP_ENABLE}" = true ] && return + + [ "${HAS_SYSTEMD}" = true ] && systemd_enable + [ "${HAS_OPENRC}" = true ] && openrc_enable + + [ "${INSTALL_K3S_SKIP_START}" = true ] && return + + POST_INSTALL_HASHES=$(get_installed_hashes) + if [ "${PRE_INSTALL_HASHES}" = "${POST_INSTALL_HASHES}" ] && [ "${INSTALL_K3S_FORCE_RESTART}" != true ]; then + info 'No change detected so skipping service start' + return + fi + + [ "${HAS_SYSTEMD}" = true ] && systemd_start + [ "${HAS_OPENRC}" = true ] && openrc_start + return 0 +} + +# --- re-evaluate args to include env command --- +eval set -- $(escape "${INSTALL_K3S_EXEC}") $(quote "$@") + +# --- run the install process -- +{ + verify_system + setup_env "$@" + download_and_verify + setup_selinux + create_symlinks + create_killall + create_uninstall + systemd_disable + create_env_file + create_service_file + service_enable_and_start +} diff --git a/pkg/driver/embed/rke2-init.sh b/pkg/driver/embed/rke2-init.sh new file mode 100644 index 0000000..d3cb5f2 --- /dev/null +++ b/pkg/driver/embed/rke2-init.sh @@ -0,0 +1,507 @@ +#!/bin/sh + +set -e + +if [ "${DEBUG}" = 1 ]; then + set -x +fi + +# Usage: +# curl ... | ENV_VAR=... sh - +# or +# ENV_VAR=... ./install.sh +# + +# Environment variables: +# +# - INSTALL_RKE2_CHANNEL +# Channel to use for fetching rke2 download URL. +# Defaults to 'latest'. +# +# - INSTALL_RKE2_METHOD +# The installation method to use. +# Default is on RPM-based systems is "rpm", all else "tar". +# +# - INSTALL_RKE2_TYPE +# Type of rke2 service. Can be either "server" or "agent". +# Default is "server". +# +# - INSTALL_RKE2_EXEC +# This is an alias for INSTALL_RKE2_TYPE, included for compatibility with K3s. +# If both are set, INSTALL_RKE2_TYPE is preferred. +# +# - INSTALL_RKE2_VERSION +# Version of rke2 to download from github. +# +# - INSTALL_RKE2_RPM_RELEASE_VERSION +# Version of the rke2 RPM release to install. +# Format would be like "1.el7" or "2.el8" +# +# - INSTALL_RKE2_TAR_PREFIX +# Installation prefix when using the tar installation method. +# Default is /usr/local, unless /usr/local is read-only or has a dedicated mount point, +# in which case /opt/rke2 is used instead. +# +# - INSTALL_RKE2_COMMIT +# Commit of RKE2 to download from temporary cloud storage. +# If set, this forces INSTALL_RKE2_METHOD=tar. +# * (for developer & QA use) +# +# - INSTALL_RKE2_AGENT_IMAGES_DIR +# Installation path for airgap images when installing from CI commit +# Default is /var/lib/rancher/rke2/agent/images +# +# - INSTALL_RKE2_ARTIFACT_PATH +# If set, the install script will use the local path for sourcing the rke2.linux-$SUFFIX and sha256sum-$ARCH.txt files +# rather than the downloading the files from the internet. +# Default is not set. +# + + +# info logs the given argument at info log level. +info() { + echo "[INFO] " "$@" +} + +# warn logs the given argument at warn log level. +warn() { + echo "[WARN] " "$@" >&2 +} + +# fatal logs the given argument at fatal log level. +fatal() { + echo "[ERROR] " "$@" >&2 + if [ -n "${SUFFIX}" ]; then + echo "[ALT] Please visit 'https://github.com/rancher/rke2/releases' directly and download the latest rke2.${SUFFIX}.tar.gz" >&2 + fi + exit 1 +} + +# check_target_mountpoint return success if the target directory is on a dedicated mount point +check_target_mountpoint() { + mountpoint -q "${INSTALL_RKE2_TAR_PREFIX}" +} + +# check_target_ro returns success if the target directory is read-only +check_target_ro() { + touch "${INSTALL_RKE2_TAR_PREFIX}"/.rke2-ro-test && rm -rf "${INSTALL_RKE2_TAR_PREFIX}"/.rke2-ro-test + test $? -ne 0 +} + + +# setup_env defines needed environment variables. +setup_env() { + STORAGE_URL="https://storage.googleapis.com/rke2-ci-builds" + INSTALL_RKE2_GITHUB_URL="https://github.com/rancher/rke2" + DEFAULT_TAR_PREFIX="/usr/local" + # --- bail if we are not root --- + if [ ! $(id -u) -eq 0 ]; then + fatal "You need to be root to perform this install" + fi + + # --- make sure install channel has a value + if [ -z "${INSTALL_RKE2_CHANNEL}" ]; then + INSTALL_RKE2_CHANNEL="stable" + fi + + # --- make sure install type has a value + if [ -z "${INSTALL_RKE2_TYPE}" ]; then + INSTALL_RKE2_TYPE="${INSTALL_RKE2_EXEC:-server}" + fi + + # --- use yum install method if available by default + if [ -z "${INSTALL_RKE2_ARTIFACT_PATH}" ] && [ -z "${INSTALL_RKE2_COMMIT}" ] && [ -z "${INSTALL_RKE2_METHOD}" ] && command -v yum >/dev/null 2>&1; then + INSTALL_RKE2_METHOD="yum" + fi + + # --- install tarball to /usr/local by default, except if /usr/local is on a separate partition or is read-only + # --- in which case we go into /opt/rke2. + if [ -z "${INSTALL_RKE2_TAR_PREFIX}" ]; then + INSTALL_RKE2_TAR_PREFIX=${DEFAULT_TAR_PREFIX} + if check_target_mountpoint || check_target_ro; then + INSTALL_RKE2_TAR_PREFIX="/opt/rke2" + warn "${DEFAULT_TAR_PREFIX} is read-only or a mount point; installing to ${INSTALL_RKE2_TAR_PREFIX}" + fi + fi + + if [ -z "${INSTALL_RKE2_AGENT_IMAGES_DIR}" ]; then + INSTALL_RKE2_AGENT_IMAGES_DIR="/var/lib/rancher/rke2/agent/images" + fi +} + +# check_method_conflict will exit with an error if the user attempts to install +# via tar method on a host with existing RPMs. +check_method_conflict() { + case ${INSTALL_RKE2_METHOD} in + yum | rpm | dnf) + return + ;; + *) + if rpm -q rke2-common >/dev/null 2>&1; then + fatal "Cannot perform ${INSTALL_RKE2_METHOD:-tar} install on host with existing RKE2 RPMs - please run rke2-uninstall.sh first" + fi + ;; + esac +} + +# setup_arch set arch and suffix, +# fatal if architecture not supported. +setup_arch() { + case ${ARCH:=$(uname -m)} in + amd64) + ARCH=amd64 + SUFFIX=$(uname -s | tr '[:upper:]' '[:lower:]')-${ARCH} + ;; + x86_64) + ARCH=amd64 + SUFFIX=$(uname -s | tr '[:upper:]' '[:lower:]')-${ARCH} + ;; + *) + fatal "unsupported architecture ${ARCH}" + ;; + esac +} + +# verify_downloader verifies existence of +# network downloader executable. +verify_downloader() { + cmd="$(command -v "${1}")" + if [ -z "${cmd}" ]; then + return 1 + fi + if [ ! -x "${cmd}" ]; then + return 1 + fi + + # Set verified executable as our downloader program and return success + DOWNLOADER=${cmd} + return 0 +} + +# setup_tmp creates a temporary directory +# and cleans up when done. +setup_tmp() { + TMP_DIR=$(mktemp -d -t rke2-install.XXXXXXXXXX) + TMP_CHECKSUMS=${TMP_DIR}/rke2.checksums + TMP_TARBALL=${TMP_DIR}/rke2.tarball + TMP_AIRGAP_CHECKSUMS=${TMP_DIR}/rke2-images.checksums + TMP_AIRGAP_TARBALL=${TMP_DIR}/rke2-images.tarball + cleanup() { + code=$? + set +e + trap - EXIT + rm -rf "${TMP_DIR}" + exit $code + } + trap cleanup INT EXIT +} + +# --- use desired rke2 version if defined or find version from channel --- +get_release_version() { + if [ -n "${INSTALL_RKE2_COMMIT}" ]; then + version="commit ${INSTALL_RKE2_COMMIT}" + elif [ -n "${INSTALL_RKE2_VERSION}" ]; then + version=${INSTALL_RKE2_VERSION} + else + info "finding release for channel ${INSTALL_RKE2_CHANNEL}" + INSTALL_RKE2_CHANNEL_URL=${INSTALL_RKE2_CHANNEL_URL:-'https://update.rke2.io/v1-release/channels'} + version_url="${INSTALL_RKE2_CHANNEL_URL}/${INSTALL_RKE2_CHANNEL}" + case ${DOWNLOADER} in + *curl) + version=$(${DOWNLOADER} -w "%{url_effective}" -L -s -S "${version_url}" -o /dev/null | sed -e 's|.*/||') + ;; + *wget) + version=$(${DOWNLOADER} -SqO /dev/null "${version_url}" 2>&1 | grep -i Location | sed -e 's|.*/||') + ;; + *) + fatal "Unsupported downloader executable '${DOWNLOADER}'" + ;; + esac + INSTALL_RKE2_VERSION="${version}" + fi +} + +# check_download performs a HEAD request to see if a file exists at a given url +check_download() { + case ${DOWNLOADER} in + *curl) + curl -o "/dev/null" -fsLI -X HEAD "$1" + ;; + *wget) + wget -q --spider "$1" + ;; + *) + fatal "downloader executable not supported: '${DOWNLOADER}'" + ;; + esac +} + +# download downloads a file from a url using either curl or wget +download() { + if [ $# -ne 2 ]; then + fatal "download needs exactly 2 arguments" + fi + + case ${DOWNLOADER} in + *curl) + curl -o "$1" -fsSL "$2" + ;; + *wget) + wget -qO "$1" "$2" + ;; + *) + fatal "downloader executable not supported: '${DOWNLOADER}'" + ;; + esac + + # Abort if download command failed + if [ $? -ne 0 ]; then + fatal "download failed" + fi +} + +# download_checksums downloads hash from github url. +download_checksums() { + if [ -n "${INSTALL_RKE2_COMMIT}" ]; then + CHECKSUMS_URL=${STORAGE_URL}/rke2.${SUFFIX}-${INSTALL_RKE2_COMMIT}.tar.gz.sha256sum + else + CHECKSUMS_URL=${INSTALL_RKE2_GITHUB_URL}/releases/download/${INSTALL_RKE2_VERSION}/sha256sum-${ARCH}.txt + fi + info "downloading checksums at ${CHECKSUMS_URL}" + download "${TMP_CHECKSUMS}" "${CHECKSUMS_URL}" + CHECKSUM_EXPECTED=$(grep "rke2.${SUFFIX}.tar.gz" "${TMP_CHECKSUMS}" | awk '{print $1}') +} + +# download_tarball downloads binary from github url. +download_tarball() { + if [ -n "${INSTALL_RKE2_COMMIT}" ]; then + TARBALL_URL=${STORAGE_URL}/rke2.${SUFFIX}-${INSTALL_RKE2_COMMIT}.tar.gz + else + TARBALL_URL=${INSTALL_RKE2_GITHUB_URL}/releases/download/${INSTALL_RKE2_VERSION}/rke2.${SUFFIX}.tar.gz + fi + info "downloading tarball at ${TARBALL_URL}" + download "${TMP_TARBALL}" "${TARBALL_URL}" +} + +# stage_local_checksums stages the local checksum hash for validation. +stage_local_checksums() { + info "staging local checksums from ${INSTALL_RKE2_ARTIFACT_PATH}/sha256sum-${ARCH}.txt" + cp -f "${INSTALL_RKE2_ARTIFACT_PATH}/sha256sum-${ARCH}.txt" "${TMP_CHECKSUMS}" + CHECKSUM_EXPECTED=$(grep "rke2.${SUFFIX}.tar.gz" "${TMP_CHECKSUMS}" | awk '{print $1}') + if [ -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.zst" ]; then + AIRGAP_CHECKSUM_EXPECTED=$(grep "rke2-images.${SUFFIX}.tar.zst" "${TMP_CHECKSUMS}" | awk '{print $1}') + elif [ -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.gz" ]; then + AIRGAP_CHECKSUM_EXPECTED=$(grep "rke2-images.${SUFFIX}.tar.gz" "${TMP_CHECKSUMS}" | awk '{print $1}') + fi +} + +# stage_local_tarball stages the local tarball. +stage_local_tarball() { + info "staging tarball from ${INSTALL_RKE2_ARTIFACT_PATH}/rke2.${SUFFIX}.tar.gz" + cp -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2.${SUFFIX}.tar.gz" "${TMP_TARBALL}" +} + +# stage_local_airgap_tarball stages the local checksum hash for validation. +stage_local_airgap_tarball() { + if [ -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.zst" ]; then + info "staging zst airgap image tarball from ${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.zst" + cp -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.zst" "${TMP_AIRGAP_TARBALL}" + AIRGAP_TARBALL_FORMAT=zst + elif [ -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.gz" ]; then + info "staging gzip airgap image tarball from ${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.gz" + cp -f "${INSTALL_RKE2_ARTIFACT_PATH}/rke2-images.${SUFFIX}.tar.gz" "${TMP_AIRGAP_TARBALL}" + AIRGAP_TARBALL_FORMAT=gz + fi +} + +# verify_tarball verifies the downloaded installer checksum. +verify_tarball() { + info "verifying tarball" + CHECKSUM_ACTUAL=$(sha256sum "${TMP_TARBALL}" | awk '{print $1}') + if [ "${CHECKSUM_EXPECTED}" != "${CHECKSUM_ACTUAL}" ]; then + fatal "download sha256 does not match ${CHECKSUM_EXPECTED}, got ${CHECKSUM_ACTUAL}" + fi +} + +# unpack_tarball extracts the tarball, correcting paths and moving systemd units as necessary +unpack_tarball() { + info "unpacking tarball file to ${INSTALL_RKE2_TAR_PREFIX}" + mkdir -p ${INSTALL_RKE2_TAR_PREFIX} + tar xzf "${TMP_TARBALL}" -C "${INSTALL_RKE2_TAR_PREFIX}" + if [ "${INSTALL_RKE2_TAR_PREFIX}" != "${DEFAULT_TAR_PREFIX}" ]; then + info "updating tarball contents to reflect install path" + sed -i "s|${DEFAULT_TAR_PREFIX}|${INSTALL_RKE2_TAR_PREFIX}|" ${INSTALL_RKE2_TAR_PREFIX}/lib/systemd/system/rke2-*.service ${INSTALL_RKE2_TAR_PREFIX}/bin/rke2-uninstall.sh + info "moving systemd units to /etc/systemd/system" + mv -f ${INSTALL_RKE2_TAR_PREFIX}/lib/systemd/system/rke2-*.service /etc/systemd/system/ + info "install complete; you may want to run: export PATH=\$PATH:${INSTALL_RKE2_TAR_PREFIX}/bin" + fi +} + +# download_airgap_checksums downloads the checksum file for the airgap image tarball +# and prepares the checksum value for later validation. +download_airgap_checksums() { + if [ -z "${INSTALL_RKE2_COMMIT}" ]; then + return + fi + AIRGAP_CHECKSUMS_URL=${STORAGE_URL}/rke2-images.${SUFFIX}-${INSTALL_RKE2_COMMIT}.tar.zst.sha256sum + # try for zst first; if that fails use gz for older release branches + if ! check_download "${AIRGAP_CHECKSUMS_URL}"; then + AIRGAP_CHECKSUMS_URL=${STORAGE_URL}/rke2-images.${SUFFIX}-${INSTALL_RKE2_COMMIT}.tar.gz.sha256sum + fi + info "downloading airgap checksums at ${AIRGAP_CHECKSUMS_URL}" + download "${TMP_AIRGAP_CHECKSUMS}" "${AIRGAP_CHECKSUMS_URL}" + AIRGAP_CHECKSUM_EXPECTED=$(grep "rke2-images.${SUFFIX}.tar" "${TMP_AIRGAP_CHECKSUMS}" | awk '{print $1}') +} + +# download_airgap_tarball downloads the airgap image tarball. +download_airgap_tarball() { + if [ -z "${INSTALL_RKE2_COMMIT}" ]; then + return + fi + AIRGAP_TARBALL_URL=${STORAGE_URL}/rke2-images.${SUFFIX}-${INSTALL_RKE2_COMMIT}.tar.zst + # try for zst first; if that fails use gz for older release branches + if ! check_download "${AIRGAP_TARBALL_URL}"; then + AIRGAP_TARBALL_URL=${STORAGE_URL}/rke2-images.${SUFFIX}-${INSTALL_RKE2_COMMIT}.tar.gz + fi + info "downloading airgap tarball at ${AIRGAP_TARBALL_URL}" + download "${TMP_AIRGAP_TARBALL}" "${AIRGAP_TARBALL_URL}" +} + +# verify_airgap_tarball compares the airgap image tarball checksum to the value +# calculated by CI when the file was uploaded. +verify_airgap_tarball() { + if [ -z "${AIRGAP_CHECKSUM_EXPECTED}" ]; then + return + fi + info "verifying airgap tarball" + AIRGAP_CHECKSUM_ACTUAL=$(sha256sum "${TMP_AIRGAP_TARBALL}" | awk '{print $1}') + if [ "${AIRGAP_CHECKSUM_EXPECTED}" != "${AIRGAP_CHECKSUM_ACTUAL}" ]; then + fatal "download sha256 does not match ${AIRGAP_CHECKSUM_EXPECTED}, got ${AIRGAP_CHECKSUM_ACTUAL}" + fi +} + +# install_airgap_tarball moves the airgap image tarball into place. +install_airgap_tarball() { + if [ -z "${AIRGAP_CHECKSUM_EXPECTED}" ]; then + return + fi + mkdir -p "${INSTALL_RKE2_AGENT_IMAGES_DIR}" + # releases that provide zst artifacts can read from the compressed archive; older releases + # that produce only gzip artifacts need to have the tarball decompressed ahead of time + if grep -qF '.tar.zst' "${TMP_AIRGAP_CHECKSUMS}" || [ "${AIRGAP_TARBALL_FORMAT}" = "zst" ]; then + info "installing airgap tarball to ${INSTALL_RKE2_AGENT_IMAGES_DIR}" + mv -f "${TMP_AIRGAP_TARBALL}" "${INSTALL_RKE2_AGENT_IMAGES_DIR}/rke2-images.${SUFFIX}.tar.zst" + else + info "decompressing airgap tarball to ${INSTALL_RKE2_AGENT_IMAGES_DIR}" + gzip -dc "${TMP_AIRGAP_TARBALL}" > "${INSTALL_RKE2_AGENT_IMAGES_DIR}/rke2-images.${SUFFIX}.tar" + fi +} + +# do_install_rpm builds a yum repo config from the channel and version to be installed, +# and calls yum to install the required packates. +do_install_rpm() { + maj_ver="7" + if [ -r /etc/redhat-release ] || [ -r /etc/centos-release ] || [ -r /etc/oracle-release ]; then + dist_version="$(. /etc/os-release && echo "$VERSION_ID")" + maj_ver=$(echo "$dist_version" | sed -E -e "s/^([0-9]+)\.?[0-9]*$/\1/") + case ${maj_ver} in + 7|8) + : + ;; + *) # In certain cases, like installing on Fedora, maj_ver will end up being something that is not 7 or 8 + maj_ver="7" + ;; + esac + fi + case "${INSTALL_RKE2_CHANNEL}" in + v*.*) + # We are operating with a version-based channel, so we should parse our version out + rke2_majmin=$(echo "${INSTALL_RKE2_CHANNEL}" | sed -E -e "s/^v([0-9]+\.[0-9]+).*/\1/") + rke2_rpm_channel=$(echo "${INSTALL_RKE2_CHANNEL}" | sed -E -e "s/^v[0-9]+\.[0-9]+-(.*)/\1/") + # If our regex fails to capture a "sane" channel out of the specified channel, fall back to `stable` + if [ "${rke2_rpm_channel}" = ${INSTALL_RKE2_CHANNEL} ]; then + info "using stable RPM repositories" + rke2_rpm_channel="stable" + fi + ;; + *) + get_release_version + rke2_majmin=$(echo "${INSTALL_RKE2_VERSION}" | sed -E -e "s/^v([0-9]+\.[0-9]+).*/\1/") + rke2_rpm_channel=${1} + ;; + esac + info "using ${rke2_majmin} series from channel ${rke2_rpm_channel}" + rpm_site="rpm.rancher.io" + if [ "${rke2_rpm_channel}" = "testing" ]; then + rpm_site="rpm-${rke2_rpm_channel}.rancher.io" + fi + rm -f /etc/yum.repos.d/rancher-rke2*.repo + cat <<-EOF >"/etc/yum.repos.d/rancher-rke2.repo" +[rancher-rke2-common-${rke2_rpm_channel}] +name=Rancher RKE2 Common (${1}) +baseurl=https://${rpm_site}/rke2/${rke2_rpm_channel}/common/centos/${maj_ver}/noarch +enabled=1 +gpgcheck=1 +gpgkey=https://${rpm_site}/public.key +[rancher-rke2-${rke2_majmin}-${rke2_rpm_channel}] +name=Rancher RKE2 ${rke2_majmin} (${1}) +baseurl=https://${rpm_site}/rke2/${rke2_rpm_channel}/${rke2_majmin}/centos/${maj_ver}/x86_64 +enabled=1 +gpgcheck=1 +gpgkey=https://${rpm_site}/public.key +EOF + if [ -z "${INSTALL_RKE2_VERSION}" ]; then + yum -y install "rke2-${INSTALL_RKE2_TYPE}" + else + rke2_rpm_version=$(echo "${INSTALL_RKE2_VERSION}" | sed -E -e "s/[\+-]/~/g" | sed -E -e "s/v(.*)/\1/") + if [ -n "${INSTALL_RKE2_RPM_RELEASE_VERSION}" ]; then + yum -y install "rke2-${INSTALL_RKE2_TYPE}-${rke2_rpm_version}-${INSTALL_RKE2_RPM_RELEASE_VERSION}" + else + yum -y install "rke2-${INSTALL_RKE2_TYPE}-${rke2_rpm_version}" + fi + fi +} + +do_install_tar() { + setup_tmp + + if [ -n "${INSTALL_RKE2_ARTIFACT_PATH}" ]; then + stage_local_checksums + stage_local_airgap_tarball + stage_local_tarball + else + get_release_version + info "using ${INSTALL_RKE2_VERSION:-commit $INSTALL_RKE2_COMMIT} as release" + download_airgap_checksums + download_airgap_tarball + download_checksums + download_tarball + fi + + verify_airgap_tarball + install_airgap_tarball + verify_tarball + unpack_tarball + systemctl daemon-reload +} + +do_install() { + setup_env + check_method_conflict + setup_arch + if [ -z "${INSTALL_RKE2_ARTIFACT_PATH}" ]; then + verify_downloader curl || verify_downloader wget || fatal "can not find curl or wget for downloading files" + fi + + case ${INSTALL_RKE2_METHOD} in + yum | rpm | dnf) + do_install_rpm "${INSTALL_RKE2_CHANNEL}" + ;; + *) + do_install_tar "${INSTALL_RKE2_CHANNEL}" + ;; + esac +} + +do_install +exit 0 diff --git a/pkg/driver/k3s.go b/pkg/driver/k3s.go new file mode 100644 index 0000000..e1a89da --- /dev/null +++ b/pkg/driver/k3s.go @@ -0,0 +1,173 @@ +package driver + +import ( + "bufio" + "context" + _ "embed" + "fmt" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/imdario/mergo" + "github.com/rancherfederal/hauler/pkg/packager/images" + "io" + "k8s.io/apimachinery/pkg/runtime/schema" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/yaml" +) + +const ( + k3sReleaseUrl = "https://github.com/k3s-io/k3s/releases/download" +) + +//go:embed embed/k3s-init.sh +var k3sInit string + +type K3s struct { + Version string + + Config K3sConfig +} + +//TODO: Would be nice if these just pointed to k3s/pkg/cli/cmds +type K3sConfig struct { + DataDir string `json:"data-dir,omitempty"` + KubeConfig string `json:"write-kubeconfig,omitempty"` + KubeConfigMode string `json:"write-kubeconfig-mode,omitempty"` + + Disable []string `json:"disable,omitempty"` +} + +//NewK3s returns a new k3s driver +func NewK3s() K3s { + //TODO: Allow for configuration overrides + return K3s{ + Config: K3sConfig{ + DataDir: "/var/lib/rancher/k3s", + KubeConfig: "/etc/rancher/k3s/k3s.yaml", + KubeConfigMode: "0644", + Disable: []string{}, + }, + } +} + +func (k K3s) Name() string { return "k3s" } + +func (k K3s) KubeConfigPath() string { return k.Config.KubeConfig } + +func (k K3s) DataPath(elem ...string) string { + base := []string{k.Config.DataDir} + return filepath.Join(append(base, elem...)...) +} + +func (k K3s) WriteConfig() error { + kCfgPath := filepath.Dir(k.Config.KubeConfig) + if err := os.MkdirAll(kCfgPath, os.ModePerm); err != nil { + return err + } + + data, err := yaml.Marshal(k.Config) + + c := make(map[string]interface{}) + if err := yaml.Unmarshal(data, &c); err != nil { + return err + } + + var uc map[string]interface{} + path := filepath.Join(kCfgPath, "config.yaml") + if data, err := os.ReadFile(path); err != nil { + err := yaml.Unmarshal(data, &uc) + if err != nil { + return err + } + } + + //Merge with user defined configs taking precedence + if err := mergo.Merge(&c, uc); err != nil { + return err + } + + mergedData, err := yaml.Marshal(&c) + if err != nil { + return err + } + + return os.WriteFile(path, mergedData, 0644) +} + +func (k K3s) Images(ctx context.Context) (map[name.Reference]v1.Image, error) { + imgs, err := k.listImages() + if err != nil { + return nil, err + } + return images.ResolveRemoteRefs(imgs...) +} + +func (k K3s) Binary() (io.ReadCloser, error) { + u, err := url.Parse(fmt.Sprintf("%s/%s/%s", k3sReleaseUrl, k.Version, k.Name())) + if err != nil { + return nil, err + } + + resp, err := http.Get(u.String()) + if err != nil || resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to return executable for k3s %s from %s", k.Version, u.String()) + } + return resp.Body, nil +} + +//SystemObjects returns a slice of object.ObjMetadata required for driver to be functional and accept new resources +//hauler's bootstrapping sequence will always wait for SystemObjects to be in a Ready status before proceeding +func (k K3s) SystemObjects() (objs []object.ObjMetadata) { + for _, dep := range []string{"coredns"} { + objMeta, _ := object.CreateObjMetadata("kube-system", dep, schema.GroupKind{Kind: "Deployment", Group: "apps"}) + objs = append(objs, objMeta) + } + return objs +} + +func (k K3s) Start(out io.Writer) error { + if err := os.WriteFile("/opt/hauler/bin/k3s-init.sh", []byte(k3sInit), 0755); err != nil { + return err + } + + cmd := exec.Command("/bin/sh", "/opt/hauler/bin/k3s-init.sh") + + cmd.Env = append(os.Environ(), []string{ + "INSTALL_K3S_SKIP_DOWNLOAD=true", + "INSTALL_K3S_SELINUX_WARN=true", + "INSTALL_K3S_SKIP_SELINUX_RPM=true", + "INSTALL_K3S_BIN_DIR=/opt/hauler/bin", + + //TODO: Provide a real dryrun option + //"INSTALL_K3S_SKIP_START=true", + }...) + + cmd.Stdout = out + return cmd.Run() +} + +func (k K3s) listImages() ([]string, error) { + u, err := url.Parse(fmt.Sprintf("%s/%s/k3s-images.txt", k3sReleaseUrl, k.Version)) + if err != nil { + return nil, err + } + + resp, err := http.Get(u.String()) + if err != nil || resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to return images for k3s %s from %s", k.Version, u.String()) + } + defer resp.Body.Close() + + var imgs []string + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + imgs = append(imgs, scanner.Text()) + } + + return imgs, nil +} diff --git a/pkg/packager/images/images.go b/pkg/packager/images/images.go index 52550d5..93997d1 100644 --- a/pkg/packager/images/images.go +++ b/pkg/packager/images/images.go @@ -74,32 +74,6 @@ func ImageMapFromBundle(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) return ResolveRemoteRefs(di...) } -func IdentifyImages(b *fleetapi.Bundle) (discoveredImages, error) { - opts := fleetapi.BundleDeploymentOptions{ - DefaultNamespace: "default", - } - - m := &manifest.Manifest{Resources: b.Spec.Resources} - - //TODO: I think this is right? - objs, err := helmdeployer.Template("anything", m, opts) - if err != nil { - return nil, err - } - - var di discoveredImages - - for _, o := range objs { - imgs, err := imageFromRuntimeObject(o.(*unstructured.Unstructured)) - if err != nil { - return nil, err - } - di = append(di, imgs...) - } - - return di, err -} - //ResolveRemoteRefs will return a slice of remote images resolved from their fully qualified name func ResolveRemoteRefs(images ...string) (map[name.Reference]v1.Image, error) { m := make(map[name.Reference]v1.Image) @@ -137,7 +111,7 @@ var knownImagePaths = []string{ "{.spec.containers[*].image}", } -////imageFromRuntimeObject will return any images found in known obj specs +//imageFromRuntimeObject will return any images found in known obj specs func imageFromRuntimeObject(obj *unstructured.Unstructured) (images []string, err error) { objData, _ := obj.MarshalJSON() diff --git a/pkg/packager/packager.go b/pkg/packager/packager.go index 8a301ce..5bf455c 100644 --- a/pkg/packager/packager.go +++ b/pkg/packager/packager.go @@ -2,32 +2,42 @@ package packager import ( "context" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" fleetapi "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" "github.com/rancher/fleet/pkg/bundle" "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/fs" + "github.com/rancherfederal/hauler/pkg/log" "github.com/rancherfederal/hauler/pkg/packager/images" - "io" "k8s.io/apimachinery/pkg/util/json" - "net/http" "path/filepath" ) type Packager interface { - Bundles(context.Context, ...string) ([]*fleetapi.Bundle, error) - Driver(context.Context, v1alpha1.Drive) error - Fleet(context.Context, v1alpha1.Fleet) error Archive(Archiver, v1alpha1.Package, string) error + + PackageBundles(context.Context, ...string) ([]*fleetapi.Bundle, error) + + PackageDriver(context.Context, driver.Driver) error + + PackageFleet(context.Context, v1alpha1.Fleet) error + + PackageImages(context.Context, map[name.Reference]v1.Image) error } type pkg struct { fs fs.PkgFs + + logger log.Logger } //NewPackager loads a new packager given a path on disk -func NewPackager(path string) Packager { +func NewPackager(path string, logger log.Logger) Packager { return pkg{ - fs: fs.NewPkgFS(path), + fs: fs.NewPkgFS(path), + logger: logger, } } @@ -44,7 +54,7 @@ func (p pkg) Archive(a Archiver, pkg v1alpha1.Package, output string) error { return Package(a, p.fs.Path(), output) } -func (p pkg) Bundles(ctx context.Context, path ...string) ([]*fleetapi.Bundle, error) { +func (p pkg) PackageBundles(ctx context.Context, path ...string) ([]*fleetapi.Bundle, error) { opts := &bundle.Options{Compress: true} var bundles []*fleetapi.Bundle @@ -67,33 +77,41 @@ func (p pkg) Bundles(ctx context.Context, path ...string) ([]*fleetapi.Bundle, e return bundles, nil } -func (p pkg) Driver(ctx context.Context, d v1alpha1.Drive) error { - if err := writeURL(p.fs, d.BinURL(), "k3s"); err != nil { - return err - } - - //TODO: Stop hardcoding - if err := writeURL(p.fs, "https://get.k3s.io", "k3s-init.sh"); err != nil { - return err - } - - imgMap, err := images.MapImager(d) +func (p pkg) PackageDriver(ctx context.Context, d driver.Driver) error { + rc, err := d.Binary() if err != nil { return err } - for ref, im := range imgMap { - err := p.fs.AddImage(ref, im) - if err != nil { - return err - } + if err := p.fs.AddBin(rc, d.Name()); err != nil { + return err + } + rc.Close() + + imgMap, err := d.Images(ctx) + if err != nil { + return err + } + + err = p.PackageImages(ctx, imgMap) + if err != nil { + return err } return nil } -//TODO: Add this to Driver? -func (p pkg) Fleet(ctx context.Context, fl v1alpha1.Fleet) error { +func (p pkg) PackageImages(ctx context.Context, imgMap map[name.Reference]v1.Image) error { + for ref, im := range imgMap { + if err := p.fs.AddImage(ref, im); err != nil { + return err + } + } + return nil +} + +//TODO: Add this to PackageDriver? +func (p pkg) PackageFleet(ctx context.Context, fl v1alpha1.Fleet) error { imgMap, err := images.MapImager(fl) if err != nil { return err @@ -116,26 +134,3 @@ func (p pkg) Fleet(ctx context.Context, fl v1alpha1.Fleet) error { return nil } - -func writeURL(fsys fs.PkgFs, rawURL string, name string) error { - rc, err := fetchURL(rawURL) - if err != nil { - return err - } - defer rc.Close() - - return fsys.AddBin(rc, name) -} - -func fetchURL(rawURL string) (io.ReadCloser, error) { - resp, err := http.Get(rawURL) - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - return nil, err - } - - return resp.Body, nil -} diff --git a/pkg/packager/packager_test.go b/pkg/packager/packager_test.go new file mode 100644 index 0000000..b84ba23 --- /dev/null +++ b/pkg/packager/packager_test.go @@ -0,0 +1,36 @@ +package packager + +import ( + "context" + "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/rancherfederal/hauler/pkg/fs" + "testing" +) + +func Test_pkg_driver(t *testing.T) { + type fields struct { + fs fs.PkgFs + } + type args struct { + ctx context.Context + d v1alpha1.IDriver + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := pkg{ + fs: tt.fields.fs, + } + if err := p.driver(tt.args.ctx, tt.args.d); (err != nil) != tt.wantErr { + t.Errorf("driver() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 5696bc5042972a43b68bc2c0d39374d97ddb50a4 Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Tue, 15 Jun 2021 15:59:07 -0600 Subject: [PATCH 02/11] move create/bootstrap to pkg subcommand, rename to build and run --- cmd/haulerctl/app/bootstrap.go | 90 -------------- cmd/haulerctl/app/create.go | 111 ------------------ cmd/haulerctl/app/create_test.go | 78 ------------ cmd/haulerctl/app/pkg.go | 4 +- cmd/haulerctl/app/pkg_boot.go | 34 ------ .../app/{pkg_create.go => pkg_build.go} | 63 ++++++++-- cmd/haulerctl/app/pkg_build_test.go | 68 +++++++++++ cmd/haulerctl/app/pkg_run.go | 84 +++++++++++++ cmd/haulerctl/app/root.go | 8 -- go.mod | 3 +- go.sum | 9 ++ pkg/bootstrap/booter.go | 29 ++--- pkg/log/log.go | 46 ++++++-- 13 files changed, 270 insertions(+), 357 deletions(-) delete mode 100644 cmd/haulerctl/app/bootstrap.go delete mode 100644 cmd/haulerctl/app/create.go delete mode 100644 cmd/haulerctl/app/create_test.go delete mode 100644 cmd/haulerctl/app/pkg_boot.go rename cmd/haulerctl/app/{pkg_create.go => pkg_build.go} (59%) create mode 100644 cmd/haulerctl/app/pkg_build_test.go create mode 100644 cmd/haulerctl/app/pkg_run.go diff --git a/cmd/haulerctl/app/bootstrap.go b/cmd/haulerctl/app/bootstrap.go deleted file mode 100644 index 9bc95dc..0000000 --- a/cmd/haulerctl/app/bootstrap.go +++ /dev/null @@ -1,90 +0,0 @@ -package app - -import ( - "context" - "github.com/rancherfederal/hauler/pkg/bootstrap" - "github.com/rancherfederal/hauler/pkg/driver" - "github.com/rancherfederal/hauler/pkg/packager" - "github.com/spf13/cobra" - "os" -) - -type deployOpts struct { - *rootOpts - - haulerDir string -} - -// NewBootstrapCommand new a new sub command of haulerctl that bootstraps a cluster -func NewBootstrapCommand() *cobra.Command { - opts := &deployOpts{ - rootOpts: &ro, - } - - cmd := &cobra.Command{ - Use: "bootstrap", - Short: "Single-command install of a k3s cluster with known tools running inside of it", - Long: `Single-command install of a k3s cluster with known tools running inside of it. Tools - include an OCI registry and Git server`, - Aliases: []string{"b", "boot"}, - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return opts.Run(args[0]) - }, - } - - f := cmd.Flags() - f.StringVarP(&opts.haulerDir, "hauler-dir", "", "/opt/hauler", "Directory to install hauler components in") - - return cmd -} - -// Run performs the operation. -func (o *deployOpts) Run(packagePath string) error { - o.logger.Infof("Bootstrapping from '%s'", packagePath) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - tmpdir, err := os.MkdirTemp("", "hauler") - if err != nil { - return err - } - defer os.Remove(tmpdir) - o.logger.Debugf("Using temporary working directory: %s", tmpdir) - - a := packager.NewArchiver() - err = packager.Unpackage(a, packagePath, tmpdir) - if err != nil { - return err - } - - b, err := bootstrap.NewBooter(tmpdir) - if err != nil { - return err - } - - d := driver.NewDriver(b.Package.Spec.Driver) - if err != nil { - return err - } - - o.logger.Infof("Performing pre %s boot steps", b.Package.Spec.Driver.Type) - if err := b.PreBoot(ctx, d, o.logger); err != nil { - return err - } - - o.logger.Infof("Booting %s", b.Package.Spec.Driver.Type) - if err := b.Boot(ctx, d, o.logger); err != nil { - return err - } - - o.logger.Infof("Performing post %s boot steps", b.Package.Spec.Driver.Type) - if err := b.PostBoot(ctx, d, o.logger); err != nil { - return err - } - - o.logger.Infof("Success! You can access the cluster with '/opt/hauler/bin/kubectl'") - - return nil -} diff --git a/cmd/haulerctl/app/create.go b/cmd/haulerctl/app/create.go deleted file mode 100644 index 5cb0f09..0000000 --- a/cmd/haulerctl/app/create.go +++ /dev/null @@ -1,111 +0,0 @@ -package app - -import ( - "context" - "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" - "github.com/rancherfederal/hauler/pkg/driver" - "github.com/rancherfederal/hauler/pkg/packager" - "github.com/spf13/cobra" - "os" - "sigs.k8s.io/yaml" -) - -type createOpts struct { - *rootOpts - - driver string - outputFile string - configFile string -} - -// NewCreateCommand creates a new sub command under -// haulerctl for creating dependency artifacts for bootstraps -func NewCreateCommand() *cobra.Command { - opts := &createOpts{ - rootOpts: &ro, - } - - cmd := &cobra.Command{ - Use: "create", - Short: "package all dependencies into a compressed archive", - 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{"c"}, - PreRunE: func(cmd *cobra.Command, args []string) error { - return opts.PreRun() - }, - RunE: func(cmd *cobra.Command, args []string) error { - return opts.Run() - }, - } - - f := cmd.Flags() - f.StringVarP(&opts.driver, "driver", "d", "k3s", - "IDriver type to use for package (k3s or rke2)") - f.StringVarP(&opts.outputFile, "output", "o", "haul.tar.zst", - "package output location relative to the current directory (haul.tar.zst)") - f.StringVarP(&opts.configFile, "config", "c", "./package.yaml", - "config file") - - return cmd -} - -func (o *createOpts) PreRun() error { - return nil -} - -// Run performs the operation. -func (o *createOpts) Run() error { - o.logger.Infof("Creating new deployable bundle using driver: %s", o.driver) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - if _, err := os.Stat(o.configFile); err != nil { - return err - } - - bundleData, err := os.ReadFile(o.configFile) - if err != nil { - return err - } - - var p v1alpha1.Package - err = yaml.Unmarshal(bundleData, &p) - if err != nil { - return err - } - - tmpdir, err := os.MkdirTemp("", "hauler") - if err != nil { - return err - } - defer os.RemoveAll(tmpdir) - - pkgr := packager.NewPackager(tmpdir, o.logger) - - o.logger.Infof("Packaging driver (%s %s) artifacts...", p.Spec.Driver.Version, p.Spec.Driver.Type) - d := driver.NewDriver(p.Spec.Driver) - if err = pkgr.PackageDriver(ctx, d); err != nil { - return err - } - - o.logger.Infof("Packaging fleet artifacts...") - if err = pkgr.PackageFleet(ctx, p.Spec.Fleet); err != nil { - return err - } - - o.logger.Infof("Packaging images and manifests defined in specified paths...") - if _, err = pkgr.PackageBundles(ctx, p.Spec.Paths...); err != nil { - return err - } - - a := packager.NewArchiver() - o.logger.Infof("Archiving and compressing package to: %s.%s", o.outputFile, a.String()) - if err = pkgr.Archive(a, p, o.outputFile); err != nil { - return err - } - - return nil -} diff --git a/cmd/haulerctl/app/create_test.go b/cmd/haulerctl/app/create_test.go deleted file mode 100644 index 7be7c69..0000000 --- a/cmd/haulerctl/app/create_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package app - -import ( - "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" - "io/ioutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "os" - "sigs.k8s.io/yaml" - "testing" -) - -func Test_createOpts_Run(t *testing.T) { - l, _ := setupCliLogger(os.Stdout, "debug") - tro := rootOpts{l} - - p := v1alpha1.Package{ - TypeMeta: metav1.TypeMeta{ - Kind: "Package", - APIVersion: "hauler.cattle.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - Spec: v1alpha1.PackageSpec{ - Fleet: v1alpha1.Fleet{Version: "0.3.5"}, - Driver: v1alpha1.Driver{ - Type: "k3s", - Version: "v1.21.1+k3s1", - }, - Paths: []string{ - "../../../testdata/docker-registry", - "../../../testdata/rawmanifests", - }, - Images: []string{}, - }, - } - - data, _ := yaml.Marshal(p) - if err := ioutil.WriteFile("create_test.package.yaml", data, 0644); err != nil { - t.Fatalf("failed to write test config file: %v", err) - } - defer os.Remove("create_test.package.yaml") - - type fields struct { - rootOpts *rootOpts - driver string - outputFile string - configFile string - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - { - name: "should work", - fields: fields{ - rootOpts: &tro, - driver: "k3s", - outputFile: "package", - configFile: "./create_test.package.yaml", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - o := &createOpts{ - rootOpts: tt.fields.rootOpts, - driver: tt.fields.driver, - outputFile: tt.fields.outputFile, - configFile: tt.fields.configFile, - } - if err := o.Run(); (err != nil) != tt.wantErr { - t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/cmd/haulerctl/app/pkg.go b/cmd/haulerctl/app/pkg.go index daf4487..c2ec986 100644 --- a/cmd/haulerctl/app/pkg.go +++ b/cmd/haulerctl/app/pkg.go @@ -19,8 +19,8 @@ func NewPkgCommand() *cobra.Command { }, } - cmd.AddCommand(NewPkgCreateCommand()) - cmd.AddCommand(NewPkgBootCommand()) + cmd.AddCommand(NewPkgBuildCommand()) + cmd.AddCommand(NewPkgRunCommand()) return cmd } diff --git a/cmd/haulerctl/app/pkg_boot.go b/cmd/haulerctl/app/pkg_boot.go deleted file mode 100644 index d5c00b6..0000000 --- a/cmd/haulerctl/app/pkg_boot.go +++ /dev/null @@ -1,34 +0,0 @@ -package app - -import "github.com/spf13/cobra" - -type pkgBootOpts struct { - cfgFile string -} - -func NewPkgBootCommand() *cobra.Command { - opts := pkgBootOpts{} - - cmd := &cobra.Command{ - Use: "boot", - Short: "", - Long: "", - Aliases: []string{"b", "bootstrap"}, - PreRunE: func(cmd *cobra.Command, args []string) error { - return opts.PreRun() - }, - RunE: func(cmd *cobra.Command, args []string) error { - return opts.Run() - }, - } - - return cmd -} - -func (o *pkgBootOpts) PreRun() error { - return nil -} - -func (o *pkgBootOpts) Run() error { - return nil -} diff --git a/cmd/haulerctl/app/pkg_create.go b/cmd/haulerctl/app/pkg_build.go similarity index 59% rename from cmd/haulerctl/app/pkg_create.go rename to cmd/haulerctl/app/pkg_build.go index 32b972f..536d861 100644 --- a/cmd/haulerctl/app/pkg_create.go +++ b/cmd/haulerctl/app/pkg_build.go @@ -3,6 +3,8 @@ package app import ( "context" "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" + "github.com/rancherfederal/hauler/pkg/driver" + "github.com/rancherfederal/hauler/pkg/packager" "github.com/sirupsen/logrus" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -10,9 +12,12 @@ import ( "sigs.k8s.io/yaml" ) -type pkgCreateOpts struct { +type pkgBuildOpts struct { + *rootOpts + cfgFile string + name string driver string driverVersion string @@ -22,14 +27,16 @@ type pkgCreateOpts struct { paths []string } -func NewPkgCreateCommand() *cobra.Command { - opts := pkgCreateOpts{} +func NewPkgBuildCommand() *cobra.Command { + opts := pkgBuildOpts{ + rootOpts: &ro, + } cmd := &cobra.Command{ - Use: "create", + Use: "build", Short: "", Long: "", - Aliases: []string{"c"}, + Aliases: []string{"b"}, PreRunE: func(cmd *cobra.Command, args []string) error { return opts.PreRun() }, @@ -39,6 +46,8 @@ func NewPkgCreateCommand() *cobra.Command { } f := cmd.PersistentFlags() + f.StringVarP(&opts.name, "name", "n", "pkg", + "name of the pkg to create, will dicate file name") f.StringVarP(&opts.cfgFile, "config", "c", "./pkg.yaml", "path to config file") f.StringVarP(&opts.driver, "driver", "d", "k3s", @@ -55,7 +64,7 @@ func NewPkgCreateCommand() *cobra.Command { return cmd } -func (o *pkgCreateOpts) PreRun() error { +func (o *pkgBuildOpts) PreRun() error { _, err := os.Stat(o.cfgFile) if os.IsNotExist(err) { logrus.Infof("Could not find %s, creating one", o.cfgFile) @@ -65,7 +74,7 @@ func (o *pkgCreateOpts) PreRun() error { APIVersion: "", }, ObjectMeta: metav1.ObjectMeta{ - Name: "", + Name: o.name, }, Spec: v1alpha1.PackageSpec{ Fleet: v1alpha1.Fleet{ @@ -96,11 +105,47 @@ func (o *pkgCreateOpts) PreRun() error { return nil } -func (o *pkgCreateOpts) Run() error { +func (o *pkgBuildOpts) Run() error { + o.logger.Infof("Building package") + ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _ = ctx + cfgData, err := os.ReadFile(o.cfgFile) + if err != nil { + return err + } + var p v1alpha1.Package + if err := yaml.Unmarshal(cfgData, &p); err != nil { + return err + } + + tmpdir, err := os.MkdirTemp("", "hauler") + if err != nil { + return err + } + + pkgr := packager.NewPackager(tmpdir, o.logger) + + d := driver.NewDriver(p.Spec.Driver) + if dErr := pkgr.PackageDriver(ctx, d); dErr != nil { + return dErr + } + + if fErr := pkgr.PackageFleet(ctx, p.Spec.Fleet); fErr != nil { + return fErr + } + + if _, bErr := pkgr.PackageBundles(ctx, p.Spec.Paths...); bErr != nil { + return bErr + } + + a := packager.NewArchiver() + if aErr := pkgr.Archive(a, p, o.name); aErr != nil { + return aErr + } + + o.logger.Successf("Finished building package") return nil } diff --git a/cmd/haulerctl/app/pkg_build_test.go b/cmd/haulerctl/app/pkg_build_test.go new file mode 100644 index 0000000..03b7b79 --- /dev/null +++ b/cmd/haulerctl/app/pkg_build_test.go @@ -0,0 +1,68 @@ +package app + +import ( + "os" + "testing" +) + +func Test_pkgBuildOpts_Run(t *testing.T) { + l, _ := setupCliLogger(os.Stdout, "debug") + tro := rootOpts{l} + + type fields struct { + rootOpts *rootOpts + cfgFile string + name string + driver string + driverVersion string + fleetVersion string + images []string + paths []string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "should work", + fields: fields{ + rootOpts: &tro, + cfgFile: "pkg.yaml", + name: "k3s", + driver: "k3s", + driverVersion: "v1.21.1+k3s1", + fleetVersion: "0.3.5", + images: nil, + paths: []string{ + "../../../testdata/docker-registry", + "../../../testdata/rawmanifests", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &pkgBuildOpts{ + rootOpts: tt.fields.rootOpts, + cfgFile: tt.fields.cfgFile, + name: tt.fields.name, + driver: tt.fields.driver, + driverVersion: tt.fields.driverVersion, + fleetVersion: tt.fields.fleetVersion, + images: tt.fields.images, + paths: tt.fields.paths, + } + + if err := o.PreRun(); err != nil { + t.Errorf("PreRun() error = %v", err) + } + defer os.Remove(o.cfgFile) + + if err := o.Run(); (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cmd/haulerctl/app/pkg_run.go b/cmd/haulerctl/app/pkg_run.go new file mode 100644 index 0000000..265c2e0 --- /dev/null +++ b/cmd/haulerctl/app/pkg_run.go @@ -0,0 +1,84 @@ +package app + +import ( + "context" + "github.com/rancherfederal/hauler/pkg/bootstrap" + "github.com/rancherfederal/hauler/pkg/driver" + "github.com/rancherfederal/hauler/pkg/packager" + "github.com/spf13/cobra" + "os" +) + +type pkgRunOpts struct { + *rootOpts + + cfgFile string +} + +func NewPkgRunCommand() *cobra.Command { + opts := pkgRunOpts{ + rootOpts: &ro, + } + + cmd := &cobra.Command{ + Use: "run", + Short: "", + Long: "", + Aliases: []string{"r"}, + Args: cobra.MinimumNArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + return opts.PreRun() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return opts.Run(args[0]) + }, + } + + return cmd +} + +func (o *pkgRunOpts) PreRun() error { + return nil +} + +func (o *pkgRunOpts) Run(pkgPath string) error { + o.logger.Infof("Running from '%s'", pkgPath) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tmpdir, err := os.MkdirTemp("", "hauler") + if err != nil { + return err + } + o.logger.Debugf("Using temporary working directory: %s", tmpdir) + + a := packager.NewArchiver() + + if err := packager.Unpackage(a, pkgPath, tmpdir); err != nil { + return err + } + o.logger.Debugf("Unpackaged %s", pkgPath) + + b, err := bootstrap.NewBooter(tmpdir) + if err != nil { + return err + } + + d := driver.NewDriver(b.Package.Spec.Driver) + + if preErr := b.PreBoot(ctx, d, o.logger); preErr != nil { + return preErr + } + + if bErr := b.Boot(ctx, d, o.logger); bErr != nil { + return bErr + } + + if postErr := b.PostBoot(ctx, d, o.logger); postErr != nil { + return postErr + } + + o.logger.Successf("Access the cluster with '/opt/hauler/bin/kubectl'") + return nil +} diff --git a/cmd/haulerctl/app/root.go b/cmd/haulerctl/app/root.go index 8add381..e008955 100644 --- a/cmd/haulerctl/app/root.go +++ b/cmd/haulerctl/app/root.go @@ -7,7 +7,6 @@ import ( "os" "time" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" homedir "github.com/mitchellh/go-homedir" @@ -72,7 +71,6 @@ func NewRootCommand() *cobra.Command { cmd.AddCommand(NewPkgCommand()) cmd.AddCommand(NewCreateCommand()) - cmd.AddCommand(NewBootstrapCommand()) f := cmd.PersistentFlags() f.StringVarP(&loglevel, "loglevel", "l", "info", @@ -111,11 +109,5 @@ func initConfig() { func setupCliLogger(out io.Writer, level string) (log.Logger, error) { l := log.NewLogger(out) - lvl, err := logrus.ParseLevel(level) - if err != nil { - return nil, err - } - - l.SetLevel(lvl) return l, nil } diff --git a/go.mod b/go.mod index b599b05..9f69993 100644 --- a/go.mod +++ b/go.mod @@ -17,13 +17,13 @@ require ( github.com/imdario/mergo v0.3.12 github.com/klauspost/compress v1.13.0 // indirect github.com/klauspost/pgzip v1.2.5 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mholt/archiver/v3 v3.5.0 github.com/mitchellh/go-homedir v1.1.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/opencontainers/runc v1.0.0-rc93 // indirect github.com/otiai10/copy v1.6.0 github.com/pelletier/go-toml v1.8.1 // indirect + github.com/pterm/pterm v0.12.24 github.com/rancher/fleet v0.3.5 github.com/rancher/fleet/pkg/apis v0.0.0 github.com/sirupsen/logrus v1.8.1 @@ -33,7 +33,6 @@ require ( github.com/ulikunitz/xz v0.5.10 // indirect golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect google.golang.org/genproto v0.0.0-20210524171403-669157292da3 // indirect google.golang.org/grpc v1.38.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 9c92ca1..0565f2a 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,8 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= @@ -686,6 +688,8 @@ github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -1167,6 +1171,8 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pterm/pterm v0.12.24 h1:VM23UV0UddFxHaEN14SCIIYcr4SE5VnoK80AnoeuGbg= +github.com/pterm/pterm v0.12.24/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= @@ -1355,6 +1361,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1647,6 +1655,7 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/bootstrap/booter.go b/pkg/bootstrap/booter.go index 37aafde..214c91b 100644 --- a/pkg/bootstrap/booter.go +++ b/pkg/bootstrap/booter.go @@ -10,7 +10,6 @@ import ( "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/fs" "github.com/rancherfederal/hauler/pkg/log" - "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/chart/loader" "io" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -46,9 +45,7 @@ func NewBooter(pkgPath string) (*booter, error) { } func (b booter) PreBoot(ctx context.Context, d driver.Driver, logger log.Logger) error { - l := logger.WithFields(logrus.Fields{ - "phase": "preboot", - }) + logger.Infof("Beginning pre boot") //TODO: Feel like there's a better way to do this if err := b.moveBin(); err != nil { @@ -67,18 +64,18 @@ func (b booter) PreBoot(ctx context.Context, d driver.Driver, logger log.Logger) return err } - l.Infof("Creating driver configuration") + logger.Debugf("Writing %s config", d.Name()) if err := d.WriteConfig(); err != nil { return err } + logger.Successf("Completed pre boot") + return nil } func (b booter) Boot(ctx context.Context, d driver.Driver, logger log.Logger) error { - l := logger.WithFields(logrus.Fields{ - "phase": "boot", - }) + logger.Infof("Beginning boot") var stdoutBuf, stderrBuf bytes.Buffer out := io.MultiWriter(os.Stdout, &stdoutBuf, &stderrBuf) @@ -88,19 +85,18 @@ func (b booter) Boot(ctx context.Context, d driver.Driver, logger log.Logger) er return err } - l.Infof("Waiting for driver core components to provision...") + logger.Infof("Waiting for driver core components to provision...") waitErr := waitForDriver(ctx, d) if waitErr != nil { return err } + logger.Successf("Completed boot") return nil } func (b booter) PostBoot(ctx context.Context, d driver.Driver, logger log.Logger) error { - l := logger.WithFields(logrus.Fields{ - "phase": "postboot", - }) + logger.Infof("Beginning post boot") cf := genericclioptions.NewConfigFlags(true) cf.KubeConfig = stringptr(d.KubeConfigPath()) @@ -111,13 +107,13 @@ func (b booter) PostBoot(ctx context.Context, d driver.Driver, logger log.Logger return err } - l.Infof("Installing fleet crds") + logger.Infof("Installing fleet crds") fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", "fleet-system", nil) if fleetCrdErr != nil { return fleetCrdErr } - l.Infof("Successfully installed '%s' to namespace '%s'", fleetCrdRelease.Name, fleetCrdRelease.Namespace) + logger.Infof("Successfully installed '%s' to namespace '%s'", fleetCrdRelease.Name, fleetCrdRelease.Namespace) fleetChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-%s.tgz", b.Package.Spec.Fleet.Version)) fleetChart, err := loader.Load(fleetChartPath) @@ -125,14 +121,15 @@ func (b booter) PostBoot(ctx context.Context, d driver.Driver, logger log.Logger return err } - l.Infof("Installing fleet") + logger.Infof("Installing fleet") fleetRelease, fleetErr := installChart(cf, fleetChart, "fleet", "fleet-system", nil) if fleetErr != nil { return fleetErr } - l.Infof("Successfully installed '%s' to namespace '%s'", fleetRelease.Name, fleetRelease.Namespace) + logger.Infof("Successfully installed '%s' to namespace '%s'", fleetRelease.Name, fleetRelease.Namespace) + logger.Successf("Completed post boot") return nil } diff --git a/pkg/log/log.go b/pkg/log/log.go index 7121ecb..09bd11c 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -1,7 +1,7 @@ package log import ( - "github.com/sirupsen/logrus" + "github.com/pterm/pterm" "io" ) @@ -10,12 +10,12 @@ type Logger interface { Infof(string, ...interface{}) Warnf(string, ...interface{}) Debugf(string, ...interface{}) - - WithFields(logrus.Fields) *logrus.Entry + Successf(string, ...interface{}) } type standardLogger struct { - *logrus.Logger + //TODO: Actually check this + level string } type Event struct { @@ -28,10 +28,42 @@ var ( ) func NewLogger(out io.Writer) *standardLogger { - logger := logrus.New() - logger.SetOutput(out) + return &standardLogger{} +} - return &standardLogger{logger} +func (l *standardLogger) Errorf(format string, args ...interface{}) { + l.logf("error", format, args...) +} + +func (l *standardLogger) Infof(format string, args ...interface{}) { + l.logf("info", format, args...) +} + +func (l *standardLogger) Warnf(format string, args ...interface{}) { + l.logf("warn", format, args...) +} + +func (l *standardLogger) Debugf(format string, args ...interface{}) { + l.logf("debug", format, args...) +} + +func (l *standardLogger) Successf(format string, args ...interface{}) { + l.logf("success", format, args...) +} + +func (l *standardLogger) logf(level string, format string, args ...interface{}) { + switch level { + case "debug": + pterm.Debug.Printfln(format, args...) + case "info": + pterm.Info.Printfln(format, args...) + case "warn": + pterm.Warning.Printfln(format, args...) + case "success": + pterm.Success.Printfln(format, args...) + default: + pterm.Error.Printfln("%s is not a valid log level", level) + } } func (l *standardLogger) InvalidArg(arg string) { From 84a76d5b0882bc7723106a1c6e9707e7d9b8a4d3 Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Wed, 16 Jun 2021 07:22:59 -0600 Subject: [PATCH 03/11] add basic logging --- .gitignore | 1 + cmd/haulerctl/app/pkg_build.go | 16 +++---- cmd/haulerctl/app/pkg_build_test.go | 20 +++++++- cmd/haulerctl/app/pkg_run.go | 8 ++-- cmd/haulerctl/app/root.go | 16 ++++--- pkg/apis/hauler.cattle.io/v1alpha1/fleet.go | 19 ++++++-- pkg/bootstrap/booter.go | 51 ++++++++++++--------- pkg/bootstrap/kube.go | 6 +-- pkg/fs/fs.go | 19 +++----- pkg/packager/packager.go | 35 +++++++++++--- testdata/flt/fleet.yaml | 2 + 11 files changed, 123 insertions(+), 70 deletions(-) create mode 100644 testdata/flt/fleet.yaml diff --git a/.gitignore b/.gitignore index c6e4fb0..b3b1d8d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ dist/ ./bundle/ tmp/ bin/ +pkg.yaml diff --git a/cmd/haulerctl/app/pkg_build.go b/cmd/haulerctl/app/pkg_build.go index 536d861..825fc62 100644 --- a/cmd/haulerctl/app/pkg_build.go +++ b/cmd/haulerctl/app/pkg_build.go @@ -5,7 +5,6 @@ import ( "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/packager" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" @@ -67,7 +66,7 @@ func NewPkgBuildCommand() *cobra.Command { func (o *pkgBuildOpts) PreRun() error { _, err := os.Stat(o.cfgFile) if os.IsNotExist(err) { - logrus.Infof("Could not find %s, creating one", o.cfgFile) + o.logger.Warnf("Did not find an existing %s, creating one", o.cfgFile) p := v1alpha1.Package{ TypeMeta: metav1.TypeMeta{ Kind: "", @@ -97,11 +96,10 @@ func (o *pkgBuildOpts) PreRun() error { if err := os.WriteFile(o.cfgFile, data, 0644); err != nil { return err } - } - - if err != nil { + } else if err != nil { return err } + return nil } @@ -129,6 +127,10 @@ func (o *pkgBuildOpts) Run() error { pkgr := packager.NewPackager(tmpdir, o.logger) d := driver.NewDriver(p.Spec.Driver) + if _, bErr := pkgr.PackageBundles(ctx, p.Spec.Paths...); bErr != nil { + return bErr + } + if dErr := pkgr.PackageDriver(ctx, d); dErr != nil { return dErr } @@ -137,10 +139,6 @@ func (o *pkgBuildOpts) Run() error { return fErr } - if _, bErr := pkgr.PackageBundles(ctx, p.Spec.Paths...); bErr != nil { - return bErr - } - a := packager.NewArchiver() if aErr := pkgr.Archive(a, p, o.name); aErr != nil { return aErr diff --git a/cmd/haulerctl/app/pkg_build_test.go b/cmd/haulerctl/app/pkg_build_test.go index 03b7b79..3f73f18 100644 --- a/cmd/haulerctl/app/pkg_build_test.go +++ b/cmd/haulerctl/app/pkg_build_test.go @@ -25,14 +25,14 @@ func Test_pkgBuildOpts_Run(t *testing.T) { wantErr bool }{ { - name: "should work", + name: "should package all types of local manifests", fields: fields{ rootOpts: &tro, cfgFile: "pkg.yaml", name: "k3s", driver: "k3s", driverVersion: "v1.21.1+k3s1", - fleetVersion: "0.3.5", + fleetVersion: "v0.3.5", images: nil, paths: []string{ "../../../testdata/docker-registry", @@ -41,6 +41,22 @@ func Test_pkgBuildOpts_Run(t *testing.T) { }, wantErr: false, }, + { + name: "should package using fleet.yaml", + fields: fields{ + rootOpts: &tro, + cfgFile: "pkg.yaml", + name: "k3s", + driver: "k3s", + driverVersion: "v1.21.1+k3s1", + fleetVersion: "v0.3.5", + images: nil, + paths: []string{ + "../../../testdata/flt", + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/haulerctl/app/pkg_run.go b/cmd/haulerctl/app/pkg_run.go index 265c2e0..cce6936 100644 --- a/cmd/haulerctl/app/pkg_run.go +++ b/cmd/haulerctl/app/pkg_run.go @@ -60,22 +60,22 @@ func (o *pkgRunOpts) Run(pkgPath string) error { } o.logger.Debugf("Unpackaged %s", pkgPath) - b, err := bootstrap.NewBooter(tmpdir) + b, err := bootstrap.NewBooter(tmpdir, o.logger) if err != nil { return err } d := driver.NewDriver(b.Package.Spec.Driver) - if preErr := b.PreBoot(ctx, d, o.logger); preErr != nil { + if preErr := b.PreBoot(ctx, d); preErr != nil { return preErr } - if bErr := b.Boot(ctx, d, o.logger); bErr != nil { + if bErr := b.Boot(ctx, d); bErr != nil { return bErr } - if postErr := b.PostBoot(ctx, d, o.logger); postErr != nil { + if postErr := b.PostBoot(ctx, d); postErr != nil { return postErr } diff --git a/cmd/haulerctl/app/root.go b/cmd/haulerctl/app/root.go index f88257b..41ecbc8 100644 --- a/cmd/haulerctl/app/root.go +++ b/cmd/haulerctl/app/root.go @@ -26,13 +26,15 @@ var ( getExample = ` # Run Hauler - haulerctl bundle images - haulerctl bundle artifacts - haulerctl relocate artifacts -i - haulerctl relocate images -i locahost:5000 - haulerctl copy - haulerctl create - haulerctl bootstrap` + hauler pkg build + hauler pkg run pkg.tar.zst + hauler bundle images + hauler bundle artifacts + hauler relocate artifacts -i + hauler relocate images -i locahost:5000 + hauler copy + hauler create + hauler bootstrap` ) type rootOpts struct { diff --git a/pkg/apis/hauler.cattle.io/v1alpha1/fleet.go b/pkg/apis/hauler.cattle.io/v1alpha1/fleet.go index a69787b..9f7e590 100644 --- a/pkg/apis/hauler.cattle.io/v1alpha1/fleet.go +++ b/pkg/apis/hauler.cattle.io/v1alpha1/fleet.go @@ -1,6 +1,9 @@ package v1alpha1 -import "fmt" +import ( + "fmt" + "strings" +) //Fleet is used as the deployment engine for all things Hauler type Fleet struct { @@ -10,12 +13,20 @@ type Fleet struct { //TODO: These should be identified from the chart version func (f Fleet) Images() ([]string, error) { - return []string{"rancher/gitjob:v0.1.15", "rancher/fleet:v0.3.5", "rancher/fleet-agent:v0.3.5"}, nil + return []string{ + fmt.Sprintf("rancher/gitjob:v0.1.15"), + fmt.Sprintf("rancher/fleet:%s", f.Version), + fmt.Sprintf("rancher/fleet-agent:%s", f.Version), + }, nil } func (f Fleet) CRDChart() string { - return fmt.Sprintf("https://github.com/rancher/fleet/releases/download/v0.3.5/fleet-crd-%s.tgz", f.Version) + return fmt.Sprintf("https://github.com/rancher/fleet/releases/download/%s/fleet-crd-%s.tgz", f.Version, f.VLess()) } func (f Fleet) Chart() string { - return fmt.Sprintf("https://github.com/rancher/fleet/releases/download/v0.3.5/fleet-%s.tgz", f.Version) + return fmt.Sprintf("https://github.com/rancher/fleet/releases/download/%s/fleet-%s.tgz", f.Version, f.VLess()) +} + +func (f Fleet) VLess() string { + return strings.ReplaceAll(f.Version, "v", "") } diff --git a/pkg/bootstrap/booter.go b/pkg/bootstrap/booter.go index 214c91b..4ba80a9 100644 --- a/pkg/bootstrap/booter.go +++ b/pkg/bootstrap/booter.go @@ -27,10 +27,12 @@ type Booter interface { type booter struct { Package v1alpha1.Package fs fs.PkgFs + + logger log.Logger } //NewBooter will build a new booter given a path to a directory containing a hauler package.json -func NewBooter(pkgPath string) (*booter, error) { +func NewBooter(pkgPath string, logger log.Logger) (*booter, error) { pkg, err := v1alpha1.LoadPackageFromDir(pkgPath) if err != nil { return nil, err @@ -41,13 +43,19 @@ func NewBooter(pkgPath string) (*booter, error) { return &booter{ Package: pkg, fs: fsys, + logger: logger, }, nil } -func (b booter) PreBoot(ctx context.Context, d driver.Driver, logger log.Logger) error { - logger.Infof("Beginning pre boot") +func (b booter) PreBoot(ctx context.Context, d driver.Driver) error { + b.logger.Infof("Beginning pre boot") + + //TODO: Feel like there's a better way to do all this dir creation + + if err := os.MkdirAll(d.DataPath(), os.ModePerm); err != nil { + return err + } - //TODO: Feel like there's a better way to do this if err := b.moveBin(); err != nil { return err } @@ -64,18 +72,17 @@ func (b booter) PreBoot(ctx context.Context, d driver.Driver, logger log.Logger) return err } - logger.Debugf("Writing %s config", d.Name()) + b.logger.Debugf("Writing %s config", d.Name()) if err := d.WriteConfig(); err != nil { return err } - logger.Successf("Completed pre boot") - + b.logger.Successf("Completed pre boot") return nil } -func (b booter) Boot(ctx context.Context, d driver.Driver, logger log.Logger) error { - logger.Infof("Beginning boot") +func (b booter) Boot(ctx context.Context, d driver.Driver) error { + b.logger.Infof("Beginning boot") var stdoutBuf, stderrBuf bytes.Buffer out := io.MultiWriter(os.Stdout, &stdoutBuf, &stderrBuf) @@ -85,51 +92,51 @@ func (b booter) Boot(ctx context.Context, d driver.Driver, logger log.Logger) er return err } - logger.Infof("Waiting for driver core components to provision...") + b.logger.Infof("Waiting for driver core components to provision...") waitErr := waitForDriver(ctx, d) if waitErr != nil { return err } - logger.Successf("Completed boot") + b.logger.Successf("Completed boot") return nil } -func (b booter) PostBoot(ctx context.Context, d driver.Driver, logger log.Logger) error { - logger.Infof("Beginning post boot") +func (b booter) PostBoot(ctx context.Context, d driver.Driver) error { + b.logger.Infof("Beginning post boot") cf := genericclioptions.NewConfigFlags(true) cf.KubeConfig = stringptr(d.KubeConfigPath()) - fleetCrdChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-crd-%s.tgz", b.Package.Spec.Fleet.Version)) + fleetCrdChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-crd-%s.tgz", b.Package.Spec.Fleet.VLess())) fleetCrdChart, err := loader.Load(fleetCrdChartPath) if err != nil { return err } - logger.Infof("Installing fleet crds") - fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", "fleet-system", nil) + b.logger.Infof("Installing fleet crds") + fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", "fleet-system", nil, b.logger) if fleetCrdErr != nil { return fleetCrdErr } - logger.Infof("Successfully installed '%s' to namespace '%s'", fleetCrdRelease.Name, fleetCrdRelease.Namespace) + b.logger.Infof("Installed '%s' to namespace '%s'", fleetCrdRelease.Name, fleetCrdRelease.Namespace) - fleetChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-%s.tgz", b.Package.Spec.Fleet.Version)) + fleetChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-%s.tgz", b.Package.Spec.Fleet.VLess())) fleetChart, err := loader.Load(fleetChartPath) if err != nil { return err } - logger.Infof("Installing fleet") - fleetRelease, fleetErr := installChart(cf, fleetChart, "fleet", "fleet-system", nil) + b.logger.Infof("Installing fleet") + fleetRelease, fleetErr := installChart(cf, fleetChart, "fleet", "fleet-system", nil, b.logger) if fleetErr != nil { return fleetErr } - logger.Infof("Successfully installed '%s' to namespace '%s'", fleetRelease.Name, fleetRelease.Namespace) + b.logger.Infof("Installed '%s' to namespace '%s'", fleetRelease.Name, fleetRelease.Namespace) - logger.Successf("Completed post boot") + b.logger.Successf("Completed post boot") return nil } diff --git a/pkg/bootstrap/kube.go b/pkg/bootstrap/kube.go index 27d3e6c..14c4898 100644 --- a/pkg/bootstrap/kube.go +++ b/pkg/bootstrap/kube.go @@ -5,7 +5,7 @@ import ( "errors" "github.com/rancherfederal/hauler/pkg/driver" "github.com/rancherfederal/hauler/pkg/kube" - log "github.com/sirupsen/logrus" + "github.com/rancherfederal/hauler/pkg/log" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/release" @@ -46,9 +46,9 @@ func waitForDriver(ctx context.Context, d driver.Driver) error { } //TODO: This is likely way too fleet specific -func installChart(cf *genericclioptions.ConfigFlags, chart *chart.Chart, releaseName, namespace string, vals map[string]interface{}) (*release.Release, error) { +func installChart(cf *genericclioptions.ConfigFlags, chart *chart.Chart, releaseName, namespace string, vals map[string]interface{}, logger log.Logger) (*release.Release, error) { actionConfig := new(action.Configuration) - if err := actionConfig.Init(cf, namespace, os.Getenv("HELM_DRIVER"), log.Debugf); err != nil { + if err := actionConfig.Init(cf, namespace, os.Getenv("HELM_DRIVER"), logger.Debugf); err != nil { return nil, err } diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go index 4c7c266..e642c9a 100644 --- a/pkg/fs/fs.go +++ b/pkg/fs/fs.go @@ -70,33 +70,26 @@ func (p PkgFs) Chart() PkgFs { } //AddBundle will add a bundle to a package and all images that are autodetected from it -func (p PkgFs) AddBundle(b *fleetapi.Bundle) error { +func (p PkgFs) AddBundle(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) { if err := p.mkdirIfNotExists(v1alpha1.BundlesDir, os.ModePerm); err != nil { - return err + return nil, err } data, err := json.Marshal(b) if err != nil { - return err + return nil, err } if err := p.Bundle().WriteFile(fmt.Sprintf("%s.json", b.Name), data, 0644); err != nil { - return err + return nil, err } imgs, err := images.ImageMapFromBundle(b) if err != nil { - return err + return nil, err } - for k, v := range imgs { - err := p.AddImage(k, v) - if err != nil { - return err - } - } - - return nil + return imgs, nil } func (p PkgFs) AddBin(r io.Reader, name string) error { diff --git a/pkg/packager/packager.go b/pkg/packager/packager.go index 5bf455c..60e6ead 100644 --- a/pkg/packager/packager.go +++ b/pkg/packager/packager.go @@ -55,10 +55,16 @@ func (p pkg) Archive(a Archiver, pkg v1alpha1.Package, output string) error { } func (p pkg) PackageBundles(ctx context.Context, path ...string) ([]*fleetapi.Bundle, error) { + p.logger.Infof("Packaging %d bundle(s)", len(path)) + opts := &bundle.Options{Compress: true} + var cImgs int + var bundles []*fleetapi.Bundle for _, pth := range path { + p.logger.Infof("Creating bundle from path: %s", pth) + bundleName := filepath.Base(pth) fb, err := bundle.Open(ctx, bundleName, pth, "", opts) if err != nil { @@ -67,17 +73,27 @@ func (p pkg) PackageBundles(ctx context.Context, path ...string) ([]*fleetapi.Bu //TODO: Figure out why bundle.Open doesn't return with GVK bn := fleetapi.NewBundle("fleet-local", bundleName, *fb.Definition) - if err := p.fs.AddBundle(bn); err != nil { + imgs, err := p.fs.AddBundle(bn) + if err != nil { + return nil, err + } + + if err := p.PackageImages(ctx, imgs); err != nil { return nil, err } bundles = append(bundles, bn) + cImgs += len(imgs) } + p.logger.Successf("Finished packaging %d bundle(s) along with %d autodetected image(s)", len(path), cImgs) return bundles, nil } func (p pkg) PackageDriver(ctx context.Context, d driver.Driver) error { + p.logger.Infof("Packaging %s components", d.Name()) + + p.logger.Infof("Adding %s executable to package", d.Name()) rc, err := d.Binary() if err != nil { return err @@ -88,6 +104,7 @@ func (p pkg) PackageDriver(ctx context.Context, d driver.Driver) error { } rc.Close() + p.logger.Infof("Adding required images for %s to package", d.Name()) imgMap, err := d.Images(ctx) if err != nil { return err @@ -98,39 +115,45 @@ func (p pkg) PackageDriver(ctx context.Context, d driver.Driver) error { return err } + p.logger.Successf("Finished packaging %s components", d.Name()) return nil } func (p pkg) PackageImages(ctx context.Context, imgMap map[name.Reference]v1.Image) error { + var i int for ref, im := range imgMap { + p.logger.Infof("Packaging image (%d/%d): %s", i+1, len(imgMap), ref.Name()) if err := p.fs.AddImage(ref, im); err != nil { return err } + i++ } return nil } //TODO: Add this to PackageDriver? func (p pkg) PackageFleet(ctx context.Context, fl v1alpha1.Fleet) error { + p.logger.Infof("Packaging fleet components") + imgMap, err := images.MapImager(fl) if err != nil { return err } - for ref, im := range imgMap { - err := p.fs.AddImage(ref, im) - if err != nil { - return err - } + if err := p.PackageImages(ctx, imgMap); err != nil { + return err } + p.logger.Infof("Adding fleet crds to package") if err := p.fs.AddChart(fl.CRDChart(), fl.Version); err != nil { return err } + p.logger.Infof("Adding fleet to package") if err := p.fs.AddChart(fl.Chart(), fl.Version); err != nil { return err } + p.logger.Successf("Finished packaging fleet components") return nil } diff --git a/testdata/flt/fleet.yaml b/testdata/flt/fleet.yaml new file mode 100644 index 0000000..cf3fd63 --- /dev/null +++ b/testdata/flt/fleet.yaml @@ -0,0 +1,2 @@ +helm: + chart: https://github.com/rancher/fleet/releases/download/v0.3.5/fleet-agent-0.3.5.tgz \ No newline at end of file From 968eba0a8b3e86ff133becfa6cedccda67cae77c Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Wed, 16 Jun 2021 09:34:23 -0600 Subject: [PATCH 04/11] ensure templating works for bundles identified through fleet.yaml --- cmd/haulerctl/app/pkg_build_test.go | 2 +- go.mod | 8 +------- go.sum | 28 ++++++++-------------------- pkg/packager/images/images.go | 24 +++++++++++++++++++----- pkg/packager/packager.go | 4 +++- testdata/custom/fleet.yaml | 8 ++++++++ testdata/flt/fleet.yaml | 2 -- 7 files changed, 40 insertions(+), 36 deletions(-) create mode 100644 testdata/custom/fleet.yaml delete mode 100644 testdata/flt/fleet.yaml diff --git a/cmd/haulerctl/app/pkg_build_test.go b/cmd/haulerctl/app/pkg_build_test.go index 3f73f18..a638816 100644 --- a/cmd/haulerctl/app/pkg_build_test.go +++ b/cmd/haulerctl/app/pkg_build_test.go @@ -52,7 +52,7 @@ func Test_pkgBuildOpts_Run(t *testing.T) { fleetVersion: "v0.3.5", images: nil, paths: []string{ - "../../../testdata/flt", + "../../../testdata/custom", }, }, wantErr: false, diff --git a/go.mod b/go.mod index d325939..47cbd38 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,11 @@ go 1.16 require ( cloud.google.com/go/storage v1.8.0 // indirect github.com/Microsoft/go-winio v0.5.0 // indirect - github.com/Microsoft/hcsshim v0.8.16 // indirect - github.com/containerd/cgroups v1.0.1 // indirect github.com/containerd/containerd v1.5.0-beta.4 - github.com/containerd/continuity v0.1.0 // indirect github.com/deislabs/oras v0.11.1 github.com/docker/docker v20.10.6+incompatible // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect - github.com/google/go-containerregistry v0.4.1 - github.com/google/uuid v1.2.0 // indirect + github.com/google/go-containerregistry v0.5.1 github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/imdario/mergo v0.3.12 github.com/klauspost/compress v1.13.0 // indirect @@ -22,7 +18,6 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/otiai10/copy v1.6.0 - github.com/pelletier/go-toml v1.8.1 // indirect github.com/pterm/pterm v0.12.24 github.com/rancher/fleet v0.3.5 github.com/rancher/fleet/pkg/apis v0.0.0 @@ -32,7 +27,6 @@ require ( github.com/spf13/viper v1.7.0 github.com/ulikunitz/xz v0.5.10 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b // indirect - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect google.golang.org/genproto v0.0.0-20210524171403-669157292da3 // indirect google.golang.org/grpc v1.38.0 // indirect diff --git a/go.sum b/go.sum index e6bdfc4..4757a6c 100644 --- a/go.sum +++ b/go.sum @@ -135,9 +135,8 @@ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEY github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.10-0.20200715222032-5eafd1556990/go.mod h1:ay/0dTb7NsG8QMDfsRfLHgZo/6xAJShLe1+ePPflihk= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15 h1:Aof83YILRs2Vx3GhHqlvvfyx1asRJKMFIMeVlHsZKtI= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16 h1:8/auA4LFIZFTGrqfKhGBSXwM6/4X1fHa/xniyEHu8ac= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -285,7 +284,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= @@ -305,9 +303,8 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68 h1:hkGVFjz+plgr5UfxZUTPFbUFIF/Km6/s+RVRIRHLrrY= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= @@ -333,9 +330,8 @@ github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e h1:6JKvHHt396/qabvMhnhUZvWaHZzfVfldxE60TK8YLhg= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -502,7 +498,6 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTg github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -724,8 +719,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-containerregistry v0.4.1 h1:Lrcj2AOoZ7WKawsoKAh2O0dH0tBqMW2lTEmozmK4Z3k= -github.com/google/go-containerregistry v0.4.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/go-containerregistry v0.5.1 h1:/+mFTs4AlwsJ/mJe8NDtKb7BxLtbZFpcn8vDsneEkwQ= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github/v28 v28.0.0/go.mod h1:+5GboIspo7F0NG2qsvfYh7en6F3EK37uyqv+c35AR3s= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= @@ -750,9 +745,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -1193,9 +1187,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -1555,10 +1548,8 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1652,8 +1643,6 @@ golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= @@ -1769,7 +1758,6 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= diff --git a/pkg/packager/images/images.go b/pkg/packager/images/images.go index 93997d1..bcd0dac 100644 --- a/pkg/packager/images/images.go +++ b/pkg/packager/images/images.go @@ -50,14 +50,27 @@ func MapImager(imager ...Imager) (map[name.Reference]v1.Image, error) { } func ImageMapFromBundle(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) { - opts := fleetapi.BundleDeploymentOptions{ - DefaultNamespace: "default", + opts := fleetapi.BundleDeploymentOptions{} + + if b.Spec.Helm != nil { + opts.Helm = &fleetapi.HelmOptions{ + Chart: b.Spec.Helm.Chart, + Repo: b.Spec.Helm.Repo, + ReleaseName: b.Spec.Helm.ReleaseName, + Version: b.Spec.Helm.Version, + Values: b.Spec.Helm.Values, + ValuesFrom: b.Spec.Helm.ValuesFrom, + ValuesFiles: b.Spec.Helm.ValuesFiles, + } } - m := &manifest.Manifest{Resources: b.Spec.Resources} + m, err := manifest.New(&b.Spec) + if err != nil { + return nil, err + } //TODO: I think this is right? - objs, err := helmdeployer.Template("anything", m, opts) + objs, err := helmdeployer.Template(b.Name, m, opts) if err != nil { return nil, err } @@ -144,6 +157,7 @@ func parseJSONPath(input interface{}, parser *jsonpath.JSONPath, template string return nil, err } - r := strings.Split(buf.String(), " ") + f := func(s rune) bool { return s == ' ' } + r := strings.FieldsFunc(buf.String(), f) return r, nil } diff --git a/pkg/packager/packager.go b/pkg/packager/packager.go index 60e6ead..9b6eb0e 100644 --- a/pkg/packager/packager.go +++ b/pkg/packager/packager.go @@ -57,7 +57,9 @@ func (p pkg) Archive(a Archiver, pkg v1alpha1.Package, output string) error { func (p pkg) PackageBundles(ctx context.Context, path ...string) ([]*fleetapi.Bundle, error) { p.logger.Infof("Packaging %d bundle(s)", len(path)) - opts := &bundle.Options{Compress: true} + opts := &bundle.Options{ + Compress: true, + } var cImgs int diff --git a/testdata/custom/fleet.yaml b/testdata/custom/fleet.yaml new file mode 100644 index 0000000..69c204f --- /dev/null +++ b/testdata/custom/fleet.yaml @@ -0,0 +1,8 @@ +#defaultNamespace: fleet-system +#helm: +# chart: https://github.com/rancher/fleet/releases/download/v0.3.5/fleet-agent-0.3.5.tgz +# releaseName: fleet-agent + +helm: + repo: https://charts.longhorn.io + chart: longhorn \ No newline at end of file diff --git a/testdata/flt/fleet.yaml b/testdata/flt/fleet.yaml deleted file mode 100644 index cf3fd63..0000000 --- a/testdata/flt/fleet.yaml +++ /dev/null @@ -1,2 +0,0 @@ -helm: - chart: https://github.com/rancher/fleet/releases/download/v0.3.5/fleet-agent-0.3.5.tgz \ No newline at end of file From 54fe2606482d7888ab140301d91aafde8202f1d4 Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Wed, 16 Jun 2021 09:48:15 -0600 Subject: [PATCH 05/11] add fleet options slightly differently --- pkg/packager/images/images.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/packager/images/images.go b/pkg/packager/images/images.go index bcd0dac..1004817 100644 --- a/pkg/packager/images/images.go +++ b/pkg/packager/images/images.go @@ -52,16 +52,17 @@ func MapImager(imager ...Imager) (map[name.Reference]v1.Image, error) { func ImageMapFromBundle(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) { opts := fleetapi.BundleDeploymentOptions{} + //TODO: Why doesn't fleet do this... if b.Spec.Helm != nil { - opts.Helm = &fleetapi.HelmOptions{ - Chart: b.Spec.Helm.Chart, - Repo: b.Spec.Helm.Repo, - ReleaseName: b.Spec.Helm.ReleaseName, - Version: b.Spec.Helm.Version, - Values: b.Spec.Helm.Values, - ValuesFrom: b.Spec.Helm.ValuesFrom, - ValuesFiles: b.Spec.Helm.ValuesFiles, - } + opts.Helm = b.Spec.Helm + } + + if b.Spec.Kustomize != nil { + opts.Kustomize = b.Spec.Kustomize + } + + if b.Spec.YAML != nil { + opts.YAML = b.Spec.YAML } m, err := manifest.New(&b.Spec) From 3c043915e49e701a1c6e9e5a9aa08de6603cb23f Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Wed, 16 Jun 2021 15:43:44 -0600 Subject: [PATCH 06/11] add user defined images to packages, make cli args and config file mutually exclusive --- cmd/haulerctl/app/pkg_build.go | 76 +++++++++++++++++++++------------- pkg/packager/packager.go | 40 ++++++++++++------ 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/cmd/haulerctl/app/pkg_build.go b/cmd/haulerctl/app/pkg_build.go index 825fc62..9ab71fe 100644 --- a/cmd/haulerctl/app/pkg_build.go +++ b/cmd/haulerctl/app/pkg_build.go @@ -47,7 +47,7 @@ func NewPkgBuildCommand() *cobra.Command { f := cmd.PersistentFlags() f.StringVarP(&opts.name, "name", "n", "pkg", "name of the pkg to create, will dicate file name") - f.StringVarP(&opts.cfgFile, "config", "c", "./pkg.yaml", + f.StringVarP(&opts.cfgFile, "config", "c", "", "path to config file") f.StringVarP(&opts.driver, "driver", "d", "k3s", "") @@ -66,28 +66,13 @@ func NewPkgBuildCommand() *cobra.Command { func (o *pkgBuildOpts) PreRun() error { _, err := os.Stat(o.cfgFile) if os.IsNotExist(err) { - o.logger.Warnf("Did not find an existing %s, creating one", o.cfgFile) - p := v1alpha1.Package{ - TypeMeta: metav1.TypeMeta{ - Kind: "", - APIVersion: "", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: o.name, - }, - Spec: v1alpha1.PackageSpec{ - Fleet: v1alpha1.Fleet{ - Version: o.fleetVersion, - }, - Driver: v1alpha1.Driver{ - Type: o.driver, - Version: o.driverVersion, - }, - Paths: o.paths, - Images: o.images, - }, + if o.cfgFile == "" { + return nil } + o.logger.Warnf("Did not find an existing %s, creating one", o.cfgFile) + p := o.toPackage() + data, err := yaml.Marshal(p) if err != nil { return err @@ -109,14 +94,21 @@ func (o *pkgBuildOpts) Run() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - cfgData, err := os.ReadFile(o.cfgFile) - if err != nil { - return err - } - var p v1alpha1.Package - if err := yaml.Unmarshal(cfgData, &p); err != nil { - return err + if o.cfgFile != "" { + o.logger.Infof("Config file '%s' specified, attempting to load existing package config", o.cfgFile) + cfgData, err := os.ReadFile(o.cfgFile) + if err != nil { + return err + } + + if err := yaml.Unmarshal(cfgData, &p); err != nil { + return err + } + + } else { + o.logger.Infof("No config file specified, strictly using cli arguments") + p = o.toPackage() } tmpdir, err := os.MkdirTemp("", "hauler") @@ -131,6 +123,10 @@ func (o *pkgBuildOpts) Run() error { return bErr } + if iErr := pkgr.PackageImages(ctx, o.images...); iErr != nil { + return iErr + } + if dErr := pkgr.PackageDriver(ctx, d); dErr != nil { return dErr } @@ -147,3 +143,27 @@ func (o *pkgBuildOpts) Run() error { o.logger.Successf("Finished building package") return nil } + +func (o *pkgBuildOpts) toPackage() v1alpha1.Package { + p := v1alpha1.Package{ + TypeMeta: metav1.TypeMeta{ + Kind: "", + APIVersion: "", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: o.name, + }, + Spec: v1alpha1.PackageSpec{ + Fleet: v1alpha1.Fleet{ + Version: o.fleetVersion, + }, + Driver: v1alpha1.Driver{ + Type: o.driver, + Version: o.driverVersion, + }, + Paths: o.paths, + Images: o.images, + }, + } + return p +} diff --git a/pkg/packager/packager.go b/pkg/packager/packager.go index 9b6eb0e..e0430ba 100644 --- a/pkg/packager/packager.go +++ b/pkg/packager/packager.go @@ -24,7 +24,7 @@ type Packager interface { PackageFleet(context.Context, v1alpha1.Fleet) error - PackageImages(context.Context, map[name.Reference]v1.Image) error + PackageImages(context.Context, ...string) error } type pkg struct { @@ -80,7 +80,7 @@ func (p pkg) PackageBundles(ctx context.Context, path ...string) ([]*fleetapi.Bu return nil, err } - if err := p.PackageImages(ctx, imgs); err != nil { + if err := p.pkgImages(ctx, imgs); err != nil { return nil, err } @@ -112,7 +112,7 @@ func (p pkg) PackageDriver(ctx context.Context, d driver.Driver) error { return err } - err = p.PackageImages(ctx, imgMap) + err = p.pkgImages(ctx, imgMap) if err != nil { return err } @@ -121,15 +121,18 @@ func (p pkg) PackageDriver(ctx context.Context, d driver.Driver) error { return nil } -func (p pkg) PackageImages(ctx context.Context, imgMap map[name.Reference]v1.Image) error { - var i int - for ref, im := range imgMap { - p.logger.Infof("Packaging image (%d/%d): %s", i+1, len(imgMap), ref.Name()) - if err := p.fs.AddImage(ref, im); err != nil { - return err - } - i++ +func (p pkg) PackageImages(ctx context.Context, imgs ...string) error { + p.logger.Infof("Packaging %d user defined images", len(imgs)) + imgMap, err := images.ResolveRemoteRefs(imgs...) + if err != nil { + return err } + + if err := p.pkgImages(ctx, imgMap); err != nil { + return err + } + + p.logger.Successf("Finished packaging %d user defined images", len(imgs)) return nil } @@ -142,7 +145,7 @@ func (p pkg) PackageFleet(ctx context.Context, fl v1alpha1.Fleet) error { return err } - if err := p.PackageImages(ctx, imgMap); err != nil { + if err := p.pkgImages(ctx, imgMap); err != nil { return err } @@ -159,3 +162,16 @@ func (p pkg) PackageFleet(ctx context.Context, fl v1alpha1.Fleet) error { p.logger.Successf("Finished packaging fleet components") return nil } + +//pkgImages is a helper function to loop through an image map and add it to a layout +func (p pkg) pkgImages(ctx context.Context, imgMap map[name.Reference]v1.Image) error { + var i int + for ref, im := range imgMap { + p.logger.Infof("Packaging image (%d/%d): %s", i+1, len(imgMap), ref.Name()) + if err := p.fs.AddImage(ref, im); err != nil { + return err + } + i++ + } + return nil +} From b8b8a231f0a8295ef9a54ce814d3e1de8d3a12c9 Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Wed, 16 Jun 2021 15:45:21 -0600 Subject: [PATCH 07/11] rename haulerctl to just hauler --- .goreleaser.yaml | 2 +- cmd/{haulerctl => hauler}/app/bundle.go | 0 cmd/{haulerctl => hauler}/app/bundle_artifacts.go | 0 cmd/{haulerctl => hauler}/app/copy.go | 2 +- cmd/{haulerctl => hauler}/app/oci.go | 0 cmd/{haulerctl => hauler}/app/oci_pull.go | 0 cmd/{haulerctl => hauler}/app/oci_push.go | 0 cmd/{haulerctl => hauler}/app/pkg.go | 0 cmd/{haulerctl => hauler}/app/pkg_build.go | 0 cmd/{haulerctl => hauler}/app/pkg_build_test.go | 0 cmd/{haulerctl => hauler}/app/pkg_run.go | 0 cmd/{haulerctl => hauler}/app/relocate.go | 0 cmd/{haulerctl => hauler}/app/relocate_artifacts.go | 0 cmd/{haulerctl => hauler}/app/relocate_images.go | 0 cmd/{haulerctl => hauler}/app/root.go | 8 ++++---- cmd/{haulerctl => hauler}/main.go | 2 +- 16 files changed, 7 insertions(+), 7 deletions(-) rename cmd/{haulerctl => hauler}/app/bundle.go (100%) rename cmd/{haulerctl => hauler}/app/bundle_artifacts.go (100%) rename cmd/{haulerctl => hauler}/app/copy.go (95%) rename cmd/{haulerctl => hauler}/app/oci.go (100%) rename cmd/{haulerctl => hauler}/app/oci_pull.go (100%) rename cmd/{haulerctl => hauler}/app/oci_push.go (100%) rename cmd/{haulerctl => hauler}/app/pkg.go (100%) rename cmd/{haulerctl => hauler}/app/pkg_build.go (100%) rename cmd/{haulerctl => hauler}/app/pkg_build_test.go (100%) rename cmd/{haulerctl => hauler}/app/pkg_run.go (100%) rename cmd/{haulerctl => hauler}/app/relocate.go (100%) rename cmd/{haulerctl => hauler}/app/relocate_artifacts.go (100%) rename cmd/{haulerctl => hauler}/app/relocate_images.go (100%) rename cmd/{haulerctl => hauler}/app/root.go (90%) rename cmd/{haulerctl => hauler}/main.go (72%) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index f0eb81a..a64cc5e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,6 +1,6 @@ project_name: hauler builds: - - main: cmd/haulerctl/main.go + - main: cmd/hauler/main.go goos: - linux - darwin diff --git a/cmd/haulerctl/app/bundle.go b/cmd/hauler/app/bundle.go similarity index 100% rename from cmd/haulerctl/app/bundle.go rename to cmd/hauler/app/bundle.go diff --git a/cmd/haulerctl/app/bundle_artifacts.go b/cmd/hauler/app/bundle_artifacts.go similarity index 100% rename from cmd/haulerctl/app/bundle_artifacts.go rename to cmd/hauler/app/bundle_artifacts.go diff --git a/cmd/haulerctl/app/copy.go b/cmd/hauler/app/copy.go similarity index 95% rename from cmd/haulerctl/app/copy.go rename to cmd/hauler/app/copy.go index afd61e8..ee02283 100644 --- a/cmd/haulerctl/app/copy.go +++ b/cmd/hauler/app/copy.go @@ -14,7 +14,7 @@ type copyOpts struct { } // NewCopyCommand creates a new sub command under -// haulerctl for coping files to local disk +// hauler for coping files to local disk func NewCopyCommand() *cobra.Command { opts := ©Opts{} diff --git a/cmd/haulerctl/app/oci.go b/cmd/hauler/app/oci.go similarity index 100% rename from cmd/haulerctl/app/oci.go rename to cmd/hauler/app/oci.go diff --git a/cmd/haulerctl/app/oci_pull.go b/cmd/hauler/app/oci_pull.go similarity index 100% rename from cmd/haulerctl/app/oci_pull.go rename to cmd/hauler/app/oci_pull.go diff --git a/cmd/haulerctl/app/oci_push.go b/cmd/hauler/app/oci_push.go similarity index 100% rename from cmd/haulerctl/app/oci_push.go rename to cmd/hauler/app/oci_push.go diff --git a/cmd/haulerctl/app/pkg.go b/cmd/hauler/app/pkg.go similarity index 100% rename from cmd/haulerctl/app/pkg.go rename to cmd/hauler/app/pkg.go diff --git a/cmd/haulerctl/app/pkg_build.go b/cmd/hauler/app/pkg_build.go similarity index 100% rename from cmd/haulerctl/app/pkg_build.go rename to cmd/hauler/app/pkg_build.go diff --git a/cmd/haulerctl/app/pkg_build_test.go b/cmd/hauler/app/pkg_build_test.go similarity index 100% rename from cmd/haulerctl/app/pkg_build_test.go rename to cmd/hauler/app/pkg_build_test.go diff --git a/cmd/haulerctl/app/pkg_run.go b/cmd/hauler/app/pkg_run.go similarity index 100% rename from cmd/haulerctl/app/pkg_run.go rename to cmd/hauler/app/pkg_run.go diff --git a/cmd/haulerctl/app/relocate.go b/cmd/hauler/app/relocate.go similarity index 100% rename from cmd/haulerctl/app/relocate.go rename to cmd/hauler/app/relocate.go diff --git a/cmd/haulerctl/app/relocate_artifacts.go b/cmd/hauler/app/relocate_artifacts.go similarity index 100% rename from cmd/haulerctl/app/relocate_artifacts.go rename to cmd/hauler/app/relocate_artifacts.go diff --git a/cmd/haulerctl/app/relocate_images.go b/cmd/hauler/app/relocate_images.go similarity index 100% rename from cmd/haulerctl/app/relocate_images.go rename to cmd/hauler/app/relocate_images.go diff --git a/cmd/haulerctl/app/root.go b/cmd/hauler/app/root.go similarity index 90% rename from cmd/haulerctl/app/root.go rename to cmd/hauler/app/root.go index 41ecbc8..b0823e3 100644 --- a/cmd/haulerctl/app/root.go +++ b/cmd/hauler/app/root.go @@ -18,7 +18,7 @@ var ( loglevel string timeout time.Duration - getLong = `haulerctl provides CLI-based air-gap migration assistance using k3s. + getLong = `hauler provides CLI-based air-gap migration assistance using k3s. Choose your functionality and new a package when internet access is available, then deploy the package into your air-gapped environment. @@ -43,11 +43,11 @@ type rootOpts struct { var ro rootOpts -// NewRootCommand defines the root haulerctl command +// NewRootCommand defines the root hauler command func NewRootCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "haulerctl", - Short: "haulerctl provides CLI-based air-gap migration assistance", + Use: "hauler", + Short: "hauler provides CLI-based air-gap migration assistance", Long: getLong, Example: getExample, SilenceUsage: true, diff --git a/cmd/haulerctl/main.go b/cmd/hauler/main.go similarity index 72% rename from cmd/haulerctl/main.go rename to cmd/hauler/main.go index 64e50c7..e3c533b 100644 --- a/cmd/haulerctl/main.go +++ b/cmd/hauler/main.go @@ -3,7 +3,7 @@ package main import ( "log" - "github.com/rancherfederal/hauler/cmd/haulerctl/app" + "github.com/rancherfederal/hauler/cmd/hauler/app" ) func main() { From 1136a6ecc3e6fc3b39c64718b750d3eefa19349f Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Thu, 17 Jun 2021 14:05:57 -0600 Subject: [PATCH 08/11] simplify readme down to current alpha capabilities with minimal examples --- README.md | 113 ++++++++++++++++++------------------ cmd/hauler/app/pkg.go | 3 +- cmd/hauler/app/pkg_build.go | 51 +++++++++++++--- cmd/hauler/app/pkg_run.go | 13 ++++- cmd/hauler/app/root.go | 4 +- pkg/log/log.go | 2 + 6 files changed, 114 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index fd842fc..880182f 100644 --- a/README.md +++ b/README.md @@ -2,66 +2,69 @@ ## WARNING - Work In Progress +Hauler is a tool designed to ease the burden of working with containers and kubernetes in an airgap. Several components of hauler are used in unison to provide airgap utilities. + +Hauler's utility is split into a few commands intended to solve increasingly complex airgapped use cases. + +__Portable self contained clusters__: + +Within the `hauler package` subset of commands, `Packages` (name to be finalized) can be created, updated, and ran. + +A `Package` is a hauler specific, configurable, self-contained, compressed archive (`*.tar.zst`) that contains all dependencies needed to 1) create a kubernetes cluster, 2) deploy resources into the cluster. + +```bash +# Build a minimal portable k8s cluster +hauler package build + +# Build a package that deploys resources when deployed +hauler package build -p path/to/chart -p path/to/manifests -i extra/image:latest -i busybox:musl + +# Build a package that deploys a cluster, oci registry, and sample app on boot +# Note the aliases introduced +hauler pkg b -p testdata/docker-registry -p testdata/rawmanifests +``` + +Hauler packages at their core stand on the shoulders of other technologies (`k3s`, `rke2`, and `fleet`), and as such, are designed to be extremely flexible. + +Common use cases are to build turn key, appliance like clusters designed to boot on disconnected or low powered devices. Or portable "utility" clusters that can act as a stepping stone for further downstream deployable infrastructure. Since ever `Package` is built as an entirely self contained archive, disconnected environments are _always_ a first class citizen. + +__Image Relocation__: + +For disconnected workloads that don't require a cluster to be created first, images can be efficiently packaged and relocated with `hauler relocate`. + +Images are stored as a compressed archive of an `oci` layout, ensuring only the required de-duplicated image layers are packaged and transferred. + +## Installation + +Hauler is and will always be a statically compiled binary, we strongly believe in a zero dependency tool is key to reducing operational complexity in airgap environments. + +Before GA, hauler can be downloaded from the releases page for every tagged release + +## Dev + +A `Vagrant` file is provided as a testing ground. The boot scripts at `vagrant-scripts/*.sh` will be ran on boot to ensure the dev environment is airgapped. + +```bash +vagrant up + +vagrant ssh +``` + +More info can be found in the [vagrant docs](VAGRANT.md). + +## WIP Warnings + API stability (including as a code library and as a network endpoint) is NOT guaranteed before `v1` API definitions and a 1.0 release. The following recommendations are made regarding usage patterns of hauler: - `alpha` (`v1alpha1`, `v1alpha2`, ...) API versions: use **_only_** through `haulerctl` - `beta` (`v1beta1`, `v1beta2`, ...) API versions: use as an **_experimental_** library and/or API endpoint - `stable` (`v1`, `v2`, ...) API versions: use as stable CLI tool, library, and/or API endpoint -## Purpose: collect, transfer, and self-host cloud-native artifacts - -Kubernetes-focused software usually relies on executables, archives, container images, helm charts, and more for installation. Collecting the dependencies, standing up tools for serving these artifacts, and mirroring them into the self-hosting solutions is usually a manual process with minimal automation. - -Hauler aims to fill this gap by standardizing low-level components of this stack and automating the collection and transfer of artifacts. - -## Usage - -Package a self contained deployable cluster - -```bash -# CLI -# bare k3s cluster -hauler create - -# k3s cluster with autodeployed manifests on boot -hauler create -p path/to/rawmanifests -p path/to/kustomizebase -p path/to/helmchart -i image - -# Config File -hauler create [-c ./package.yaml] -``` - -Bootstrap a cluster from a packaged archive - -```bash -hauler boot package.tar.zst -``` - -Relocate a set of images - -```bash -hauler save -p path/to/manifests -i image:tag -i image@sha256:... - -hauler relocate bundle.tar.zst airgap-registry:5000 -``` - -## Additional Details - -- [Roadmap](./ROADMAP.md) -- [Vagrant](./VAGRANT.md) - ### Build -To build hauler, the Go CLI v1.14 or higher is required. See for downloads and see for installation instructions. +```bash +# Current arch build +make build -To build hauler for your local machine (usually for the `package` step), run the following: - -```shell -mkdir bin -go build -o bin ./cmd/... -``` - -To build hauler for linux amd64 (required for the `deploy` step in an air-gapped environment), run the following: - -```shell -mkdir bin-linux-amd64 -GOOS=linux GOARCH=amd64 go build -o bin-linux-amd64 ./cmd/... -``` +# Multiarch dev build +goreleaser build --rm-dist --snapshot +``` \ No newline at end of file diff --git a/cmd/hauler/app/pkg.go b/cmd/hauler/app/pkg.go index c2ec986..b1a5391 100644 --- a/cmd/hauler/app/pkg.go +++ b/cmd/hauler/app/pkg.go @@ -11,8 +11,7 @@ func NewPkgCommand() *cobra.Command { cmd := &cobra.Command{ Use: "pkg", - Short: "", - Long: "", + Short: "Interact with packages", Aliases: []string{"p", "package"}, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() diff --git a/cmd/hauler/app/pkg_build.go b/cmd/hauler/app/pkg_build.go index 9ab71fe..10049be 100644 --- a/cmd/hauler/app/pkg_build.go +++ b/cmd/hauler/app/pkg_build.go @@ -17,6 +17,7 @@ type pkgBuildOpts struct { cfgFile string name string + dir string driver string driverVersion string @@ -32,9 +33,27 @@ func NewPkgBuildCommand() *cobra.Command { } cmd := &cobra.Command{ - Use: "build", - Short: "", - Long: "", + Use: "build", + Short: "Build a self contained compressed archive of manifests and images", + Long: ` +Compressed archives created with this command can be extracted and run anywhere the underlying 'driver' can be run. + +Archives are built by collecting all the dependencies (images and manifests) required. + +Examples: + + # Build a package containing a helm chart with images autodetected from the generated helm chart + hauler package build -p path/to/helm/chart + + # Build a package, sourcing from multiple manifest sources and additional images not autodetected + hauler pkg build -p path/to/raw/manifests -p path/to/kustomize -i busybox:latest -i busybox:musl + + # Build a package using a different version of k3s + hauler p build -p path/to/chart --driver-version "v1.20.6+k3s1" + + # Build a package from a config file (if ./pkg.yaml does not exist, one will be created) + hauler package build -c ./pkg.yaml +`, Aliases: []string{"b"}, PreRunE: func(cmd *cobra.Command, args []string) error { return opts.PreRun() @@ -49,16 +68,18 @@ func NewPkgBuildCommand() *cobra.Command { "name of the pkg to create, will dicate file name") f.StringVarP(&opts.cfgFile, "config", "c", "", "path to config file") + f.StringVar(&opts.dir, "directory", "", + "Working directory for building package, if empty, an ephemeral temporary directory will be used. Set this to persist package artifacts between builds.") f.StringVarP(&opts.driver, "driver", "d", "k3s", "") f.StringVar(&opts.driverVersion, "driver-version", "v1.21.1+k3s1", "") f.StringVar(&opts.fleetVersion, "fleet-version", "v0.3.5", "") - f.StringSliceVarP(&opts.images, "image", "i", []string{}, - "") f.StringSliceVarP(&opts.paths, "path", "p", []string{}, "") + f.StringSliceVarP(&opts.images, "image", "i", []string{}, + "") return cmd } @@ -111,12 +132,24 @@ func (o *pkgBuildOpts) Run() error { p = o.toPackage() } - tmpdir, err := os.MkdirTemp("", "hauler") - if err != nil { - return err + var wdir string + if o.dir != "" { + if _, err := os.Stat(o.dir); err != nil { + o.logger.Errorf("Failed to use specified working directory: %s\n%v", err) + return err + } + + wdir = o.dir + } else { + tmpdir, err := os.MkdirTemp("", "hauler") + if err != nil { + return err + } + defer os.RemoveAll(tmpdir) + wdir = tmpdir } - pkgr := packager.NewPackager(tmpdir, o.logger) + pkgr := packager.NewPackager(wdir, o.logger) d := driver.NewDriver(p.Spec.Driver) if _, bErr := pkgr.PackageBundles(ctx, p.Spec.Paths...); bErr != nil { diff --git a/cmd/hauler/app/pkg_run.go b/cmd/hauler/app/pkg_run.go index cce6936..23cb49f 100644 --- a/cmd/hauler/app/pkg_run.go +++ b/cmd/hauler/app/pkg_run.go @@ -21,9 +21,16 @@ func NewPkgRunCommand() *cobra.Command { } cmd := &cobra.Command{ - Use: "run", - Short: "", - Long: "", + Use: "run", + Short: "Run a compressed archive", + Long: ` +Run a compressed archive created from a 'hauler package build'. + +Examples: + + # Run a package + hauler package run pkg.tar.zst +`, Aliases: []string{"r"}, Args: cobra.MinimumNArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/hauler/app/root.go b/cmd/hauler/app/root.go index b0823e3..ae46fcd 100644 --- a/cmd/hauler/app/root.go +++ b/cmd/hauler/app/root.go @@ -32,9 +32,7 @@ var ( hauler bundle artifacts hauler relocate artifacts -i hauler relocate images -i locahost:5000 - hauler copy - hauler create - hauler bootstrap` + hauler copy` ) type rootOpts struct { diff --git a/pkg/log/log.go b/pkg/log/log.go index 09bd11c..df0b715 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -61,6 +61,8 @@ func (l *standardLogger) logf(level string, format string, args ...interface{}) pterm.Warning.Printfln(format, args...) case "success": pterm.Success.Printfln(format, args...) + case "error": + pterm.Error.Printfln(format, args...) default: pterm.Error.Printfln("%s is not a valid log level", level) } From 183cd3a0fb278f8e1343fd40f2ef7410e2116fb1 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Thu, 17 Jun 2021 16:42:32 -0400 Subject: [PATCH 09/11] chore: added tests and changed from logrus to logger interface --- cmd/hauler/app/copy.go | 8 ++-- cmd/hauler/app/relocate.go | 6 ++- cmd/hauler/app/relocate_artifacts.go | 19 +++++---- cmd/hauler/app/relocate_images.go | 30 +++++++------- pkg/bootstrap/booter.go | 8 ++-- pkg/bootstrap/config.go | 29 ++++++++++++++ pkg/bootstrap/config_test.go | 20 ++++++++++ pkg/bootstrap/kube.go | 10 ++--- pkg/oci/layout_test.go | 53 +++++++++++++++++++++++++ pkg/oci/oci_test.go | 58 ++++++++++++++++++++++++++++ pkg/packager/packager_test.go | 5 ++- 11 files changed, 205 insertions(+), 41 deletions(-) create mode 100644 pkg/bootstrap/config.go create mode 100644 pkg/bootstrap/config_test.go create mode 100644 pkg/oci/layout_test.go diff --git a/cmd/hauler/app/copy.go b/cmd/hauler/app/copy.go index ee02283..64339b9 100644 --- a/cmd/hauler/app/copy.go +++ b/cmd/hauler/app/copy.go @@ -4,11 +4,11 @@ import ( "context" "github.com/rancherfederal/hauler/pkg/oci" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) type copyOpts struct { + *rootOpts dir string sourceRef string } @@ -16,7 +16,9 @@ type copyOpts struct { // NewCopyCommand creates a new sub command under // hauler for coping files to local disk func NewCopyCommand() *cobra.Command { - opts := ©Opts{} + opts := ©Opts{ + rootOpts: &ro, + } cmd := &cobra.Command{ Use: "copy", @@ -41,7 +43,7 @@ func (o *copyOpts) Run(src string) error { defer cancel() if err := oci.Get(ctx, src, o.dir); err != nil { - logrus.Error(err) + o.logger.Errorf("error copy artifact %s to local directory %s: %v", src, o.dir, err) } return nil diff --git a/cmd/hauler/app/relocate.go b/cmd/hauler/app/relocate.go index 08035d1..9133d20 100644 --- a/cmd/hauler/app/relocate.go +++ b/cmd/hauler/app/relocate.go @@ -8,6 +8,8 @@ type relocateOpts struct { inputFile string } +var rlo relocateOpts + // NewRelocateCommand creates a new sub command under // haulterctl for relocating images and artifacts func NewRelocateCommand() *cobra.Command { @@ -27,8 +29,8 @@ func NewRelocateCommand() *cobra.Command { f.StringVarP(&opts.inputFile, "input", "i", "haul.tar.zst", "package output location relative to the current directory (haul.tar.zst)") - cmd.AddCommand(NewRelocateArtifactsCommand(opts)) - cmd.AddCommand(NewRelocateImagesCommand(opts)) + cmd.AddCommand(NewRelocateArtifactsCommand()) + cmd.AddCommand(NewRelocateImagesCommand()) return cmd } diff --git a/cmd/hauler/app/relocate_artifacts.go b/cmd/hauler/app/relocate_artifacts.go index 5f13928..11df2e7 100644 --- a/cmd/hauler/app/relocate_artifacts.go +++ b/cmd/hauler/app/relocate_artifacts.go @@ -8,18 +8,21 @@ import ( "github.com/rancherfederal/hauler/pkg/oci" "github.com/rancherfederal/hauler/pkg/packager" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) type relocateArtifactsOpts struct { - relocate *relocateOpts + *rootOpts + *relocateOpts destRef string } // NewRelocateArtifactsCommand creates a new sub command of relocate for artifacts -func NewRelocateArtifactsCommand(relocate *relocateOpts) *cobra.Command { - opts := &relocateArtifactsOpts{relocate: relocate} +func NewRelocateArtifactsCommand() *cobra.Command { + opts := &relocateArtifactsOpts{ + rootOpts: &ro, + relocateOpts: &rlo, + } cmd := &cobra.Command{ Use: "artifacts", @@ -43,20 +46,20 @@ func (o *relocateArtifactsOpts) Run(dst string) error { tmpdir, err := os.MkdirTemp("", "hauler") if err != nil { - logrus.Error(err) + o.logger.Errorf("error creating temporary directory hauler: %v", err) } - packager.Unpackage(ar, o.relocate.inputFile, tmpdir) + packager.Unpackage(ar, o.inputFile, tmpdir) files, err := ioutil.ReadDir(tmpdir) if err != nil { - logrus.Error(err) + o.logger.Errorf("error reading files from temporary directory: %v", err) } for _, f := range files { if err := oci.Put(ctx, filepath.Join(tmpdir, f.Name()), dst); err != nil { - logrus.Error(err) + o.logger.Errorf("error pushing artifact to registry %s: %v", dst, err) } } diff --git a/cmd/hauler/app/relocate_images.go b/cmd/hauler/app/relocate_images.go index 3c503de..08d6098 100644 --- a/cmd/hauler/app/relocate_images.go +++ b/cmd/hauler/app/relocate_images.go @@ -1,7 +1,6 @@ package app import ( - "fmt" "os" "path/filepath" "strings" @@ -15,13 +14,17 @@ import ( ) type relocateImagesOpts struct { - relocate *relocateOpts - destRef string + *rootOpts + *relocateOpts + destRef string } // NewRelocateImagesCommand creates a new sub command of relocate for images -func NewRelocateImagesCommand(relocate *relocateOpts) *cobra.Command { - opts := &relocateImagesOpts{relocate: relocate} +func NewRelocateImagesCommand() *cobra.Command { + opts := &relocateImagesOpts{ + rootOpts: &ro, + relocateOpts: &rlo, + } cmd := &cobra.Command{ Use: "images", @@ -42,13 +45,13 @@ func (o *relocateImagesOpts) Run(dst string) error { tmpdir, err := os.MkdirTemp("", "hauler") if err != nil { - return err + o.logger.Errorf("error making temp directory: %v", err) } - packager.Unpackage(ar, o.relocate.inputFile, tmpdir) + packager.Unpackage(ar, o.inputFile, tmpdir) if err != nil { - return err + o.logger.Errorf("error unpackaging bundle: %v", err) } path := filepath.Join(tmpdir, "layout") @@ -56,7 +59,7 @@ func (o *relocateImagesOpts) Run(dst string) error { ly, err := layout.FromPath(path) if err != nil { - return err + o.logger.Errorf("error creating OCI layout: %v", err) } for nm, hash := range oci.ListImages(ly) { @@ -65,11 +68,10 @@ func (o *relocateImagesOpts) Run(dst string) error { img, err := ly.Image(hash) - fmt.Printf("Copy %s to %s", n[1], dst) - fmt.Println() + o.logger.Infof("Copy %s to %s", n[1], dst) if err != nil { - return err + o.logger.Errorf("error creating image from layout: %v", err) } dstimg := dst + "/" + n[1] @@ -77,11 +79,11 @@ func (o *relocateImagesOpts) Run(dst string) error { tag, err := name.ParseReference(dstimg) if err != nil { - return err + o.logger.Errorf("err parsing destination image %s: %v", dstimg, err) } if err := remote.Write(tag, img); err != nil { - return err + o.logger.Errorf("error writing image to destination registry %s: %v", dst, err) } } diff --git a/pkg/bootstrap/booter.go b/pkg/bootstrap/booter.go index 4ba80a9..90a0777 100644 --- a/pkg/bootstrap/booter.go +++ b/pkg/bootstrap/booter.go @@ -12,7 +12,6 @@ import ( "github.com/rancherfederal/hauler/pkg/log" "helm.sh/helm/v3/pkg/chart/loader" "io" - "k8s.io/cli-runtime/pkg/genericclioptions" "os" "path/filepath" ) @@ -105,8 +104,7 @@ func (b booter) Boot(ctx context.Context, d driver.Driver) error { func (b booter) PostBoot(ctx context.Context, d driver.Driver) error { b.logger.Infof("Beginning post boot") - cf := genericclioptions.NewConfigFlags(true) - cf.KubeConfig = stringptr(d.KubeConfigPath()) + cf := NewBootConfig("fleet-system", d.KubeConfigPath()) fleetCrdChartPath := b.fs.Chart().Path(fmt.Sprintf("fleet-crd-%s.tgz", b.Package.Spec.Fleet.VLess())) fleetCrdChart, err := loader.Load(fleetCrdChartPath) @@ -115,7 +113,7 @@ func (b booter) PostBoot(ctx context.Context, d driver.Driver) error { } b.logger.Infof("Installing fleet crds") - fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", "fleet-system", nil, b.logger) + fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", nil, b.logger) if fleetCrdErr != nil { return fleetCrdErr } @@ -129,7 +127,7 @@ func (b booter) PostBoot(ctx context.Context, d driver.Driver) error { } b.logger.Infof("Installing fleet") - fleetRelease, fleetErr := installChart(cf, fleetChart, "fleet", "fleet-system", nil, b.logger) + fleetRelease, fleetErr := installChart(cf, fleetChart, "fleet", nil, b.logger) if fleetErr != nil { return fleetErr } diff --git a/pkg/bootstrap/config.go b/pkg/bootstrap/config.go new file mode 100644 index 0000000..a2c2e10 --- /dev/null +++ b/pkg/bootstrap/config.go @@ -0,0 +1,29 @@ +package bootstrap + +import ( + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +type BootSettings struct { + config *genericclioptions.ConfigFlags + Namespace string + KubeConfig string +} + +func NewBootConfig(ns, kubepath string) *BootSettings { + env := &BootSettings{ + Namespace: ns, + KubeConfig: kubepath, + } + + env.config = &genericclioptions.ConfigFlags{ + Namespace: &env.Namespace, + KubeConfig: &env.KubeConfig, + } + return env +} + +// RESTClientGetter gets the kubeconfig from BootSettings +func (s *BootSettings) RESTClientGetter() genericclioptions.RESTClientGetter { + return s.config +} \ No newline at end of file diff --git a/pkg/bootstrap/config_test.go b/pkg/bootstrap/config_test.go new file mode 100644 index 0000000..c0db8e8 --- /dev/null +++ b/pkg/bootstrap/config_test.go @@ -0,0 +1,20 @@ +package bootstrap + +import ( + "testing" +) + +func TestBootSettings(t *testing.T) { + + ns := "test" + kpath := "somepath" + + settings := NewBootConfig(ns, kpath) + + if settings.Namespace != ns { + t.Errorf("expected namespace %q, got %q", ns, settings.Namespace) + } + if settings.KubeConfig != kpath { + t.Errorf("expected kube-config %q, got %q", kpath, settings.KubeConfig) + } +} diff --git a/pkg/bootstrap/kube.go b/pkg/bootstrap/kube.go index 14c4898..da2f5a1 100644 --- a/pkg/bootstrap/kube.go +++ b/pkg/bootstrap/kube.go @@ -9,7 +9,6 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/release" - "k8s.io/cli-runtime/pkg/genericclioptions" "os" "time" ) @@ -46,9 +45,9 @@ func waitForDriver(ctx context.Context, d driver.Driver) error { } //TODO: This is likely way too fleet specific -func installChart(cf *genericclioptions.ConfigFlags, chart *chart.Chart, releaseName, namespace string, vals map[string]interface{}, logger log.Logger) (*release.Release, error) { +func installChart(cf *BootSettings, chart *chart.Chart, releaseName string, vals map[string]interface{}, logger log.Logger) (*release.Release, error) { actionConfig := new(action.Configuration) - if err := actionConfig.Init(cf, namespace, os.Getenv("HELM_DRIVER"), logger.Debugf); err != nil { + if err := actionConfig.Init(cf.RESTClientGetter(), cf.Namespace, os.Getenv("HELM_DRIVER"), logger.Debugf); err != nil { return nil, err } @@ -58,10 +57,7 @@ func installChart(cf *genericclioptions.ConfigFlags, chart *chart.Chart, release client.Wait = true //TODO: Do this better - client.Namespace, cf.Namespace = namespace, stringptr(namespace) + client.Namespace = cf.Namespace return client.Run(chart, vals) } - -//still can't figure out why helm does it this way -func stringptr(val string) *string { return &val } diff --git a/pkg/oci/layout_test.go b/pkg/oci/layout_test.go new file mode 100644 index 0000000..39adc28 --- /dev/null +++ b/pkg/oci/layout_test.go @@ -0,0 +1,53 @@ +package oci + +import ( + "fmt" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/google/go-containerregistry/pkg/v1/random" +) + +func Test_ListImages(t *testing.T) { + + img, err := random.Image(1024, 5) + + if err != nil { + fmt.Printf("error creating test image: %v", err) + } + + ly := createLayout(img, ".") + dg := getDigest(img) + + m := ListImages(ly) + + for _, hash := range m { + if hash != dg { + t.Errorf("error got %v want %v", hash, dg) + } + } + +} + +func createLayout(img v1.Image, path string) layout.Path { + + p, err := layout.FromPath(path) + if err != nil { + fmt.Printf("error creating layout: %v", err) + } + p.AppendImage(img) + + return p +} + +func getDigest(img v1.Image) v1.Hash { + + digest, err := img.Digest() + + if err != nil { + fmt.Printf("error getting digest: %v", err) + } + + return digest +} diff --git a/pkg/oci/oci_test.go b/pkg/oci/oci_test.go index 03607d3..6f70060 100644 --- a/pkg/oci/oci_test.go +++ b/pkg/oci/oci_test.go @@ -1 +1,59 @@ package oci + +import ( + "context" + "fmt" + "io/ioutil" + "net/http/httptest" + "net/url" + "os" + "testing" + "time" + + "github.com/google/go-containerregistry/pkg/registry" +) + +const timeout = 1 * time.Minute + +func Test_Get_Put(t *testing.T) { + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + // Set up a fake registry. + s := httptest.NewServer(registry.New()) + defer s.Close() + + u, err := url.Parse(s.URL) + if err != nil { + t.Fatal(err) + } + + file, err := ioutil.TempFile(".", "artifact.txt") + if err != nil { + t.Fatal(err) + } + + text := []byte("Some stuff!") + if _, err = file.Write(text); err != nil { + t.Fatal(err) + } + + img := fmt.Sprintf("%s/artifact:latest", u.Host) + + if err := Put(ctx, file.Name(), img); err != nil { + t.Fatal(err) + } + + dir, err := ioutil.TempDir(".", "tmp") + if err != nil { + t.Fatal(err) + } + + if err := Get(ctx, img, dir); err != nil { + t.Fatal(err) + } + + defer os.Remove(file.Name()) + defer os.RemoveAll(dir) +} diff --git a/pkg/packager/packager_test.go b/pkg/packager/packager_test.go index b84ba23..dc942a3 100644 --- a/pkg/packager/packager_test.go +++ b/pkg/packager/packager_test.go @@ -2,9 +2,10 @@ package packager import ( "context" + "testing" + "github.com/rancherfederal/hauler/pkg/apis/hauler.cattle.io/v1alpha1" "github.com/rancherfederal/hauler/pkg/fs" - "testing" ) func Test_pkg_driver(t *testing.T) { @@ -13,7 +14,7 @@ func Test_pkg_driver(t *testing.T) { } type args struct { ctx context.Context - d v1alpha1.IDriver + d v1alpha1.Driver } tests := []struct { name string From cb898136caab0628e44b5499554b4a5dec882592 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Thu, 17 Jun 2021 17:46:38 -0400 Subject: [PATCH 10/11] chore: slightly refactored copy and relocate commands --- cmd/hauler/app/copy.go | 10 +++++ cmd/hauler/app/relocate.go | 15 +++----- cmd/hauler/app/relocate_artifacts.go | 56 +++++++++++----------------- cmd/hauler/app/relocate_images.go | 41 ++++++++++++-------- cmd/hauler/app/root.go | 11 +++--- 5 files changed, 69 insertions(+), 64 deletions(-) diff --git a/cmd/hauler/app/copy.go b/cmd/hauler/app/copy.go index 64339b9..feb6310 100644 --- a/cmd/hauler/app/copy.go +++ b/cmd/hauler/app/copy.go @@ -7,6 +7,14 @@ import ( "github.com/spf13/cobra" ) +var ( + copyLong = `hauler copies artifacts stored on a registry to local disk` + + copyExample = ` + # Run Hauler + hauler copy locahost:5000/artifacts:latest` +) + type copyOpts struct { *rootOpts dir string @@ -23,6 +31,8 @@ func NewCopyCommand() *cobra.Command { cmd := &cobra.Command{ Use: "copy", Short: "Download artifacts from OCI registry to local disk", + Long: copyLong, + Example: copyExample, Aliases: []string{"c", "cp"}, //Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/hauler/app/relocate.go b/cmd/hauler/app/relocate.go index 9133d20..b94b85d 100644 --- a/cmd/hauler/app/relocate.go +++ b/cmd/hauler/app/relocate.go @@ -6,14 +6,15 @@ import ( type relocateOpts struct { inputFile string + *rootOpts } -var rlo relocateOpts - // NewRelocateCommand creates a new sub command under // haulterctl for relocating images and artifacts func NewRelocateCommand() *cobra.Command { - opts := &relocateOpts{} + opts := &relocateOpts{ + rootOpts: &ro, + } cmd := &cobra.Command{ Use: "relocate", @@ -25,12 +26,8 @@ func NewRelocateCommand() *cobra.Command { }, } - f := cmd.PersistentFlags() - f.StringVarP(&opts.inputFile, "input", "i", "haul.tar.zst", - "package output location relative to the current directory (haul.tar.zst)") - - cmd.AddCommand(NewRelocateArtifactsCommand()) - cmd.AddCommand(NewRelocateImagesCommand()) + cmd.AddCommand(NewRelocateArtifactsCommand(opts)) + cmd.AddCommand(NewRelocateImagesCommand(opts)) return cmd } diff --git a/cmd/hauler/app/relocate_artifacts.go b/cmd/hauler/app/relocate_artifacts.go index 11df2e7..4845cf1 100644 --- a/cmd/hauler/app/relocate_artifacts.go +++ b/cmd/hauler/app/relocate_artifacts.go @@ -2,65 +2,53 @@ package app import ( "context" - "io/ioutil" - "os" - "path/filepath" "github.com/rancherfederal/hauler/pkg/oci" - "github.com/rancherfederal/hauler/pkg/packager" "github.com/spf13/cobra" ) type relocateArtifactsOpts struct { - *rootOpts *relocateOpts - destRef string + destRef string } +var ( + relocateArtifactsLong = `hauler relocate artifacts process an archive with files + to be pushed to a registry` + + relocateArtifactsExample = ` + # Run Hauler + hauler relocate artifacts artifacts.tar.zst locahost:5000/artifacts:latest` +) + // NewRelocateArtifactsCommand creates a new sub command of relocate for artifacts -func NewRelocateArtifactsCommand() *cobra.Command { +func NewRelocateArtifactsCommand(relocate *relocateOpts) *cobra.Command { opts := &relocateArtifactsOpts{ - rootOpts: &ro, - relocateOpts: &rlo, + relocateOpts: relocate, } cmd := &cobra.Command{ - Use: "artifacts", - Short: "Use artifact from bundle artifacts to populate a target file server with the artifact's contents", + Use: "artifacts", + Short: "Use artifact from bundle artifacts to populate a target file server with the artifact's contents", + Long: relocateArtifactsLong, + Example: relocateArtifactsExample, RunE: func(cmd *cobra.Command, args []string) error { - opts.destRef = args[0] - return opts.Run(opts.destRef) + opts.inputFile = args[0] + opts.destRef = args[1] + return opts.Run(opts.destRef, opts.inputFile) }, } return cmd } -func (o *relocateArtifactsOpts) Run(dst string) error { +func (o *relocateArtifactsOpts) Run(dst string, input string) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - ar := packager.NewArchiver() - - tmpdir, err := os.MkdirTemp("", "hauler") - - if err != nil { - o.logger.Errorf("error creating temporary directory hauler: %v", err) - } - - packager.Unpackage(ar, o.inputFile, tmpdir) - - files, err := ioutil.ReadDir(tmpdir) - - if err != nil { - o.logger.Errorf("error reading files from temporary directory: %v", err) - } - - for _, f := range files { - if err := oci.Put(ctx, filepath.Join(tmpdir, f.Name()), dst); err != nil { - o.logger.Errorf("error pushing artifact to registry %s: %v", dst, err) - } + if err := oci.Put(ctx, input, dst); err != nil { + o.logger.Errorf("error pushing artifact to registry %s: %v", dst, err) } return nil diff --git a/cmd/hauler/app/relocate_images.go b/cmd/hauler/app/relocate_images.go index 08d6098..8c9fbe5 100644 --- a/cmd/hauler/app/relocate_images.go +++ b/cmd/hauler/app/relocate_images.go @@ -13,46 +13,55 @@ import ( "github.com/spf13/cobra" ) +var ( + relocateImagesLong = `hauler relocate images processes a bundle provides by hauler + package build and copies all of the collected images to a registry` + + relocateImagesExample = ` + # Run Hauler + hauler relocate images pkg.tar.zst locahost:5000` +) + type relocateImagesOpts struct { - *rootOpts *relocateOpts destRef string } // NewRelocateImagesCommand creates a new sub command of relocate for images -func NewRelocateImagesCommand() *cobra.Command { +func NewRelocateImagesCommand(relocate *relocateOpts) *cobra.Command { opts := &relocateImagesOpts{ - rootOpts: &ro, - relocateOpts: &rlo, + relocateOpts: relocate, } cmd := &cobra.Command{ - Use: "images", - Short: "Use artifact from bundle images to populate a target registry with the artifact's images", + Use: "images", + Short: "Use artifact from bundle images to populate a target registry with the artifact's images", + Long: relocateImagesLong, + Example: relocateImagesExample, RunE: func(cmd *cobra.Command, args []string) error { - opts.destRef = args[0] - return opts.Run(opts.destRef) + opts.inputFile = args[0] + opts.destRef = args[1] + return opts.Run(opts.destRef, opts.inputFile) }, } return cmd } -func (o *relocateImagesOpts) Run(dst string) error { - - ar := packager.NewArchiver() +func (o *relocateImagesOpts) Run(dst string, input string) error { tmpdir, err := os.MkdirTemp("", "hauler") - if err != nil { - o.logger.Errorf("error making temp directory: %v", err) + return err } + o.logger.Debugf("Using temporary working directory: %s", tmpdir) - packager.Unpackage(ar, o.inputFile, tmpdir) + a := packager.NewArchiver() - if err != nil { - o.logger.Errorf("error unpackaging bundle: %v", err) + if err := packager.Unpackage(a, input, tmpdir); err != nil { + o.logger.Errorf("error unpackaging input %s: %v", input, err) } + o.logger.Debugf("Unpackaged %s", input) path := filepath.Join(tmpdir, "layout") diff --git a/cmd/hauler/app/root.go b/cmd/hauler/app/root.go index ae46fcd..e9abcf1 100644 --- a/cmd/hauler/app/root.go +++ b/cmd/hauler/app/root.go @@ -2,11 +2,12 @@ package app import ( "fmt" - "github.com/rancherfederal/hauler/pkg/log" "io" "os" "time" + "github.com/rancherfederal/hauler/pkg/log" + "github.com/spf13/cobra" homedir "github.com/mitchellh/go-homedir" @@ -30,9 +31,9 @@ var ( hauler pkg run pkg.tar.zst hauler bundle images hauler bundle artifacts - hauler relocate artifacts -i - hauler relocate images -i locahost:5000 - hauler copy` + hauler relocate artifacts artifacts.tar.zst + hauler relocate images pkg.tar.zst locahost:5000 + hauler copy local:5000/artifacts:latest` ) type rootOpts struct { @@ -72,7 +73,7 @@ func NewRootCommand() *cobra.Command { cmd.AddCommand(NewPkgCommand()) f := cmd.PersistentFlags() - f.StringVarP(&loglevel, "loglevel", "l", "info", + f.StringVarP(&loglevel, "loglevel", "l", "debug", "Log level (debug, info, warn, error, fatal, panic)") f.StringVarP(&cfgFile, "config", "c", "./hauler.yaml", "config file (./hauler.yaml)") From 078a4fa01dea6496b0648f70e4f1a790b19d4e88 Mon Sep 17 00:00:00 2001 From: Josh Wolf Date: Fri, 18 Jun 2021 07:04:54 -0600 Subject: [PATCH 11/11] clean up unused code/scripts --- cmd/hauler/app/bundle.go | 36 -- cmd/hauler/app/bundle_artifacts.go | 51 -- cmd/hauler/app/copy.go | 7 +- cmd/hauler/app/relocate_artifacts.go | 11 +- cmd/hauler/app/relocate_images.go | 11 +- cmd/hauler/app/root.go | 58 +- k3ama.sh | 128 ----- pkg/bootstrap/booter.go | 2 +- pkg/bootstrap/config.go | 14 +- scripts/k3s-install.sh | 801 --------------------------- 10 files changed, 38 insertions(+), 1081 deletions(-) delete mode 100644 cmd/hauler/app/bundle.go delete mode 100644 cmd/hauler/app/bundle_artifacts.go delete mode 100755 k3ama.sh delete mode 100755 scripts/k3s-install.sh diff --git a/cmd/hauler/app/bundle.go b/cmd/hauler/app/bundle.go deleted file mode 100644 index 139a5da..0000000 --- a/cmd/hauler/app/bundle.go +++ /dev/null @@ -1,36 +0,0 @@ -package app - -import ( - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -type bundleOpts struct { - bundleDir string -} - -// NewBundleCommand creates a new sub command under -// haulterctl for bundling images and artifacts -func NewBundleCommand() *cobra.Command { - opts := &bundleOpts{} - - cmd := &cobra.Command{ - Use: "bundle", - Short: "bundle images or artifact for relocation", - Long: "", - Aliases: []string{"b"}, - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, - } - - f := cmd.PersistentFlags() - f.StringVarP(&opts.bundleDir, "bundledir", "b", "./bundle", - "directory locating a bundle, if one exists we will append (./bundle)") - - cmd.AddCommand(NewBundleArtifactsCommand(opts)) - - viper.AutomaticEnv() - - return cmd -} diff --git a/cmd/hauler/app/bundle_artifacts.go b/cmd/hauler/app/bundle_artifacts.go deleted file mode 100644 index 7e67cd2..0000000 --- a/cmd/hauler/app/bundle_artifacts.go +++ /dev/null @@ -1,51 +0,0 @@ -package app - -import ( - "context" - "fmt" - - "github.com/spf13/cobra" -) - -type bundleArtifactsOpts struct { - bundle *bundleOpts -} - -// NewBundleArtifactsCommand creates a new sub command of bundle for artifacts -func NewBundleArtifactsCommand(bundle *bundleOpts) *cobra.Command { - - opts := &bundleArtifactsOpts{bundle: bundle} - - cmd := &cobra.Command{ - Use: "artifacts", - Short: "Choose a folder on disk, new artifact containing all of folder's contents", - RunE: func(cmd *cobra.Command, args []string) error { - return opts.Run() - }, - } - - return cmd -} - -func (o *bundleArtifactsOpts) Run() error { - - //TODO - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - //b := bundle.NewLayoutStore(o.bundleDir) - // - //images := []string{"alpine:latest", "registry:2.7.1"} - // - //for _, i := range images { - // if err := b.Add(ctx, i); err != nil { - // return err - // } - //} - _ = ctx - - fmt.Println("bundle artifacts") - fmt.Println(o.bundle.bundleDir) - - return nil -} diff --git a/cmd/hauler/app/copy.go b/cmd/hauler/app/copy.go index feb6310..763f6ba 100644 --- a/cmd/hauler/app/copy.go +++ b/cmd/hauler/app/copy.go @@ -11,8 +11,9 @@ var ( copyLong = `hauler copies artifacts stored on a registry to local disk` copyExample = ` - # Run Hauler - hauler copy locahost:5000/artifacts:latest` +# Run Hauler +hauler copy locahost:5000/artifacts:latest +` ) type copyOpts struct { @@ -34,7 +35,7 @@ func NewCopyCommand() *cobra.Command { Long: copyLong, Example: copyExample, Aliases: []string{"c", "cp"}, - //Args: cobra.MinimumNArgs(1), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.sourceRef = args[0] return opts.Run(opts.sourceRef) diff --git a/cmd/hauler/app/relocate_artifacts.go b/cmd/hauler/app/relocate_artifacts.go index 4845cf1..85c25c4 100644 --- a/cmd/hauler/app/relocate_artifacts.go +++ b/cmd/hauler/app/relocate_artifacts.go @@ -13,12 +13,12 @@ type relocateArtifactsOpts struct { } var ( - relocateArtifactsLong = `hauler relocate artifacts process an archive with files - to be pushed to a registry` + relocateArtifactsLong = `hauler relocate artifacts process an archive with files to be pushed to a registry` relocateArtifactsExample = ` - # Run Hauler - hauler relocate artifacts artifacts.tar.zst locahost:5000/artifacts:latest` +# Run Hauler +hauler relocate artifacts artifacts.tar.zst locahost:5000/artifacts:latest +` ) // NewRelocateArtifactsCommand creates a new sub command of relocate for artifacts @@ -32,6 +32,8 @@ func NewRelocateArtifactsCommand(relocate *relocateOpts) *cobra.Command { Short: "Use artifact from bundle artifacts to populate a target file server with the artifact's contents", Long: relocateArtifactsLong, Example: relocateArtifactsExample, + Args: cobra.MinimumNArgs(2), + Aliases: []string{"a", "art", "af"}, RunE: func(cmd *cobra.Command, args []string) error { opts.inputFile = args[0] opts.destRef = args[1] @@ -43,7 +45,6 @@ func NewRelocateArtifactsCommand(relocate *relocateOpts) *cobra.Command { } func (o *relocateArtifactsOpts) Run(dst string, input string) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() diff --git a/cmd/hauler/app/relocate_images.go b/cmd/hauler/app/relocate_images.go index 8c9fbe5..bca390b 100644 --- a/cmd/hauler/app/relocate_images.go +++ b/cmd/hauler/app/relocate_images.go @@ -14,12 +14,13 @@ import ( ) var ( - relocateImagesLong = `hauler relocate images processes a bundle provides by hauler - package build and copies all of the collected images to a registry` + relocateImagesLong = `hauler relocate images processes a bundle provides by hauler package build and copies all of +the collected images to a registry` relocateImagesExample = ` - # Run Hauler - hauler relocate images pkg.tar.zst locahost:5000` +# Run Hauler +hauler relocate images pkg.tar.zst locahost:5000 +` ) type relocateImagesOpts struct { @@ -38,6 +39,8 @@ func NewRelocateImagesCommand(relocate *relocateOpts) *cobra.Command { Short: "Use artifact from bundle images to populate a target registry with the artifact's images", Long: relocateImagesLong, Example: relocateImagesExample, + Args: cobra.MinimumNArgs(2), + Aliases: []string{"i", "img", "imgs"}, RunE: func(cmd *cobra.Command, args []string) error { opts.inputFile = args[0] opts.destRef = args[1] diff --git a/cmd/hauler/app/root.go b/cmd/hauler/app/root.go index e9abcf1..b4991dc 100644 --- a/cmd/hauler/app/root.go +++ b/cmd/hauler/app/root.go @@ -1,7 +1,6 @@ package app import ( - "fmt" "io" "os" "time" @@ -9,31 +8,27 @@ import ( "github.com/rancherfederal/hauler/pkg/log" "github.com/spf13/cobra" - - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" ) var ( - cfgFile string loglevel string timeout time.Duration getLong = `hauler provides CLI-based air-gap migration assistance using k3s. - Choose your functionality and new a package when internet access is available, - then deploy the package into your air-gapped environment. - ` +Choose your functionality and new a package when internet access is available, +then deploy the package into your air-gapped environment. +` getExample = ` - # Run Hauler - hauler pkg build - hauler pkg run pkg.tar.zst - hauler bundle images - hauler bundle artifacts - hauler relocate artifacts artifacts.tar.zst - hauler relocate images pkg.tar.zst locahost:5000 - hauler copy local:5000/artifacts:latest` +hauler pkg build +hauler pkg run pkg.tar.zst + +hauler relocate artifacts artifacts.tar.zst +hauler relocate images pkg.tar.zst locahost:5000 + +hauler copy localhost:5000/artifacts:latest +` ) type rootOpts struct { @@ -64,48 +59,21 @@ func NewRootCommand() *cobra.Command { }, } - cobra.OnInitialize(initConfig) + cobra.OnInitialize() cmd.AddCommand(NewRelocateCommand()) - cmd.AddCommand(NewBundleCommand()) cmd.AddCommand(NewCopyCommand()) - cmd.AddCommand(NewPkgCommand()) f := cmd.PersistentFlags() f.StringVarP(&loglevel, "loglevel", "l", "debug", "Log level (debug, info, warn, error, fatal, panic)") - f.StringVarP(&cfgFile, "config", "c", "./hauler.yaml", - "config file (./hauler.yaml)") f.DurationVar(&timeout, "timeout", 1*time.Minute, - "timeout for operations") + "TODO: timeout for operations") return cmd } -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - cobra.CheckErr(err) - - // Search config in home directory with name ".hauler" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".hauler") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} - func setupCliLogger(out io.Writer, level string) (log.Logger, error) { l := log.NewLogger(out) diff --git a/k3ama.sh b/k3ama.sh deleted file mode 100755 index ecee310..0000000 --- a/k3ama.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/bash - -# , , _______________________________ -# ,-----------|'------'| | | -# /. '-' |-' |_____________________________| -# |/| | | -# | .________.'----' _______________________________ -# | || | || | | -# \__|' \__|' |_____________________________| -# -# |‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾| -# |________________________________________________________| -# | -# |‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾| -# |________________________________________________________| -# -# k3ama - airgap migration assistant - -LOCAL_IMAGES_FILEPATH=/var/lib/rancher/k3s/agent/images -ADDL_IMAGES=./artifacts/images - -copy_images(){ - cp -rvf ${ADDL_IMAGES}/* ${LOCAL_IMAGES_FILEPATH} -} - -install_k3s(){ - AIRGAP_IMAGES_TAR="$1" - - ## Note: currently requires root - mkdir -p ${LOCAL_IMAGES_FILEPATH} - echo "copying ${AIRGAP_IMAGES_TAR} -> ${LOCAL_IMAGES_FILEPATH}" - cp artifacts/k3s-airgap-images-amd64.tar /var/lib/rancher/k3s/agent/images - # copy over the k3s binary - cp ./artifacts/k3s /usr/local/bin/k3s - chmod +x /usr/local/bin/k3s - - INSTALL_K3S_SKIP_DOWNLOAD=true ./scripts/k3s-install.sh -} - -uninstall_k3s(){ - if [ -f "/usr/local/bin/k3s-uninstall.sh" ]; then - /usr/local/bin/k3s-uninstall.sh - else - echo "k3s is not installed" - fi -} - -check_deps(){ - #TODO - echo "TODO: check to ensure that the dependencies are in place." - #rpm -qa | grep k3s-selinux -} - -#gather_selinux_rpms(){ -# if ! yum list installed yum-utils >/dev/null 2>&1; then -# yum install -y yum-utils -# fi - #wget -O ./https://rpm.rancher.io/k3s-selinux-0.1.1-rc1.el7.noarch.rpm - #yumdownloader --destdir=. --resolve container-selinux selinux-policy-base -#} - -usage () { - echo "USAGE: $0 [--image-list rancher-images.txt] [--images rancher-images.tar.gz]" - echo " [-l|--image-list path] text file with list of images; one image per line." - echo " [-i|--images path] tar.gz generated by docker save." - echo " [-h|--help] Usage message" -} - -check_firewalld(){ - if pgrep -x "firewalld" >/dev/null - then - echo "[FATAL] disable firewalld first" - fi -} - -check_selinux(){ - # yes i know we want selinux, but it's a pain in the ass right now and i will come back to it - SELINUXSTATUS=$(getenforce) - if [ "$SELINUXSTATUS" == "Permissive" ]; then - echo "[FATAL] disable selinux" - exit 1 - else - echo "SELINUX disabled. continuing" - fi -} - -copy_yaml_manifests(){ - cp -r ./yaml/* /var/lib/rancher/k3s/server/manifests -} - -copy_local_bins(){ - if [ -f "./artifacts/k9s" ]; then - cp -v ./artifacts/k9s /usr/local/bin/ - fi -} - -copy_local_kubectl(){ - echo "TODO" -} - -iptable_block_docker_io() { - # iptables -A OUTPUT -p tcp -m string --string "docker.io" --algo kmp -j REJECT - echo "iptable_block_docker_io() disabled" -} -## TODO: Make this interactive with case statements - -uninstall_k3s -copy_local_bins -iptable_block_docker_io -check_deps -check_firewalld -#check_selinux -install_k3s ./artifacts/k3s-airgap-images-amd64.tar -copy_images -copy_yaml_manifests - - - - -/usr/local/bin/k3s kubectl get pods -A -w - - -####### -# Notes: -# - workaround: busybox is not included in the main images.txt list and therefor the pvcs cannot create. -# - VAGRANT FAIL: INFO[0000] Preparing data dir /var/lib/rancher/k3s/data ... for some reason local-path provisioner cannot create vols in vagrant -# - bug: RunContainerError results you try to reinstall k3s on top of an old instance WHEN RUNNING SELINUX -####### \ No newline at end of file diff --git a/pkg/bootstrap/booter.go b/pkg/bootstrap/booter.go index 90a0777..4924fd6 100644 --- a/pkg/bootstrap/booter.go +++ b/pkg/bootstrap/booter.go @@ -113,7 +113,7 @@ func (b booter) PostBoot(ctx context.Context, d driver.Driver) error { } b.logger.Infof("Installing fleet crds") - fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", nil, b.logger) + fleetCrdRelease, fleetCrdErr := installChart(cf, fleetCrdChart, "fleet-crd", nil, b.logger) if fleetCrdErr != nil { return fleetCrdErr } diff --git a/pkg/bootstrap/config.go b/pkg/bootstrap/config.go index a2c2e10..1b209fe 100644 --- a/pkg/bootstrap/config.go +++ b/pkg/bootstrap/config.go @@ -5,20 +5,20 @@ import ( ) type BootSettings struct { - config *genericclioptions.ConfigFlags - Namespace string + config *genericclioptions.ConfigFlags + Namespace string KubeConfig string } func NewBootConfig(ns, kubepath string) *BootSettings { env := &BootSettings{ - Namespace: ns, - KubeConfig: kubepath, + Namespace: ns, + KubeConfig: kubepath, } env.config = &genericclioptions.ConfigFlags{ - Namespace: &env.Namespace, - KubeConfig: &env.KubeConfig, + Namespace: &env.Namespace, + KubeConfig: &env.KubeConfig, } return env } @@ -26,4 +26,4 @@ func NewBootConfig(ns, kubepath string) *BootSettings { // RESTClientGetter gets the kubeconfig from BootSettings func (s *BootSettings) RESTClientGetter() genericclioptions.RESTClientGetter { return s.config -} \ No newline at end of file +} diff --git a/scripts/k3s-install.sh b/scripts/k3s-install.sh deleted file mode 100755 index 1e4180e..0000000 --- a/scripts/k3s-install.sh +++ /dev/null @@ -1,801 +0,0 @@ -#!/bin/sh -set -e - -# Usage: -# curl ... | ENV_VAR=... sh - -# or -# ENV_VAR=... ./install.sh -# -# Example: -# Installing a server without traefik: -# curl ... | INSTALL_K3S_EXEC="--disable=traefik" sh - -# Installing an agent to point at a server: -# curl ... | K3S_TOKEN=xxx K3S_URL=https://server-url:6443 sh - -# -# Environment variables: -# - K3S_* -# Environment variables which begin with K3S_ will be preserved for the -# systemd service to use. Setting K3S_URL without explicitly setting -# a systemd exec command will default the command to "agent", and we -# enforce that K3S_TOKEN or K3S_CLUSTER_SECRET is also set. -# -# - INSTALL_K3S_SKIP_DOWNLOAD -# If set to true will not download k3s hash or binary. -# -# - INSTALL_K3S_SYMLINK -# If set to 'skip' will not create symlinks, 'force' will overwrite, -# default will symlink if command does not exist in path. -# -# - INSTALL_K3S_SKIP_ENABLE -# If set to true will not enable or start k3s service. -# -# - INSTALL_K3S_SKIP_START -# If set to true will not start k3s service. -# -# - INSTALL_K3S_VERSION -# Version of k3s to download from github. Will attempt to download from the -# stable channel if not specified. -# -# - INSTALL_K3S_COMMIT -# Commit of k3s to download from temporary cloud storage. -# * (for developer & QA use) -# -# - INSTALL_K3S_BIN_DIR -# Directory to install k3s binary, links, and uninstall script to, or use -# /usr/local/bin as the default -# -# - INSTALL_K3S_BIN_DIR_READ_ONLY -# If set to true will not write files to INSTALL_K3S_BIN_DIR, forces -# setting INSTALL_K3S_SKIP_DOWNLOAD=true -# -# - INSTALL_K3S_SYSTEMD_DIR -# Directory to install systemd service and environment files to, or use -# /etc/systemd/system as the default -# -# - INSTALL_K3S_EXEC or script arguments -# Command with flags to use for launching k3s in the systemd service, if -# the command is not specified will default to "agent" if K3S_URL is set -# or "server" if not. The final systemd command resolves to a combination -# of EXEC and script args ($@). -# -# The following commands result in the same behavior: -# curl ... | INSTALL_K3S_EXEC="--disable=traefik" sh -s - -# curl ... | INSTALL_K3S_EXEC="server --disable=traefik" sh -s - -# curl ... | INSTALL_K3S_EXEC="server" sh -s - --disable=traefik -# curl ... | sh -s - server --disable=traefik -# curl ... | sh -s - --disable=traefik -# -# - INSTALL_K3S_NAME -# Name of systemd service to create, will default from the k3s exec command -# if not specified. If specified the name will be prefixed with 'k3s-'. -# -# - INSTALL_K3S_TYPE -# Type of systemd service to create, will default from the k3s exec command -# if not specified. -# -# - INSTALL_K3S_SELINUX_WARN -# If set to true will continue if k3s-selinux policy is not found. -# -# - INSTALL_K3S_CHANNEL_URL -# Channel URL for fetching k3s download URL. -# Defaults to 'https://update.k3s.io/v1-release/channels'. -# -# - INSTALL_K3S_CHANNEL -# Channel to use for fetching k3s download URL. -# Defaults to 'stable'. - -GITHUB_URL=https://github.com/rancher/k3s/releases -STORAGE_URL=https://storage.googleapis.com/k3s-ci-builds -DOWNLOADER= - -# --- helper functions for logs --- -info() -{ - echo '[INFO] ' "$@" -} -warn() -{ - echo '[WARN] ' "$@" >&2 -} -fatal() -{ - echo '[ERROR] ' "$@" >&2 - exit 1 -} - -# --- fatal if no systemd or openrc --- -verify_system() { - if [ -x /sbin/openrc-run ]; then - HAS_OPENRC=true - return - fi - if [ -d /run/systemd ]; then - HAS_SYSTEMD=true - return - fi - fatal 'Can not find systemd or openrc to use as a process supervisor for k3s' -} - -# --- add quotes to command arguments --- -quote() { - for arg in "$@"; do - printf '%s\n' "$arg" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" - done -} - -# --- add indentation and trailing slash to quoted args --- -quote_indent() { - printf ' \\\n' - for arg in "$@"; do - printf '\t%s \\\n' "$(quote "$arg")" - done -} - -# --- escape most punctuation characters, except quotes, forward slash, and space --- -escape() { - printf '%s' "$@" | sed -e 's/\([][!#$%&()*;<=>?\_`{|}]\)/\\\1/g;' -} - -# --- escape double quotes --- -escape_dq() { - printf '%s' "$@" | sed -e 's/"/\\"/g' -} - -# --- ensures $K3S_URL is empty or begins with https://, exiting fatally otherwise --- -verify_k3s_url() { - case "${K3S_URL}" in - "") - ;; - https://*) - ;; - *) - fatal "Only https:// URLs are supported for K3S_URL (have ${K3S_URL})" - ;; - esac -} - -# --- define needed environment variables --- -setup_env() { - # --- use command args if passed or create default --- - case "$1" in - # --- if we only have flags discover if command should be server or agent --- - (-*|"") - if [ -z "${K3S_URL}" ]; then - CMD_K3S=server - else - if [ -z "${K3S_TOKEN}" ] && [ -z "${K3S_CLUSTER_SECRET}" ]; then - fatal "Defaulted k3s exec command to 'agent' because K3S_URL is defined, but K3S_TOKEN or K3S_CLUSTER_SECRET is not defined." - fi - CMD_K3S=agent - fi - ;; - # --- command is provided --- - (*) - CMD_K3S=$1 - shift - ;; - esac - - verify_k3s_url - - CMD_K3S_EXEC="${CMD_K3S}$(quote_indent "$@")" - - # --- use systemd name if defined or create default --- - if [ -n "${INSTALL_K3S_NAME}" ]; then - SYSTEM_NAME=k3s-${INSTALL_K3S_NAME} - else - if [ "${CMD_K3S}" = server ]; then - SYSTEM_NAME=k3s - else - SYSTEM_NAME=k3s-${CMD_K3S} - fi - fi - - # --- check for invalid characters in system name --- - valid_chars=$(printf '%s' "${SYSTEM_NAME}" | sed -e 's/[][!#$%&()*;<=>?\_`{|}/[:space:]]/^/g;' ) - if [ "${SYSTEM_NAME}" != "${valid_chars}" ]; then - invalid_chars=$(printf '%s' "${valid_chars}" | sed -e 's/[^^]/ /g') - fatal "Invalid characters for system name: - ${SYSTEM_NAME} - ${invalid_chars}" - fi - - # --- use sudo if we are not already root --- - SUDO=sudo - if [ $(id -u) -eq 0 ]; then - SUDO= - fi - - # --- use systemd type if defined or create default --- - if [ -n "${INSTALL_K3S_TYPE}" ]; then - SYSTEMD_TYPE=${INSTALL_K3S_TYPE} - else - if [ "${CMD_K3S}" = server ]; then - SYSTEMD_TYPE=notify - else - SYSTEMD_TYPE=exec - fi - fi - - # --- use binary install directory if defined or create default --- - if [ -n "${INSTALL_K3S_BIN_DIR}" ]; then - BIN_DIR=${INSTALL_K3S_BIN_DIR} - else - BIN_DIR=/usr/local/bin - fi - - # --- use systemd directory if defined or create default --- - if [ -n "${INSTALL_K3S_SYSTEMD_DIR}" ]; then - SYSTEMD_DIR="${INSTALL_K3S_SYSTEMD_DIR}" - else - SYSTEMD_DIR=/etc/systemd/system - fi - - # --- set related files from system name --- - SERVICE_K3S=${SYSTEM_NAME}.service - UNINSTALL_K3S_SH=${UNINSTALL_K3S_SH:-${BIN_DIR}/${SYSTEM_NAME}-uninstall.sh} - KILLALL_K3S_SH=${KILLALL_K3S_SH:-${BIN_DIR}/k3s-killall.sh} - - # --- use service or environment location depending on systemd/openrc --- - if [ "${HAS_SYSTEMD}" = true ]; then - FILE_K3S_SERVICE=${SYSTEMD_DIR}/${SERVICE_K3S} - FILE_K3S_ENV=${SYSTEMD_DIR}/${SERVICE_K3S}.env - elif [ "${HAS_OPENRC}" = true ]; then - $SUDO mkdir -p /etc/rancher/k3s - FILE_K3S_SERVICE=/etc/init.d/${SYSTEM_NAME} - FILE_K3S_ENV=/etc/rancher/k3s/${SYSTEM_NAME}.env - fi - - # --- get hash of config & exec for currently installed k3s --- - PRE_INSTALL_HASHES=$(get_installed_hashes) - - # --- if bin directory is read only skip download --- - if [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ]; then - INSTALL_K3S_SKIP_DOWNLOAD=true - fi - - # --- setup channel values - INSTALL_K3S_CHANNEL_URL=${INSTALL_K3S_CHANNEL_URL:-'https://update.k3s.io/v1-release/channels'} - INSTALL_K3S_CHANNEL=${INSTALL_K3S_CHANNEL:-'stable'} -} - -# --- check if skip download environment variable set --- -can_skip_download() { - if [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != true ]; then - return 1 - fi -} - -# --- verify an executabe k3s binary is installed --- -verify_k3s_is_executable() { - if [ ! -x ${BIN_DIR}/k3s ]; then - fatal "Executable k3s binary not found at ${BIN_DIR}/k3s" - fi -} - -# --- set arch and suffix, fatal if architecture not supported --- -setup_verify_arch() { - if [ -z "$ARCH" ]; then - ARCH=$(uname -m) - fi - case $ARCH in - amd64) - ARCH=amd64 - SUFFIX= - ;; - x86_64) - ARCH=amd64 - SUFFIX= - ;; - arm64) - ARCH=arm64 - SUFFIX=-${ARCH} - ;; - aarch64) - ARCH=arm64 - SUFFIX=-${ARCH} - ;; - arm*) - ARCH=arm - SUFFIX=-${ARCH}hf - ;; - *) - fatal "Unsupported architecture $ARCH" - esac -} - -# --- verify existence of network downloader executable --- -verify_downloader() { - # Return failure if it doesn't exist or is no executable - [ -x "$(which $1)" ] || return 1 - - # Set verified executable as our downloader program and return success - DOWNLOADER=$1 - return 0 -} - -# --- create tempory directory and cleanup when done --- -setup_tmp() { - TMP_DIR=$(mktemp -d -t k3s-install.XXXXXXXXXX) - TMP_HASH=${TMP_DIR}/k3s.hash - TMP_BIN=${TMP_DIR}/k3s.bin - cleanup() { - code=$? - set +e - trap - EXIT - rm -rf ${TMP_DIR} - exit $code - } - trap cleanup INT EXIT -} - -# --- use desired k3s version if defined or find version from channel --- -get_release_version() { - if [ -n "${INSTALL_K3S_COMMIT}" ]; then - VERSION_K3S="commit ${INSTALL_K3S_COMMIT}" - elif [ -n "${INSTALL_K3S_VERSION}" ]; then - VERSION_K3S=${INSTALL_K3S_VERSION} - else - info "Finding release for channel ${INSTALL_K3S_CHANNEL}" - version_url="${INSTALL_K3S_CHANNEL_URL}/${INSTALL_K3S_CHANNEL}" - case $DOWNLOADER in - curl) - VERSION_K3S=$(curl -w '%{url_effective}' -L -s -S ${version_url} -o /dev/null | sed -e 's|.*/||') - ;; - wget) - VERSION_K3S=$(wget -SqO /dev/null ${version_url} 2>&1 | grep -i Location | sed -e 's|.*/||') - ;; - *) - fatal "Incorrect downloader executable '$DOWNLOADER'" - ;; - esac - fi - info "Using ${VERSION_K3S} as release" -} - -# --- download from github url --- -download() { - [ $# -eq 2 ] || fatal 'download needs exactly 2 arguments' - - case $DOWNLOADER in - curl) - curl -o $1 -sfL $2 - ;; - wget) - wget -qO $1 $2 - ;; - *) - fatal "Incorrect executable '$DOWNLOADER'" - ;; - esac - - # Abort if download command failed - [ $? -eq 0 ] || fatal 'Download failed' -} - -# --- download hash from github url --- -download_hash() { - if [ -n "${INSTALL_K3S_COMMIT}" ]; then - HASH_URL=${STORAGE_URL}/k3s${SUFFIX}-${INSTALL_K3S_COMMIT}.sha256sum - else - HASH_URL=${GITHUB_URL}/download/${VERSION_K3S}/sha256sum-${ARCH}.txt - fi - info "Downloading hash ${HASH_URL}" - download ${TMP_HASH} ${HASH_URL} - HASH_EXPECTED=$(grep " k3s${SUFFIX}$" ${TMP_HASH}) - HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*} -} - -# --- check hash against installed version --- -installed_hash_matches() { - if [ -x ${BIN_DIR}/k3s ]; then - HASH_INSTALLED=$(sha256sum ${BIN_DIR}/k3s) - HASH_INSTALLED=${HASH_INSTALLED%%[[:blank:]]*} - if [ "${HASH_EXPECTED}" = "${HASH_INSTALLED}" ]; then - return - fi - fi - return 1 -} - -# --- download binary from github url --- -download_binary() { - if [ -n "${INSTALL_K3S_COMMIT}" ]; then - BIN_URL=${STORAGE_URL}/k3s${SUFFIX}-${INSTALL_K3S_COMMIT} - else - BIN_URL=${GITHUB_URL}/download/${VERSION_K3S}/k3s${SUFFIX} - fi - info "Downloading binary ${BIN_URL}" - download ${TMP_BIN} ${BIN_URL} -} - -# --- verify downloaded binary hash --- -verify_binary() { - info "Verifying binary download" - HASH_BIN=$(sha256sum ${TMP_BIN}) - HASH_BIN=${HASH_BIN%%[[:blank:]]*} - if [ "${HASH_EXPECTED}" != "${HASH_BIN}" ]; then - fatal "Download sha256 does not match ${HASH_EXPECTED}, got ${HASH_BIN}" - fi -} - -# --- setup permissions and move binary to system directory --- -setup_binary() { - chmod 755 ${TMP_BIN} - info "Installing k3s to ${BIN_DIR}/k3s" - $SUDO chown root:root ${TMP_BIN} - $SUDO mv -f ${TMP_BIN} ${BIN_DIR}/k3s -} - -# --- setup selinux policy --- -setup_selinux() { - policy_hint="please install: - yum install -y container-selinux selinux-policy-base - rpm -i https://rpm.rancher.io/k3s-selinux-0.1.1-rc1.el7.noarch.rpm -" - policy_error=fatal - if [ "$INSTALL_K3S_SELINUX_WARN" = true ]; then - policy_error=warn - fi - - if ! $SUDO chcon -u system_u -r object_r -t container_runtime_exec_t ${BIN_DIR}/k3s >/dev/null 2>&1; then - if $SUDO grep '^\s*SELINUX=enforcing' /etc/selinux/config >/dev/null 2>&1; then - $policy_error "Failed to apply container_runtime_exec_t to ${BIN_DIR}/k3s, ${policy_hint}" - fi - else - if [ ! -f /usr/share/selinux/packages/k3s.pp ]; then - $policy_error "Failed to find the k3s-selinux policy, ${policy_hint}" - fi - fi -} - -# --- download and verify k3s --- -download_and_verify() { - if can_skip_download; then - info 'Skipping k3s download and verify' - verify_k3s_is_executable - return - fi - - setup_verify_arch - verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files' - setup_tmp - get_release_version - download_hash - - if installed_hash_matches; then - info 'Skipping binary downloaded, installed k3s matches hash' - return - fi - - download_binary - verify_binary - setup_binary -} - -# --- add additional utility links --- -create_symlinks() { - [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return - [ "${INSTALL_K3S_SYMLINK}" = skip ] && return - - for cmd in kubectl crictl ctr; do - if [ ! -e ${BIN_DIR}/${cmd} ] || [ "${INSTALL_K3S_SYMLINK}" = force ]; then - which_cmd=$(which ${cmd} 2>/dev/null || true) - if [ -z "${which_cmd}" ] || [ "${INSTALL_K3S_SYMLINK}" = force ]; then - info "Creating ${BIN_DIR}/${cmd} symlink to k3s" - $SUDO ln -sf k3s ${BIN_DIR}/${cmd} - else - info "Skipping ${BIN_DIR}/${cmd} symlink to k3s, command exists in PATH at ${which_cmd}" - fi - else - info "Skipping ${BIN_DIR}/${cmd} symlink to k3s, already exists" - fi - done -} - -# --- create killall script --- -create_killall() { - [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return - info "Creating killall script ${KILLALL_K3S_SH}" - $SUDO tee ${KILLALL_K3S_SH} >/dev/null << \EOF -#!/bin/sh -[ $(id -u) -eq 0 ] || exec sudo $0 $@ - -for bin in /var/lib/rancher/k3s/data/**/bin/; do - [ -d $bin ] && export PATH=$PATH:$bin:$bin/aux -done - -set -x - -for service in /etc/systemd/system/k3s*.service; do - [ -s $service ] && systemctl stop $(basename $service) -done - -for service in /etc/init.d/k3s*; do - [ -x $service ] && $service stop -done - -pschildren() { - ps -e -o ppid= -o pid= | \ - sed -e 's/^\s*//g; s/\s\s*/\t/g;' | \ - grep -w "^$1" | \ - cut -f2 -} - -pstree() { - for pid in $@; do - echo $pid - for child in $(pschildren $pid); do - pstree $child - done - done -} - -killtree() { - kill -9 $( - { set +x; } 2>/dev/null; - pstree $@; - set -x; - ) 2>/dev/null -} - -getshims() { - ps -e -o pid= -o args= | sed -e 's/^ *//; s/\s\s*/\t/;' | grep -w 'k3s/data/[^/]*/bin/containerd-shim' | cut -f1 -} - -killtree $({ set +x; } 2>/dev/null; getshims; set -x) - -do_unmount() { - { set +x; } 2>/dev/null - MOUNTS= - while read ignore mount ignore; do - MOUNTS="$mount\n$MOUNTS" - done /dev/null | grep 'master cni0' | while read ignore iface ignore; do - iface=${iface%%@*} - [ -z "$iface" ] || ip link delete $iface -done -ip link delete cni0 -ip link delete flannel.1 -rm -rf /var/lib/cni/ -iptables-save | grep -v KUBE- | grep -v CNI- | iptables-restore -EOF - $SUDO chmod 755 ${KILLALL_K3S_SH} - $SUDO chown root:root ${KILLALL_K3S_SH} -} - -# --- create uninstall script --- -create_uninstall() { - [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return - info "Creating uninstall script ${UNINSTALL_K3S_SH}" - $SUDO tee ${UNINSTALL_K3S_SH} >/dev/null << EOF -#!/bin/sh -set -x -[ \$(id -u) -eq 0 ] || exec sudo \$0 \$@ - -${KILLALL_K3S_SH} - -if which systemctl; then - systemctl disable ${SYSTEM_NAME} - systemctl reset-failed ${SYSTEM_NAME} - systemctl daemon-reload -fi -if which rc-update; then - rc-update delete ${SYSTEM_NAME} default -fi - -rm -f ${FILE_K3S_SERVICE} -rm -f ${FILE_K3S_ENV} - -remove_uninstall() { - rm -f ${UNINSTALL_K3S_SH} -} -trap remove_uninstall EXIT - -if (ls ${SYSTEMD_DIR}/k3s*.service || ls /etc/init.d/k3s*) >/dev/null 2>&1; then - set +x; echo 'Additional k3s services installed, skipping uninstall of k3s'; set -x - exit -fi - -for cmd in kubectl crictl ctr; do - if [ -L ${BIN_DIR}/\$cmd ]; then - rm -f ${BIN_DIR}/\$cmd - fi -done - -rm -rf /etc/rancher/k3s -rm -rf /run/k3s -rm -rf /run/flannel -rm -rf /var/lib/rancher/k3s -rm -rf /var/lib/kubelet -rm -f ${BIN_DIR}/k3s -rm -f ${KILLALL_K3S_SH} -EOF - $SUDO chmod 755 ${UNINSTALL_K3S_SH} - $SUDO chown root:root ${UNINSTALL_K3S_SH} -} - -# --- disable current service if loaded -- -systemd_disable() { - $SUDO rm -f /etc/systemd/system/${SERVICE_K3S} || true - $SUDO rm -f /etc/systemd/system/${SERVICE_K3S}.env || true - $SUDO systemctl disable ${SYSTEM_NAME} >/dev/null 2>&1 || true -} - -# --- capture current env and create file containing k3s_ variables --- -create_env_file() { - info "env: Creating environment file ${FILE_K3S_ENV}" - UMASK=$(umask) - umask 0377 - env | grep '^K3S_' | $SUDO tee ${FILE_K3S_ENV} >/dev/null - env | egrep -i '^(NO|HTTP|HTTPS)_PROXY' | $SUDO tee -a ${FILE_K3S_ENV} >/dev/null - umask $UMASK -} - -# --- write systemd service file --- -create_systemd_service_file() { - info "systemd: Creating service file ${FILE_K3S_SERVICE}" - $SUDO tee ${FILE_K3S_SERVICE} >/dev/null << EOF -[Unit] -Description=Lightweight Kubernetes -Documentation=https://k3s.io -Wants=network-online.target - -[Install] -WantedBy=multi-user.target - -[Service] -Type=${SYSTEMD_TYPE} -EnvironmentFile=${FILE_K3S_ENV} -KillMode=process -Delegate=yes -# Having non-zero Limit*s causes performance problems due to accounting overhead -# in the kernel. We recommend using cgroups to do container-local accounting. -LimitNOFILE=1048576 -LimitNPROC=infinity -LimitCORE=infinity -TasksMax=infinity -TimeoutStartSec=0 -Restart=always -RestartSec=5s -ExecStartPre=-/sbin/modprobe br_netfilter -ExecStartPre=-/sbin/modprobe overlay -ExecStart=${BIN_DIR}/k3s \\ - ${CMD_K3S_EXEC} - -EOF -} - -# --- write openrc service file --- -create_openrc_service_file() { - LOG_FILE=/var/log/${SYSTEM_NAME}.log - - info "openrc: Creating service file ${FILE_K3S_SERVICE}" - $SUDO tee ${FILE_K3S_SERVICE} >/dev/null << EOF -#!/sbin/openrc-run - -depend() { - after network-online - want cgroups -} - -start_pre() { - rm -f /tmp/k3s.* -} - -supervisor=supervise-daemon -name=${SYSTEM_NAME} -command="${BIN_DIR}/k3s" -command_args="$(escape_dq "${CMD_K3S_EXEC}") - >>${LOG_FILE} 2>&1" - -output_log=${LOG_FILE} -error_log=${LOG_FILE} - -pidfile="/var/run/${SYSTEM_NAME}.pid" -respawn_delay=5 -respawn_max=0 - -set -o allexport -if [ -f /etc/environment ]; then source /etc/environment; fi -if [ -f ${FILE_K3S_ENV} ]; then source ${FILE_K3S_ENV}; fi -set +o allexport -EOF - $SUDO chmod 0755 ${FILE_K3S_SERVICE} - - $SUDO tee /etc/logrotate.d/${SYSTEM_NAME} >/dev/null << EOF -${LOG_FILE} { - missingok - notifempty - copytruncate -} -EOF -} - -# --- write systemd or openrc service file --- -create_service_file() { - [ "${HAS_SYSTEMD}" = true ] && create_systemd_service_file - [ "${HAS_OPENRC}" = true ] && create_openrc_service_file - return 0 -} - -# --- get hashes of the current k3s bin and service files -get_installed_hashes() { - $SUDO sha256sum ${BIN_DIR}/k3s ${FILE_K3S_SERVICE} ${FILE_K3S_ENV} 2>&1 || true -} - -# --- enable and start systemd service --- -systemd_enable() { - info "systemd: Enabling ${SYSTEM_NAME} unit" - $SUDO systemctl enable ${FILE_K3S_SERVICE} >/dev/null - $SUDO systemctl daemon-reload >/dev/null -} - -systemd_start() { - info "systemd: Starting ${SYSTEM_NAME}" - $SUDO systemctl restart ${SYSTEM_NAME} -} - -# --- enable and start openrc service --- -openrc_enable() { - info "openrc: Enabling ${SYSTEM_NAME} service for default runlevel" - $SUDO rc-update add ${SYSTEM_NAME} default >/dev/null -} - -openrc_start() { - info "openrc: Starting ${SYSTEM_NAME}" - $SUDO ${FILE_K3S_SERVICE} restart -} - -# --- startup systemd or openrc service --- -service_enable_and_start() { - [ "${INSTALL_K3S_SKIP_ENABLE}" = true ] && return - - [ "${HAS_SYSTEMD}" = true ] && systemd_enable - [ "${HAS_OPENRC}" = true ] && openrc_enable - - [ "${INSTALL_K3S_SKIP_START}" = true ] && return - - POST_INSTALL_HASHES=$(get_installed_hashes) - if [ "${PRE_INSTALL_HASHES}" = "${POST_INSTALL_HASHES}" ]; then - info 'No change detected so skipping service start' - return - fi - - [ "${HAS_SYSTEMD}" = true ] && systemd_start - [ "${HAS_OPENRC}" = true ] && openrc_start - return 0 -} - -# --- re-evaluate args to include env command --- -eval set -- $(escape "${INSTALL_K3S_EXEC}") $(quote "$@") - -# --- run the install process -- -{ - verify_system - setup_env "$@" - download_and_verify - setup_selinux - create_symlinks - create_killall - create_uninstall - systemd_disable - create_env_file - create_service_file - service_enable_and_start -}