mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-13 13:14:05 +00:00
Add name generation and normalize common create flows
Adds `ObjectMeta.GenerateName`, an optional string field that defines name generation behavior if a Name is not provided. Adds `pkg/api/rest`, which defines the default Kubernetes API pattern for creation (and will cover update as well). Will allow registries and REST objects to be merged by moving logic on api out of those places. Add `pkg/api/rest/resttest`, which will be the test suite that verifies a RESTStorage object follows the Kubernetes API conventions and begin reducing our duplicated tests.
This commit is contained in:
66
pkg/api/rest/create.go
Normal file
66
pkg/api/rest/create.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// RESTCreateStrategy defines the minimum validation, accepted input, and
|
||||
// name generation behavior to create an object that follows Kubernetes
|
||||
// API conventions.
|
||||
type RESTCreateStrategy interface {
|
||||
runtime.ObjectTyper
|
||||
// The name generate is used when the standard GenerateName field is set.
|
||||
// The NameGenerator will be invoked prior to validation.
|
||||
api.NameGenerator
|
||||
|
||||
// ResetBeforeCreate is invoked on create before validation to remove any fields
|
||||
// that may not be persisted.
|
||||
ResetBeforeCreate(obj runtime.Object)
|
||||
// Validate is invoked after default fields in the object have been filled in before
|
||||
// the object is persisted.
|
||||
Validate(obj runtime.Object) errors.ValidationErrorList
|
||||
}
|
||||
|
||||
// BeforeCreate ensures that common operations for all resources are performed on creation. It only returns
|
||||
// errors that can be converted to api.Status. It invokes ResetBeforeCreate, then GenerateName, then Validate.
|
||||
// It returns nil if the object should be created.
|
||||
func BeforeCreate(strategy RESTCreateStrategy, ctx api.Context, obj runtime.Object) error {
|
||||
_, kind, err := strategy.ObjectVersionAndKind(obj)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
objectMeta, err := api.ObjectMetaFor(obj)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if !api.ValidNamespace(ctx, objectMeta) {
|
||||
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
|
||||
}
|
||||
strategy.ResetBeforeCreate(obj)
|
||||
api.FillObjectMetaSystemFields(ctx, objectMeta)
|
||||
api.GenerateName(strategy, objectMeta)
|
||||
|
||||
if errs := strategy.Validate(obj); len(errs) > 0 {
|
||||
return errors.NewInvalid(kind, objectMeta.Name, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
18
pkg/api/rest/doc.go
Normal file
18
pkg/api/rest/doc.go
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 defines common logic around changes to Kubernetes resources.
|
||||
package rest
|
118
pkg/api/rest/resttest/resttest.go
Normal file
118
pkg/api/rest/resttest/resttest.go
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 resttest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
type Tester struct {
|
||||
*testing.T
|
||||
storage apiserver.RESTStorage
|
||||
}
|
||||
|
||||
func New(t *testing.T, storage apiserver.RESTStorage) *Tester {
|
||||
return &Tester{
|
||||
T: t,
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
func copyOrDie(obj runtime.Object) runtime.Object {
|
||||
out, err := api.Scheme.Copy(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *Tester) TestCreate(valid runtime.Object, invalid ...runtime.Object) {
|
||||
t.TestCreateHasMetadata(copyOrDie(valid))
|
||||
t.TestCreateGeneratesName(copyOrDie(valid))
|
||||
t.TestCreateRejectsMismatchedNamespace(copyOrDie(valid))
|
||||
t.TestCreateInvokesValidation(invalid...)
|
||||
}
|
||||
|
||||
func (t *Tester) TestCreateHasMetadata(valid runtime.Object) {
|
||||
objectMeta, err := api.ObjectMetaFor(valid)
|
||||
if err != nil {
|
||||
t.Fatalf("object does not have ObjectMeta: %v\n%#v", err, valid)
|
||||
}
|
||||
|
||||
objectMeta.Name = "test"
|
||||
objectMeta.Namespace = api.NamespaceDefault
|
||||
|
||||
channel, err := t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if obj := <-channel; obj.Object == nil {
|
||||
t.Fatalf("Unexpected object from channel: %#v", obj)
|
||||
}
|
||||
if !api.HasObjectMetaSystemFieldValues(objectMeta) {
|
||||
t.Errorf("storage did not populate object meta field values")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tester) TestCreateGeneratesName(valid runtime.Object) {
|
||||
objectMeta, err := api.ObjectMetaFor(valid)
|
||||
if err != nil {
|
||||
t.Fatalf("object does not have ObjectMeta: %v\n%#v", err, valid)
|
||||
}
|
||||
|
||||
objectMeta.GenerateName = "test-"
|
||||
|
||||
_, err = t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if objectMeta.Name == "test-" || !strings.HasPrefix(objectMeta.Name, "test-") {
|
||||
t.Errorf("unexpected name: %#v", valid)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tester) TestCreateInvokesValidation(invalid ...runtime.Object) {
|
||||
for i, obj := range invalid {
|
||||
ctx := api.NewDefaultContext()
|
||||
_, err := t.storage.(apiserver.RESTCreater).Create(ctx, obj)
|
||||
if !errors.IsInvalid(err) {
|
||||
t.Errorf("%d: Expected to get an invalid resource error, got %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tester) TestCreateRejectsMismatchedNamespace(valid runtime.Object) {
|
||||
objectMeta, err := api.ObjectMetaFor(valid)
|
||||
if err != nil {
|
||||
t.Fatalf("object does not have ObjectMeta: %v\n%#v", err, valid)
|
||||
}
|
||||
|
||||
objectMeta.Namespace = "not-default"
|
||||
|
||||
_, err = t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error, but we didn't get one")
|
||||
} else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") {
|
||||
t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error())
|
||||
}
|
||||
}
|
47
pkg/api/rest/types.go
Normal file
47
pkg/api/rest/types.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// rcStrategy implements behavior for Replication Controllers.
|
||||
// TODO: move to a replicationcontroller specific package.
|
||||
type rcStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
api.NameGenerator
|
||||
}
|
||||
|
||||
// ReplicationControllers is the default logic that applies when creating and updating Replication Controller
|
||||
// objects.
|
||||
var ReplicationControllers RESTCreateStrategy = rcStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||
|
||||
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
|
||||
func (rcStrategy) ResetBeforeCreate(obj runtime.Object) {
|
||||
controller := obj.(*api.ReplicationController)
|
||||
controller.Status = api.ReplicationControllerStatus{}
|
||||
}
|
||||
|
||||
// Validate validates a new replication controller.
|
||||
func (rcStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||
controller := obj.(*api.ReplicationController)
|
||||
return validation.ValidateReplicationController(controller)
|
||||
}
|
Reference in New Issue
Block a user