From fd336f9e2c1bb541fd4284d13a13cdf2f7239073 Mon Sep 17 00:00:00 2001 From: cdjianghan <328804165@qq.com> Date: Tue, 13 Dec 2022 13:40:14 +0800 Subject: [PATCH] add imageCrdBuilder split some function to make the logic clear fit new buildah implement add image id arch and size read --- pkg/buildah/imageCrdBuilder.go | 304 +++++++++++++++++++++++++++++++++ pkg/buildah/push.go | 5 + 2 files changed, 309 insertions(+) create mode 100644 pkg/buildah/imageCrdBuilder.go diff --git a/pkg/buildah/imageCrdBuilder.go b/pkg/buildah/imageCrdBuilder.go new file mode 100644 index 000000000..ee902bee6 --- /dev/null +++ b/pkg/buildah/imageCrdBuilder.go @@ -0,0 +1,304 @@ +// Copyright © 2022 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buildah + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "os" + "path/filepath" + "strconv" + "time" + + "github.com/openconfig/gnmi/errlist" + + "github.com/containers/buildah" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + imagev1 "github.com/labring/sealos/controllers/imagehub/api/v1" + "github.com/labring/sealos/pkg/client-go/kubernetes" + "github.com/labring/sealos/pkg/utils/file" + "github.com/labring/sealos/pkg/utils/logger" +) + +const ( + AppPath = "app" + READMEpath = "README.md" + ConfigFileName = "config.yaml" + TemplateFileName = "template.yaml" + CMDFileName = "CMD" + ImagehubGroup = "imagehub.sealos.io" + ImagehubVersion = "v1" + ImagehubResource = "images" +) + +type ImageCRDBuilder struct { + imagename string + clustername string + AppConfig *imagev1.Image +} + +func NewAndRunImageCRDBuilder(args []string, iopts *pushOptions) { + //run without error returns + icb := &ImageCRDBuilder{args[0], "", &imagev1.Image{}} + if iopts.imagecrd { + icb.Run() + } +} + +func (icb *ImageCRDBuilder) Run() { + MountPoint, err := icb.CreateContainer() + if err != nil { + // get err when create container ,abort all + return + } + defer func() { + err = icb.DeleteContainer() + if err != nil { + logger.Warn(err) + } + }() + err = icb.GetAppContent(MountPoint) + if err != nil { + logger.Warn(err) + } + err = icb.AppContentApply() + if err != nil { + logger.Warn(err) + } +} + +func (icb *ImageCRDBuilder) CreateContainer() (string, error) { + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) + clusterName := icb.imagename + strconv.Itoa(rnd.Int()) + + realImpl, err := New("") + if err != nil { + return "", err + } + + builderInfo, err := realImpl.Create(clusterName, icb.imagename) + if err != nil { + return "", err + } + if iseist := file.IsExist(builderInfo.MountPoint); !iseist { + return "", fmt.Errorf("mountPoint not Exist") + } + icb.clustername = clusterName + return builderInfo.MountPoint, nil +} + +/* +Action path example: + app/ + ├── config.yaml + ├── action1 + │ ├── cmd + │ └── template.yaml + ├── action2 + │ ├── cmd + │ └── template.yaml + x +The template and cmd of the action are stored in the folder named after the action,config is used to configure the basic information of the image CRD. +*/ + +// GetAppContent do content read in filereadPipeline, if one content get failed ,it won't abort. +func (icb *ImageCRDBuilder) GetAppContent(MountPoint string) error { + //app config is the based file ,it should read first + if err := icb.ReadAppConfig(MountPoint); err != nil { + return fmt.Errorf("base config cant find") + } + var errl errlist.List + //Using a pipe helps with future file reading needs + fileReadPipeLine := []func(MountPoint string) error{ + icb.ReadImageContent, + icb.ReadReadme, + icb.ReadActions, + } + for _, f := range fileReadPipeLine { + if err := f(MountPoint); err != nil { + errl.Add(err) + } + } + return errl.Err() +} + +func (icb *ImageCRDBuilder) AppContentApply() error { + client, err := kubernetes.NewKubernetesClient("", "") + if err != nil { + return fmt.Errorf("new KubernetesClient err: %v", err) + } + dyclient := client.KubernetesDynamic().Resource(schema.GroupVersionResource{ + Group: ImagehubGroup, + Version: ImagehubVersion, + Resource: ImagehubResource, + }) + utd := unstructured.Unstructured{} + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(icb.AppConfig) + if err != nil { + return fmt.Errorf("appconfig ToUnstructured err : %v", err) + } + utd.Object = obj + if _, err = dyclient.Create(context.TODO(), &utd, metav1.CreateOptions{}); err != nil { + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("image create err : %v", err) + } + if utd.GetResourceVersion() == "" { + var objGET *unstructured.Unstructured + objGET, err = dyclient.Get(context.TODO(), utd.GetName(), metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("unable to get obj: %v", err) + } + utd.SetResourceVersion(objGET.GetResourceVersion()) + } + if _, err = dyclient.Update(context.TODO(), &utd, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("image update err : %v", err) + } + } + return nil +} + +func (icb *ImageCRDBuilder) DeleteContainer() error { + realImpl, err := New("") + if err != nil { + return fmt.Errorf("deleteContainer err : %v", err) + } + return realImpl.Delete(icb.imagename) +} + +func (icb *ImageCRDBuilder) ReadAppConfig(MountPoint string) error { + if file.IsExist(filepath.Join(MountPoint, AppPath, ConfigFileName)) { + c, err := file.ReadAll(filepath.Join(MountPoint, AppPath, ConfigFileName)) + if err != nil { + return fmt.Errorf("read config.yaml err: %v", err) + } + if err = icb.ConfigParse(c); err != nil { + return fmt.Errorf("readAppConfig : %v", err) + } + } + return nil +} + +func (icb *ImageCRDBuilder) ConfigParse(c []byte) error { + config := imagev1.Image{} + _, _, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(c, nil, &config) + if err != nil { + return fmt.Errorf("configParse err : %v", err) + } + config.Spec.DetailInfo.Actions = make(map[string]imagev1.Action) + icb.AppConfig = &config + return nil +} + +func (icb *ImageCRDBuilder) ReadReadme(MountPoint string) error { + if file.IsExist(filepath.Join(MountPoint, READMEpath)) { + c, err := file.ReadAll(filepath.Join(MountPoint, READMEpath)) + if err != nil { + return fmt.Errorf("read README.md err: %v", err) + } + icb.AppConfig.Spec.DetailInfo.Description = string(c) + } + return nil +} + +func (icb *ImageCRDBuilder) ReadImageContent(MountPoint string) error { + realImpl, err := New("") + if err != nil { + return fmt.Errorf("deleteContainer err : %v", err) + } + containerInfo, err := realImpl.InspectContainer(icb.imagename) + if err != nil { + return fmt.Errorf("readImageContent InspectImage err : %v", err) + } + var config v1.Image + if err = json.Unmarshal([]byte(containerInfo.Config), &config); err != nil { + return fmt.Errorf("get image config err : %v", err) + } + var manifest v1.Manifest + if err = json.Unmarshal([]byte(containerInfo.Manifest), &manifest); err != nil { + return fmt.Errorf("get image config err : %v", err) + } + icb.BindImageContent(containerInfo, config, manifest) + return nil +} + +func (icb *ImageCRDBuilder) BindImageContent(containerInfo buildah.BuilderInfo, config v1.Image, manifest v1.Manifest) { + icb.AppConfig.Spec.DetailInfo.ID = containerInfo.FromImageID + icb.AppConfig.Spec.DetailInfo.Arch = config.Architecture + var size int64 + size = manifest.Config.Size * 1000 + for _, layer := range manifest.Layers { + size += layer.Size + } + icb.AppConfig.Spec.DetailInfo.Size = size +} + +// ReadActions read all action in /app dir +func (icb *ImageCRDBuilder) ReadActions(MountPoint string) error { + if file.IsExist(filepath.Join(MountPoint, AppPath)) { + fileEntrys, err := os.ReadDir(filepath.Join(MountPoint, AppPath)) + if err != nil { + return fmt.Errorf("read actions in app dir err: %v ", err) + } + for _, f := range fileEntrys { + if !f.IsDir() { + continue + } + if _, isExist := icb.AppConfig.Spec.DetailInfo.Actions[f.Name()]; isExist { + logger.Warn("action name repeat") + } + icb.AppConfig.Spec.DetailInfo.Actions[f.Name()], err = icb.ReadTemplateAndCmd(MountPoint, f.Name()) + if err != nil { + return fmt.Errorf("read action : %v err: %v ", filepath.Join(MountPoint, AppPath, f.Name()), err) + } + } + } + return nil +} + +// ReadTemplateAndCmd read template and cmd yaml file in target action dir +func (icb *ImageCRDBuilder) ReadTemplateAndCmd(MountPoint string, actionName string) (imagev1.Action, error) { + actioncontent := imagev1.Action{ + Name: actionName, + Template: "", + CMD: "", + } + fileEntrys, err := os.ReadDir(filepath.Join(MountPoint, AppPath, actionName)) + if err != nil { + return imagev1.Action{}, fmt.Errorf("read path: %v err: %v", filepath.Join(MountPoint, AppPath), err) + } + var byt []byte + for _, f := range fileEntrys { + if f.Name() == TemplateFileName { + byt, _ = file.ReadAll(filepath.Join(MountPoint, AppPath, actionName, f.Name())) + actioncontent.Template = string(byt) + } + if f.Name() == CMDFileName { + byt, _ = file.ReadAll(filepath.Join(MountPoint, AppPath, actionName, f.Name())) + actioncontent.CMD = string(byt) + } + } + return actioncontent, nil +} diff --git a/pkg/buildah/push.go b/pkg/buildah/push.go index 2c39bb7b8..d47ba231c 100644 --- a/pkg/buildah/push.go +++ b/pkg/buildah/push.go @@ -62,6 +62,7 @@ type pushOptions struct { encryptionKeys []string encryptLayers []int insecure bool + imagecrd bool } func newDefaultPushOptions() *pushOptions { @@ -94,6 +95,7 @@ func (opts *pushOptions) RegisterFlags(fs *pflag.FlagSet) error { fs.StringSliceVar(&opts.encryptionKeys, "encryption-key", opts.encryptionKeys, "key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)") fs.IntSliceVar(&opts.encryptLayers, "encrypt-layer", opts.encryptLayers, "layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified") fs.BoolVar(&opts.tlsVerify, "tls-verify", opts.tlsVerify, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") + fs.BoolVar(&opts.imagecrd, "imagecrd", opts.imagecrd, "") return markFlagsHidden(fs, []string{"signature-policy", "blob-cache", "tls-verify"}...) } @@ -120,6 +122,9 @@ func newPushCommand() *cobra.Command { PostRun: func(cmd *cobra.Command, args []string) { NewAndRunImageCRBuilder(args) }, + PostRun: func(cmd *cobra.Command, args []string) { + NewAndRunImageCRDBuilder(args, opts) + }, Example: fmt.Sprintf(`%[1]s push imageID docker://registry.example.com/repository:tag %[1]s push imageID docker-daemon:image:tagi %[1]s push imageID oci:/path/to/layout:image:tag`, rootCmd.CommandPath()),