diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index 1a804d08fcb..b438a335aab 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -31,6 +31,7 @@ import ( batchv1 "k8s.io/api/batch/v1" batchv1beta1 "k8s.io/api/batch/v1beta1" certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + coordinationv1beta1 "k8s.io/api/coordination/v1beta1" apiv1 "k8s.io/api/core/v1" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" @@ -48,6 +49,7 @@ import ( "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/certificates" + "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/helper" "k8s.io/kubernetes/pkg/apis/extensions" @@ -393,6 +395,14 @@ func AddHandlers(h printers.PrintHandler) { h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequest) h.TableHandler(certificateSigningRequestColumnDefinitions, printCertificateSigningRequestList) + leaseColumnDefinitions := []metav1beta1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, + {Name: "Holder", Type: "string", Description: coordinationv1beta1.LeaseSpec{}.SwaggerDoc()["holderIdentity"]}, + {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, + } + h.TableHandler(leaseColumnDefinitions, printLease) + h.TableHandler(leaseColumnDefinitions, printLeaseList) + storageClassColumnDefinitions := []metav1beta1.TableColumnDefinition{ {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Provisioner", Type: "string", Description: storagev1.StorageClass{}.SwaggerDoc()["provisioner"]}, @@ -1707,6 +1717,31 @@ func printStorageClassList(list *storage.StorageClassList, options printers.Prin return rows, nil } +func printLease(obj *coordination.Lease, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { + row := metav1beta1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + + var holderIdentity string + if obj.Spec.HolderIdentity != nil { + holderIdentity = *obj.Spec.HolderIdentity + } + row.Cells = append(row.Cells, obj.Name, holderIdentity, translateTimestamp(obj.CreationTimestamp)) + return []metav1beta1.TableRow{row}, nil +} + +func printLeaseList(list *coordination.LeaseList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { + rows := make([]metav1beta1.TableRow, 0, len(list.Items)) + for i := range list.Items { + r, err := printLease(&list.Items[i], options) + if err != nil { + return nil, err + } + rows = append(rows, r...) + } + return rows, nil +} + func printStatus(obj *metav1.Status, options printers.PrintOptions) ([]metav1beta1.TableRow, error) { row := metav1beta1.TableRow{ Object: runtime.RawExtension{Object: obj}, diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index ee612d19cc2..7168e36ef9a 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -46,6 +46,7 @@ import ( "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/policy" @@ -3299,6 +3300,55 @@ func TestPrintStorageClass(t *testing.T) { } } +func TestPrintLease(t *testing.T) { + holder1 := "holder1" + holder2 := "holder2" + tests := []struct { + sc coordination.Lease + expect string + }{ + { + coordination.Lease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "lease1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: coordination.LeaseSpec{ + HolderIdentity: &holder1, + }, + }, + "lease1\tholder1\t0s\n", + }, + { + coordination.Lease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "lease2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Spec: coordination.LeaseSpec{ + HolderIdentity: &holder2, + }, + }, + "lease2\tholder2\t5m\n", + }, + } + + buf := bytes.NewBuffer([]byte{}) + for _, test := range tests { + table, err := printers.NewTablePrinter().With(AddHandlers).PrintTable(&test.sc, printers.PrintOptions{}) + if err != nil { + t.Fatal(err) + } + if err := printers.PrintTable(table, buf, printers.PrintOptions{NoHeaders: true}); err != nil { + t.Fatal(err) + } + if buf.String() != test.expect { + t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) + } + buf.Reset() + } +} + func verifyTable(t *testing.T, table *metav1beta1.Table) { var panicErr interface{} func() { diff --git a/pkg/registry/coordination/lease/doc.go b/pkg/registry/coordination/lease/doc.go new file mode 100644 index 00000000000..f744241447d --- /dev/null +++ b/pkg/registry/coordination/lease/doc.go @@ -0,0 +1,17 @@ +/* +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 lease diff --git a/pkg/registry/coordination/lease/storage/storage.go b/pkg/registry/coordination/lease/storage/storage.go new file mode 100644 index 00000000000..37d66095e9f --- /dev/null +++ b/pkg/registry/coordination/lease/storage/storage.go @@ -0,0 +1,54 @@ +/* +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 storage + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + coordinationapi "k8s.io/kubernetes/pkg/apis/coordination" + "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + printerstorage "k8s.io/kubernetes/pkg/printers/storage" + "k8s.io/kubernetes/pkg/registry/coordination/lease" +) + +// REST implements a RESTStorage for leases against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against leases. +func NewREST(optsGetter generic.RESTOptionsGetter) *REST { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &coordinationapi.Lease{} }, + NewListFunc: func() runtime.Object { return &coordinationapi.LeaseList{} }, + DefaultQualifiedResource: coordinationapi.Resource("leases"), + + CreateStrategy: lease.Strategy, + UpdateStrategy: lease.Strategy, + DeleteStrategy: lease.Strategy, + + TableConvertor: printerstorage.TableConvertor{TablePrinter: printers.NewTablePrinter().With(printersinternal.AddHandlers)}, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + + return &REST{store} +} diff --git a/pkg/registry/coordination/lease/strategy.go b/pkg/registry/coordination/lease/strategy.go new file mode 100644 index 00000000000..dd3df56e08c --- /dev/null +++ b/pkg/registry/coordination/lease/strategy.go @@ -0,0 +1,75 @@ +/* +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 lease + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/coordination" + "k8s.io/kubernetes/pkg/apis/coordination/validation" +) + +// leaseStrategy implements verification logic for Leases. +type leaseStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Lease objects. +var Strategy = leaseStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns true because all Lease' need to be within a namespace. +func (leaseStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate prepares Lease for creation. +func (leaseStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (leaseStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { +} + +// Validate validates a new Lease. +func (leaseStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + lease := obj.(*coordination.Lease) + return validation.ValidateLease(lease) +} + +// Canonicalize normalizes the object after validation. +func (leaseStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is true for Lease; this means you may create one with a PUT request. +func (leaseStrategy) AllowCreateOnUpdate() bool { + return true +} + +// ValidateUpdate is the default update validation for an end user. +func (leaseStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateLeaseUpdate(obj.(*coordination.Lease), old.(*coordination.Lease)) +} + +// AllowUnconditionalUpdate is the default update policy for Lease objects. +func (leaseStrategy) AllowUnconditionalUpdate() bool { + return false +} diff --git a/pkg/registry/coordination/rest/storage_coordination.go b/pkg/registry/coordination/rest/storage_coordination.go new file mode 100644 index 00000000000..19a88d9c90e --- /dev/null +++ b/pkg/registry/coordination/rest/storage_coordination.go @@ -0,0 +1,54 @@ +/* +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 rest + +import ( + coordinationv1beta1 "k8s.io/api/coordination/v1beta1" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/coordination" + leasestorage "k8s.io/kubernetes/pkg/registry/coordination/lease/storage" +) + +type RESTStorageProvider struct{} + +func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(coordination.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) + // If you add a version here, be sure to add an entry in `k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go with specific priorities. + // TODO refactor the plumbing to provide the information in the APIGroupInfo + + if apiResourceConfigSource.VersionEnabled(coordinationv1beta1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[coordinationv1beta1.SchemeGroupVersion.Version] = p.v1beta1Storage(apiResourceConfigSource, restOptionsGetter) + } + return apiGroupInfo, true +} + +func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + storage := map[string]rest.Storage{} + // leases + leaseStorage := leasestorage.NewREST(restOptionsGetter) + storage["leases"] = leaseStorage + + return storage +} + +func (p RESTStorageProvider) GroupName() string { + return coordination.GroupName +}