From a872a019afa14e78a2d08a74b832e02672915260 Mon Sep 17 00:00:00 2001 From: cbluebird Date: Tue, 24 Dec 2024 17:52:37 +0800 Subject: [PATCH] pref(devbox): pref the release retag Signed-off-by: cbluebird --- controllers/devbox/cmd/main.go | 2 +- controllers/devbox/go.mod | 5 +- controllers/devbox/go.sum | 10 +- .../controller/devboxrelease_controller.go | 33 +++-- .../controller/utils/registry/client.go | 138 +++++------------- .../controller/utils/registry/client_test.go | 16 +- 6 files changed, 75 insertions(+), 129 deletions(-) diff --git a/controllers/devbox/cmd/main.go b/controllers/devbox/cmd/main.go index 62416e9441d..2b045091b7c 100644 --- a/controllers/devbox/cmd/main.go +++ b/controllers/devbox/cmd/main.go @@ -200,7 +200,7 @@ func main() { if err = (&controller.DevBoxReleaseReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - Registry: ®istry.Client{ + Registry: ®istry.Registry{ Username: registryUser, Password: registryPassword, }, diff --git a/controllers/devbox/go.mod b/controllers/devbox/go.mod index bbc5f3ecdce..93fca53ae11 100644 --- a/controllers/devbox/go.mod +++ b/controllers/devbox/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/go-containerregistry v0.20.2 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 + github.com/regclient/regclient v0.8.0 golang.org/x/crypto v0.21.0 k8s.io/api v0.30.1 k8s.io/apimachinery v0.30.1 @@ -75,8 +76,8 @@ require ( golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.18.0 // indirect diff --git a/controllers/devbox/go.sum b/controllers/devbox/go.sum index 75d20f5afd4..580436d5a67 100644 --- a/controllers/devbox/go.sum +++ b/controllers/devbox/go.sum @@ -115,6 +115,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/regclient/regclient v0.8.0 h1:xNAMDlADcyMvFAlGXoqDOxlSUBG4mqWBFgjQqVTP8Og= +github.com/regclient/regclient v0.8.0/go.mod h1:h9+Y6dBvqBkdlrj6EIhbTOv0xUuIFl7CdI1bZvEB42g= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -184,10 +186,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/controllers/devbox/internal/controller/devboxrelease_controller.go b/controllers/devbox/internal/controller/devboxrelease_controller.go index 538f67c7da2..53ee99e11e2 100644 --- a/controllers/devbox/internal/controller/devboxrelease_controller.go +++ b/controllers/devbox/internal/controller/devboxrelease_controller.go @@ -38,10 +38,17 @@ import ( // DevBoxReleaseReconciler reconciles a DevBoxRelease object type DevBoxReleaseReconciler struct { client.Client - Registry *registry.Client + Registry *registry.Registry Scheme *runtime.Scheme } +type ImageInfo struct { + TotalName string + Repository string + ImageName string + Tag string +} + // +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxreleases,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxreleases/status,verbs=get;update;patch // +kubebuilder:rbac:groups=devbox.sealos.io,resources=devboxreleases/finalizers,verbs=update @@ -113,17 +120,17 @@ func (r *DevBoxReleaseReconciler) CreateReleaseTag(ctx context.Context, devboxRe if err := r.Get(ctx, devboxInfo, devbox); err != nil { return err } - hostName, imageName, oldTag, err := r.GetImageInfo(devbox) + imageInfo, err := r.GetImageRef(devbox) if err != nil { return err } - logger.Info("Tagging image", "host", hostName, "image", imageName, "oldTag", oldTag, "newTag", devboxRelease.Spec.NewTag) - devboxRelease.Status.OriginalImage = imageName + ":" + oldTag + logger.Info("Tagging image", "host", imageInfo.Repository, "image", imageInfo.ImageName, "oldTag", imageInfo.Tag, "newTag", devboxRelease.Spec.NewTag) + devboxRelease.Status.OriginalImage = fmt.Sprintf("%s:%s", imageInfo.ImageName, imageInfo.Tag) if err = r.Status().Update(ctx, devboxRelease); err != nil { logger.Error(err, "Failed to update status", "devbox", devboxRelease.Spec.DevboxName, "newTag", devboxRelease.Spec.NewTag) return err } - return r.Registry.TagImage(hostName, imageName, oldTag, devboxRelease.Spec.NewTag) + return r.Registry.Tag(imageInfo.TotalName, fmt.Sprintf("%s/%s:%s", imageInfo.Repository, imageInfo.ImageName, devboxRelease.Spec.NewTag)) } func (r *DevBoxReleaseReconciler) DeleteReleaseTag(_ context.Context, _ *devboxv1alpha1.DevBoxRelease) error { @@ -131,20 +138,26 @@ func (r *DevBoxReleaseReconciler) DeleteReleaseTag(_ context.Context, _ *devboxv return nil } -func (r *DevBoxReleaseReconciler) GetImageInfo(devbox *devboxv1alpha1.Devbox) (string, string, string, error) { +func (r *DevBoxReleaseReconciler) GetImageRef(devbox *devboxv1alpha1.Devbox) (ImageInfo, error) { if len(devbox.Status.CommitHistory) == 0 { - return "", "", "", fmt.Errorf("commit history is empty") + return ImageInfo{}, fmt.Errorf("commit history is empty") } commitHistory := helper.GetLastSuccessCommitHistory(devbox) if commitHistory == nil { - return "", "", "", fmt.Errorf("no successful commit history found") + return ImageInfo{}, fmt.Errorf("no successful commit history found") } + res, err := reference.ParseReference(commitHistory.Image) if err != nil { - return "", "", "", err + return ImageInfo{}, err } repo := res.Context() - return repo.RegistryStr(), repo.RepositoryStr(), res.Identifier(), nil + return ImageInfo{ + TotalName: commitHistory.Image, + Repository: repo.RegistryStr(), + ImageName: repo.RepositoryStr(), + Tag: res.Identifier(), + }, nil } // SetupWithManager sets up the controller with the Manager. diff --git a/controllers/devbox/internal/controller/utils/registry/client.go b/controllers/devbox/internal/controller/utils/registry/client.go index 13304369646..89fab653407 100644 --- a/controllers/devbox/internal/controller/utils/registry/client.go +++ b/controllers/devbox/internal/controller/utils/registry/client.go @@ -15,128 +15,62 @@ package registry import ( - "bytes" + "context" "errors" - "io" - "net/http" - "time" - "github.com/avast/retry-go" + "github.com/google/go-containerregistry/pkg/name" + registry "github.com/regclient/regclient" + "github.com/regclient/regclient/config" + "github.com/regclient/regclient/types/ref" ) -type Client struct { - Username string - Password string -} - var ( ErrorManifestNotFound = errors.New("manifest not found") ) -func (t *Client) TagImage(hostName string, imageName string, oldTag string, newTag string) error { - return retry.Do(func() error { - manifest, err := t.pullManifest(t.Username, t.Password, hostName, imageName, oldTag) - if err != nil { - return err - } - return t.pushManifest(t.Username, t.Password, hostName, imageName, newTag, manifest) - }, retry.Delay(time.Second*5), retry.Attempts(3), retry.LastErrorOnly(true)) +type Registry struct { + Username string + Password string } -//func (t *Client) login(authPath string, username string, password string, imageName string) (string, error) { -// var ( -// client = http.DefaultClient -// url = authPath + imageName + ":pull,push" -// ) -// -// req, err := http.NewRequest("GET", url, nil) -// if err != nil { -// return "", err -// } -// -// req.SetBasicAuth(username, password) -// -// resp, err := client.Do(req) -// if err != nil { -// return "", err -// } -// -// if resp.StatusCode != http.StatusOK { -// return "", errors.New(resp.Status) -// } -// -// bodyText, err := ioutil.ReadAll(resp.Body) -// if err != nil { -// return "", err -// } -// var data struct { -// Token string `json:"token"` -// AccessToken string `json:"access_token"` -// ExpiresIn int `json:"expires_in"` -// IssuedAt string `json:"issued_at"` -// } -// if err := json.Unmarshal(bodyText, &data); err != nil { -// return "", err -// } -// if data.Token == "" { -// return "", errors.New("empty token") -// } -// return data.Token, nil -//} - -func (t *Client) pullManifest(username string, password string, hostName string, imageName string, tag string) ([]byte, error) { - var ( - client = http.DefaultClient - url = "http://" + hostName + "/v2/" + imageName + "/manifests/" + tag - ) - req, err := http.NewRequest("GET", url, nil) +func (r *Registry) Tag(originImage, newImage string) error { + original, err := r.parseReference(originImage) if err != nil { - return nil, err + return err } - req.SetBasicAuth(username, password) - req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") - - resp, err := client.Do(req) + target, err := r.parseReference(newImage) if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, ErrorManifestNotFound - } - - if resp.StatusCode != http.StatusOK { - return nil, errors.New(resp.Status) + return err } - bodyText, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return bodyText, nil -} + originHost := r.wrapHost(target.Registry) + targetHost := r.wrapHost(original.Registry) -func (t *Client) pushManifest(username string, password string, hostName string, imageName string, tag string, manifest []byte) error { - var ( - client = http.DefaultClient - url = "http://" + hostName + "/v2/" + imageName + "/manifests/" + tag + hub := registry.New( + registry.WithConfigHost(*originHost), + registry.WithConfigHost(*targetHost), ) - req, err := http.NewRequest("PUT", url, bytes.NewBuffer(manifest)) - if err != nil { - return err - } + return hub.ImageCopy(context.Background(), original, target) +} - req.SetBasicAuth(username, password) - req.Header.Set("Content-type", "application/vnd.docker.distribution.manifest.v2+json") +func (r *Registry) wrapHost(hostUrl string) *config.Host { + host := config.HostNewDefName(nil, "http://"+hostUrl) + host.User = r.Username + host.Pass = r.Password + host.TLS = config.TLSDisabled + return host +} - resp, err := client.Do(req) +func (r *Registry) parseReference(image string) (ref.Ref, error) { + original, err := name.ParseReference(image) if err != nil { - return err + return ref.Ref{}, err } - - if resp.StatusCode != http.StatusCreated { - return errors.New(resp.Status) + originalRef := ref.Ref{ + Scheme: "reg", + Registry: original.Context().RegistryStr(), + Repository: original.Context().RepositoryStr(), + Tag: original.Identifier(), } - - return nil + return originalRef, nil } diff --git a/controllers/devbox/internal/controller/utils/registry/client_test.go b/controllers/devbox/internal/controller/utils/registry/client_test.go index 18dad98e9a5..7e4e0282c8a 100644 --- a/controllers/devbox/internal/controller/utils/registry/client_test.go +++ b/controllers/devbox/internal/controller/utils/registry/client_test.go @@ -22,10 +22,8 @@ func TestClient_TagImage(t1 *testing.T) { Password string } type args struct { - hostName string - imageName string - oldTag string - newTag string + originName string + newName string } tests := []struct { name string @@ -40,20 +38,18 @@ func TestClient_TagImage(t1 *testing.T) { Password: "passw0rd", }, args: args{ - hostName: "sealos.hub:5000", - imageName: "default/devbox-sample", - oldTag: "2024-08-21-072021", - newTag: "test", + originName: "sealos.hub:5000/default/devbox-sample:2024-08-21-072021", + newName: "sealos.hub:5000/default/devbox-sample:test", }, }, } for _, tt := range tests { t1.Run(tt.name, func(t1 *testing.T) { - t := &Client{ + t := &Registry{ Username: tt.fields.Username, Password: tt.fields.Password, } - if err := t.TagImage(tt.args.hostName, tt.args.imageName, tt.args.oldTag, tt.args.newTag); (err != nil) != tt.wantErr { + if err := t.Tag(tt.args.originName, tt.args.newName); (err != nil) != tt.wantErr { t1.Errorf("TagImage() error = %v, wantErr %v", err, tt.wantErr) } })