diff --git a/cmd/haulerctl/app/bootstrap.go b/cmd/haulerctl/app/bootstrap.go index 07cbe59..c90e12d 100644 --- a/cmd/haulerctl/app/bootstrap.go +++ b/cmd/haulerctl/app/bootstrap.go @@ -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 } diff --git a/cmd/haulerctl/app/create_test.package.yaml b/cmd/haulerctl/app/create_test.package.yaml deleted file mode 100644 index d344e68..0000000 --- a/cmd/haulerctl/app/create_test.package.yaml +++ /dev/null @@ -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 diff --git a/pkg/apis/hauler.cattle.io/v1alpha1/driver.go b/pkg/apis/hauler.cattle.io/v1alpha1/driver.go index 7a1f633..dff6eff 100644 --- a/pkg/apis/hauler.cattle.io/v1alpha1/driver.go +++ b/pkg/apis/hauler.cattle.io/v1alpha1/driver.go @@ -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 diff --git a/pkg/bootstrap/booter.go b/pkg/bootstrap/booter.go index 8d95de7..d3d5354 100644 --- a/pkg/bootstrap/booter.go +++ b/pkg/bootstrap/booter.go @@ -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) } diff --git a/pkg/packager/images.go b/pkg/packager/images.go index 9abec93..8a58f37 100644 --- a/pkg/packager/images.go +++ b/pkg/packager/images.go @@ -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 +} \ No newline at end of file diff --git a/pkg/packager/images_test.go b/pkg/packager/images_test.go index cc25950..bbe03f9 100644 --- a/pkg/packager/images_test.go +++ b/pkg/packager/images_test.go @@ -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) + } + }) + } +} diff --git a/pkg/packager/packager.go b/pkg/packager/packager.go index 2283513..f8c7be9 100644 --- a/pkg/packager/packager.go +++ b/pkg/packager/packager.go @@ -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 } diff --git a/testdata/rawmanifests/deployment.yaml b/testdata/rawmanifests/deployment.yaml index 50693a9..13ba734 100644 --- a/testdata/rawmanifests/deployment.yaml +++ b/testdata/rawmanifests/deployment.yaml @@ -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