add some tests for image discovery, flesh out boot process

This commit is contained in:
Josh Wolf
2021-06-10 14:42:13 -06:00
parent 4287dd643d
commit 541d774857
8 changed files with 167 additions and 66 deletions
+12 -3
View File
@@ -71,6 +71,8 @@ func (o *deployOpts) Run(packagePath string) error {
return err
}
d := v1alpha1.NewDriver(p.Spec.Driver.Kind)
b, err := bootstrap.NewBooter(tmpdir)
if err != nil {
return err
@@ -82,14 +84,21 @@ func (o *deployOpts) Run(packagePath string) error {
}
o.logger.Infof("Performing pre %s boot steps", p.Spec.Driver.Kind)
if err := b.PreBoot(ctx, d); err != nil {
return err
}
o.logger.Infof("Booting %s", p.Spec.Driver.Kind)
if err := b.Boot(ctx, d); err != nil {
return err
}
o.logger.Infof("Performing post %s boot steps", p.Spec.Driver.Kind)
if err := b.PostBoot(ctx, d); err != nil {
return err
}
o.logger.Infof("Success!")
_ = ctx
o.logger.Infof("Success! You can access the cluster with '/opt/hauler/bin/kubectl'")
return nil
}
@@ -1,13 +0,0 @@
apiVersion: hauler.cattle.io/v1alpha1
kind: Package
metadata:
creationTimestamp: null
name: test
spec:
driver:
kind: k3s
version: v1.21.1+k3s1
fleet:
version: 0.3.5
paths:
- ../../../testdata/rawmanifests
+4 -4
View File
@@ -11,7 +11,7 @@ type Drive interface {
LibPath() string
EtcPath() string
Config() (map[string]interface{}, error)
Config() (*map[string]interface{}, error)
SystemObjects() (objs []object.ObjMetadata)
}
@@ -44,7 +44,7 @@ func (k k3s) Images() ([]string, error) {
}, nil
}
func (k k3s) Config() (map[string]interface{}, error) {
func (k k3s) Config() (*map[string]interface{}, error) {
// TODO: This should be typed
c := make(map[string]interface{})
c["write-kubeconfig-mode"] = "0644"
@@ -52,7 +52,7 @@ func (k k3s) Config() (map[string]interface{}, error) {
//TODO: Add uid or something to ensure this works for multi-node setups
c["node-name"] = "hauler"
return c, nil
return &c, nil
}
func (k k3s) SystemObjects() (objs []object.ObjMetadata) {
@@ -74,7 +74,7 @@ func (r rke2) Images() ([]string, error) { return []string{}, n
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) 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
+14 -10
View File
@@ -18,9 +18,9 @@ import (
type Booter interface {
Init() error
PreBoot() error
Boot() error
PostBoot() error
PreBoot(context.Context) error
Boot(context.Context, v1alpha1.Drive) error
PostBoot(context.Context, v1alpha1.Drive) error
}
type booter struct {
@@ -55,7 +55,11 @@ func (b booter) Init() error {
return nil
}
func (b booter) PreBoot() error {
func (b booter) PreBoot(ctx context.Context, d v1alpha1.Drive) error {
if err := b.writeConfig(d); err != nil {
return err
}
return nil
}
@@ -68,15 +72,15 @@ func (b booter) Boot(ctx context.Context, d v1alpha1.Drive) error {
"INSTALL_K3S_SELINUX_WARN=true",
"INSTALL_K3S_SKIP_SELINUX_RPM=true",
"INSTALL_K3S_BIN_DIR=/opt/hauler/bin",
//"INSTALL_K3S_SKIP_START=true",
}...)
out, err := cmd.CombinedOutput()
if err != nil {
return err
return fmt.Errorf("%s\n%v", out, err)
}
//TODO: Figure out what to do with output
_ = out
return waitForDriver(ctx, d)
}
@@ -121,7 +125,7 @@ func (b booter) moveBin() error {
return err
}
return copy.Copy(b.fs.Path(), path)
return copy.Copy(b.fs.Bin().Path(), path)
}
func (b booter) moveImages(d v1alpha1.Drive) error {
@@ -145,7 +149,7 @@ func (b booter) moveBundles(d v1alpha1.Drive) error {
return err
}
return copy.Copy(b.fs.Path(), path)
return copy.Copy(b.fs.Bundle().Path(), path)
}
func (b booter) moveCharts(d v1alpha1.Drive) error {
@@ -154,7 +158,7 @@ func (b booter) moveCharts(d v1alpha1.Drive) error {
return err
}
return copy.Copy(b.fs.Path(), path)
return copy.Copy(b.fs.Chart().Path(), path)
}
func (b booter) writeConfig(d v1alpha1.Drive) error {
@@ -183,5 +187,5 @@ func (b booter) writeConfig(d v1alpha1.Drive) error {
}
data, err := yaml.Marshal(c)
return os.WriteFile(path, data, 0600)
return os.WriteFile(path, data, 0644)
}
+42 -28
View File
@@ -2,8 +2,6 @@ package packager
import (
"bytes"
"encoding/json"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -12,13 +10,20 @@ import (
"github.com/rancher/fleet/pkg/helmdeployer"
"github.com/rancher/fleet/pkg/manifest"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/client-go/util/jsonpath"
"strings"
)
type Imager interface {
Images() ([]string, error)
}
type discoveredImages []string
func (d discoveredImages) Images() ([]string, error) {
return d, nil
}
//ConcatImages will gather images from various Imager sources and return a single slilce
func ConcatImages(imager ...Imager) (map[name.Reference]v1.Image, error) {
m := make(map[name.Reference]v1.Image)
@@ -43,7 +48,7 @@ func ConcatImages(imager ...Imager) (map[name.Reference]v1.Image, error) {
return m, nil
}
func IdentifyImages(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) {
func IdentifyImages(b *fleetapi.Bundle) (discoveredImages, error) {
opts := fleetapi.BundleDeploymentOptions{
DefaultNamespace: "default",
}
@@ -56,13 +61,17 @@ func IdentifyImages(b *fleetapi.Bundle) (map[name.Reference]v1.Image, error) {
return nil, err
}
var di discoveredImages
for _, o := range objs {
u := o.(*unstructured.Unstructured)
_ = u
imageFromRuntimeObject(u)
imgs, err := imageFromRuntimeObject(o.(*unstructured.Unstructured))
if err != nil {
return nil, err
}
di = append(di, imgs...)
}
return nil, err
return di, err
}
//ResolveRemoteRefs will return a slice of remote images resolved from their fully qualified name
@@ -70,6 +79,9 @@ func ResolveRemoteRefs(images ...string) (map[name.Reference]v1.Image, error) {
m := make(map[name.Reference]v1.Image)
for _, i := range images {
if i == "" { continue }
//TODO: This will error out if remote is a v1 image, do better error handling for this
ref, err := name.ParseReference(i)
if err != nil {
return nil, err
@@ -87,40 +99,42 @@ func ResolveRemoteRefs(images ...string) (map[name.Reference]v1.Image, error) {
}
var knownImagePaths = []string{
"spec.template.spec.containers.#.image",
"{.spec.template.spec.containers[*].image}",
}
////imageFromRuntimeObject will return any images found in known obj specs
func imageFromRuntimeObject(obj *unstructured.Unstructured) ([]string, error) {
data, err := obj.MarshalJSON()
if err != nil {
func imageFromRuntimeObject(obj *unstructured.Unstructured) (images []string, err error) {
objData, _ := obj.MarshalJSON()
var data interface{}
if err := json.Unmarshal(objData, &data); err != nil {
return nil, err
}
var images []string
j := jsonpath.New("imagePath")
var imageData interface{}
err = json.Unmarshal(data, &imageData)
if err != nil {
return nil, err
}
j := jsonpath.New("")
j.AllowMissingKeys(true)
for _, path := range knownImagePaths {
j.Parse(path)
buf := new(bytes.Buffer)
err = j.Execute(buf, imageData)
r, err := parseJSONPath(data, j, path)
if err != nil {
return nil, err
}
images = append(images, buf.String())
images = append(images, r...)
}
return images, nil
}
func parseJSONPath(input interface{}, parser *jsonpath.JSONPath, template string) ([]string, error) {
buf := new(bytes.Buffer)
if err := parser.Parse(template); err != nil {
return nil, err
}
if err := parser.Execute(buf, input); err != nil {
return nil, err
}
r:= strings.Split(buf.String(), " ")
return r, nil
}
+83
View File
@@ -1 +1,84 @@
package packager
import (
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/client-go/util/jsonpath"
"reflect"
"testing"
)
var (
jsona = []byte(`{
"flatImage": "name/of/image:with-tag",
"deeply": {
"nested": {
"image": "another/image/name:with-a-tag",
"set": [
{ "image": "first/in/list:123" },
{ "image": "second/in:456" }
]
}
}
}`)
)
func Test_parseJSONPath(t *testing.T) {
var data interface{}
if err := json.Unmarshal(jsona, &data); err != nil {
t.Errorf("failed to unmarshal test article, %v", err)
}
j := jsonpath.New("")
type args struct {
input interface{}
name string
template string
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{
name: "should find flat path with string result",
args: args{
input: data,
name: "wut",
template: "{.flatImage}",
},
want: []string{"name/of/image:with-tag"},
},
{
name: "should find nested path with string result",
args: args{
input: data,
name: "wut",
template: "{.deeply.nested.image}",
},
want: []string{"another/image/name:with-a-tag"},
},
{
name: "should find nested path with slice result",
args: args{
input: data,
name: "wut",
template: "{.deeply.nested.set[*].image}",
},
want: []string{"first/in/list:123", "second/in:456"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseJSONPath(tt.args.input, j, tt.args.template)
if (err != nil) != tt.wantErr {
t.Errorf("parseJSONPath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseJSONPath() got = %v, want %v", got, tt.want)
}
})
}
}
+6 -2
View File
@@ -28,6 +28,8 @@ func Create(ctx context.Context, p v1alpha1.Package, fsys fs.PkgFs, a archiver.A
Compress: true,
}
var di discoveredImages
//Get and write bundles to disk
for _, path := range p.Spec.Paths {
bundleName := filepath.Base(path)
@@ -39,11 +41,13 @@ func Create(ctx context.Context, p v1alpha1.Package, fsys fs.PkgFs, a archiver.A
//TODO: Figure out why bundle.Open doesn't return with GVK
bn := fleetapi.NewBundle("fleet-local", bundleName, *fb.Definition)
_, err = IdentifyImages(bn)
imgs, err := IdentifyImages(bn)
if err != nil {
return err
}
di = append(di, imgs...)
if err := fsys.AddBundle(bn); err != nil {
return err
}
@@ -68,7 +72,7 @@ func Create(ctx context.Context, p v1alpha1.Package, fsys fs.PkgFs, a archiver.A
return err
}
imgMap, err := ConcatImages(d, p.Spec.Fleet)
imgMap, err := ConcatImages(d, p.Spec.Fleet, di)
if err != nil {
return err
}
+6 -6
View File
@@ -1,22 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
name: podinfo
spec:
selector:
matchLabels:
app: guestbook
app: podinfo
tier: frontend
replicas: 3
replicas: 1
template:
metadata:
labels:
app: guestbook
app: podinfo
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google-samples/gb-frontend:v4
- name: podinfo
image: stefanprodan/podinfo:5.2.1
resources:
requests:
cpu: 100m