未验证 提交 8976ee24 编写于 作者: H hongming

remove useless go moudle

Signed-off-by: Nhongming <talonwan@yunify.com>
上级 b7a2705a
......@@ -3,7 +3,6 @@ kind: GlobalRoleBinding
metadata:
labels:
controller-tools.k8s.io: "1.0"
iam.kubesphere.io/single-user-bind: admin
name: admin
roleRef:
apiGroup: iam.kubesphere.io/v1alpha2
......
......@@ -143,7 +143,6 @@ replace (
github.com/bmizerany/assert => github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/cespare/xxhash => github.com/cespare/xxhash v1.1.0
github.com/chai2010/gettext-go => github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5
github.com/cheekybits/genny => github.com/cheekybits/genny v1.0.0
github.com/client9/misspell => github.com/client9/misspell v0.3.4
github.com/coreos/bbolt => github.com/coreos/bbolt v1.3.3
github.com/coreos/etcd => github.com/coreos/etcd v3.3.17+incompatible
......@@ -264,14 +263,11 @@ replace (
github.com/kr/pty => github.com/kr/pty v1.1.5
github.com/kr/text => github.com/kr/text v0.1.0
github.com/kubernetes-sigs/application => github.com/kubesphere/application v0.0.0-20191210100950-18cc93526ab4
github.com/kubernetes-sigs/federation-v2 => github.com/kubernetes-sigs/federation-v2 v0.0.10
github.com/kubesphere/s2ioperator => github.com/kubesphere/s2ioperator v0.0.14
github.com/kubesphere/sonargo => github.com/kubesphere/sonargo v0.0.2
github.com/leodido/go-urn => github.com/leodido/go-urn v1.1.0
github.com/lib/pq => github.com/lib/pq v1.2.0
github.com/liggitt/tabwriter => github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/lithammer/dedent => github.com/lithammer/dedent v1.1.0
github.com/lucas-clemente/quic-go => github.com/lucas-clemente/quic-go v0.11.1
github.com/magiconair/properties => github.com/magiconair/properties v1.8.0
github.com/mailru/easyjson => github.com/mailru/easyjson v0.7.0
github.com/mattn/go-colorable => github.com/mattn/go-colorable v0.1.2
......@@ -296,7 +292,6 @@ replace (
github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.1
github.com/openshift/api => github.com/openshift/api v0.0.0-20180801171038-322a19404e37
github.com/openshift/build-machinery-go => github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160
github.com/openshift/generic-admission-server => github.com/openshift/generic-admission-server v1.14.0
github.com/opentracing/opentracing-go => github.com/opentracing/opentracing-go v1.1.0
github.com/pborman/uuid => github.com/pborman/uuid v1.2.0
......@@ -407,7 +402,6 @@ replace (
k8s.io/apiserver => k8s.io/apiserver v0.0.0-20191114103151-9ca1dc586682
k8s.io/cli-runtime => k8s.io/cli-runtime v0.17.3
k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
k8s.io/cluster-registry => k8s.io/cluster-registry v0.0.6
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20191004115455-8e001e5d1894
k8s.io/component-base => k8s.io/component-base v0.0.0-20191114102325-35a9586014f7
k8s.io/gengo => k8s.io/gengo v0.0.0-20191120174120-e74f70b9b27e
......
......@@ -37,6 +37,12 @@ const (
ResourceKindWorkspaceRole = "WorkspaceRole"
ResourcesSingularWorkspaceRole = "workspacerole"
ResourcesPluralWorkspaceRole = "workspaceroles"
ResourceKindClusterRole = "ClusterRole"
ResourcesSingularClusterRole = "clusterrole"
ResourcesPluralClusterRole = "clusterroles"
ResourceKindRole = "Role"
ResourcesSingularRole = "role"
ResourcesPluralRole = "roles"
RegoOverrideAnnotation = "iam.kubesphere.io/rego-override"
GlobalScope = "Global"
ClusterScope = "Cluster"
......
......@@ -11,13 +11,18 @@ import (
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog"
clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/bearertoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
authorizationoptions "kubesphere.io/kubesphere/pkg/apiserver/authorization/options"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/path"
unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union"
apiserverconfig "kubesphere.io/kubesphere/pkg/apiserver/config"
......@@ -27,7 +32,7 @@ import (
"kubesphere.io/kubesphere/pkg/informers"
configv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/config/v1alpha2"
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
iamapi "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
monitoringv1alpha3 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha3"
networkv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/network/v1alpha2"
......@@ -118,7 +123,6 @@ func (s *APIServer) PrepareRun() error {
s.container.Filter(logRequestAndResponse)
s.container.Router(restful.CurlyRouter{})
s.container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) {
klog.Error(panicReason)
logStackOnRecover(panicReason, httpWriter)
})
......@@ -144,7 +148,7 @@ func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(networkv1alpha2.AddToContainer(s.container, s.Config.NetworkOptions.WeaveScopeHost))
urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes()))
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory))
urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.InformerFactory))
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
urlruntime.Must(iamapi.AddToContainer(s.container, im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory),
am.NewAMOperator(s.InformerFactory),
......@@ -189,7 +193,6 @@ func (s *APIServer) buildHandlerChain() {
{Group: iamv1alpha2.SchemeGroupVersion.Group, Resource: iamv1alpha2.ResourcesPluralGlobalRoleBinding},
{Group: tenantv1alpha1.SchemeGroupVersion.Group, Resource: tenantv1alpha1.ResourcePluralWorkspace},
{Group: clusterv1alpha1.SchemeGroupVersion.Group, Resource: clusterv1alpha1.ResourcesPluralCluster},
{Group: clusterv1alpha1.SchemeGroupVersion.Group, Resource: clusterv1alpha1.ResourcesPluralAgent},
},
}
......
......@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/spf13/viper"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
authorizationoptions "kubesphere.io/kubesphere/pkg/apiserver/authorization/options"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
......@@ -11,6 +12,7 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus"
"kubesphere.io/kubesphere/pkg/simple/client/multicluster"
"kubesphere.io/kubesphere/pkg/simple/client/network"
"kubesphere.io/kubesphere/pkg/simple/client/notification"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
......@@ -58,20 +60,20 @@ const (
// Config defines everything needed for apiserver to deal with external services
type Config struct {
DevopsOptions *jenkins.Options `json:"devops,omitempty" yaml:"devops,omitempty" mapstructure:"devops"`
SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"`
KubernetesOptions *k8s.KubernetesOptions `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty" mapstructure:"kubernetes"`
ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"`
NetworkOptions *network.Options `json:"network,omitempty" yaml:"network,omitempty" mapstructure:"network"`
LdapOptions *ldap.Options `json:"ldap,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"`
RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"`
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"`
MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"`
LoggingOptions *elasticsearch.Options `json:"logging,omitempty" yaml:"logging,omitempty" mapstructure:"logging"`
AuthenticationOptions *authoptions.AuthenticationOptions `json:"authentication,omitempty" yaml:"authentication,omitempty" mapstructure:"authentication"`
DevopsOptions *jenkins.Options `json:"devops,omitempty" yaml:"devops,omitempty" mapstructure:"devops"`
SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"`
KubernetesOptions *k8s.KubernetesOptions `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty" mapstructure:"kubernetes"`
ServiceMeshOptions *servicemesh.Options `json:"servicemesh,omitempty" yaml:"servicemesh,omitempty" mapstructure:"servicemesh"`
NetworkOptions *network.Options `json:"network,omitempty" yaml:"network,omitempty" mapstructure:"network"`
LdapOptions *ldap.Options `json:"-,omitempty" yaml:"ldap,omitempty" mapstructure:"ldap"`
RedisOptions *cache.Options `json:"redis,omitempty" yaml:"redis,omitempty" mapstructure:"redis"`
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
OpenPitrixOptions *openpitrix.Options `json:"openpitrix,omitempty" yaml:"openpitrix,omitempty" mapstructure:"openpitrix"`
MonitoringOptions *prometheus.Options `json:"monitoring,omitempty" yaml:"monitoring,omitempty" mapstructure:"monitoring"`
LoggingOptions *elasticsearch.Options `json:"logging,omitempty" yaml:"logging,omitempty" mapstructure:"logging"`
AuthenticationOptions *authoptions.AuthenticationOptions `json:"authentication,omitempty" yaml:"authentication,omitempty" mapstructure:"authentication"`
AuthorizationOptions *authorizationoptions.AuthorizationOptions `json:"authorization,omitempty" yaml:"authorization,omitempty" mapstructure:"authorization"`
MultiClusterOptions *multicluster.Options `json:"multicluster,omitempty" yaml:"multicluster,omitempty" mapstructure:"multicluster"`
MultiClusterOptions *multicluster.Options `json:"multicluster,omitempty" yaml:"multicluster,omitempty" mapstructure:"multicluster"`
// Options used for enabling components, not actually used now. Once we switch Alerting/Notification API to kubesphere,
// we can add these options to kubesphere command lines
AlertingOptions *alerting.Options `json:"alerting,omitempty" yaml:"alerting,omitempty" mapstructure:"alerting"`
......@@ -96,6 +98,7 @@ func New() *Config {
LoggingOptions: elasticsearch.NewElasticSearchOptions(),
AuthenticationOptions: authoptions.NewAuthenticateOptions(),
AuthorizationOptions: authorizationoptions.NewAuthorizationOptions(),
MultiClusterOptions: multicluster.NewOptions(),
}
}
......
......@@ -16,13 +16,13 @@ func TestParseQueryParameter(t *testing.T) {
}{
{
"test normal case",
"label=app.kubernetes.io/name:book&name=foo&status=Running&page=1&limit=10&ascending=true",
"label=app.kubernetes.io/name=book&name=foo&status=Running&page=1&limit=10&ascending=true",
&Query{
Pagination: newPagination(10, 0),
SortBy: FieldCreationTimeStamp,
Ascending: true,
Filters: map[Field]Value{
FieldLabel: Value("app.kubernetes.io/name:book"),
FieldLabel: Value("app.kubernetes.io/name=book"),
FieldName: Value("foo"),
FieldStatus: Value("Running"),
},
......
......@@ -136,12 +136,11 @@ func (h *iamHandler) ListNamespaceUsers(req *restful.Request, resp *restful.Resp
if subject.Kind == iamv1alpha2.ResourceKindUser {
user, err := h.im.DescribeUser(subject.Name)
if errors.IsNotFound(err) {
klog.Errorf("orphan subject: %+v", subject)
continue
}
if err != nil {
if errors.IsNotFound(err) {
klog.Errorf("orphan subject: %+v", subject)
continue
}
api.HandleInternalError(resp, req, err)
return
}
......@@ -200,12 +199,11 @@ func (h *iamHandler) ListWorkspaceUsers(request *restful.Request, response *rest
if subject.Kind == iamv1alpha2.ResourceKindUser {
user, err := h.im.DescribeUser(subject.Name)
if errors.IsNotFound(err) {
klog.Errorf("orphan subject: %+v", subject)
continue
}
if err != nil {
if errors.IsNotFound(err) {
klog.Errorf("orphan subject: %+v", subject)
continue
}
api.HandleInternalError(response, request, err)
return
}
......
......@@ -47,7 +47,6 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf
ws.Route(ws.GET("/users").
To(handler.ListUsers).
Doc("List all users.").
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, api.ListResult{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
// global resource
......
......@@ -9,14 +9,13 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/tenant"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
)
type tenantHandler struct {
tenant tenant.Interface
}
func newTenantHandler(_ k8s.Client, factory informers.InformerFactory) *tenantHandler {
func newTenantHandler(factory informers.InformerFactory) *tenantHandler {
return &tenantHandler{
tenant: tenant.New(factory),
......
......@@ -27,7 +27,6 @@ import (
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"net/http"
)
......@@ -37,9 +36,9 @@ const (
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informers.InformerFactory) error {
func AddToContainer(c *restful.Container, factory informers.InformerFactory) error {
ws := runtime.NewWebService(GroupVersion)
handler := newTenantHandler(k8sClient, factory)
handler := newTenantHandler(factory)
ws.Route(ws.GET("/workspaces").
To(handler.ListWorkspaces).
......
......@@ -344,14 +344,13 @@ func (am *amOperator) ListGlobalRoles(query *query.Query) (*api.ListResult, erro
// GetRoleReferenceRules attempts to resolve the RoleBinding or ClusterRoleBinding.
func (am *amOperator) GetRoleReferenceRules(roleRef rbacv1.RoleRef, bindingNamespace string) ([]rbacv1.PolicyRule, error) {
switch roleRef.Kind {
case "Role":
case iamv1alpha2.ResourceKindRole:
role, err := am.k8sinformer.Rbac().V1().Roles().Lister().Roles(bindingNamespace).Get(roleRef.Name)
if err != nil {
return nil, err
}
return role.Rules, nil
case "ClusterRole":
case iamv1alpha2.ResourceKindClusterRole:
clusterRole, err := am.k8sinformer.Rbac().V1().ClusterRoles().Lister().Get(roleRef.Name)
if err != nil {
return nil, err
......
......@@ -19,6 +19,7 @@
package im
import (
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/query"
......@@ -81,5 +82,12 @@ func (im *ldapOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, e
}
func (im *ldapOperator) ListUsers(query *query.Query) (*api.ListResult, error) {
panic("not implement")
result, err := im.ldapClient.List(query)
if err != nil {
klog.Error(err)
return nil, err
}
return result, nil
}
......@@ -83,9 +83,9 @@ func (d *applicationsGetter) filter(object runtime.Object, filter query.Filter)
return v1alpha3.DefaultObjectMetaFilter(application.ObjectMeta, filter)
}
func lastUpdateTime(deployment *appv1beta1.Application) time.Time {
lut := deployment.CreationTimestamp.Time
for _, condition := range deployment.Status.Conditions {
func lastUpdateTime(application *appv1beta1.Application) time.Time {
lut := application.CreationTimestamp.Time
for _, condition := range application.Status.Conditions {
if condition.LastUpdateTime.After(lut) {
lut = condition.LastUpdateTime.Time
}
......
......@@ -143,7 +143,7 @@ func (t *tenantOperator) ListNamespaces(user user.Info, workspace string, queryP
if decision == authorizer.DecisionAllow {
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s:%s", tenantv1alpha1.WorkspaceLabel, workspace))
queryParam.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", tenantv1alpha1.WorkspaceLabel, workspace))
result, err := t.resourceGetter.List("namespaces", "", queryParam)
......
package ldap
import (
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/query"
)
// Interface defines CRUD behaviors of manipulating users
......@@ -20,4 +22,6 @@ type Interface interface {
// Authenticate checks if (name, password) is valid, return ErrInvalidCredentials if not
Authenticate(name string, password string) error
List(query *query.Query) (*api.ListResult, error)
}
......@@ -22,8 +22,13 @@ import (
"github.com/go-ldap/ldap"
"github.com/google/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/server/errors"
"sort"
"strings"
"sync"
"time"
)
......@@ -109,6 +114,7 @@ func (l *ldapInterfaceImpl) createSearchBase() error {
if err != nil {
return err
}
defer conn.Close()
createIfNotExistsFunc := func(request *ldap.AddRequest) error {
searchRequest := &ldap.SearchRequest{
......@@ -165,10 +171,10 @@ func (l *ldapInterfaceImpl) newConn() (ldap.Client, error) {
if err != nil {
return nil, err
}
defer conn.Close()
err = conn.Bind(l.managerDN, l.managerPassword)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
......@@ -357,6 +363,7 @@ func (l *ldapInterfaceImpl) Authenticate(username, password string) error {
if err != nil {
return err
}
defer conn.Close()
dn := l.dnForUsername(username)
err = conn.Bind(dn, password)
......@@ -365,3 +372,106 @@ func (l *ldapInterfaceImpl) Authenticate(username, password string) error {
}
return err
}
func (l *ldapInterfaceImpl) List(query *query.Query) (*api.ListResult, error) {
conn, err := l.newConn()
if err != nil {
return nil, err
}
defer conn.Close()
pageControl := ldap.NewControlPaging(1000)
users := make([]iamv1alpha2.User, 0)
filter := "(&(objectClass=inetOrgPerson))"
if keyword := query.Filters["keyword"]; keyword != "" {
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=*%s*)(mail=*%s*)(description=*%s*)))", keyword, keyword, keyword)
}
if username := query.Filters["username"]; username != "" {
uidFilter := ""
for _, username := range strings.Split(string(username), "|") {
uidFilter += fmt.Sprintf("(uid=%s)", username)
}
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", uidFilter)
}
if email := query.Filters["email"]; email != "" {
emailFilter := ""
for _, username := range strings.Split(string(email), "|") {
emailFilter += fmt.Sprintf("(mail=%s)", username)
}
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", emailFilter)
}
for {
userSearchRequest := ldap.NewSearchRequest(
l.userSearchBase,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"uid", "mail", "description", "preferredLanguage", "createTimestamp"},
[]ldap.Control{pageControl},
)
response, err := conn.Search(userSearchRequest)
if err != nil {
klog.Errorln("search user", err)
return nil, err
}
for _, entry := range response.Entries {
uid := entry.GetAttributeValue("uid")
email := entry.GetAttributeValue("mail")
description := entry.GetAttributeValue("description")
lang := entry.GetAttributeValue("preferredLanguage")
createTimestamp, _ := time.Parse("20060102150405Z", entry.GetAttributeValue("createTimestamp"))
user := iamv1alpha2.User{ObjectMeta: metav1.ObjectMeta{Name: uid, CreationTimestamp: metav1.Time{Time: createTimestamp}}, Spec: iamv1alpha2.UserSpec{
Email: email,
Lang: lang,
Description: description,
}}
users = append(users, user)
}
updatedControl := ldap.FindControl(response.Controls, ldap.ControlTypePaging)
if ctrl, ok := updatedControl.(*ldap.ControlPaging); ctrl != nil && ok && len(ctrl.Cookie) != 0 {
pageControl.SetCookie(ctrl.Cookie)
continue
}
break
}
sort.Slice(users, func(i, j int) bool {
if !query.Ascending {
i, j = j, i
}
switch query.SortBy {
case "username":
return strings.Compare(users[i].Name, users[j].Name) <= 0
case "createTime":
fallthrough
default:
return users[i].CreationTimestamp.Before(&users[j].CreationTimestamp)
}
})
items := make([]interface{}, 0)
for i, user := range users {
if i >= query.Pagination.Offset && len(items) < query.Pagination.Limit {
items = append(items, user)
}
}
return &api.ListResult{
Items: items,
TotalItems: len(users),
}, nil
}
......@@ -2,7 +2,9 @@ package ldap
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/query"
)
// simpleLdap is a implementation of ldap.Interface, you should never use this in production env!
......@@ -74,3 +76,16 @@ func (s simpleLdap) Authenticate(name string, password string) error {
return nil
}
func (l *simpleLdap) List(query *query.Query) (*api.ListResult, error) {
items := make([]interface{}, 0)
for _, user := range l.store {
items = append(items, user)
}
return &api.ListResult{
Items: items,
TotalItems: len(items),
}, nil
}
......@@ -4,80 +4,8 @@ import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/simple/client/logging"
"testing"
"time"
)
func TestMainBool(t *testing.T) {
var tests = []struct {
description string
searchFilter logging.SearchFilter
expected *bodyBuilder
}{
{
description: "filter 2 namespaces",
searchFilter: logging.SearchFilter{
NamespaceFilter: map[string]time.Time{
"kubesphere-system": time.Unix(1582000000, 0),
"kubesphere-logging-system": time.Unix(1582969999, 0),
},
},
expected: &bodyBuilder{Body{
Query: &Query{
Bool: Bool{
Filter: []Match{
{
Bool: &Bool{
Should: []Match{
{
Bool: &Bool{
Filter: []Match{
{
MatchPhrase: map[string]string{"kubernetes.namespace_name.keyword": "kubesphere-system"},
},
{
Range: &Range{&Time{Gte: func() *time.Time { t := time.Unix(1582000000, 0); return &t }()}},
},
},
},
},
{
Bool: &Bool{
Filter: []Match{
{
MatchPhrase: map[string]string{"kubernetes.namespace_name.keyword": "kubesphere-logging-system"},
},
{
Range: &Range{&Time{Gte: func() *time.Time { t := time.Unix(1582969999, 0); return &t }()}},
},
},
},
},
},
MinimumShouldMatch: 1,
},
},
},
},
},
}},
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
body, err := newBodyBuilder().mainBool(test.searchFilter).bytes()
expected, _ := test.expected.bytes()
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(body, expected); diff != "" {
t.Fatalf("%T differ (-got, +want): %s", expected, diff)
}
})
}
}
func TestCardinalityAggregation(t *testing.T) {
var test = struct {
description string
......
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- apelisse
reviewers:
- apelisse
/*
Copyright 2017 The Kubernetes Authors.
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 openapi is a collection of libraries for fetching the openapi spec
// from a Kubernetes server and then indexing the type definitions.
// The openapi spec contains the object model definitions and extensions metadata
// such as the patchStrategy and patchMergeKey for creating patches.
package openapi // k8s.io/kubectl/pkg/util/openapi
/*
Copyright 2017 The Kubernetes Authors.
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 openapi
import (
"errors"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
for _, extension := range extensions {
if extension.GetValue().GetYaml() == "" ||
extension.GetName() != "x-kubernetes-group-version-kind" {
continue
}
var value map[string]string
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
if err != nil {
continue
}
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
return true
}
return false
}
return false
}
// SupportsDryRun is a method that let's us look in the OpenAPI if the
// specific group-version-kind supports the dryRun query parameter for
// the PATCH end-point.
func SupportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) {
for _, path := range doc.GetPaths().GetPath() {
// Is this describing the gvk we're looking for?
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
continue
}
for _, param := range path.GetValue().GetPatch().GetParameters() {
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" {
return true, nil
}
}
return false, nil
}
return false, errors.New("couldn't find GVK in openapi")
}
/*
Copyright 2017 The Kubernetes Authors.
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 openapi
import "github.com/go-openapi/spec"
// PrintColumnsKey is the key that defines which columns should be printed
const PrintColumnsKey = "x-kubernetes-print-columns"
// GetPrintColumns looks for the open API extension for the display columns.
func GetPrintColumns(extensions spec.Extensions) (string, bool) {
return extensions.GetString(PrintColumnsKey)
}
/*
Copyright 2017 The Kubernetes Authors.
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 openapi
import (
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
)
// Resources interface describe a resources provider, that can give you
// resource based on group-version-kind.
type Resources interface {
LookupResource(gvk schema.GroupVersionKind) proto.Schema
}
// groupVersionKindExtensionKey is the key used to lookup the
// GroupVersionKind value for an object definition from the
// definition's "extensions" map.
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
// document is an implementation of `Resources`. It looks for
// resources in an openapi Schema.
type document struct {
// Maps gvk to model name
resources map[schema.GroupVersionKind]string
models proto.Models
}
var _ Resources = &document{}
// NewOpenAPIData creates a new `Resources` out of the openapi document
func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
models, err := proto.NewOpenAPIData(doc)
if err != nil {
return nil, err
}
resources := map[schema.GroupVersionKind]string{}
for _, modelName := range models.ListModels() {
model := models.LookupModel(modelName)
if model == nil {
panic("ListModels returns a model that can't be looked-up.")
}
gvkList := parseGroupVersionKind(model)
for _, gvk := range gvkList {
if len(gvk.Kind) > 0 {
resources[gvk] = modelName
}
}
}
return &document{
resources: resources,
models: models,
}, nil
}
func (d *document) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
modelName, found := d.resources[gvk]
if !found {
return nil
}
return d.models.LookupModel(modelName)
}
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
extensions := s.GetExtensions()
gvkListResult := []schema.GroupVersionKind{}
// Get the extensions
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
if !ok {
return []schema.GroupVersionKind{}
}
// gvk extension must be a list of at least 1 element.
gvkList, ok := gvkExtension.([]interface{})
if !ok {
return []schema.GroupVersionKind{}
}
for _, gvk := range gvkList {
// gvk extension list must be a map with group, version, and
// kind fields
gvkMap, ok := gvk.(map[interface{}]interface{})
if !ok {
continue
}
group, ok := gvkMap["group"].(string)
if !ok {
continue
}
version, ok := gvkMap["version"].(string)
if !ok {
continue
}
kind, ok := gvkMap["kind"].(string)
if !ok {
continue
}
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
})
}
return gvkListResult
}
/*
Copyright 2017 The Kubernetes Authors.
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 openapi
import (
"sync"
"k8s.io/client-go/discovery"
)
// synchronizedOpenAPIGetter fetches the openapi schema once and then caches it in memory
type synchronizedOpenAPIGetter struct {
// Cached results
sync.Once
openAPISchema Resources
err error
openAPIClient discovery.OpenAPISchemaInterface
}
var _ Getter = &synchronizedOpenAPIGetter{}
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
type Getter interface {
// OpenAPIData returns the parsed OpenAPIData
Get() (Resources, error)
}
// NewOpenAPIGetter returns an object to return OpenAPIDatas which reads
// from a server, and then stores in memory for subsequent invocations
func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) Getter {
return &synchronizedOpenAPIGetter{
openAPIClient: openAPIClient,
}
}
// Resources implements Getter
func (g *synchronizedOpenAPIGetter) Get() (Resources, error) {
g.Do(func() {
s, err := g.openAPIClient.OpenAPISchema()
if err != nil {
g.err = err
return
}
g.openAPISchema, g.err = NewOpenAPIData(s)
})
// Return the save result
return g.openAPISchema, g.err
}
......@@ -1368,8 +1368,6 @@ k8s.io/kube-openapi/pkg/schemaconv
k8s.io/kube-openapi/pkg/util
k8s.io/kube-openapi/pkg/util/proto
k8s.io/kube-openapi/pkg/util/sets
# k8s.io/kubectl v0.17.3 => k8s.io/kubectl v0.17.3
k8s.io/kubectl/pkg/util/openapi
# k8s.io/utils v0.0.0-20191114184206-e782cd3c129f => k8s.io/utils v0.0.0-20191114184206-e782cd3c129f
k8s.io/utils/buffer
k8s.io/utils/integer
......@@ -1444,7 +1442,6 @@ sigs.k8s.io/controller-tools/pkg/webhook
# sigs.k8s.io/kubefed v0.2.0-alpha.1 => sigs.k8s.io/kubefed v0.2.0-alpha.1
sigs.k8s.io/kubefed/pkg/apis
sigs.k8s.io/kubefed/pkg/apis/core/common
sigs.k8s.io/kubefed/pkg/apis/core/typeconfig
sigs.k8s.io/kubefed/pkg/apis/core/v1alpha1
sigs.k8s.io/kubefed/pkg/apis/core/v1beta1
sigs.k8s.io/kubefed/pkg/apis/multiclusterdns/v1alpha1
......@@ -1452,13 +1449,8 @@ sigs.k8s.io/kubefed/pkg/apis/scheduling/v1alpha1
sigs.k8s.io/kubefed/pkg/client/generic
sigs.k8s.io/kubefed/pkg/client/generic/scheme
sigs.k8s.io/kubefed/pkg/controller/util
sigs.k8s.io/kubefed/pkg/kubefedctl
sigs.k8s.io/kubefed/pkg/kubefedctl/enable
sigs.k8s.io/kubefed/pkg/kubefedctl/federate
sigs.k8s.io/kubefed/pkg/kubefedctl/options
sigs.k8s.io/kubefed/pkg/kubefedctl/orphaning
sigs.k8s.io/kubefed/pkg/kubefedctl/util
sigs.k8s.io/kubefed/pkg/version
# sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca => sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca
sigs.k8s.io/structured-merge-diff/fieldpath
sigs.k8s.io/structured-merge-diff/merge
......
/*
Copyright 2018 The Kubernetes Authors.
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 typeconfig
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Interface defines how to interact with a FederatedTypeConfig
type Interface interface {
GetObjectMeta() metav1.ObjectMeta
GetTargetType() metav1.APIResource
GetNamespaced() bool
GetPropagationEnabled() bool
GetFederatedType() metav1.APIResource
GetStatusType() *metav1.APIResource
GetStatusEnabled() bool
GetFederatedNamespaced() bool
IsNamespace() bool
}
/*
Copyright 2018 The Kubernetes Authors.
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 typeconfig
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GroupQualifiedName returns the plural name of the api resource
// optionally qualified by its group:
//
// '<target plural name>[.<target group name>]'
//
// This is the naming scheme for FederatedTypeConfig resources. The
// scheme ensures that, for a given KubeFed control plane,
// federation of a target type will be configured by at most one
// FederatedTypeConfig.
func GroupQualifiedName(apiResource metav1.APIResource) string {
if len(apiResource.Group) == 0 {
return apiResource.Name
}
return fmt.Sprintf("%s.%s", apiResource.Name, apiResource.Group)
}
/*
Copyright 2018 The Kubernetes Authors.
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 kubefedctl
import (
"context"
"fmt"
"io"
"strings"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
apiextv1b1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
"k8s.io/klog"
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
"sigs.k8s.io/kubefed/pkg/kubefedctl/enable"
"sigs.k8s.io/kubefed/pkg/kubefedctl/options"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
const (
federatedGroupUsage = "The name of the API group to use for deleting the federated CRD type when the federated type config does not exist. Only used with --delete-crd."
targetVersionUsage = "The API version of the target type to use for deletion of the federated CRD type when the federated type config does not exist. Only used with --delete-crd."
)
var (
disable_long = `
Disables propagation of a Kubernetes API type. This command
can also optionally delete the API resources added by the enable
command.
Current context is assumed to be a Kubernetes cluster hosting
the kubefed control plane. Please use the
--host-cluster-context flag otherwise.`
disable_example = `
# Disable propagation of the kubernetes API type 'Deployment', named
in FederatedTypeConfig as 'deployments.apps'
kubefedctl disable deployments.apps
# Disable propagation of the kubernetes API type 'Deployment', named
in FederatedTypeConfig as 'deployments.apps', and delete the
corresponding Federated API resource
kubefedctl disable deployments.apps --delete-crd`
)
type disableType struct {
options.GlobalSubcommandOptions
options.CommonEnableOptions
disableTypeOptions
}
type disableTypeOptions struct {
deleteCRD bool
enableTypeDirective *enable.EnableTypeDirective
}
// Bind adds the disable specific arguments to the flagset passed in as an
// argument.
func (o *disableTypeOptions) Bind(flags *pflag.FlagSet) {
flags.BoolVar(&o.deleteCRD, "delete-crd", false, "Whether to remove the API resource added by 'enable'.")
}
// NewCmdTypeDisable defines the `disable` command that
// disables federation of a Kubernetes API type.
func NewCmdTypeDisable(cmdOut io.Writer, config util.FedConfig) *cobra.Command {
opts := &disableType{}
cmd := &cobra.Command{
Use: "disable NAME",
Short: "Disables propagation of a Kubernetes API type",
Long: disable_long,
Example: disable_example,
Run: func(cmd *cobra.Command, args []string) {
err := opts.Complete(args)
if err != nil {
klog.Fatalf("Error: %v", err)
}
err = opts.Run(cmdOut, config)
if err != nil {
klog.Fatalf("Error: %v", err)
}
},
}
flags := cmd.Flags()
opts.GlobalSubcommandBind(flags)
opts.CommonSubcommandBind(flags, federatedGroupUsage, targetVersionUsage)
opts.Bind(flags)
return cmd
}
// Complete ensures that options are valid and marshals them if necessary.
func (j *disableType) Complete(args []string) error {
j.enableTypeDirective = enable.NewEnableTypeDirective()
directive := j.enableTypeDirective
if err := j.SetName(args); err != nil {
return err
}
if !j.deleteCRD {
if len(j.TargetVersion) > 0 {
return errors.New("--version flag valid only with --delete-crd")
} else if j.FederatedGroup != options.DefaultFederatedGroup {
return errors.New("--kubefed-group flag valid only with --delete-crd")
}
}
if len(j.TargetVersion) > 0 {
directive.Spec.TargetVersion = j.TargetVersion
}
if len(j.FederatedGroup) > 0 {
directive.Spec.FederatedGroup = j.FederatedGroup
}
return nil
}
// Run is the implementation of the `disable` command.
func (j *disableType) Run(cmdOut io.Writer, config util.FedConfig) error {
hostConfig, err := config.HostConfig(j.HostClusterContext, j.Kubeconfig)
if err != nil {
return errors.Wrap(err, "Failed to get host cluster config")
}
// If . is specified, the target name is assumed as a group qualified name.
// In such case, ignore the lookup to make sure deletion of a federatedtypeconfig
// for which the corresponding target has been removed.
name := j.TargetName
if !strings.Contains(j.TargetName, ".") {
apiResource, err := enable.LookupAPIResource(hostConfig, j.TargetName, "")
if err != nil {
return err
}
name = typeconfig.GroupQualifiedName(*apiResource)
}
typeConfigName := ctlutil.QualifiedName{
Namespace: j.KubeFedNamespace,
Name: name,
}
j.enableTypeDirective.Name = typeConfigName.Name
return DisableFederation(cmdOut, hostConfig, j.enableTypeDirective, typeConfigName, j.deleteCRD, j.DryRun, true)
}
func DisableFederation(cmdOut io.Writer, config *rest.Config, enableTypeDirective *enable.EnableTypeDirective,
typeConfigName ctlutil.QualifiedName, deleteCRD, dryRun, verifyStopped bool) error {
client, err := genericclient.New(config)
if err != nil {
return errors.Wrap(err, "Failed to get kubefed clientset")
}
write := func(data string) {
if cmdOut == nil {
return
}
if _, err := cmdOut.Write([]byte(data)); err != nil {
klog.Fatalf("Unexpected err: %v\n", err)
}
}
typeConfig := &fedv1b1.FederatedTypeConfig{}
ftcExists, err := checkFederatedTypeConfigExists(client, typeConfig, typeConfigName, write)
if err != nil {
return err
}
if dryRun {
return nil
}
// Disable propagation and verify it is stopped before deleting the CRD
// when no custom resources exist. This avoids spurious error messages in
// the controller manager log as watches are terminated and cannot be
// reestablished.
if ftcExists {
if deleteCRD {
err = checkFederatedTypeCustomResourcesExist(config, typeConfig, write)
if err != nil {
return err
}
}
if typeConfig.GetPropagationEnabled() {
err = disablePropagation(client, typeConfig, typeConfigName, write)
if err != nil {
return err
}
}
if verifyStopped {
err = verifyPropagationControllerStopped(client, typeConfigName, write)
if err != nil {
return err
}
}
}
if deleteCRD {
if !ftcExists {
typeConfig, err = generatedFederatedTypeConfig(config, enableTypeDirective)
if err != nil {
return err
}
}
err = deleteFederatedType(config, typeConfig, write)
if err != nil {
return err
}
}
if ftcExists {
err = deleteFederatedTypeConfig(client, typeConfig, typeConfigName, write)
if err != nil {
return err
}
}
return nil
}
func checkFederatedTypeConfigExists(client genericclient.Client, typeConfig *fedv1b1.FederatedTypeConfig, typeConfigName ctlutil.QualifiedName, write func(string)) (bool, error) {
err := client.Get(context.TODO(), typeConfig, typeConfigName.Namespace, typeConfigName.Name)
if err == nil {
return true, nil
}
if apierrors.IsNotFound(err) {
write(fmt.Sprintf("FederatedTypeConfig %q does not exist\n", typeConfigName))
return false, nil
}
return false, errors.Wrapf(err, "Error retrieving FederatedTypeConfig %q", typeConfigName)
}
func disablePropagation(client genericclient.Client, typeConfig *fedv1b1.FederatedTypeConfig, typeConfigName ctlutil.QualifiedName, write func(string)) error {
if typeConfig.GetPropagationEnabled() {
typeConfig.Spec.Propagation = fedv1b1.PropagationDisabled
err := client.Update(context.TODO(), typeConfig)
if err != nil {
return errors.Wrapf(err, "Error disabling propagation for FederatedTypeConfig %q", typeConfigName)
}
write(fmt.Sprintf("Disabled propagation for FederatedTypeConfig %q\n", typeConfigName))
} else {
write(fmt.Sprintf("Propagation already disabled for FederatedTypeConfig %q\n", typeConfigName))
}
return nil
}
func verifyPropagationControllerStopped(client genericclient.Client, typeConfigName ctlutil.QualifiedName, write func(string)) error {
write(fmt.Sprintf("Verifying propagation controller is stopped for FederatedTypeConfig %q\n", typeConfigName))
var typeConfig *fedv1b1.FederatedTypeConfig
err := wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
typeConfig = &fedv1b1.FederatedTypeConfig{}
err := client.Get(context.TODO(), typeConfig, typeConfigName.Namespace, typeConfigName.Name)
if err != nil {
klog.Errorf("Error retrieving FederatedTypeConfig %q: %v", typeConfigName, err)
return false, nil
}
if typeConfig.Status.PropagationController == fedv1b1.ControllerStatusNotRunning {
return true, nil
}
return false, nil
})
if err != nil {
return errors.Wrapf(err, "Unable to verify propagation controller for FederatedTypeConfig %q is stopped: %v", typeConfigName, err)
}
write(fmt.Sprintf("Propagation controller for FederatedTypeConfig %q is stopped\n", typeConfigName))
return nil
}
func deleteFederatedTypeConfig(client genericclient.Client, typeConfig *fedv1b1.FederatedTypeConfig, typeConfigName ctlutil.QualifiedName, write func(string)) error {
err := client.Delete(context.TODO(), typeConfig, typeConfig.Namespace, typeConfig.Name)
if err != nil {
return errors.Wrapf(err, "Error deleting FederatedTypeConfig %q", typeConfigName)
}
write(fmt.Sprintf("federatedtypeconfig %q deleted\n", typeConfigName))
return nil
}
func generatedFederatedTypeConfig(config *rest.Config, enableTypeDirective *enable.EnableTypeDirective) (*fedv1b1.FederatedTypeConfig, error) {
apiResource, err := enable.LookupAPIResource(config, enableTypeDirective.Name, enableTypeDirective.Spec.TargetVersion)
if err != nil {
return nil, err
}
typeConfig := enable.GenerateTypeConfigForTarget(*apiResource, enableTypeDirective).(*fedv1b1.FederatedTypeConfig)
return typeConfig, nil
}
func deleteFederatedType(config *rest.Config, typeConfig typeconfig.Interface, write func(string)) error {
err := checkFederatedTypeCustomResourcesExist(config, typeConfig, write)
if err != nil {
return err
}
crdName := typeconfig.GroupQualifiedName(typeConfig.GetFederatedType())
err = deleteFederatedCRD(config, crdName, write)
if err != nil {
return err
}
return nil
}
func checkFederatedTypeCustomResourcesExist(config *rest.Config, typeConfig typeconfig.Interface, write func(string)) error {
federatedTypeAPIResource := typeConfig.GetFederatedType()
crdName := typeconfig.GroupQualifiedName(federatedTypeAPIResource)
exists, err := customResourcesExist(config, &federatedTypeAPIResource)
if err != nil {
return err
} else if exists {
return errors.Errorf("Cannot delete CRD %q while resource instances exist. Please try kubefedctl disable again after removing the resource instances or without the '--delete-crd' option\n", crdName)
}
return nil
}
func customResourcesExist(config *rest.Config, resource *metav1.APIResource) (bool, error) {
client, err := ctlutil.NewResourceClient(config, resource)
if err != nil {
return false, err
}
options := metav1.ListOptions{}
objList, err := client.Resources("").List(options)
if apierrors.IsNotFound(err) {
return false, nil
} else if err != nil {
return false, err
}
return len(objList.Items) != 0, nil
}
func deleteFederatedCRD(config *rest.Config, crdName string, write func(string)) error {
client, err := apiextv1b1client.NewForConfig(config)
if err != nil {
return errors.Wrap(err, "Error creating crd client")
}
err = client.CustomResourceDefinitions().Delete(crdName, nil)
if apierrors.IsNotFound(err) {
write(fmt.Sprintf("customresourcedefinition %q does not exist\n", crdName))
} else if err != nil {
return errors.Wrapf(err, "Error deleting crd %q", crdName)
} else {
write(fmt.Sprintf("customresourcedefinition %q deleted\n", crdName))
}
return nil
}
/*
Copyright 2019 The Kubernetes Authors.
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 enable
import (
"k8s.io/apimachinery/pkg/runtime/schema"
fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
)
// Deprecated APIs removed in 1.16 will be served by current equivalent APIs
// https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/
//
// Only allow one of the equivalent APIs for federation to avoid the possibility
// of multiple sync controllers fighting to update the same resource
var equivalentAPIs = map[string][]schema.GroupVersion{
"deployments": {
{
Group: "apps",
Version: "v1",
},
{
Group: "apps",
Version: "v1beta1",
},
{
Group: "apps",
Version: "v1beta2",
},
{
Group: "extensions",
Version: "v1beta1",
},
},
"daemonsets": {
{
Group: "apps",
Version: "v1",
},
{
Group: "apps",
Version: "v1beta1",
},
{
Group: "apps",
Version: "v1beta2",
},
{
Group: "extensions",
Version: "v1beta1",
},
},
"statefulsets": {
{
Group: "apps",
Version: "v1",
},
{
Group: "apps",
Version: "v1beta1",
},
{
Group: "apps",
Version: "v1beta2",
},
},
"replicasets": {
{
Group: "apps",
Version: "v1",
},
{
Group: "apps",
Version: "v1beta1",
},
{
Group: "apps",
Version: "v1beta2",
},
{
Group: "extensions",
Version: "v1beta1",
},
},
"networkpolicies": {
{
Group: "networking.k8s.io",
Version: "v1",
},
{
Group: "extensions",
Version: "v1beta1",
},
},
"podsecuritypolicies": {
{
Group: "policy",
Version: "v1beta1",
},
{
Group: "extensions",
Version: "v1beta1",
},
},
"ingresses": {
{
Group: "networking.k8s.io",
Version: "v1beta1",
},
{
Group: "extensions",
Version: "v1beta1",
},
},
}
func IsEquivalentAPI(existingAPI, newAPI *fedv1b1.APIResource) bool {
if existingAPI.PluralName != newAPI.PluralName {
return false
}
apis, ok := equivalentAPIs[existingAPI.PluralName]
if !ok {
return false
}
for _, gv := range apis {
if gv.Group == existingAPI.Group && gv.Version == existingAPI.Version {
// skip exactly matched API from equivalent API list
continue
}
if gv.Group == newAPI.Group && gv.Version == newAPI.Version {
return true
}
}
return false
}
/*
Copyright 2018 The Kubernetes Authors.
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 enable
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/kubefed/pkg/kubefedctl/options"
)
// EnableTypeDirectiveSpec defines the desired state of EnableTypeDirective.
type EnableTypeDirectiveSpec struct {
// The API version of the target type.
// +optional
TargetVersion string `json:"targetVersion,omitempty"`
// The name of the API group to use for generated federated types.
// +optional
FederatedGroup string `json:"federatedGroup,omitempty"`
// The API version to use for generated federated types.
// +optional
FederatedVersion string `json:"federatedVersion,omitempty"`
}
// TODO(marun) This should become a proper API type and drive enabling
// type federation via a controller. For now its only purpose is to
// enable loading of configuration from disk.
type EnableTypeDirective struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec EnableTypeDirectiveSpec `json:"spec,omitempty"`
}
func (ft *EnableTypeDirective) SetDefaults() {
ft.Spec.FederatedGroup = options.DefaultFederatedGroup
ft.Spec.FederatedVersion = options.DefaultFederatedVersion
}
func NewEnableTypeDirective() *EnableTypeDirective {
ft := &EnableTypeDirective{}
ft.SetDefaults()
return ft
}
/*
Copyright 2018 The Kubernetes Authors.
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 enable
import (
"context"
"fmt"
"io"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextv1b1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"k8s.io/klog"
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
genericclient "sigs.k8s.io/kubefed/pkg/client/generic"
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
"sigs.k8s.io/kubefed/pkg/kubefedctl/options"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
const (
federatedGroupUsage = "The name of the API group to use for the generated federated type."
targetVersionUsage = "Optional, the API version of the target type."
)
var (
enable_long = `
Enables a Kubernetes API type (including a CRD) to be propagated
to clusters registered with a KubeFed control plane. A CRD for
the federated type will be generated and a FederatedTypeConfig will
be created to configure a sync controller.
Current context is assumed to be a Kubernetes cluster hosting
the kubefed control plane. Please use the
--host-cluster-context flag otherwise.`
enable_example = `
# Enable federation of Deployments
kubefedctl enable deployments.apps --host-cluster-context=cluster1
# Enable federation of Deployments identified by name specified in
# deployment.yaml
kubefedctl enable -f deployment.yaml`
)
type enableType struct {
options.GlobalSubcommandOptions
options.CommonEnableOptions
enableTypeOptions
}
type enableTypeOptions struct {
federatedVersion string
output string
outputYAML bool
filename string
enableTypeDirective *EnableTypeDirective
}
// Bind adds the join specific arguments to the flagset passed in as an
// argument.
func (o *enableTypeOptions) Bind(flags *pflag.FlagSet) {
flags.StringVar(&o.federatedVersion, "federated-version", options.DefaultFederatedVersion, "The API version to use for the generated federated type.")
flags.StringVarP(&o.output, "output", "o", "", "If provided, the resources that would be created in the API by the command are instead output to stdout in the provided format. Valid values are ['yaml'].")
flags.StringVarP(&o.filename, "filename", "f", "", "If provided, the command will be configured from the provided yaml file. Only --output will be accepted from the command line")
}
// NewCmdTypeEnable defines the `enable` command that
// enables federation of a Kubernetes API type.
func NewCmdTypeEnable(cmdOut io.Writer, config util.FedConfig) *cobra.Command {
opts := &enableType{}
cmd := &cobra.Command{
Use: "enable (NAME | -f FILENAME)",
Short: "Enables propagation of a Kubernetes API type",
Long: enable_long,
Example: enable_example,
Run: func(cmd *cobra.Command, args []string) {
err := opts.Complete(args)
if err != nil {
klog.Fatalf("Error: %v", err)
}
err = opts.Run(cmdOut, config)
if err != nil {
klog.Fatalf("Error: %v", err)
}
},
}
flags := cmd.Flags()
opts.GlobalSubcommandBind(flags)
opts.CommonSubcommandBind(flags, federatedGroupUsage, targetVersionUsage)
opts.Bind(flags)
return cmd
}
// Complete ensures that options are valid and marshals them if necessary.
func (j *enableType) Complete(args []string) error {
j.enableTypeDirective = NewEnableTypeDirective()
fd := j.enableTypeDirective
if j.output == "yaml" {
j.outputYAML = true
} else if len(j.output) > 0 {
return errors.Errorf("Invalid value for --output: %s", j.output)
}
if len(j.filename) > 0 {
err := DecodeYAMLFromFile(j.filename, fd)
if err != nil {
return errors.Wrapf(err, "Failed to load yaml from file %q", j.filename)
}
return nil
}
if err := j.SetName(args); err != nil {
return err
}
fd.Name = j.TargetName
if len(j.TargetVersion) > 0 {
fd.Spec.TargetVersion = j.TargetVersion
}
if len(j.FederatedGroup) > 0 {
fd.Spec.FederatedGroup = j.FederatedGroup
}
if len(j.federatedVersion) > 0 {
fd.Spec.FederatedVersion = j.federatedVersion
}
return nil
}
// Run is the implementation of the `enable` command.
func (j *enableType) Run(cmdOut io.Writer, config util.FedConfig) error {
hostConfig, err := config.HostConfig(j.HostClusterContext, j.Kubeconfig)
if err != nil {
return errors.Wrap(err, "Failed to get host cluster config")
}
resources, err := GetResources(hostConfig, j.enableTypeDirective)
if err != nil {
return err
}
if j.outputYAML {
concreteTypeConfig := resources.TypeConfig.(*fedv1b1.FederatedTypeConfig)
objects := []pkgruntime.Object{concreteTypeConfig, resources.CRD}
err := writeObjectsToYAML(objects, cmdOut)
if err != nil {
return errors.Wrap(err, "Failed to write objects to YAML")
}
// -o yaml implies dry run
return nil
}
return CreateResources(cmdOut, hostConfig, resources, j.KubeFedNamespace, j.DryRun)
}
type typeResources struct {
TypeConfig typeconfig.Interface
CRD *apiextv1b1.CustomResourceDefinition
}
func GetResources(config *rest.Config, enableTypeDirective *EnableTypeDirective) (*typeResources, error) {
apiResource, err := LookupAPIResource(config, enableTypeDirective.Name, enableTypeDirective.Spec.TargetVersion)
if err != nil {
return nil, err
}
klog.V(2).Infof("Found type %q", resourceKey(*apiResource))
typeConfig := GenerateTypeConfigForTarget(*apiResource, enableTypeDirective)
accessor, err := newSchemaAccessor(config, *apiResource)
if err != nil {
return nil, errors.Wrap(err, "Error initializing validation schema accessor")
}
shortNames := []string{}
for _, shortName := range apiResource.ShortNames {
shortNames = append(shortNames, fmt.Sprintf("f%s", shortName))
}
crd := federatedTypeCRD(typeConfig, accessor, shortNames)
return &typeResources{
TypeConfig: typeConfig,
CRD: crd,
}, nil
}
// TODO(marun) Allow updates to the configuration for a type that has
// already been enabled for kubefed. This would likely involve
// updating the version of the target type and the validation of the schema.
func CreateResources(cmdOut io.Writer, config *rest.Config, resources *typeResources, namespace string, dryRun bool) error {
write := func(data string) {
if cmdOut != nil {
if _, err := cmdOut.Write([]byte(data)); err != nil {
klog.Fatalf("Unexpected err: %v\n", err)
}
}
}
hostClientset, err := util.HostClientset(config)
if err != nil {
return errors.Wrap(err, "Failed to create host clientset")
}
_, err = hostClientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return errors.Wrapf(err, "KubeFed system namespace %q does not exist", namespace)
} else if err != nil {
return errors.Wrapf(err, "Error attempting to determine whether KubeFed system namespace %q exists", namespace)
}
client, err := genericclient.New(config)
if err != nil {
return errors.Wrap(err, "Failed to get kubefed clientset")
}
concreteTypeConfig := resources.TypeConfig.(*fedv1b1.FederatedTypeConfig)
existingTypeConfig := &fedv1b1.FederatedTypeConfig{}
err = client.Get(context.TODO(), existingTypeConfig, namespace, concreteTypeConfig.Name)
if err != nil && !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "Error retrieving FederatedTypeConfig %q", concreteTypeConfig.Name)
}
if err == nil {
fedType := existingTypeConfig.GetFederatedType()
target := existingTypeConfig.GetTargetType()
concreteType := concreteTypeConfig.GetFederatedType()
if fedType.Name != concreteType.Name || fedType.Version != concreteType.Version || fedType.Group != concreteType.Group {
return errors.Errorf("Federation is already enabled for %q with federated type %q. Changing the federated type to %q is not supported.",
qualifiedAPIResourceName(target),
qualifiedAPIResourceName(fedType),
qualifiedAPIResourceName(concreteType))
}
}
crdClient, err := apiextv1b1client.NewForConfig(config)
if err != nil {
return errors.Wrap(err, "Failed to create crd clientset")
}
existingCRD, err := crdClient.CustomResourceDefinitions().Get(resources.CRD.Name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
if !dryRun {
_, err = crdClient.CustomResourceDefinitions().Create(resources.CRD)
if err != nil {
return errors.Wrapf(err, "Error creating CRD %q", resources.CRD.Name)
}
}
write(fmt.Sprintf("customresourcedefinition.apiextensions.k8s.io/%s created\n", resources.CRD.Name))
} else if err != nil {
return errors.Wrapf(err, "Error getting CRD %q", resources.CRD.Name)
} else {
ftcs := &fedv1b1.FederatedTypeConfigList{}
err := client.List(context.TODO(), ftcs, namespace)
if err != nil {
return errors.Wrap(err, "Error getting FederatedTypeConfig list")
}
for _, ftc := range ftcs.Items {
targetAPI := concreteTypeConfig.Spec.TargetType
existingAPI := ftc.Spec.TargetType
if IsEquivalentAPI(&existingAPI, &targetAPI) {
existingName := qualifiedAPIResourceName(ftc.GetTargetType())
name := qualifiedAPIResourceName(concreteTypeConfig.GetTargetType())
qualifiedFTCName := ctlutil.QualifiedName{
Namespace: ftc.Namespace,
Name: ftc.Name,
}
return errors.Errorf("Failed to enable %q. Federation of this type is already enabled for equivalent type %q by FederatedTypeConfig %q",
name, existingName, qualifiedFTCName)
}
if concreteTypeConfig.Name == ftc.Name {
continue
}
fedType := ftc.Spec.FederatedType
name := typeconfig.GroupQualifiedName(metav1.APIResource{Name: fedType.PluralName, Group: fedType.Group})
if name == existingCRD.Name {
return errors.Errorf("Failed to enable federation of %q due to the FederatedTypeConfig for %q already referencing a federated type CRD named %q. If these target types are distinct despite sharing the same kind, specifying a non-default --federated-group should allow %q to be enabled.",
concreteTypeConfig.Name, ftc.Name, name, concreteTypeConfig.Name)
}
}
existingCRD.Spec = resources.CRD.Spec
if !dryRun {
_, err = crdClient.CustomResourceDefinitions().Update(existingCRD)
if err != nil {
return errors.Wrapf(err, "Error updating CRD %q", resources.CRD.Name)
}
}
write(fmt.Sprintf("customresourcedefinition.apiextensions.k8s.io/%s updated\n", resources.CRD.Name))
}
concreteTypeConfig.Namespace = namespace
err = client.Get(context.TODO(), existingTypeConfig, namespace, concreteTypeConfig.Name)
createdOrUpdated := "created"
if err != nil {
if !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "Error retrieving FederatedTypeConfig %q", concreteTypeConfig.Name)
}
if !dryRun {
err = client.Create(context.TODO(), concreteTypeConfig)
if err != nil {
return errors.Wrapf(err, "Error creating FederatedTypeConfig %q", concreteTypeConfig.Name)
}
}
} else {
existingTypeConfig.Spec = concreteTypeConfig.Spec
if !dryRun {
err = client.Update(context.TODO(), existingTypeConfig)
if err != nil {
return errors.Wrapf(err, "Error updating FederatedTypeConfig %q", concreteTypeConfig.Name)
}
}
createdOrUpdated = "updated"
}
write(fmt.Sprintf("federatedtypeconfig.core.kubefed.io/%s %s in namespace %s\n",
concreteTypeConfig.Name, createdOrUpdated, namespace))
return nil
}
func GenerateTypeConfigForTarget(apiResource metav1.APIResource, enableTypeDirective *EnableTypeDirective) typeconfig.Interface {
spec := enableTypeDirective.Spec
kind := apiResource.Kind
pluralName := apiResource.Name
typeConfig := &fedv1b1.FederatedTypeConfig{
// Explicitly including TypeMeta will ensure it will be
// serialized properly to yaml.
TypeMeta: metav1.TypeMeta{
Kind: "FederatedTypeConfig",
APIVersion: "core.kubefed.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: typeconfig.GroupQualifiedName(apiResource),
},
Spec: fedv1b1.FederatedTypeConfigSpec{
TargetType: fedv1b1.APIResource{
Version: apiResource.Version,
Kind: kind,
Scope: NamespacedToScope(apiResource),
},
Propagation: fedv1b1.PropagationEnabled,
FederatedType: fedv1b1.APIResource{
Group: spec.FederatedGroup,
Version: spec.FederatedVersion,
Kind: fmt.Sprintf("Federated%s", kind),
PluralName: fmt.Sprintf("federated%s", pluralName),
Scope: FederatedNamespacedToScope(apiResource),
},
},
}
// Set defaults that would normally be set by the api
fedv1b1.SetFederatedTypeConfigDefaults(typeConfig)
return typeConfig
}
func qualifiedAPIResourceName(resource metav1.APIResource) string {
if resource.Group == "" {
return fmt.Sprintf("%s/%s", resource.Name, resource.Version)
}
return fmt.Sprintf("%s.%s/%s", resource.Name, resource.Group, resource.Version)
}
func federatedTypeCRD(typeConfig typeconfig.Interface, accessor schemaAccessor, shortNames []string) *apiextv1b1.CustomResourceDefinition {
templateSchema := accessor.templateSchema()
schema := federatedTypeValidationSchema(templateSchema)
return CrdForAPIResource(typeConfig.GetFederatedType(), schema, shortNames)
}
func writeObjectsToYAML(objects []pkgruntime.Object, w io.Writer) error {
for _, obj := range objects {
if _, err := w.Write([]byte("---\n")); err != nil {
return errors.Wrap(err, "Error encoding object to yaml")
}
if err := writeObjectToYAML(obj, w); err != nil {
return errors.Wrap(err, "Error encoding object to yaml")
}
}
return nil
}
func writeObjectToYAML(obj pkgruntime.Object, w io.Writer) error {
json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj)
if err != nil {
return err
}
unstructuredObj := &unstructured.Unstructured{}
if _, _, err := unstructured.UnstructuredJSONScheme.Decode(json, nil, unstructuredObj); err != nil {
return err
}
return util.WriteUnstructuredToYaml(unstructuredObj, w)
}
/*
Copyright 2018 The Kubernetes Authors.
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 enable
import (
"fmt"
"github.com/pkg/errors"
apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextv1b1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kubectl/pkg/util/openapi"
)
type schemaAccessor interface {
templateSchema() map[string]apiextv1b1.JSONSchemaProps
}
func newSchemaAccessor(config *rest.Config, apiResource metav1.APIResource) (schemaAccessor, error) {
// Assume the resource may be a CRD, and fall back to OpenAPI if that is not the case.
crdAccessor, err := newCRDSchemaAccessor(config, apiResource)
if err != nil {
return nil, err
}
if crdAccessor != nil {
return crdAccessor, nil
}
return newOpenAPISchemaAccessor(config, apiResource)
}
type crdSchemaAccessor struct {
validation *apiextv1b1.CustomResourceValidation
}
func newCRDSchemaAccessor(config *rest.Config, apiResource metav1.APIResource) (schemaAccessor, error) {
// CRDs must have a group
if len(apiResource.Group) == 0 {
return nil, nil
}
// Check whether the target resource is a crd
crdClient, err := apiextv1b1client.NewForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "Failed to create crd clientset")
}
crdName := fmt.Sprintf("%s.%s", apiResource.Name, apiResource.Group)
crd, err := crdClient.CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return nil, nil
}
if err != nil {
return nil, errors.Wrapf(err, "Error attempting retrieval of crd %q", crdName)
}
return &crdSchemaAccessor{validation: crd.Spec.Validation}, nil
}
func (a *crdSchemaAccessor) templateSchema() map[string]apiextv1b1.JSONSchemaProps {
if a.validation != nil && a.validation.OpenAPIV3Schema != nil {
return a.validation.OpenAPIV3Schema.Properties
}
return nil
}
type openAPISchemaAccessor struct {
targetResource proto.Schema
}
func newOpenAPISchemaAccessor(config *rest.Config, apiResource metav1.APIResource) (schemaAccessor, error) {
client, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "Error creating discovery client")
}
resources, err := openapi.NewOpenAPIGetter(client).Get()
if err != nil {
return nil, errors.Wrap(err, "Error loading openapi schema")
}
gvk := schema.GroupVersionKind{
Group: apiResource.Group,
Version: apiResource.Version,
Kind: apiResource.Kind,
}
targetResource := resources.LookupResource(gvk)
if targetResource == nil {
return nil, errors.Errorf("Unable to find openapi schema for %q", gvk)
}
return &openAPISchemaAccessor{
targetResource: targetResource,
}, nil
}
func (a *openAPISchemaAccessor) templateSchema() map[string]apiextv1b1.JSONSchemaProps {
var templateSchema *apiextv1b1.JSONSchemaProps
visitor := &jsonSchemaVistor{
collect: func(schema apiextv1b1.JSONSchemaProps) {
templateSchema = &schema
},
}
a.targetResource.Accept(visitor)
return templateSchema.Properties
}
// jsonSchemaVistor converts proto.Schema resources into json schema.
// A local visitor (and associated callback) is intended to be created
// whenever a function needs to recurse.
//
// TODO(marun) Generate more extensive schema if/when openapi schema
// provides more detail as per https://github.com/ant31/crd-validation
type jsonSchemaVistor struct {
collect func(schema apiextv1b1.JSONSchemaProps)
}
func (v *jsonSchemaVistor) VisitArray(a *proto.Array) {
arraySchema := apiextv1b1.JSONSchemaProps{
Type: "array",
Items: &apiextv1b1.JSONSchemaPropsOrArray{},
}
localVisitor := &jsonSchemaVistor{
collect: func(schema apiextv1b1.JSONSchemaProps) {
arraySchema.Items.Schema = &schema
},
}
a.SubType.Accept(localVisitor)
v.collect(arraySchema)
}
func (v *jsonSchemaVistor) VisitMap(m *proto.Map) {
mapSchema := apiextv1b1.JSONSchemaProps{
Type: "object",
AdditionalProperties: &apiextv1b1.JSONSchemaPropsOrBool{
Allows: true,
},
}
localVisitor := &jsonSchemaVistor{
collect: func(schema apiextv1b1.JSONSchemaProps) {
mapSchema.AdditionalProperties.Schema = &schema
},
}
m.SubType.Accept(localVisitor)
v.collect(mapSchema)
}
func (v *jsonSchemaVistor) VisitPrimitive(p *proto.Primitive) {
schema := schemaForPrimitive(p)
v.collect(schema)
}
func (v *jsonSchemaVistor) VisitKind(k *proto.Kind) {
kindSchema := apiextv1b1.JSONSchemaProps{
Type: "object",
Properties: make(map[string]apiextv1b1.JSONSchemaProps),
Required: k.RequiredFields,
}
for key, fieldSchema := range k.Fields {
// Status cannot be defined for a template
if key == "status" {
continue
}
localVisitor := &jsonSchemaVistor{
collect: func(schema apiextv1b1.JSONSchemaProps) {
kindSchema.Properties[key] = schema
},
}
fieldSchema.Accept(localVisitor)
}
v.collect(kindSchema)
}
func (v *jsonSchemaVistor) VisitReference(r proto.Reference) {
// Short-circuit the recursive definition of JSONSchemaProps (used for CRD validation)
//
// TODO(marun) Implement proper support for recursive schema
if r.Reference() == "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps" {
v.collect(apiextv1b1.JSONSchemaProps{Type: "object"})
return
}
r.SubSchema().Accept(v)
}
func schemaForPrimitive(p *proto.Primitive) apiextv1b1.JSONSchemaProps {
schema := apiextv1b1.JSONSchemaProps{}
if p.Format == "int-or-string" {
schema.AnyOf = []apiextv1b1.JSONSchemaProps{
{
Type: "integer",
Format: "int32",
},
{
Type: "string",
},
}
return schema
}
if len(p.Format) > 0 {
schema.Format = p.Format
}
schema.Type = p.Type
return schema
}
/*
Copyright 2018 The Kubernetes Authors.
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 enable
import (
"fmt"
"io"
"os"
"strings"
"github.com/pkg/errors"
apiextv1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"sigs.k8s.io/kubefed/pkg/apis/core/common"
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
)
func DecodeYAMLFromFile(filename string, obj interface{}) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
return DecodeYAML(f, obj)
}
func DecodeYAML(r io.Reader, obj interface{}) error {
decoder := yaml.NewYAMLToJSONDecoder(r)
return decoder.Decode(obj)
}
func CrdForAPIResource(apiResource metav1.APIResource, validation *apiextv1b1.CustomResourceValidation, shortNames []string) *apiextv1b1.CustomResourceDefinition {
scope := apiextv1b1.ClusterScoped
if apiResource.Namespaced {
scope = apiextv1b1.NamespaceScoped
}
return &apiextv1b1.CustomResourceDefinition{
// Explicitly including TypeMeta will ensure it will be
// serialized properly to yaml.
TypeMeta: metav1.TypeMeta{
Kind: "CustomResourceDefinition",
APIVersion: "apiextensions.k8s.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: typeconfig.GroupQualifiedName(apiResource),
},
Spec: apiextv1b1.CustomResourceDefinitionSpec{
Group: apiResource.Group,
Version: apiResource.Version,
Scope: scope,
Names: apiextv1b1.CustomResourceDefinitionNames{
Plural: apiResource.Name,
Kind: apiResource.Kind,
ShortNames: shortNames,
},
Validation: validation,
Subresources: &apiextv1b1.CustomResourceSubresources{
Status: &apiextv1b1.CustomResourceSubresourceStatus{},
},
},
}
}
func LookupAPIResource(config *rest.Config, key, targetVersion string) (*metav1.APIResource, error) {
resourceLists, err := GetServerPreferredResources(config)
if err != nil {
return nil, err
}
var targetResource *metav1.APIResource
var matchedResources []string
for _, resourceList := range resourceLists {
// The list holds the GroupVersion for its list of APIResources
gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
return nil, errors.Wrap(err, "Error parsing GroupVersion")
}
if len(targetVersion) > 0 && gv.Version != targetVersion {
continue
}
for _, resource := range resourceList.APIResources {
group := gv.Group
if NameMatchesResource(key, resource, group) {
if targetResource == nil {
targetResource = resource.DeepCopy()
targetResource.Group = group
targetResource.Version = gv.Version
}
matchedResources = append(matchedResources, groupQualifiedName(resource.Name, gv.Group))
}
}
}
if len(matchedResources) > 1 {
return nil, errors.Errorf("Multiple resources are matched by %q: %s. A group-qualified plural name must be provided.", key, strings.Join(matchedResources, ", "))
}
if targetResource != nil {
return targetResource, nil
}
return nil, errors.Errorf("Unable to find api resource named %q.", key)
}
func NameMatchesResource(name string, apiResource metav1.APIResource, group string) bool {
lowerCaseName := strings.ToLower(name)
if lowerCaseName == apiResource.Name ||
lowerCaseName == apiResource.SingularName ||
lowerCaseName == strings.ToLower(apiResource.Kind) ||
lowerCaseName == fmt.Sprintf("%s.%s", apiResource.Name, group) {
return true
}
for _, shortName := range apiResource.ShortNames {
if lowerCaseName == strings.ToLower(shortName) {
return true
}
}
return false
}
func GetServerPreferredResources(config *rest.Config) ([]*metav1.APIResourceList, error) {
// TODO(marun) Consider using a caching scheme ala kubectl
client, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, errors.Wrap(err, "Error creating discovery client")
}
resourceLists, err := client.ServerPreferredResources()
if err != nil {
return nil, errors.Wrap(err, "Error listing api resources")
}
return resourceLists, nil
}
func NamespacedToScope(apiResource metav1.APIResource) apiextv1b1.ResourceScope {
if apiResource.Namespaced {
return apiextv1b1.NamespaceScoped
}
return apiextv1b1.ClusterScoped
}
func FederatedNamespacedToScope(apiResource metav1.APIResource) apiextv1b1.ResourceScope {
// Special-case the scope of federated namespace since it will
// hopefully be the only instance of the scope of a federated
// type differing from the scope of its target.
if typeconfig.GroupQualifiedName(apiResource) == common.NamespaceName {
// FederatedNamespace is namespaced to allow the control plane to run
// with only namespace-scoped permissions e.g. to determine placement.
return apiextv1b1.NamespaceScoped
}
return NamespacedToScope(apiResource)
}
func resourceKey(apiResource metav1.APIResource) string {
var group string
if len(apiResource.Group) == 0 {
group = "core"
} else {
group = apiResource.Group
}
var version string
if len(apiResource.Version) == 0 {
version = "v1"
} else {
version = apiResource.Version
}
return fmt.Sprintf("%s.%s/%s", apiResource.Name, group, version)
}
func groupQualifiedName(name, group string) string {
apiResource := metav1.APIResource{
Name: name,
Group: group,
}
return typeconfig.GroupQualifiedName(apiResource)
}
/*
Copyright 2018 The Kubernetes Authors.
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 enable
import (
v1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"sigs.k8s.io/kubefed/pkg/controller/util"
)
func federatedTypeValidationSchema(templateSchema map[string]v1beta1.JSONSchemaProps) *v1beta1.CustomResourceValidation {
schema := ValidationSchema(v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"placement": {
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
// References to one or more clusters allow a
// scheduling mechanism to explicitly indicate
// placement. If one or more clusters is provided,
// the clusterSelector field will be ignored.
"clusters": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"name": {
Type: "string",
},
},
Required: []string{
"name",
},
},
},
},
"clusterSelector": {
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"matchExpressions": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"key": {
Type: "string",
},
"operator": {
Type: "string",
},
"values": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "string",
},
},
},
},
Required: []string{
"key",
"operator",
},
},
},
},
"matchLabels": {
Type: "object",
AdditionalProperties: &v1beta1.JSONSchemaPropsOrBool{
Schema: &v1beta1.JSONSchemaProps{
Type: "string",
},
},
},
},
},
},
},
"overrides": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"clusterName": {
Type: "string",
},
"clusterOverrides": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"op": {
Type: "string",
Pattern: "^(add|remove|replace)?$",
},
"path": {
Type: "string",
},
"value": {
// Supporting the override of an arbitrary field
// precludes up-front validation. Errors in
// the definition of override values will need to
// be caught during propagation.
AnyOf: []v1beta1.JSONSchemaProps{
{
Type: "string",
},
{
Type: "integer",
},
{
Type: "boolean",
},
{
Type: "object",
},
{
Type: "array",
},
},
},
},
Required: []string{
"path",
},
},
},
},
},
},
},
},
},
})
if templateSchema != nil {
specProperties := schema.OpenAPIV3Schema.Properties["spec"].Properties
specProperties["template"] = v1beta1.JSONSchemaProps{
Type: "object",
}
// Add retainReplicas field to types that exposes a replicas
// field that could be targeted by HPA.
if templateSpec, ok := templateSchema["spec"]; ok {
// TODO: find a simpler way to detect that a resource is scalable than having to compute the entire schema.
if replicasField, ok := templateSpec.Properties["replicas"]; ok {
if replicasField.Type == "integer" && replicasField.Format == "int32" {
specProperties[util.RetainReplicasField] = v1beta1.JSONSchemaProps{
Type: "boolean",
}
}
}
}
}
return schema
}
func ValidationSchema(specProps v1beta1.JSONSchemaProps) *v1beta1.CustomResourceValidation {
return &v1beta1.CustomResourceValidation{
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{
Properties: map[string]v1beta1.JSONSchemaProps{
"apiVersion": {
Type: "string",
},
"kind": {
Type: "string",
},
// TODO(marun) Add a comprehensive schema for metadata
"metadata": {
Type: "object",
},
"spec": specProps,
"status": {
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"conditions": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"type": {
Type: "string",
},
"status": {
Type: "string",
},
"reason": {
Type: "string",
},
"lastUpdateTime": {
Format: "date-time",
Type: "string",
},
"lastTransitionTime": {
Format: "date-time",
Type: "string",
},
},
Required: []string{
"type",
"status",
},
},
},
},
"clusters": {
Type: "array",
Items: &v1beta1.JSONSchemaPropsOrArray{
Schema: &v1beta1.JSONSchemaProps{
Type: "object",
Properties: map[string]v1beta1.JSONSchemaProps{
"name": {
Type: "string",
},
"status": {
Type: "string",
},
},
Required: []string{
"name",
},
},
},
},
"observedGeneration": {
Format: "int64",
Type: "integer",
},
},
},
},
// Require a spec (even if empty) as an aid to users
// manually creating federated configmaps or
// secrets. These target types do not include a spec,
// and the absence of the spec in a federated
// equivalent could indicate a malformed resource.
Required: []string{
"spec",
},
},
}
}
/*
Copyright 2019 The Kubernetes Authors.
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 federate
import (
"bufio"
"io"
"os"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
versionhelper "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/rest"
"k8s.io/klog"
"sigs.k8s.io/yaml"
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
"sigs.k8s.io/kubefed/pkg/kubefedctl/enable"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
func RemoveUnwantedFields(resource *unstructured.Unstructured) error {
unstructured.RemoveNestedField(resource.Object, "apiVersion")
unstructured.RemoveNestedField(resource.Object, "kind")
unstructured.RemoveNestedField(resource.Object, "status")
// All metadata fields save labels should be cleared. Other
// metadata fields will be set by the system on creation or
// subsequently by controllers.
labels, _, err := unstructured.NestedMap(resource.Object, "metadata", "labels")
if err != nil {
return errors.Wrap(err, "Failed to retrieve metadata.labels")
}
unstructured.RemoveNestedField(resource.Object, "metadata")
if len(labels) > 0 {
err := unstructured.SetNestedMap(resource.Object, labels, "metadata", "labels")
if err != nil {
return errors.Wrap(err, "Failed to set metadata.labels")
}
}
return nil
}
func SetBasicMetaFields(resource *unstructured.Unstructured, apiResource metav1.APIResource, name, namespace, generateName string) {
resource.SetKind(apiResource.Kind)
gv := schema.GroupVersion{Group: apiResource.Group, Version: apiResource.Version}
resource.SetAPIVersion(gv.String())
resource.SetName(name)
if generateName != "" {
resource.SetGenerateName(generateName)
}
if apiResource.Namespaced {
resource.SetNamespace(namespace)
}
}
func namespacedAPIResourceMap(config *rest.Config, skipAPIResourceNames []string) (map[string]metav1.APIResource, error) {
apiResourceLists, err := enable.GetServerPreferredResources(config)
if err != nil {
return nil, err
}
apiResources := make(map[string]metav1.APIResource)
for _, apiResourceList := range apiResourceLists {
if len(apiResourceList.APIResources) == 0 {
continue
}
gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
if err != nil {
return nil, errors.Wrap(err, "Error parsing GroupVersion")
}
group := gv.Group
if apiResourceGroupMatchesSkipName(skipAPIResourceNames, group) {
// A whole group is skipped by the user
continue
}
if group == "extensions" {
// The strategy involved to choose a Group higher in order for k8s core
// APIs is to consider "extensions" as the outdated group [This seems to
// be true for all k8s APIResources, so far]. For example if "deployments"
// exists in "extensions" and "apps"; "deployments.apps" will be chosen.
// This doesn't apply to events but events are listed in
// controllerCreatedAPIResourceNames and so are skipped always.
// Skipping this also assumes that "extensions" is not the only
// group exposed for this resource on the API Server, which probably
// is safe as "extensions" is deprecated.
// TODO(irfanurrehman): Document this.
continue
}
for _, apiResource := range apiResourceList.APIResources {
if !apiResource.Namespaced || util.IsFederatedAPIResource(apiResource.Kind, group) ||
apiResourceMatchesSkipName(apiResource, skipAPIResourceNames, group) {
continue
}
// For all other resources (say CRDs) same kinds in different groups
// are treated as individual types. If there happens to be an API Resource
// which enables conversion and allows query of the same resource across
// different groups, a specific group resource will have to be chosen by
// the user using --skip-names to skip the not chosen one(s).
// TODO(irfanurrehman): Document this.
// The individual apiResources do not have the group and version set
apiResource.Group = group
apiResource.Version = gv.Version
groupQualifiedName := typeconfig.GroupQualifiedName(apiResource)
if previousAPIResource, ok := apiResources[groupQualifiedName]; ok {
if versionhelper.CompareKubeAwareVersionStrings(gv.Version, previousAPIResource.Version) <= 0 {
// The newer version is not latest keep the previous.
continue
}
}
apiResources[groupQualifiedName] = apiResource
}
}
return apiResources, nil
}
func apiResourceGroupMatchesSkipName(skipAPIResourceNames []string, group string) bool {
for _, name := range skipAPIResourceNames {
if name == "" {
continue
}
if name == group {
return true
}
}
return false
}
func apiResourceMatchesSkipName(apiResource metav1.APIResource, skipAPIResourceNames []string, group string) bool {
names := append(controllerCreatedAPIResourceNames, skipAPIResourceNames...)
for _, name := range names {
if name == "" {
continue
}
if enable.NameMatchesResource(name, apiResource, group) {
return true
}
}
return false
}
// resources stores a list of resources for an api type
type resources struct {
// resource type information
apiResource metav1.APIResource
// resource list
resources []*unstructured.Unstructured
}
func getResourcesInNamespace(config *rest.Config, namespace string, skipAPIResourceNames []string) ([]resources, error) {
apiResources, err := namespacedAPIResourceMap(config, skipAPIResourceNames)
if err != nil {
return nil, err
}
resourcesInNamespace := []resources{}
for _, apiResource := range apiResources {
client, err := ctlutil.NewResourceClient(config, &apiResource)
if err != nil {
return nil, errors.Wrapf(err, "Error creating client for %s", apiResource.Kind)
}
resourceList, err := client.Resources(namespace).List(metav1.ListOptions{})
if apierrors.IsNotFound(err) || resourceList == nil {
continue
}
if err != nil {
return nil, errors.Wrapf(err, "Error listing resources for %s", apiResource.Kind)
}
// It would be a waste of cycles to iterate through empty slices while federating resource
if len(resourceList.Items) == 0 {
continue
}
targetResources := resources{apiResource: apiResource}
for _, item := range resourceList.Items {
resource := item
errors := validation.IsDNS1123Subdomain(resource.GetName())
if len(errors) == 0 {
targetResources.resources = append(targetResources.resources, &resource)
} else {
klog.Warningf("Skipping resource %s of type %s because it does not conform to the DNS-1123 subdomain spec.", resource.GetName(), apiResource.Name)
klog.Warningf("The following error(s) were reported during DNS-1123 validation: ")
for _, err := range errors {
klog.Warningf(err)
}
}
}
resourcesInNamespace = append(resourcesInNamespace, targetResources)
}
return resourcesInNamespace, nil
}
// decodeUnstructuredFromFile reads a list of yamls into a slice of unstructured objects
func DecodeUnstructuredFromFile(filename string) ([]*unstructured.Unstructured, error) {
var f *os.File
if filename == "-" {
f = os.Stdin
} else {
var err error
f, err = os.Open(filename)
if err != nil {
return nil, err
}
}
defer f.Close()
var unstructuredList []*unstructured.Unstructured
reader := utilyaml.NewYAMLReader(bufio.NewReader(f))
for {
unstructuedObj := &unstructured.Unstructured{}
// Read one YAML document at a time, until io.EOF is returned
buf, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if len(buf) == 0 {
break
}
if err := yaml.Unmarshal(buf, unstructuedObj); err != nil {
return nil, err
}
unstructuredList = append(unstructuredList, unstructuedObj)
}
return unstructuredList, nil
}
/*
Copyright 2018 The Kubernetes Authors.
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 kubefedctl
import (
"flag"
"io"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/client-go/tools/clientcmd"
apiserverflag "k8s.io/component-base/cli/flag"
"sigs.k8s.io/kubefed/pkg/kubefedctl/enable"
"sigs.k8s.io/kubefed/pkg/kubefedctl/federate"
"sigs.k8s.io/kubefed/pkg/kubefedctl/orphaning"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
// NewKubeFedCtlCommand creates the `kubefedctl` command and its nested children.
func NewKubeFedCtlCommand(out io.Writer) *cobra.Command {
// Parent command to which all subcommands are added.
rootCmd := &cobra.Command{
Use: "kubefedctl",
Short: "kubefedctl controls a Kubernetes Cluster Federation",
Long: "kubefedctl controls a Kubernetes Cluster Federation. Find more information at https://sigs.k8s.io/kubefed.",
RunE: runHelp,
}
// Add the command line flags from other dependencies (e.g., klog), but do not
// warn if they contain underscores.
pflag.CommandLine.SetNormalizeFunc(apiserverflag.WordSepNormalizeFunc)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
rootCmd.PersistentFlags().AddFlagSet(pflag.CommandLine)
// From this point and forward we get warnings on flags that contain "_" separators
rootCmd.SetGlobalNormalizationFunc(apiserverflag.WarnWordSepNormalizeFunc)
// Prevent klog errors about logging before parsing.
_ = flag.CommandLine.Parse(nil)
fedConfig := util.NewFedConfig(clientcmd.NewDefaultPathOptions())
rootCmd.AddCommand(enable.NewCmdTypeEnable(out, fedConfig))
rootCmd.AddCommand(NewCmdTypeDisable(out, fedConfig))
rootCmd.AddCommand(federate.NewCmdFederateResource(out, fedConfig))
rootCmd.AddCommand(NewCmdJoin(out, fedConfig))
rootCmd.AddCommand(NewCmdUnjoin(out, fedConfig))
rootCmd.AddCommand(orphaning.NewCmdOrphaning(out, fedConfig))
rootCmd.AddCommand(NewCmdVersion(out))
return rootCmd
}
func runHelp(cmd *cobra.Command, args []string) error {
return cmd.Help()
}
/*
Copyright 2019 The Kubernetes Authors.
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 orphaning
import (
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
var (
orphaning_disable_long = `
Removes previously added "orphaning enable" ('kubefed.io/orphan: true')
annotation from a federated resource. When the federated resource is subsequently marked for deletion,
the resources it manages in member clusters will be removed before the federated resource is removed.
Current context is assumed to be a Kubernetes cluster hosting
the kubefed control plane. Please use the
--host-cluster-context flag otherwise.`
orphaning_disable_example = `
# Disable the orphaning mode for a federated resource of type FederatedDeployment and named foo
kubefedctl orphaning disable FederatedDeployment foo --host-cluster-context=cluster1`
)
// newCmdDisableOrphaning removes the 'kubefed.io/orphan: true' annotation from the federated resource
func newCmdDisableOrphaning(cmdOut io.Writer, config util.FedConfig) *cobra.Command {
opts := &orphanResource{}
cmd := &cobra.Command{
Use: "disable <resource type> <resource name>",
Short: "Disable orphaning deletion to ensure the removal of managed resources before removing the managing federated resource",
Long: orphaning_disable_long,
Example: orphaning_disable_example,
Run: func(cmd *cobra.Command, args []string) {
err := opts.Complete(args, config)
if err != nil {
klog.Fatalf("Error: %v", err)
}
err = opts.RunDisable(cmdOut, config)
if err != nil {
klog.Fatalf("Error: %v", err)
}
},
}
flags := cmd.Flags()
opts.GlobalSubcommandBind(flags)
err := opts.Bind(flags)
if err != nil {
klog.Fatalf("Error: %v", err)
}
return cmd
}
// RunDisable implements the `disable` command.
func (o *orphanResource) RunDisable(cmdOut io.Writer, config util.FedConfig) error {
resourceClient, err := o.GetResourceClient(config, cmdOut)
if err != nil {
return err
}
fedResource, err := o.GetFederatedResource(resourceClient)
if err != nil {
return err
}
if !ctlutil.IsOrphaningEnabled(fedResource) {
return nil
}
ctlutil.DisableOrphaning(fedResource)
_, err = resourceClient.Update(fedResource, metav1.UpdateOptions{})
if err != nil {
return errors.Wrapf(err, "Failed to update resource %s %q", fedResource.GetKind(),
ctlutil.QualifiedName{Name: fedResource.GetName(), Namespace: fedResource.GetNamespace()})
}
return nil
}
/*
Copyright 2019 The Kubernetes Authors.
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 orphaning
import (
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
var (
orphaning_enable_long = `
Prevents the removal of managed resources from member clusters when their managing federated
resource is removed. This is accomplished by adding 'kubefed.io/orphan: true' as an annotation to the
federated resource.
Current context is assumed to be a Kubernetes cluster hosting
the kubefed control plane. Please use the
--host-cluster-context flag otherwise.`
orphan_enable_example = `
# Enable the orphaning mode for a federated resource of type FederatedDeployment and named foo
kubefedctl orphaning enable FederatedDeployment foo --host-cluster-context=cluster1`
)
// newCmdEnableOrphaning adds 'kubefed.io/orphan: true' as an annotation to the federated resource
func newCmdEnableOrphaning(cmdOut io.Writer, config util.FedConfig) *cobra.Command {
opts := &orphanResource{}
cmd := &cobra.Command{
Use: "enable <resource type> <resource name>",
Short: "Enable the orphaning (i.e. retention) of resources managed by a federated resource upon its removal.",
Long: orphaning_enable_long,
Example: orphan_enable_example,
Run: func(cmd *cobra.Command, args []string) {
err := opts.Complete(args, config)
if err != nil {
klog.Fatalf("Error: %v", err)
}
err = opts.RunEnable(cmdOut, config)
if err != nil {
klog.Fatalf("Error: %v", err)
}
},
}
flags := cmd.Flags()
opts.GlobalSubcommandBind(flags)
err := opts.Bind(flags)
if err != nil {
klog.Fatalf("Error: %v", err)
}
return cmd
}
// RunEnable implements the `enable` command.
func (o *orphanResource) RunEnable(cmdOut io.Writer, config util.FedConfig) error {
resourceClient, err := o.GetResourceClient(config, cmdOut)
if err != nil {
return err
}
fedResource, err := o.GetFederatedResource(resourceClient)
if err != nil {
return err
}
if ctlutil.IsOrphaningEnabled(fedResource) {
return nil
}
ctlutil.EnableOrphaning(fedResource)
_, err = resourceClient.Update(fedResource, metav1.UpdateOptions{})
if err != nil {
return errors.Wrapf(err, "Failed to update resource %s %q", fedResource.GetKind(),
ctlutil.QualifiedName{Name: fedResource.GetName(), Namespace: fedResource.GetNamespace()})
}
return nil
}
/*
Copyright 2019 The Kubernetes Authors.
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 orphaning
import (
"fmt"
"io"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/klog"
"sigs.k8s.io/kubefed/pkg/apis/core/typeconfig"
ctlutil "sigs.k8s.io/kubefed/pkg/controller/util"
"sigs.k8s.io/kubefed/pkg/kubefedctl/enable"
"sigs.k8s.io/kubefed/pkg/kubefedctl/options"
"sigs.k8s.io/kubefed/pkg/kubefedctl/util"
)
type orphanResource struct {
options.GlobalSubcommandOptions
typeName string
resourceName string
resourceNamespace string
}
// Bind adds the join specific arguments to the flagset passed in as an argument.
func (o *orphanResource) Bind(flags *pflag.FlagSet) error {
flags.StringVarP(&o.resourceNamespace, "namespace", "n", "", "If present, the namespace scope for this CLI request")
err := flags.MarkHidden("kubefed-namespace")
if err != nil {
return err
}
err = flags.MarkHidden("dry-run")
if err != nil {
return err
}
return nil
}
// NewCmdOrphaning the head of orphaning-deletion sub commands
func NewCmdOrphaning(cmdOut io.Writer, config util.FedConfig) *cobra.Command {
cmd := &cobra.Command{
Use: "orphaning-deletion",
Short: "Manage orphaning delete policy",
Long: "Manage orphaning delete policy",
Run: func(cmd *cobra.Command, args []string) {
err := cmd.Help()
if err != nil {
klog.Fatalf("Error: %v", err)
}
},
}
cmd.AddCommand(newCmdEnableOrphaning(cmdOut, config))
cmd.AddCommand(newCmdDisableOrphaning(cmdOut, config))
cmd.AddCommand(newCmdStatusOrphaning(cmdOut, config))
return cmd
}
// Complete ensures that options are valid and marshals them if necessary.
func (o *orphanResource) Complete(args []string, config util.FedConfig) error {
if len(args) == 0 {
return errors.New("resource type is required")
}
o.typeName = args[0]
if len(args) == 1 {
return errors.New("resource name is required")
}
o.resourceName = args[1]
if len(o.resourceNamespace) == 0 {
var err error
o.resourceNamespace, err = util.GetNamespace(o.HostClusterContext, o.Kubeconfig, config)
return err
}
return nil
}
// Returns a Federated Resources Interface
func (o *orphanResource) GetResourceClient(config util.FedConfig, cmdOut io.Writer) (dynamic.ResourceInterface, error) {
hostClientConfig := config.GetClientConfig(o.HostClusterContext, o.Kubeconfig)
if err := o.SetHostClusterContextFromConfig(hostClientConfig); err != nil {
return nil, err
}
hostConfig, err := hostClientConfig.ClientConfig()
if err != nil {
return nil, errors.Wrapf(err, "Unable to load configuration for cluster context %q in kubeconfig %q.`",
o.HostClusterContext, o.Kubeconfig)
}
// Lookup kubernetes API availability
apiResource, err := enable.LookupAPIResource(hostConfig, o.typeName, "")
if err != nil {
return nil, errors.Wrapf(err, "Failed to find targeted %s type", o.typeName)
}
klog.V(2).Infof("API Resource for %s/%s found", typeconfig.GroupQualifiedName(*apiResource), apiResource.Version)
if !util.IsFederatedAPIResource(apiResource.Kind, apiResource.Group) {
fmt.Fprintf(cmdOut, "Warning: %s/%s might not be a federated resource\n",
typeconfig.GroupQualifiedName(*apiResource), apiResource.Version)
}
targetClient, err := ctlutil.NewResourceClient(hostConfig, apiResource)
if err != nil {
return nil, errors.Wrapf(err, "Error creating client for %s", apiResource.Kind)
}
resourceClient := targetClient.Resources(o.resourceNamespace)
return resourceClient, nil
}
// Returns the Federated resource where the orphaning-deletion will be managed
func (o *orphanResource) GetFederatedResource(resourceClient dynamic.ResourceInterface) (*unstructured.Unstructured, error) {
resource, err := resourceClient.Get(o.resourceName, metav1.GetOptions{})
if err != nil {
return nil, errors.Wrapf(err, "Failed to retrieve resource: %q",
ctlutil.QualifiedName{Name: o.resourceName, Namespace: o.resourceNamespace})
}
return resource, nil
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册