Support apiextensions.k8s.io/v1

Signed-off-by: Jimmi Dyson <jimmidyson@gmail.com>
pull/8460/head
Jimmi Dyson 5 years ago
parent 73d88af866
commit fc89c12df7
No known key found for this signature in database
GPG Key ID: 87E8177D08CBC17B

@ -39,6 +39,7 @@ import (
batch "k8s.io/api/batch/v1" batch "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
extv1beta1 "k8s.io/api/extensions/v1beta1" extv1beta1 "k8s.io/api/extensions/v1beta1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@ -81,16 +82,24 @@ type Client struct {
Log func(string, ...interface{}) Log func(string, ...interface{})
} }
var addToScheme sync.Once
// New creates a new Client. // New creates a new Client.
func New(getter genericclioptions.RESTClientGetter) *Client { func New(getter genericclioptions.RESTClientGetter) *Client {
if getter == nil { if getter == nil {
getter = genericclioptions.NewConfigFlags(true) getter = genericclioptions.NewConfigFlags(true)
} }
err := apiextv1beta1.AddToScheme(scheme.Scheme) // Add CRDs to the scheme. They are missing by default.
if err != nil { addToScheme.Do(func() {
panic(err) if err := apiextv1.AddToScheme(scheme.Scheme); err != nil {
} // This should never happen.
panic(err)
}
if err := apiextv1beta1.AddToScheme(scheme.Scheme); err != nil {
panic(err)
}
})
return &Client{ return &Client{
Factory: cmdutil.NewFactory(getter), Factory: cmdutil.NewFactory(getter),
@ -385,7 +394,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
return nil return nil
} }
//Get the relation pods // Get the relation pods
objs, err = c.getSelectRelationPod(info, objs) objs, err = c.getSelectRelationPod(info, objs)
if err != nil { if err != nil {
c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
@ -733,32 +742,20 @@ func (c *Client) pollCRDEstablished(t time.Duration) ResourceActorFunc {
} }
func (c *Client) pollCRDUntilEstablished(timeout time.Duration, info *resource.Info) error { func (c *Client) pollCRDUntilEstablished(timeout time.Duration, info *resource.Info) error {
return wait.PollImmediate(time.Second, timeout, func() (bool, error) { obj, err := asVersioned(info)
err := info.Get() if err != nil {
if err != nil { return fmt.Errorf("unable to get versioned CRD: %v", err)
return false, fmt.Errorf("unable to get CRD: %v", err) }
}
crd := &apiextv1beta1.CustomResourceDefinition{}
err = scheme.Scheme.Convert(info.Object, crd, nil)
if err != nil {
return false, fmt.Errorf("unable to convert to CRD type: %v", err)
}
for _, cond := range crd.Status.Conditions { return wait.PollImmediate(time.Second, timeout, func() (bool, error) {
switch cond.Type { switch value := obj.(type) {
case apiextv1beta1.Established: case *apiextv1beta1.CustomResourceDefinition:
if cond.Status == apiextv1beta1.ConditionTrue { return c.crdBetaReady(*value), nil
return true, nil case *apiextv1.CustomResourceDefinition:
} return c.crdReady(*value), nil
case apiextv1beta1.NamesAccepted: default:
if cond.Status == apiextv1beta1.ConditionFalse { return false, fmt.Errorf("invalid CRD kind: %T", value)
return false, fmt.Errorf("naming conflict detected for CRD %s", crd.GetName())
}
}
} }
return false, nil
}) })
} }
@ -839,10 +836,11 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P
_, isUnstructured := versionedObject.(runtime.Unstructured) _, isUnstructured := versionedObject.(runtime.Unstructured)
// On newer K8s versions, CRDs aren't unstructured but has this dedicated type // On newer K8s versions, CRDs aren't unstructured but has this dedicated type
_, isCRD := versionedObject.(*apiextv1beta1.CustomResourceDefinition) _, isV1CRD := versionedObject.(*apiextv1.CustomResourceDefinition)
_, isV1Beta1CRD := versionedObject.(*apiextv1beta1.CustomResourceDefinition)
switch { switch {
case runtime.IsNotRegisteredError(err), isUnstructured, isCRD: case runtime.IsNotRegisteredError(err), isUnstructured, isV1CRD, isV1Beta1CRD:
// fall back to generic JSON merge patch // fall back to generic JSON merge patch
patch, err := jsonpatch.CreateMergePatch(oldData, newData) patch, err := jsonpatch.CreateMergePatch(oldData, newData)
if err != nil { if err != nil {
@ -1171,6 +1169,49 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]run
return objs, nil return objs, nil
} }
// Because the v1 extensions API is not available on all supported k8s versions
// yet and because Go doesn't support generics, we need to have a duplicate
// function to support the v1beta1 types
func (c *Client) crdBetaReady(crd apiextv1beta1.CustomResourceDefinition) bool {
for _, cond := range crd.Status.Conditions {
switch cond.Type {
case apiextv1beta1.Established:
if cond.Status == apiextv1beta1.ConditionTrue {
return true
}
case apiextv1beta1.NamesAccepted:
if cond.Status == apiextv1beta1.ConditionFalse {
// This indicates a naming conflict, but it's probably not the
// job of this function to fail because of that. Instead,
// we treat it as a success, since the process should be able to
// continue.
return true
}
}
}
return false
}
func (c *Client) crdReady(crd apiextv1.CustomResourceDefinition) bool {
for _, cond := range crd.Status.Conditions {
switch cond.Type {
case apiextv1.Established:
if cond.Status == apiextv1.ConditionTrue {
return true
}
case apiextv1.NamesAccepted:
if cond.Status == apiextv1.ConditionFalse {
// This indicates a naming conflict, but it's probably not the
// job of this function to fail because of that. Instead,
// we treat it as a success, since the process should be able to
// continue.
return true
}
}
}
return false
}
func asVersionedOrUnstructured(info *resource.Info) runtime.Object { func asVersionedOrUnstructured(info *resource.Info) runtime.Object {
obj, _ := asVersioned(info) obj, _ := asVersioned(info)
return obj return obj

@ -27,6 +27,7 @@ import (
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -43,6 +44,10 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = apiextv1.AddToScheme(scheme.Scheme)
if err != nil {
panic(err)
}
// Tiller use the scheme from go-client, but the cmdtesting // Tiller use the scheme from go-client, but the cmdtesting
// package used here is hardcoded to use the scheme from // package used here is hardcoded to use the scheme from
@ -52,6 +57,10 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = apiextv1.AddToScheme(kubectlscheme.Scheme)
if err != nil {
panic(err)
}
} }
var ( var (
@ -533,32 +542,64 @@ func TestResourceSortOrder(t *testing.T) {
func TestWaitUntilCRDEstablished(t *testing.T) { func TestWaitUntilCRDEstablished(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
conditions []apiextv1beta1.CustomResourceDefinitionCondition conditions []apiextv1.CustomResourceDefinitionCondition
crdManifest string
returnConditionsAfter int returnConditionsAfter int
success bool success bool
}{ }{
"crd reaches established state after 2 requests": { "v1beta1 crd reaches established state after 2 requests": {
conditions: []apiextv1beta1.CustomResourceDefinitionCondition{ conditions: []apiextv1.CustomResourceDefinitionCondition{
{ {
Type: apiextv1beta1.Established, Type: apiextv1.Established,
Status: apiextv1beta1.ConditionTrue, Status: apiextv1.ConditionTrue,
}, },
}, },
crdManifest: crdV1Beta1Manifest,
returnConditionsAfter: 2, returnConditionsAfter: 2,
success: true, success: true,
}, },
"crd does not reach established state before timeout": { "v1beta1 crd does not reach established state before timeout": {
conditions: []apiextv1beta1.CustomResourceDefinitionCondition{}, conditions: []apiextv1.CustomResourceDefinitionCondition{},
crdManifest: crdV1Beta1Manifest,
returnConditionsAfter: 100, returnConditionsAfter: 100,
success: false, success: false,
}, },
"crd name is not accepted": { "v1beta1 crd name is not accepted": {
conditions: []apiextv1beta1.CustomResourceDefinitionCondition{ conditions: []apiextv1.CustomResourceDefinitionCondition{
{ {
Type: apiextv1beta1.NamesAccepted, Type: apiextv1.NamesAccepted,
Status: apiextv1beta1.ConditionFalse, Status: apiextv1.ConditionFalse,
}, },
}, },
crdManifest: crdV1Beta1Manifest,
returnConditionsAfter: 1,
success: false,
},
"v1 crd reaches established state after 2 requests": {
conditions: []apiextv1.CustomResourceDefinitionCondition{
{
Type: apiextv1.Established,
Status: apiextv1.ConditionTrue,
},
},
crdManifest: crdV1Manifest,
returnConditionsAfter: 2,
success: true,
},
"v1 crd does not reach established state before timeout": {
conditions: []apiextv1.CustomResourceDefinitionCondition{},
crdManifest: crdV1Manifest,
returnConditionsAfter: 100,
success: false,
},
"v1 crd name is not accepted": {
conditions: []apiextv1.CustomResourceDefinitionCondition{
{
Type: apiextv1.NamesAccepted,
Status: apiextv1.ConditionFalse,
},
},
crdManifest: crdV1Manifest,
returnConditionsAfter: 1, returnConditionsAfter: 1,
success: false, success: false,
}, },
@ -569,8 +610,8 @@ func TestWaitUntilCRDEstablished(t *testing.T) {
c := newTestClient() c := newTestClient()
defer c.Cleanup() defer c.Cleanup()
crdWithoutConditions := newCrdWithStatus("name", apiextv1beta1.CustomResourceDefinitionStatus{}) crdWithoutConditions := newCrdWithStatus("name", apiextv1.CustomResourceDefinitionStatus{})
crdWithConditions := newCrdWithStatus("name", apiextv1beta1.CustomResourceDefinitionStatus{ crdWithConditions := newCrdWithStatus("name", apiextv1.CustomResourceDefinitionStatus{
Conditions: tc.conditions, Conditions: tc.conditions,
}) })
@ -579,7 +620,7 @@ func TestWaitUntilCRDEstablished(t *testing.T) {
GroupVersion: schema.GroupVersion{Version: "v1"}, GroupVersion: schema.GroupVersion{Version: "v1"},
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
var crd apiextv1beta1.CustomResourceDefinition var crd apiextv1.CustomResourceDefinition
if requestCount < tc.returnConditionsAfter { if requestCount < tc.returnConditionsAfter {
crd = crdWithoutConditions crd = crdWithoutConditions
} else { } else {
@ -590,7 +631,7 @@ func TestWaitUntilCRDEstablished(t *testing.T) {
}), }),
} }
err := c.WaitUntilCRDEstablished(strings.NewReader(crdManifest), 5*time.Second) err := c.WaitUntilCRDEstablished(strings.NewReader(crdV1Beta1Manifest), 5*time.Second)
if err != nil && tc.success { if err != nil && tc.success {
t.Errorf("%s: expected no error, but got %v", name, err) t.Errorf("%s: expected no error, but got %v", name, err)
} }
@ -601,13 +642,13 @@ func TestWaitUntilCRDEstablished(t *testing.T) {
} }
} }
func newCrdWithStatus(name string, status apiextv1beta1.CustomResourceDefinitionStatus) apiextv1beta1.CustomResourceDefinition { func newCrdWithStatus(name string, status apiextv1.CustomResourceDefinitionStatus) apiextv1.CustomResourceDefinition {
crd := apiextv1beta1.CustomResourceDefinition{ crd := apiextv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
Namespace: metav1.NamespaceDefault, Namespace: metav1.NamespaceDefault,
}, },
Spec: apiextv1beta1.CustomResourceDefinitionSpec{}, Spec: apiextv1.CustomResourceDefinitionSpec{},
Status: status, Status: status,
} }
return crd return crd
@ -881,7 +922,7 @@ spec:
- containerPort: 80 - containerPort: 80
` `
const crdManifest = ` const crdV1Beta1Manifest = `
apiVersion: apiextensions.k8s.io/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
@ -918,3 +959,41 @@ status:
conditions: [] conditions: []
storedVersions: [] storedVersions: []
` `
const crdV1Manifest = `
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
labels:
controller-tools.k8s.io: "1.0"
name: applications.app.k8s.io
spec:
group: app.k8s.io
names:
kind: Application
plural: applications
scope: Namespaced
validation:
openAPIV3Schema:
properties:
apiVersion:
description: 'Description'
type: string
kind:
description: 'Kind'
type: string
metadata:
type: object
spec:
type: object
status:
type: object
version: v1beta1
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
`

Loading…
Cancel
Save