This PR does the following things:

1. add new registry api under resources.kubesphere.io/v1alpha3
2. deprecate registry api v1alpha2

Registry API v1alpha2 uses docker client to authenticate image registry
secret, which depends on docker.sock. We used to mount host
`/var/run/docker.sock` to deployment. It will prevent us imgrating to
containerd since no `docker.sock` exists. Registry API v1alpha3 comes to
rescure, it wraps library go-containerregistry and compatible with
docker registry, Harbor etc.
This commit is contained in:
Jeff
2021-08-23 19:56:28 +08:00
parent c740fef5b4
commit 3d2fd1b538
88 changed files with 10002 additions and 16 deletions

View File

@@ -17,14 +17,19 @@ limitations under the License.
package v1alpha3
import (
"fmt"
"net/http"
"strings"
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/models/components"
v2 "kubesphere.io/kubesphere/pkg/models/registries/v2"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
@@ -35,6 +40,7 @@ type Handler struct {
resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter
resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter
componentsGetter components.ComponentsGetter
registryHelper v2.RegistryHelper
}
func New(resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter, resourcesGetterV1alpha2 *resourcev1alpha2.ResourceGetter, componentsGetter components.ComponentsGetter) *Handler {
@@ -42,6 +48,7 @@ func New(resourceGetterV1alpha3 *resourcev1alpha3.ResourceGetter, resourcesGette
resourceGetterV1alpha3: resourceGetterV1alpha3,
resourcesGetterV1alpha2: resourcesGetterV1alpha2,
componentsGetter: componentsGetter,
registryHelper: v2.NewRegistryHelper(),
}
}
@@ -203,3 +210,86 @@ func (h *Handler) handleGetComponents(request *restful.Request, response *restfu
response.WriteEntity(result)
}
// handleVerifyImageRepositorySecret verifies image secret against registry, it takes k8s.io/api/core/v1/types.Secret
// as input, and authenticate registry with credential specified. Returns http.StatusOK if authenticate successfully,
// returns http.StatusUnauthorized if failed.
func (h *Handler) handleVerifyImageRepositorySecret(request *restful.Request, response *restful.Response) {
secret := &v1.Secret{}
err := request.ReadEntity(secret)
if err != nil {
api.HandleBadRequest(response, request, err)
}
ok, err := h.registryHelper.Auth(secret)
if !ok {
klog.Error(err)
api.HandleUnauthorized(response, request, err)
} else {
response.WriteHeaderAndJson(http.StatusOK, secret, restful.MIME_JSON)
}
}
// handleGetImageConfig fetches container image spec described in https://github.com/opencontainers/image-spec/blob/main/manifest.md
func (h *Handler) handleGetImageConfig(request *restful.Request, response *restful.Response) {
secretName := request.QueryParameter("secret")
namespace := request.PathParameter("namespace")
image := request.QueryParameter("image")
var secret *v1.Secret
// empty secret means anoymous fetching
if len(secretName) != 0 {
object, err := h.resourceGetterV1alpha3.Get("secrets", namespace, secretName)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
}
secret = object.(*v1.Secret)
}
config, err := h.registryHelper.Config(secret, image)
if err != nil {
canonicalizeRegistryError(request, response, err)
return
}
response.WriteHeaderAndJson(http.StatusOK, config, restful.MIME_JSON)
}
// handleGetRepositoryTags fetchs all tags of given repository, no paging.
func (h *Handler) handleGetRepositoryTags(request *restful.Request, response *restful.Response) {
secretName := request.QueryParameter("secret")
namespace := request.PathParameter("namespace")
repository := request.QueryParameter("repository")
var secret *v1.Secret
if len(repository) == 0 {
api.HandleBadRequest(response, request, fmt.Errorf("empty repository name"))
return
}
if len(secretName) != 0 {
object, err := h.resourceGetterV1alpha3.Get("secrets", namespace, secretName)
if errors.IsNotFound(err) {
api.HandleNotFound(response, request, err)
}
secret = object.(*v1.Secret)
}
tags, err := h.registryHelper.ListRepositoryTags(secret, repository)
if err != nil {
canonicalizeRegistryError(request, response, err)
return
}
response.WriteHeaderAndJson(http.StatusOK, tags, restful.MIME_JSON)
}
func canonicalizeRegistryError(request *restful.Request, response *restful.Response, err error) {
if strings.Contains(err.Error(), "Unauthorized") {
api.HandleUnauthorized(response, request, err)
} else if strings.Contains(err.Error(), "MANIFEST_UNKNOWN") {
api.HandleNotFound(response, request, err)
} else {
api.HandleBadRequest(response, request, err)
}
}

View File

@@ -19,6 +19,7 @@ package v1alpha3
import (
"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/cache"
@@ -28,6 +29,7 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/components"
v2 "kubesphere.io/kubesphere/pkg/models/registries/v2"
resourcev1alpha2 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
@@ -114,6 +116,34 @@ func AddToContainer(c *restful.Container, informerFactory informers.InformerFact
Doc("Get the health status of system components.").
Returns(http.StatusOK, ok, v1alpha2.HealthStatus{}))
webservice.Route(webservice.POST("/namespaces/{namespace}/registrysecrets/{secret}").
To(handler.handleVerifyImageRepositorySecret).
Param(webservice.PathParameter("namespace", "Namespace of the image repository secret to create.").Required(true)).
Param(webservice.PathParameter("secret", "Secret name of the image repository credential to create").Required(true)).
Param(webservice.BodyParameter("secretSpec", "Secret specification, definition in k8s.io/api/core/v1/types.Secret")).
Reads(v1.Secret{}).
Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}).
Doc("Verify image repostiry secret.").
Returns(http.StatusOK, ok, v1.Secret{}))
webservice.Route(webservice.GET("/namespaces/{namespace}/imageconfig").
To(handler.handleGetImageConfig).
Param(webservice.PathParameter("namespace", "Namespace of the image repository secret.").Required(true)).
Param(webservice.QueryParameter("secret", "Secret name of the image repository credential, left empty means anonymous fetch.").Required(false)).
Param(webservice.QueryParameter("image", "Image name to query, e.g. kubesphere/ks-apiserver:v3.1.1").Required(true)).
Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}).
Doc("Get image config.").
Returns(http.StatusOK, ok, v2.ImageConfig{}))
webservice.Route(webservice.GET("/namespaces/{namespace}/repositorytags").
To(handler.handleGetRepositoryTags).
Param(webservice.PathParameter("namespace", "Namespace of the image repository secret.").Required(true)).
Param(webservice.QueryParameter("repository", "Repository to query, e.g. calico/cni.").Required(true)).
Param(webservice.QueryParameter("secret", "Secret name of the image repository credential, left empty means anonymous fetch.").Required(false)).
Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}).
Doc("List repository tags, this is an experimental API, use it by your own caution.").
Returns(http.StatusOK, ok, v2.RepositoryTags{}))
c.Add(webservice)
return nil