Files
cloudpods/pkg/image/models/container_images.go
Zexi Li edcbd48487 feat(glance,region): support container registry and image (#25686)
Move container registry management into glance with import from
kubeserver, add container_image model, resolve container_image_id in
compute, and switch dashboard create flow to glance.
2026-09-15 15:32:27 +08:00

302 lines
9.9 KiB
Go

// 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 (
"context"
"fmt"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
identityapi "yunion.io/x/onecloud/pkg/apis/identity"
api "yunion.io/x/onecloud/pkg/apis/image"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/image/options"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/identity"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
func init() {
GetContainerImageManager()
}
var containerImageManager *SContainerImageManager
func GetContainerImageManager() *SContainerImageManager {
if containerImageManager != nil {
return containerImageManager
}
containerImageManager = &SContainerImageManager{
SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
SContainerImage{},
"container_images_tbl",
"container_image",
"container_images",
),
}
containerImageManager.SetVirtualObject(containerImageManager)
return containerImageManager
}
type SContainerImageManager struct {
db.SSharableVirtualResourceBaseManager
}
type SContainerImage struct {
db.SSharableVirtualResourceBase
ImageName string `width:"256" charset:"utf8" nullable:"false" list:"user" create:"required" update:"user"`
ImageLabel string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"required" update:"user"`
CredentialId string `width:"128" charset:"utf8" nullable:"true" list:"user" create:"optional" update:"user"`
RegistryId string `width:"128" charset:"ascii" nullable:"true" list:"user" create:"optional" update:"user"`
}
func fetchImageCredential(ctx context.Context, userCred mcclient.TokenCredential, cid string) (*identityapi.CredentialDetails, error) {
s := auth.GetSession(ctx, userCred, options.Options.Region)
credJson, err := identity.Credentials.Get(s, cid, nil)
if err != nil {
return nil, errors.Wrap(err, "Credentials.Get")
}
details := identityapi.CredentialDetails{}
err = credJson.Unmarshal(&details)
if err != nil {
return nil, errors.Wrap(err, "Unmarshal")
}
return &details, nil
}
func normalizeRegistryHost(url string) string {
host := strings.TrimSpace(url)
host = strings.TrimPrefix(host, "https://")
host = strings.TrimPrefix(host, "http://")
return strings.TrimRight(host, "/")
}
func ensureImageNameMatchesRegistry(imageName string, reg *SContainerRegistry) error {
if reg == nil {
return nil
}
prefix := normalizeRegistryHost(reg.Url)
if prefix == "" {
return httperrors.NewInputParameterError("container registry %s has empty url", reg.GetId())
}
if imageName == prefix || strings.HasPrefix(imageName, prefix+"/") {
return nil
}
return httperrors.NewInputParameterError("image_name %q must start with registry prefix %q", imageName, prefix)
}
func (man *SContainerImageManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.ContainerImageCreateInput) (*api.ContainerImageCreateInput, error) {
var err error
input.SharableVirtualResourceCreateInput, err = man.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput)
if err != nil {
return input, errors.Wrap(err, "validate SharableVirtualResourceCreateInput")
}
if input.ImageName == "" {
return input, httperrors.NewInputParameterError("image_name is required")
}
if input.ImageLabel == "" {
return input, httperrors.NewInputParameterError("image_label is required")
}
if len(input.CredentialId) > 0 {
cred, err := fetchImageCredential(ctx, userCred, input.CredentialId)
if err != nil {
return input, errors.Wrap(err, "fetchImageCredential")
}
input.CredentialId = cred.Id
}
if len(input.RegistryId) > 0 {
obj, err := GetContainerRegistryManager().FetchByIdOrName(ctx, userCred, input.RegistryId)
if err != nil {
return input, errors.Wrapf(err, "fetch container registry %s", input.RegistryId)
}
input.RegistryId = obj.GetId()
reg, ok := obj.(*SContainerRegistry)
if !ok {
return input, httperrors.NewGeneralError(errors.Errorf("invalid container registry object"))
}
if err := ensureImageNameMatchesRegistry(input.ImageName, reg); err != nil {
return input, err
}
if len(input.CredentialId) == 0 && reg.CredentialId != "" {
input.CredentialId = reg.CredentialId
}
}
input.Status = api.CONTAINER_IMAGE_STATUS_READY
return input, nil
}
func (man *SContainerImageManager) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.ContainerImageUpdateInput) (*api.ContainerImageUpdateInput, error) {
var err error
input.SharableVirtualResourceCreateInput, err = man.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput)
if err != nil {
return input, errors.Wrap(err, "validate SharableVirtualResourceCreateInput")
}
if input.CredentialId != nil && len(*input.CredentialId) > 0 {
cred, err := fetchImageCredential(ctx, userCred, *input.CredentialId)
if err != nil {
return input, errors.Wrap(err, "fetchImageCredential")
}
input.CredentialId = &cred.Id
}
if input.RegistryId != nil && len(*input.RegistryId) > 0 {
obj, err := GetContainerRegistryManager().FetchByIdOrName(ctx, userCred, *input.RegistryId)
if err != nil {
return input, errors.Wrapf(err, "fetch container registry %s", *input.RegistryId)
}
id := obj.GetId()
input.RegistryId = &id
reg, ok := obj.(*SContainerRegistry)
if !ok {
return input, httperrors.NewGeneralError(errors.Errorf("invalid container registry object"))
}
if input.ImageName != nil {
if err := ensureImageNameMatchesRegistry(*input.ImageName, reg); err != nil {
return input, err
}
}
if input.CredentialId == nil || len(*input.CredentialId) == 0 {
if reg.CredentialId != "" {
input.CredentialId = &reg.CredentialId
}
}
}
return input, nil
}
func (image *SContainerImage) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ContainerImageUpdateInput) (api.ContainerImageUpdateInput, error) {
if input.CredentialId != nil && len(*input.CredentialId) > 0 {
cred, err := fetchImageCredential(ctx, userCred, *input.CredentialId)
if err != nil {
return input, errors.Wrap(err, "fetchImageCredential")
}
input.CredentialId = &cred.Id
}
registryId := image.RegistryId
if input.RegistryId != nil {
registryId = *input.RegistryId
}
if len(registryId) > 0 {
obj, err := GetContainerRegistryManager().FetchByIdOrName(ctx, userCred, registryId)
if err != nil {
return input, errors.Wrapf(err, "fetch container registry %s", registryId)
}
id := obj.GetId()
if input.RegistryId != nil {
input.RegistryId = &id
}
reg, ok := obj.(*SContainerRegistry)
if !ok {
return input, httperrors.NewGeneralError(errors.Errorf("invalid container registry object"))
}
imageName := image.ImageName
if input.ImageName != nil {
imageName = *input.ImageName
}
if err := ensureImageNameMatchesRegistry(imageName, reg); err != nil {
return input, err
}
if input.CredentialId == nil || len(*input.CredentialId) == 0 {
if input.RegistryId != nil && reg.CredentialId != "" {
input.CredentialId = &reg.CredentialId
}
}
}
return input, nil
}
func (man *SContainerImageManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
input api.ContainerImageListInput,
) (*sqlchemy.SQuery, error) {
q, err := man.SSharableVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.SharableVirtualResourceListInput)
if err != nil {
return nil, errors.Wrapf(err, "SSharableBaseResourceManager.ListItemFilter")
}
if input.IsPublic != nil {
if *input.IsPublic {
q = q.IsTrue("is_public")
} else {
q = q.IsFalse("is_public")
}
}
if len(input.ImageLabel) > 0 {
q = q.Equals("image_label", input.ImageLabel)
}
if len(input.ImageName) > 0 {
q = q.Equals("image_name", input.ImageName)
}
if len(input.RegistryId) > 0 {
q = q.Equals("registry_id", input.RegistryId)
}
return q, nil
}
func (manager *SContainerImageManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.ContainerImageDetails {
rows := make([]api.ContainerImageDetails, len(objs))
virtRows := manager.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
registryIds := make([]string, 0)
for i := range objs {
image := objs[i].(*SContainerImage)
if image.RegistryId != "" {
registryIds = append(registryIds, image.RegistryId)
}
}
registryNames, err := db.FetchIdNameMap2(GetContainerRegistryManager(), registryIds)
if err != nil {
log.Errorf("FetchIdNameMap2 container_registries: %v", err)
}
for i := range rows {
rows[i] = api.ContainerImageDetails{
SharableVirtualResourceDetails: virtRows[i],
}
image := objs[i].(*SContainerImage)
if name, ok := registryNames[image.RegistryId]; ok {
rows[i].Registry = name
}
}
return rows
}
func (image *SContainerImage) ToContainerImage() string {
return fmt.Sprintf("%s:%s", image.ImageName, image.ImageLabel)
}