Support manual triggering of a repository update. (#6414)

* Support manual triggering of a repository update.

* cherry pick add api for workload template (#1982)

* cherry pick (add operator application (#1970))

* Modify routing implementation to improve readability

* cherry pick from kse dfc40e5adf5aa2e67d1

* Filter by Routing Parameter Namespace (#1990)

* add doc for workloadtemplates

---------

Co-authored-by: inksnw <inksnw@gmail.com>
This commit is contained in:
KubeSphere CI Bot
2025-03-11 11:36:02 +08:00
committed by GitHub
parent c9c856dfda
commit bb60d39434
37 changed files with 679 additions and 318 deletions

View File

@@ -0,0 +1,203 @@
package v1alpha1
import (
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apiserver/pkg/authentication/user"
tenantv1beta1 "kubesphere.io/api/tenant/v1beta1"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"k8s.io/klog/v2"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/utils/stringutils"
"github.com/emicklei/go-restful/v3"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/server/errors"
k8suitl "kubesphere.io/kubesphere/pkg/utils/k8sutil"
)
func (h *templateHandler) listWorkloadTemplate(req *restful.Request, resp *restful.Response) {
secretList := corev1.SecretList{}
requirements, _ := labels.SelectorFromSet(map[string]string{SchemeGroupVersion.Group: "true"}).Requirements()
userSelector := query.ParseQueryParameter(req).Selector()
combinedSelector := labels.NewSelector()
combinedSelector = combinedSelector.Add(requirements...)
if userSelector != nil {
userRequirements, _ := userSelector.Requirements()
combinedSelector = combinedSelector.Add(userRequirements...)
}
opts := []client.ListOption{
client.MatchingLabelsSelector{Selector: combinedSelector},
}
namespace := req.PathParameter("namespace")
if namespace != "" {
opts = append(opts, client.InNamespace(namespace))
}
err := h.client.List(req.Request.Context(), &secretList, opts...)
if err != nil {
api.HandleError(resp, req, err)
return
}
workspace := req.PathParameter("workspace")
if workspace == "" {
resp.WriteEntity(k8suitl.ConvertToListResult(&secretList, req))
return
}
user, ok := request.UserFrom(req.Request.Context())
if !ok {
err := fmt.Errorf("cannot obtain user info")
klog.Errorln(err)
api.HandleForbidden(resp, nil, err)
return
}
filteredList, err := h.FilterByPermissions(workspace, user, secretList)
if err != nil {
api.HandleError(resp, req, err)
return
}
resp.WriteEntity(k8suitl.ConvertToListResult(filteredList, req))
}
func (h *templateHandler) FilterByPermissions(workspace string, user user.Info, secretList corev1.SecretList) (*corev1.SecretList, error) {
listNS := authorizer.AttributesRecord{
User: user,
Verb: "list",
Workspace: workspace,
Resource: "namespaces",
ResourceRequest: true,
ResourceScope: request.WorkspaceScope,
}
decision, _, err := h.authorizer.Authorize(listNS)
if err != nil {
klog.Error(err)
return nil, err
}
var namespaceList []string
if decision == authorizer.DecisionAllow {
queryParam := query.New()
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1beta1.WorkspaceLabel, workspace))
result, err := h.resourceGetter.List("namespaces", "", queryParam)
if err != nil {
klog.Error(err)
return nil, err
}
for _, item := range result.Items {
ns := item.(*corev1.Namespace)
listWorkLoadTemplate := authorizer.AttributesRecord{
User: user,
Verb: "list",
Namespace: ns.Name,
Resource: "workloadtemplates",
ResourceRequest: true,
ResourceScope: request.NamespaceScope,
}
decision, _, err = h.authorizer.Authorize(listWorkLoadTemplate)
if err != nil {
klog.Error(err)
return nil, err
}
if decision == authorizer.DecisionAllow {
namespaceList = append(namespaceList, ns.Name)
} else {
klog.Infof("user %s has no permission to list workloadtemplate in namespace %s", user.GetName(), ns.Name)
}
}
}
filteredList := &corev1.SecretList{}
for _, item := range secretList.Items {
if !stringutils.StringIn(item.Namespace, namespaceList) {
continue
}
filteredList.Items = append(filteredList.Items, item)
}
return filteredList, nil
}
func (h *templateHandler) applyWorkloadTemplate(req *restful.Request, resp *restful.Response) {
secret := &corev1.Secret{}
err := req.ReadEntity(secret)
if err != nil {
api.HandleError(resp, req, err)
return
}
if req.PathParameter("workloadtemplate") == "" {
// create new
ns := req.PathParameter("namespace")
err = h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: secret.Name, Namespace: ns}, secret)
if err != nil && !apierrors.IsNotFound(err) {
api.HandleError(resp, req, err)
return
}
if err == nil {
api.HandleConflict(resp, req, fmt.Errorf("workloadtemplate %s already exists", secret.Name))
return
}
}
newSecret := &corev1.Secret{}
newSecret.Name = secret.Name
newSecret.Namespace = req.PathParameter("namespace")
mutateFn := func() error {
newSecret.Annotations = secret.Annotations
if secret.Labels == nil {
secret.Labels = make(map[string]string)
}
newSecret.Labels = secret.Labels
newSecret.Labels[SchemeGroupVersion.Group] = "true"
newSecret.StringData = secret.StringData
newSecret.Type = corev1.SecretType(fmt.Sprintf("%s/%s", SchemeGroupVersion.Group, "workloadtemplate"))
return nil
}
_, err = controllerutil.CreateOrUpdate(req.Request.Context(), h.client, newSecret, mutateFn)
if err != nil {
api.HandleError(resp, req, err)
return
}
newSecret.SetManagedFields(nil)
resp.WriteAsJson(newSecret)
}
func (h *templateHandler) deleteWorkloadTemplate(req *restful.Request, resp *restful.Response) {
name := req.PathParameter("workloadtemplate")
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name}}
secret.Namespace = req.PathParameter("namespace")
err := h.client.Delete(req.Request.Context(), secret)
if err != nil {
api.HandleError(resp, req, err)
return
}
resp.WriteEntity(errors.None)
}
func (h *templateHandler) getWorkloadTemplate(req *restful.Request, resp *restful.Response) {
name := req.PathParameter("workloadtemplate")
secret := &corev1.Secret{}
ns := req.PathParameter("namespace")
err := h.client.Get(req.Request.Context(), runtimeclient.ObjectKey{Name: name, Namespace: ns}, secret)
if err != nil {
api.HandleError(resp, req, err)
return
}
secret.SetManagedFields(nil)
resp.WriteAsJson(secret)
}

View File

@@ -0,0 +1,89 @@
package v1alpha1
import (
"github.com/Masterminds/semver/v3"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
resourcesv1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
"github.com/emicklei/go-restful/v3"
"k8s.io/apimachinery/pkg/runtime/schema"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
"kubesphere.io/kubesphere/pkg/apiserver/rest"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
)
var (
SchemeGroupVersion = schema.GroupVersion{Group: "workloadtemplate.kubesphere.io", Version: "v1alpha1"}
)
type templateHandler struct {
client runtimeclient.Client
authorizer authorizer.Authorizer
resourceGetter *resourcesv1alpha3.Getter
}
func NewHandler(cacheClient runtimeclient.Client, k8sVersion *semver.Version, authorizer authorizer.Authorizer) rest.Handler {
handler := &templateHandler{
client: cacheClient,
authorizer: authorizer,
resourceGetter: resourcesv1alpha3.NewResourceGetter(cacheClient, k8sVersion),
}
return handler
}
func (h *templateHandler) AddToContainer(c *restful.Container) (err error) {
ws := runtime.NewWebService(SchemeGroupVersion)
ws.Route(ws.GET("/workloadtemplates").
To(h.listWorkloadTemplate).
Doc("List workload templates").
Notes("List workload templates.").
Operation("listWorkloadTemplate"))
ws.Route(ws.GET("/workspaces/{workspace}/workloadtemplates").
To(h.listWorkloadTemplate).
Doc("List workload templates in a workspace").
Notes("List workload templates in a workspace.").
Operation("listWorkloadTemplate").
Param(ws.PathParameter("workspace", "workspace")))
ws.Route(ws.GET("/namespaces/{namespace}/workloadtemplates").
To(h.listWorkloadTemplate).
Doc("List workload templates in a namespace").
Notes("List workload templates in a namespace.").
Operation("listWorkloadTemplate").
Param(ws.PathParameter("namespace", "namespace")))
ws.Route(ws.POST("/namespaces/{namespace}/workloadtemplates").
To(h.applyWorkloadTemplate).
Doc("Apply a workload template in a namespace").
Notes("Apply a workload template in a namespace.").
Operation("applyWorkloadTemplate").
Param(ws.PathParameter("namespace", "namespace")))
ws.Route(ws.PUT("/namespaces/{namespace}/workloadtemplates/{workloadtemplate}").
To(h.applyWorkloadTemplate).
Doc("Update a workload template").
Notes("Update a workload template in a namespace.").
Operation("applyWorkloadTemplate").
Param(ws.PathParameter("namespace", "namespace")).
Param(ws.PathParameter("workloadtemplate", "workloadtemplate")))
ws.Route(ws.DELETE("/namespaces/{namespace}/workloadtemplates/{workloadtemplate}").
To(h.deleteWorkloadTemplate).
Doc("Delete a workload template in a namespace").
Notes("List workload templates in a namespace.").
Operation("deleteWorkloadTemplate").
Param(ws.PathParameter("namespace", "namespace")).
Param(ws.PathParameter("workloadtemplate", "workloadtemplate")))
ws.Route(ws.GET("/namespaces/{namespace}/workloadtemplates/{workloadtemplate}").
To(h.getWorkloadTemplate).
Doc("Get a workload template in a namespace").
Notes("Get a workload template in a namespace.").
Operation("getWorkloadTemplate").
Param(ws.PathParameter("namespace", "namespace")).
Param(ws.PathParameter("workloadtemplate", "workloadtemplate")))
c.Add(ws)
return nil
}