From bd61d7c2fceea27f0ada81dc12fb00228a4c816b Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Wed, 7 Oct 2026 17:45:23 +0800 Subject: [PATCH] fix: save container image panic (#25786) Co-authored-by: Qiu Jian --- pkg/apis/compute/container.go | 5 + pkg/compute/models/container_commit_test.go | 119 ++++++++++++++++++++ pkg/compute/models/containers.go | 57 +++++++--- 3 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 pkg/compute/models/container_commit_test.go diff --git a/pkg/apis/compute/container.go b/pkg/apis/compute/container.go index 183a999926..43a553b3cf 100644 --- a/pkg/apis/compute/container.go +++ b/pkg/apis/compute/container.go @@ -294,10 +294,15 @@ type KubeServerContainerRegistryConfigHarbor struct { KubeServerContainerRegistryConfigCommon } +type KubeServerContainerRegistryConfigCustom struct { + KubeServerContainerRegistryConfigCommon +} + type KubeServerContainerRegistryConfig struct { Type string `json:"type"` Common *KubeServerContainerRegistryConfigCommon `json:"common"` Harbor *KubeServerContainerRegistryConfigHarbor `json:"harbor"` + Custom *KubeServerContainerRegistryConfigCustom `json:"custom"` } type KubeServerContainerRegistryDetails struct { diff --git a/pkg/compute/models/container_commit_test.go b/pkg/compute/models/container_commit_test.go new file mode 100644 index 0000000000..a86e016f8c --- /dev/null +++ b/pkg/compute/models/container_commit_test.go @@ -0,0 +1,119 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "testing" + + "yunion.io/x/onecloud/pkg/apis" + api "yunion.io/x/onecloud/pkg/apis/compute" + hostapi "yunion.io/x/onecloud/pkg/apis/host" +) + +func TestContainerCommitRegistryAuthNilCommon(t *testing.T) { + reg := &api.KubeServerContainerRegistryDetails{ + Type: "common", + Config: &api.KubeServerContainerRegistryConfig{ + Type: "common", + }, + } + hostInput := &hostapi.ContainerCommitInput{ + Auth: new(apis.ContainerPullImageAuthConfig), + } + if err := applyContainerCommitRegistryAuth(hostInput, reg); err != nil { + t.Fatalf("anonymous common registry: %v", err) + } + if hostInput.Auth.Username != "" || hostInput.Auth.Password != "" { + t.Fatalf("expected empty auth, got %#v", hostInput.Auth) + } +} + +func TestContainerCommitRegistryAuth(t *testing.T) { + cases := []struct { + name string + reg *api.KubeServerContainerRegistryDetails + user string + pass string + }{ + { + name: "common", + reg: &api.KubeServerContainerRegistryDetails{ + Type: "common", + Config: &api.KubeServerContainerRegistryConfig{ + Common: &api.KubeServerContainerRegistryConfigCommon{Username: "u", Password: "p"}, + }, + }, + user: "u", + pass: "p", + }, + { + name: "harbor", + reg: &api.KubeServerContainerRegistryDetails{ + Type: "harbor", + Config: &api.KubeServerContainerRegistryConfig{ + Harbor: &api.KubeServerContainerRegistryConfigHarbor{ + KubeServerContainerRegistryConfigCommon: api.KubeServerContainerRegistryConfigCommon{Username: "hu", Password: "hp"}, + }, + }, + }, + user: "hu", + pass: "hp", + }, + { + name: "custom", + reg: &api.KubeServerContainerRegistryDetails{ + Type: "custom", + Config: &api.KubeServerContainerRegistryConfig{ + Custom: &api.KubeServerContainerRegistryConfigCustom{ + KubeServerContainerRegistryConfigCommon: api.KubeServerContainerRegistryConfigCommon{Username: "cu", Password: "cp"}, + }, + }, + }, + user: "cu", + pass: "cp", + }, + { + name: "harbor without config section", + reg: &api.KubeServerContainerRegistryDetails{ + Type: "harbor", + Config: &api.KubeServerContainerRegistryConfig{Type: "harbor"}, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + hostInput := &hostapi.ContainerCommitInput{ + Auth: new(apis.ContainerPullImageAuthConfig), + } + if err := applyContainerCommitRegistryAuth(hostInput, tc.reg); err != nil { + t.Fatal(err) + } + if hostInput.Auth.Username != tc.user || hostInput.Auth.Password != tc.pass { + t.Fatalf("auth = %#v, want user=%q pass=%q", hostInput.Auth, tc.user, tc.pass) + } + }) + } +} + +func TestContainerCommitRegistryAuthInvalidType(t *testing.T) { + reg := &api.KubeServerContainerRegistryDetails{ + Type: "unknown", + Config: &api.KubeServerContainerRegistryConfig{}, + } + err := applyContainerCommitRegistryAuth(&hostapi.ContainerCommitInput{}, reg) + if err == nil { + t.Fatal("expected error for unknown registry type") + } +} diff --git a/pkg/compute/models/containers.go b/pkg/compute/models/containers.go index 60685fc42b..0424762fa8 100644 --- a/pkg/compute/models/containers.go +++ b/pkg/compute/models/containers.go @@ -1418,6 +1418,48 @@ func (c *SContainer) PerformStatus(ctx context.Context, userCred mcclient.TokenC return c.SVirtualResourceBase.PerformStatus(ctx, userCred, query, input.PerformStatusInput) } +// containerCommitRegistryAuth returns the username/password section for a registry. +// A nil result means the registry has no credentials (anonymous pull/push is allowed). +// Callers must not dereference the nested config directly: glance omits common/harbor/custom +// when the registry has no credential, and that nil pointer used to panic in PerformCommit. +func containerCommitRegistryAuth(reg *api.KubeServerContainerRegistryDetails) (*api.KubeServerContainerRegistryConfigCommon, error) { + if reg == nil || reg.Config == nil { + return nil, nil + } + switch reg.Type { + case "common": + return reg.Config.Common, nil + case "harbor": + if reg.Config.Harbor == nil { + return nil, nil + } + return ®.Config.Harbor.KubeServerContainerRegistryConfigCommon, nil + case "custom": + if reg.Config.Custom == nil { + return nil, nil + } + return ®.Config.Custom.KubeServerContainerRegistryConfigCommon, nil + default: + return nil, httperrors.NewInputParameterError("invalid registry type %s", reg.Type) + } +} + +func applyContainerCommitRegistryAuth(hostInput *hostapi.ContainerCommitInput, reg *api.KubeServerContainerRegistryDetails) error { + cfg, err := containerCommitRegistryAuth(reg) + if err != nil { + return err + } + if cfg == nil { + return nil + } + if hostInput.Auth == nil { + hostInput.Auth = new(apis.ContainerPullImageAuthConfig) + } + hostInput.Auth.Username = cfg.Username + hostInput.Auth.Password = cfg.Password + return nil +} + func (c *SContainer) getContainerHostCommitInput(ctx context.Context, userCred mcclient.TokenCredential, input *api.ContainerCommitInput) (*hostapi.ContainerCommitInput, error) { var hostInput = &hostapi.ContainerCommitInput{ Auth: new(apis.ContainerPullImageAuthConfig), @@ -1469,19 +1511,8 @@ func (c *SContainer) getContainerHostCommitInput(ctx context.Context, userCred m } } repoUrl = reg.Url - if reg.Config != nil { - switch reg.Type { - case "common": - cfg := reg.Config.Common - hostInput.Auth.Username = cfg.Username - hostInput.Auth.Password = cfg.Password - case "harbor": - cfg := reg.Config.Harbor - hostInput.Auth.Username = cfg.Username - hostInput.Auth.Password = cfg.Password - default: - return nil, httperrors.NewInputParameterError("invalid registry type %s", reg.Type) - } + if err := applyContainerCommitRegistryAuth(hostInput, reg); err != nil { + return nil, err } } else if input.ExternalRegistry != nil { repoUrl = input.ExternalRegistry.Url