mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
Add support for Namespace as Kind
Add example for using namespaces
This commit is contained in:
parent
151be7773c
commit
0bd0e12bbc
@ -69,4 +69,4 @@ DNS_REPLICAS=1
|
||||
|
||||
# Optional: Enable setting flags for kube-apiserver to turn on behavior in active-dev
|
||||
RUNTIME_CONFIG=""
|
||||
#RUNTIME_CONFIG="api/v1beta3"
|
||||
#RUNTIME_CONFIG="api/v1beta3"
|
||||
|
@ -75,7 +75,7 @@ grains:
|
||||
cloud_provider: vagrant
|
||||
roles:
|
||||
- kubernetes-master
|
||||
admission_control: AlwaysAdmit
|
||||
admission_control: NamespaceExists,AlwaysAdmit
|
||||
runtime_config: '$(echo "$RUNTIME_CONFIG" | sed -e "s/'/''/g")'
|
||||
EOF
|
||||
|
||||
|
191
examples/kubernetes-namespaces/README.md
Normal file
191
examples/kubernetes-namespaces/README.md
Normal file
@ -0,0 +1,191 @@
|
||||
## Kubernetes Namespaces
|
||||
|
||||
Kubernetes Namespaces help different projects, teams, or customers to share a Kubernetes cluster.
|
||||
|
||||
It does this by providing the following:
|
||||
|
||||
1. A scope for [Names](identifiers.md).
|
||||
2. A mechanism to attach authorization and policy to a subsection of the cluster.
|
||||
|
||||
Use of multiple namespaces is optional.
|
||||
|
||||
This example demonstrates how to use Kubernetes namespaces to subdivide your cluster.
|
||||
|
||||
### Step Zero: Prerequisites
|
||||
|
||||
This example assumes the following:
|
||||
|
||||
1. You have an existing Kubernetes cluster.
|
||||
2. You have a basic understanding of Kubernetes pods, services, and replication controllers.
|
||||
|
||||
### Step One: Understand the default namespace
|
||||
|
||||
By default, a Kubernetes cluster will instantiate a default namespace when provisioning the cluster to hold the default set of pods,
|
||||
services, and replication controllers used by the cluster.
|
||||
|
||||
Assuming you have a fresh cluster, you can introspect the available namespace's by doing the following:
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh get namespaces
|
||||
NAME LABELS
|
||||
default <none>
|
||||
```
|
||||
|
||||
### Step Two: Create new namespaces
|
||||
|
||||
For this exercise, we will create two additional Kubernetes namespaces to hold our content.
|
||||
|
||||
Let's imagine a scenario where an organization is using a shared Kubernetes cluster for development and production use cases.
|
||||
|
||||
The development team would like to maintain a space in the cluster where they can get a view on the list of pods, services, and replication-controllers
|
||||
they use to build and run their application. In this space, Kubernetes resources come and go, and the restrictions on who can or cannot modify resources
|
||||
are relaxed to enable agile development.
|
||||
|
||||
The operations team would like to maintain a space in the cluster where they can enforce strict procedures on who can or cannot manipulate the set of
|
||||
pods, services, and replication controllers that run the production site.
|
||||
|
||||
One pattern this organization could follow is to partition the Kubernetes cluster into two namespaces: development and production.
|
||||
|
||||
Let's create two new namespaces to hold our work.
|
||||
|
||||
Use the file `examples/kubernetes-namespaces/namespace-dev.json` which describes a development namespace:
|
||||
|
||||
```js
|
||||
{
|
||||
"kind": "Namespace",
|
||||
"apiVersion":"v1beta1",
|
||||
"id": "development",
|
||||
"spec": {},
|
||||
"status": {},
|
||||
"labels": {
|
||||
"name": "development"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Create the development namespace using kubectl.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh create -f examples/kubernetes-namespaces/namespace-dev.json
|
||||
```
|
||||
|
||||
And then lets create the production namespace using kubectl.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh create -f examples/kubernetes-namespaces/namespace-prod.json
|
||||
```
|
||||
|
||||
To be sure things are right, let's list all of the namespaces in our cluster.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh get namespaces
|
||||
NAME LABELS
|
||||
default <none>
|
||||
development name=development
|
||||
production name=production
|
||||
```
|
||||
|
||||
### Step Three: Create pods in each namespace
|
||||
|
||||
A Kubernetes namespace provides the scope for pods, services, and replication controllers in the cluster.
|
||||
|
||||
Users interacting with one namespace do not see the content in another namespace.
|
||||
|
||||
To demonstrate this, let's spin up a simple replication controller and pod in the development namespace.
|
||||
|
||||
The first step is to define a context for the kubectl client to work in each namespace.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh config set-context dev --namespace=development
|
||||
$ cluster/kubectl.sh config set-context prod --namespace=production
|
||||
```
|
||||
|
||||
The above commands provided two request contexts you can alternate against depending on what namespace you
|
||||
wish to work against.
|
||||
|
||||
Let's switch to operate in the development namespace.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh config use-context dev
|
||||
```
|
||||
|
||||
You can verify your current context by doing the following:
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh config view
|
||||
clusters: {}
|
||||
contexts:
|
||||
dev:
|
||||
cluster: ""
|
||||
namespace: development
|
||||
user: ""
|
||||
prod:
|
||||
cluster: ""
|
||||
namespace: production
|
||||
user: ""
|
||||
current-context: dev
|
||||
preferences: {}
|
||||
users: {}
|
||||
```
|
||||
|
||||
At this point, all requests we make to the Kubernetes cluster from the command line are scoped to the development namespace.
|
||||
|
||||
Let's create some content.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh run-container snowflake --image=kubernetes/serve_hostname --replicas=2
|
||||
```
|
||||
|
||||
We have just created a replication controller whose replica size is 2 that is running the pod called snowflake with a basic container that just serves the hostname.
|
||||
|
||||
```shell
|
||||
cluster/kubectl.sh get rc
|
||||
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
|
||||
snowflake snowflake kubernetes/serve_hostname run-container=snowflake 2
|
||||
|
||||
$ cluster/kubectl.sh get pods
|
||||
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS
|
||||
snowflake-fplln 10.246.0.5 snowflake kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=snowflake Running
|
||||
snowflake-gziey 10.246.0.4 snowflake kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=snowflake Running
|
||||
```
|
||||
|
||||
And this is great, developers are able to do what they want, and they do not have to worry about affecting content in the production namespace.
|
||||
|
||||
Let's switch to the production namespace and show how resources in one namespace are hidden from the other.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh config use-context prod
|
||||
```
|
||||
|
||||
The production namespace should be empty.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh get rc
|
||||
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
|
||||
|
||||
$ cluster/kubectl.sh get pods
|
||||
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS
|
||||
```
|
||||
|
||||
Production likes to run cattle, so let's create some cattle pods.
|
||||
|
||||
```shell
|
||||
$ cluster/kubectl.sh run-container cattle --image=kubernetes/serve_hostname --replicas=5
|
||||
|
||||
$ cluster/kubectl.sh get rc
|
||||
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
|
||||
cattle cattle kubernetes/serve_hostname run-container=cattle 5
|
||||
|
||||
$ cluster/kubectl.sh get pods
|
||||
POD IP CONTAINER(S) IMAGE(S) HOST LABELS STATUS
|
||||
cattle-0133o 10.246.0.7 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||
cattle-hh2gd 10.246.0.10 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||
cattle-ls6k1 10.246.0.9 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||
cattle-nyxxv 10.246.0.8 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||
cattle-oh43e 10.246.0.6 cattle kubernetes/serve_hostname 10.245.1.3/10.245.1.3 run-container=cattle Running
|
||||
```
|
||||
|
||||
At this point, it should be clear that the resources users create in one namespace are hidden from the other namespace.
|
||||
|
||||
As the policy support in Kubernetes evolves, we will extend this scenario to show how you can provide different
|
||||
authorization rules for each namespace.
|
10
examples/kubernetes-namespaces/namespace-dev.json
Normal file
10
examples/kubernetes-namespaces/namespace-dev.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"kind": "Namespace",
|
||||
"apiVersion":"v1beta1",
|
||||
"id": "development",
|
||||
"spec": {},
|
||||
"status": {},
|
||||
"labels": {
|
||||
"name": "development"
|
||||
},
|
||||
}
|
10
examples/kubernetes-namespaces/namespace-prod.json
Normal file
10
examples/kubernetes-namespaces/namespace-prod.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"kind": "Namespace",
|
||||
"apiVersion":"v1beta1",
|
||||
"id": "production",
|
||||
"spec": {},
|
||||
"status": {},
|
||||
"labels": {
|
||||
"name": "production"
|
||||
},
|
||||
}
|
@ -61,7 +61,7 @@ func TestValidNamespace(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx = api.NewContext()
|
||||
ns := api.Namespace(ctx)
|
||||
ns := api.NamespaceValue(ctx)
|
||||
if ns != "" {
|
||||
t.Errorf("Expected the empty string")
|
||||
}
|
||||
|
@ -122,8 +122,9 @@ func init() {
|
||||
// the list of kinds that are scoped at the root of the api hierarchy
|
||||
// if a kind is not enumerated here, it is assumed to have a namespace scope
|
||||
kindToRootScope := map[string]bool{
|
||||
"Node": true,
|
||||
"Minion": true,
|
||||
"Node": true,
|
||||
"Minion": true,
|
||||
"Namespace": true,
|
||||
}
|
||||
|
||||
// enumerate all supported versions, get the kinds, and register with the mapper how to address our resources
|
||||
|
@ -51,7 +51,7 @@ var RESTScopeNamespaceLegacy = &restScope{
|
||||
|
||||
var RESTScopeNamespace = &restScope{
|
||||
name: RESTScopeNameNamespace,
|
||||
paramName: "ns",
|
||||
paramName: "namespaces",
|
||||
paramPath: true,
|
||||
paramDescription: "object name and auth scope, such as for teams and projects",
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ func init() {
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
)
|
||||
// Legacy names are supported
|
||||
Scheme.AddKnownTypeWithName("", "Minion", &Node{})
|
||||
@ -81,3 +83,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
|
@ -118,7 +118,7 @@ type nodeStrategy struct {
|
||||
// objects.
|
||||
var Nodes RESTCreateStrategy = nodeStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||
|
||||
// NamespaceScoped is false for services.
|
||||
// NamespaceScoped is false for nodes.
|
||||
func (nodeStrategy) NamespaceScoped() bool {
|
||||
return false
|
||||
}
|
||||
@ -134,3 +134,30 @@ func (nodeStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||
node := obj.(*api.Node)
|
||||
return validation.ValidateMinion(node)
|
||||
}
|
||||
|
||||
// namespaceStrategy implements behavior for nodes
|
||||
type namespaceStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
api.NameGenerator
|
||||
}
|
||||
|
||||
// Namespaces is the default logic that applies when creating and updating Namespace
|
||||
// objects.
|
||||
var Namespaces RESTCreateStrategy = namespaceStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||
|
||||
// NamespaceScoped is false for namespaces.
|
||||
func (namespaceStrategy) NamespaceScoped() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
|
||||
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
|
||||
_ = obj.(*api.Namespace)
|
||||
// Namespace allow *all* fields, including status, to be set.
|
||||
}
|
||||
|
||||
// Validate validates a new namespace.
|
||||
func (namespaceStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||
namespace := obj.(*api.Namespace)
|
||||
return validation.ValidateNamespace(namespace)
|
||||
}
|
||||
|
@ -833,6 +833,35 @@ type NodeList struct {
|
||||
Items []Node `json:"items"`
|
||||
}
|
||||
|
||||
// NamespaceSpec describes the attributes on a Namespace
|
||||
type NamespaceSpec struct {
|
||||
}
|
||||
|
||||
// NamespaceStatus is information about the current status of a Namespace.
|
||||
type NamespaceStatus struct {
|
||||
}
|
||||
|
||||
// A namespace provides a scope for Names.
|
||||
// Use of multiple namespaces is optional
|
||||
type Namespace struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec defines the behavior of the Namespace.
|
||||
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||
|
||||
// Status describes the current status of a Namespace
|
||||
Status NamespaceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// NamespaceList is a list of Namespaces.
|
||||
type NamespaceList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []Namespace `json:"items"`
|
||||
}
|
||||
|
||||
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
||||
type Binding struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
@ -731,6 +731,21 @@ func init() {
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *Namespace, out *newer.Namespace, s conversion.Scope) error {
|
||||
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
||||
*out = LimitRangeSpec{}
|
||||
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||
|
@ -51,6 +51,8 @@ func init() {
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
)
|
||||
// Future names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
|
||||
@ -82,3 +84,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
|
@ -666,6 +666,36 @@ type MinionList struct {
|
||||
Items []Minion `json:"items" description:"list of nodes"`
|
||||
}
|
||||
|
||||
// NamespaceSpec describes the attributes on a Namespace
|
||||
type NamespaceSpec struct {
|
||||
}
|
||||
|
||||
// NamespaceStatus is information about the current status of a Namespace.
|
||||
type NamespaceStatus struct {
|
||||
}
|
||||
|
||||
// A namespace provides a scope for Names.
|
||||
// Use of multiple namespaces is optional
|
||||
type Namespace struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Labels
|
||||
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize namespaces"`
|
||||
|
||||
// Spec defines the behavior of the Namespace.
|
||||
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||
|
||||
// Status describes the current status of a Namespace
|
||||
Status NamespaceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// NamespaceList is a list of Namespaces.
|
||||
type NamespaceList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
Items []Namespace `json:"items"`
|
||||
}
|
||||
|
||||
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
||||
type Binding struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
@ -651,6 +651,21 @@ func init() {
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *Namespace, out *newer.Namespace, s conversion.Scope) error {
|
||||
if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.TypeMeta, &out.ObjectMeta, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Spec, &out.Spec, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Convert(&in.Labels, &out.ObjectMeta.Labels, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(in *newer.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error {
|
||||
*out = LimitRangeSpec{}
|
||||
out.Limits = make([]LimitRangeItem, len(in.Limits), len(in.Limits))
|
||||
|
@ -51,6 +51,8 @@ func init() {
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
)
|
||||
// Future names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
|
||||
@ -82,3 +84,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
|
@ -627,6 +627,36 @@ type MinionList struct {
|
||||
Items []Minion `json:"items" description:"list of nodes"`
|
||||
}
|
||||
|
||||
// NamespaceSpec describes the attributes on a Namespace
|
||||
type NamespaceSpec struct {
|
||||
}
|
||||
|
||||
// NamespaceStatus is information about the current status of a Namespace.
|
||||
type NamespaceStatus struct {
|
||||
}
|
||||
|
||||
// A namespace provides a scope for Names.
|
||||
// Use of multiple namespaces is optional
|
||||
type Namespace struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Labels
|
||||
Labels map[string]string `json:"labels,omitempty" description:"map of string keys and values that can be used to organize and categorize namespaces"`
|
||||
|
||||
// Spec defines the behavior of the Namespace.
|
||||
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||
|
||||
// Status describes the current status of a Namespace
|
||||
Status NamespaceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// NamespaceList is a list of Namespaces.
|
||||
type NamespaceList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
Items []Namespace `json:"items"`
|
||||
}
|
||||
|
||||
// Binding is written by a scheduler to cause a pod to be bound to a host.
|
||||
type Binding struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
@ -51,6 +51,8 @@ func init() {
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&ResourceQuotaUsage{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
)
|
||||
// Legacy names are supported
|
||||
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
|
||||
@ -82,3 +84,5 @@ func (*LimitRangeList) IsAnAPIObject() {}
|
||||
func (*ResourceQuota) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaList) IsAnAPIObject() {}
|
||||
func (*ResourceQuotaUsage) IsAnAPIObject() {}
|
||||
func (*Namespace) IsAnAPIObject() {}
|
||||
func (*NamespaceList) IsAnAPIObject() {}
|
||||
|
@ -848,6 +848,35 @@ type NodeList struct {
|
||||
Items []Node `json:"items"`
|
||||
}
|
||||
|
||||
// NamespaceSpec describes the attributes on a Namespace
|
||||
type NamespaceSpec struct {
|
||||
}
|
||||
|
||||
// NamespaceStatus is information about the current status of a Namespace.
|
||||
type NamespaceStatus struct {
|
||||
}
|
||||
|
||||
// A namespace provides a scope for Names.
|
||||
// Use of multiple namespaces is optional
|
||||
type Namespace struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Spec defines the behavior of the Namespace.
|
||||
Spec NamespaceSpec `json:"spec,omitempty"`
|
||||
|
||||
// Status describes the current status of a Namespace
|
||||
Status NamespaceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// NamespaceList is a list of Namespaces.
|
||||
type NamespaceList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []Namespace `json:"items"`
|
||||
}
|
||||
|
||||
// Binding is written by a scheduler to cause a pod to be bound to a node. Name is not
|
||||
// required for Bindings.
|
||||
type Binding struct {
|
||||
|
@ -109,6 +109,13 @@ func ValidateNodeName(name string, prefix bool) (bool, string) {
|
||||
return nameIsDNSSubdomain(name, prefix)
|
||||
}
|
||||
|
||||
// ValidateNamespaceName can be used to check whether the given namespace name is valid.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// trailing dashes are allowed.
|
||||
func ValidateNamespaceName(name string, prefix bool) (bool, string) {
|
||||
return nameIsDNSSubdomain(name, prefix)
|
||||
}
|
||||
|
||||
// nameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
|
||||
func nameIsDNSSubdomain(name string, prefix bool) (bool, string) {
|
||||
if prefix {
|
||||
@ -839,3 +846,27 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) errs.ValidationErro
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateNamespace tests if required fields are set.
|
||||
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName).Prefix("metadata")...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateNamespaceUpdate tests to make sure a mamespace update can be applied. Modifies oldNamespace.
|
||||
func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList {
|
||||
allErrs := errs.ValidationErrorList{}
|
||||
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &namespace.ObjectMeta).Prefix("metadata")...)
|
||||
|
||||
// TODO: move reset function to its own location
|
||||
// Ignore metadata changes now that they have been tested
|
||||
oldNamespace.ObjectMeta = namespace.ObjectMeta
|
||||
|
||||
// TODO: Add a 'real' ValidationError type for this error and provide print actual diffs.
|
||||
if !api.Semantic.DeepEqual(oldNamespace, namespace) {
|
||||
glog.V(4).Infof("Update failed validation %#v vs %#v", oldNamespace, namespace)
|
||||
allErrs = append(allErrs, fmt.Errorf("update contains more than labels or annotation changes"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
@ -2353,3 +2353,114 @@ func TestValidateResourceQuota(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNamespace(t *testing.T) {
|
||||
validLabels := map[string]string{"a": "b"}
|
||||
invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
|
||||
successCases := []api.Namespace{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "abc", Labels: validLabels},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "abc-123"},
|
||||
},
|
||||
}
|
||||
for _, successCase := range successCases {
|
||||
if errs := ValidateNamespace(&successCase); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
errorCases := map[string]struct {
|
||||
R api.Namespace
|
||||
D string
|
||||
}{
|
||||
"zero-length name": {
|
||||
api.Namespace{ObjectMeta: api.ObjectMeta{Name: ""}},
|
||||
"",
|
||||
},
|
||||
"defined-namespace": {
|
||||
api.Namespace{ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: "makesnosense"}},
|
||||
"",
|
||||
},
|
||||
"invalid-labels": {
|
||||
api.Namespace{ObjectMeta: api.ObjectMeta{Name: "abc", Labels: invalidLabels}},
|
||||
"",
|
||||
},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateNamespace(&v.R)
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNamespaceUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
oldNamespace api.Namespace
|
||||
namespace api.Namespace
|
||||
valid bool
|
||||
}{
|
||||
{api.Namespace{}, api.Namespace{}, true},
|
||||
{api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo"}},
|
||||
api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "bar"},
|
||||
}, false},
|
||||
{api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
}, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"foo": "baz"},
|
||||
},
|
||||
}, true},
|
||||
{api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
}, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"foo": "baz"},
|
||||
},
|
||||
}, true},
|
||||
{api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"bar": "foo"},
|
||||
},
|
||||
}, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"foo": "baz"},
|
||||
},
|
||||
}, true},
|
||||
{api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"foo": "baz"},
|
||||
},
|
||||
}, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"Foo": "baz"},
|
||||
},
|
||||
}, false},
|
||||
}
|
||||
for i, test := range tests {
|
||||
errs := ValidateNamespaceUpdate(&test.oldNamespace, &test.namespace)
|
||||
if test.valid && len(errs) > 0 {
|
||||
t.Errorf("%d: Unexpected error: %v", i, errs)
|
||||
t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
|
||||
}
|
||||
if !test.valid && len(errs) == 0 {
|
||||
t.Errorf("%d: Unexpected non-error", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
|
||||
storage.requestedLabelSelector = label
|
||||
storage.requestedFieldSelector = field
|
||||
storage.requestedResourceVersion = resourceVersion
|
||||
storage.requestedResourceNamespace = api.Namespace(ctx)
|
||||
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
|
||||
if err := storage.errors["watch"]; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -243,7 +243,7 @@ func (storage *SimpleRESTStorage) Watch(ctx api.Context, label, field labels.Sel
|
||||
// Implement Redirector.
|
||||
func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (string, error) {
|
||||
// validate that the namespace context on the request matches the expected input
|
||||
storage.requestedResourceNamespace = api.Namespace(ctx)
|
||||
storage.requestedResourceNamespace = api.NamespaceValue(ctx)
|
||||
if storage.expectedResourceNamespace != storage.requestedResourceNamespace {
|
||||
return "", fmt.Errorf("Expected request namespace %s, but got namespace %s", storage.expectedResourceNamespace, storage.requestedResourceNamespace)
|
||||
}
|
||||
|
@ -223,8 +223,10 @@ type APIRequestInfoResolver struct {
|
||||
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
||||
// Valid Inputs:
|
||||
// Storage paths
|
||||
// /ns/{namespace}/{resource}
|
||||
// /ns/{namespace}/{resource}/{resourceName}
|
||||
// /namespaces
|
||||
// /namespaces/{namespace}
|
||||
// /namespaces/{namespace}/{resource}
|
||||
// /namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /{resource}
|
||||
// /{resource}/{resourceName}
|
||||
// /{resource}/{resourceName}?namespace={namespace}
|
||||
@ -287,15 +289,16 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
|
||||
|
||||
}
|
||||
|
||||
// URL forms: /ns/{namespace}/{resource}/*, where parts are adjusted to be relative to kind
|
||||
if currentParts[0] == "ns" {
|
||||
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||
if currentParts[0] == "namespaces" {
|
||||
if len(currentParts) < 3 {
|
||||
return requestInfo, fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
|
||||
requestInfo.Namespace = ""
|
||||
requestInfo.Resource = "namespaces"
|
||||
} else {
|
||||
requestInfo.Resource = currentParts[2]
|
||||
requestInfo.Namespace = currentParts[1]
|
||||
currentParts = currentParts[2:]
|
||||
}
|
||||
requestInfo.Resource = currentParts[2]
|
||||
requestInfo.Namespace = currentParts[1]
|
||||
currentParts = currentParts[2:]
|
||||
|
||||
} else {
|
||||
// URL forms: /{resource}/*
|
||||
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
|
||||
|
@ -77,9 +77,13 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
||||
expectedName string
|
||||
expectedParts []string
|
||||
}{
|
||||
|
||||
// resource paths
|
||||
{"GET", "/ns/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/ns/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/namespaces", "list", "", "", "namespaces", "Namespace", "", []string{"namespaces"}},
|
||||
{"GET", "/namespaces/other", "get", "", "", "namespaces", "Namespace", "other", []string{"namespaces", "other"}},
|
||||
|
||||
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
@ -87,16 +91,16 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
||||
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
|
||||
// special verbs
|
||||
{"GET", "/proxy/ns/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/proxy/namespaces/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/ns/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/namespaces/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/watch/ns/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/watch/namespaces/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
|
||||
// fully-qualified paths
|
||||
{"GET", "/api/v1beta1/ns/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/ns/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/namespaces/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/namespaces/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
@ -105,7 +109,7 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
||||
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/watch/ns/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/watch/namespaces/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
}
|
||||
|
||||
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
|
||||
@ -141,11 +145,9 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
errorCases := map[string]string{
|
||||
"no resource path": "/",
|
||||
"missing resource type": "/ns/other",
|
||||
"just apiversion": "/api/v1beta1/",
|
||||
"apiversion with no resource": "/api/v1beta1/",
|
||||
"apiversion with just namespace": "/api/v1beta1/ns/other",
|
||||
"no resource path": "/",
|
||||
"just apiversion": "/api/v1beta1/",
|
||||
"apiversion with no resource": "/api/v1beta1/",
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
req, err := http.NewRequest("GET", v, nil)
|
||||
|
@ -294,7 +294,7 @@ func TestProxy(t *testing.T) {
|
||||
server *httptest.Server
|
||||
proxyTestPattern string
|
||||
}{
|
||||
{namespaceServer, "/prefix/version/proxy/ns/" + item.reqNamespace + "/foo/id" + item.path},
|
||||
{namespaceServer, "/prefix/version/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path},
|
||||
{legacyNamespaceServer, "/prefix/version/proxy/foo/id" + item.path + "?namespace=" + item.reqNamespace},
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
||||
for _, item := range table {
|
||||
simpleStorage.errors["resourceLocation"] = item.err
|
||||
simpleStorage.resourceLocation = item.id
|
||||
resp, err := client.Get(server.URL + "/prefix/version/redirect/ns/other/foo/" + item.id)
|
||||
resp, err := client.Get(server.URL + "/prefix/version/redirect/namespaces/other/foo/" + item.id)
|
||||
if resp == nil {
|
||||
t.Fatalf("Unexpected nil resp")
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ type RESTHandler struct {
|
||||
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err != nil {
|
||||
glog.Errorf("Unable to handle request %s %s %v", requestInfo.Namespace, requestInfo.Kind, err)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ type Interface interface {
|
||||
LimitRangesNamespacer
|
||||
ResourceQuotasNamespacer
|
||||
ResourceQuotaUsagesNamespacer
|
||||
NamespacesInterface
|
||||
}
|
||||
|
||||
func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface {
|
||||
@ -78,6 +79,10 @@ func (c *Client) ResourceQuotaUsages(namespace string) ResourceQuotaUsageInterfa
|
||||
return newResourceQuotaUsages(c, namespace)
|
||||
}
|
||||
|
||||
func (c *Client) Namespaces() NamespaceInterface {
|
||||
return newNamespaces(c)
|
||||
}
|
||||
|
||||
// VersionInterface has a method to retrieve the server version.
|
||||
type VersionInterface interface {
|
||||
ServerVersion() (*version.Info, error)
|
||||
|
@ -44,6 +44,7 @@ type Fake struct {
|
||||
EventsList api.EventList
|
||||
LimitRangesList api.LimitRangeList
|
||||
ResourceQuotasList api.ResourceQuotaList
|
||||
NamespacesList api.NamespaceList
|
||||
Err error
|
||||
Watch watch.Interface
|
||||
}
|
||||
@ -84,6 +85,10 @@ func (c *Fake) Services(namespace string) ServiceInterface {
|
||||
return &FakeServices{Fake: c, Namespace: namespace}
|
||||
}
|
||||
|
||||
func (c *Fake) Namespaces() NamespaceInterface {
|
||||
return &FakeNamespaces{Fake: c}
|
||||
}
|
||||
|
||||
func (c *Fake) ServerVersion() (*version.Info, error) {
|
||||
c.Actions = append(c.Actions, FakeAction{Action: "get-version", Value: nil})
|
||||
versionInfo := version.Get()
|
||||
|
53
pkg/client/fake_namespaces.go
Normal file
53
pkg/client/fake_namespaces.go
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
// FakeNamespaces implements NamespacesInterface. Meant to be embedded into a struct to get a default
|
||||
// implementation. This makes faking out just the methods you want to test easier.
|
||||
type FakeNamespaces struct {
|
||||
Fake *Fake
|
||||
}
|
||||
|
||||
func (c *FakeNamespaces) List(selector labels.Selector) (*api.NamespaceList, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "list-namespaces"})
|
||||
return api.Scheme.CopyOrDie(&c.Fake.NamespacesList).(*api.NamespaceList), nil
|
||||
}
|
||||
|
||||
func (c *FakeNamespaces) Get(name string) (*api.Namespace, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-namespace", Value: name})
|
||||
return &api.Namespace{ObjectMeta: api.ObjectMeta{Name: name}}, nil
|
||||
}
|
||||
|
||||
func (c *FakeNamespaces) Delete(name string) error {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "delete-namespace", Value: name})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *FakeNamespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "create-namespace"})
|
||||
return &api.Namespace{}, nil
|
||||
}
|
||||
|
||||
func (c *FakeNamespaces) Update(namespace *api.Namespace) (*api.Namespace, error) {
|
||||
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "update-namespace", Value: namespace.Name})
|
||||
return &api.Namespace{}, nil
|
||||
}
|
88
pkg/client/namespaces.go
Normal file
88
pkg/client/namespaces.go
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
type NamespacesInterface interface {
|
||||
Namespaces() NamespaceInterface
|
||||
}
|
||||
|
||||
type NamespaceInterface interface {
|
||||
Create(item *api.Namespace) (*api.Namespace, error)
|
||||
Get(name string) (result *api.Namespace, err error)
|
||||
List(selector labels.Selector) (*api.NamespaceList, error)
|
||||
Delete(name string) error
|
||||
Update(item *api.Namespace) (*api.Namespace, error)
|
||||
}
|
||||
|
||||
// namespaces implements NamespacesInterface
|
||||
type namespaces struct {
|
||||
r *Client
|
||||
}
|
||||
|
||||
// newNamespaces returns a namespaces object.
|
||||
func newNamespaces(c *Client) *namespaces {
|
||||
return &namespaces{r: c}
|
||||
}
|
||||
|
||||
// Create creates a new namespace.
|
||||
func (c *namespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
|
||||
result := &api.Namespace{}
|
||||
err := c.r.Post().Resource("namespaces").Body(namespace).Do().Into(result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// List lists all the namespaces in the cluster.
|
||||
func (c *namespaces) List(selector labels.Selector) (*api.NamespaceList, error) {
|
||||
result := &api.NamespaceList{}
|
||||
err := c.r.Get().Resource("namespaces").SelectorParam("labels", selector).Do().Into(result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Update takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs.
|
||||
func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, err error) {
|
||||
result = &api.Namespace{}
|
||||
if len(namespace.ResourceVersion) == 0 {
|
||||
err = fmt.Errorf("invalid update object, missing resource version: %v", namespace)
|
||||
return
|
||||
}
|
||||
err = c.r.Put().Resource("namespaces").Name(namespace.Name).Body(namespace).Do().Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Get gets an existing namespace
|
||||
func (c *namespaces) Get(name string) (*api.Namespace, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, errors.New("name is required parameter to Get")
|
||||
}
|
||||
|
||||
result := &api.Namespace{}
|
||||
err := c.r.Get().Resource("namespaces").Name(name).Do().Into(result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Delete deletes an existing namespace.
|
||||
func (c *namespaces) Delete(name string) error {
|
||||
return c.r.Delete().Resource("namespaces").Name(name).Do().Error()
|
||||
}
|
134
pkg/client/namespaces_test.go
Normal file
134
pkg/client/namespaces_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
func TestNamespaceCreate(t *testing.T) {
|
||||
// we create a namespace relative to another namespace
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "POST",
|
||||
Path: "/namespaces",
|
||||
Body: namespace,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: namespace},
|
||||
}
|
||||
|
||||
// from the source ns, provision a new global namespace "foo"
|
||||
response, err := c.Setup().Namespaces().Create(namespace)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%#v should be nil.", err)
|
||||
}
|
||||
|
||||
if e, a := response.Name, namespace.Name; e != a {
|
||||
t.Errorf("%#v != %#v.", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceGet(t *testing.T) {
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "GET",
|
||||
Path: "/namespaces/foo",
|
||||
Body: nil,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: namespace},
|
||||
}
|
||||
|
||||
response, err := c.Setup().Namespaces().Get("foo")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%#v should be nil.", err)
|
||||
}
|
||||
|
||||
if e, r := response.Name, namespace.Name; e != r {
|
||||
t.Errorf("%#v != %#v.", e, r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceList(t *testing.T) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
Items: []api.Namespace{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{
|
||||
Method: "GET",
|
||||
Path: "/ns",
|
||||
Body: nil,
|
||||
},
|
||||
Response: Response{StatusCode: 200, Body: namespaceList},
|
||||
}
|
||||
response, err := c.Setup().Namespaces().List(labels.Everything())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%#v should be nil.", err)
|
||||
}
|
||||
|
||||
if len(response.Items) != 1 {
|
||||
t.Errorf("%#v response.Items should have len 1.", response.Items)
|
||||
}
|
||||
|
||||
responseNamespace := response.Items[0]
|
||||
if e, r := responseNamespace.Name, "foo"; e != r {
|
||||
t.Errorf("%#v != %#v.", e, r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceUpdate(t *testing.T) {
|
||||
requestNamespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
ResourceVersion: "1",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
"name": "baz",
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "PUT", Path: "/namespaces/foo"},
|
||||
Response: Response{StatusCode: 200, Body: requestNamespace},
|
||||
}
|
||||
receivedNamespace, err := c.Setup().Namespaces().Update(requestNamespace)
|
||||
c.Validate(t, receivedNamespace, err)
|
||||
}
|
||||
|
||||
func TestNamespaceDelete(t *testing.T) {
|
||||
c := &testClient{
|
||||
Request: testRequest{Method: "DELETE", Path: "/namespaces/foo"},
|
||||
Response: Response{StatusCode: 200},
|
||||
}
|
||||
err := c.Setup().Namespaces().Delete("foo")
|
||||
c.Validate(t, nil, err)
|
||||
}
|
@ -298,7 +298,7 @@ func (r *Request) Body(obj interface{}) *Request {
|
||||
func (r *Request) finalURL() string {
|
||||
p := r.path
|
||||
if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 {
|
||||
p = path.Join(p, "ns", r.namespace)
|
||||
p = path.Join(p, "namespaces", r.namespace)
|
||||
}
|
||||
if len(r.resource) != 0 {
|
||||
resource := r.resource
|
||||
|
@ -112,7 +112,7 @@ func TestRequestSetsNamespace(t *testing.T) {
|
||||
Path: "/",
|
||||
},
|
||||
}).Namespace("foo")
|
||||
if s := r.finalURL(); s != "ns/foo" {
|
||||
if s := r.finalURL(); s != "namespaces/foo" {
|
||||
t.Errorf("namespace should be in path: %s", s)
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,7 @@ func TestRequestOrdersNamespaceInPath(t *testing.T) {
|
||||
baseURL: &url.URL{},
|
||||
path: "/test/",
|
||||
}).Name("bar").Resource("baz").Namespace("foo")
|
||||
if s := r.finalURL(); s != "/test/ns/foo/baz/bar" {
|
||||
if s := r.finalURL(); s != "/test/namespaces/foo/baz/bar" {
|
||||
t.Errorf("namespace should be in order in path: %s", s)
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func TestCreateObject(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods" && m == "POST":
|
||||
case p == "/namespaces/test/pods" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -64,9 +64,9 @@ func TestCreateMultipleObject(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods" && m == "POST":
|
||||
case p == "/namespaces/test/pods" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case p == "/ns/test/services" && m == "POST":
|
||||
case p == "/namespaces/test/services" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -98,11 +98,11 @@ func TestCreateDirectory(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods" && m == "POST":
|
||||
case p == "/namespaces/test/pods" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case p == "/ns/test/services" && m == "POST":
|
||||
case p == "/namespaces/test/services" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
case p == "/ns/test/replicationcontrollers" && m == "POST":
|
||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
||||
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
|
@ -35,7 +35,7 @@ func TestDeleteObject(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -63,7 +63,7 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -90,7 +90,7 @@ func TestDeleteNoObjects(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods" && m == "GET":
|
||||
case p == "/namespaces/test/pods" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{})}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -123,9 +123,9 @@ func TestDeleteMultipleObject(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case p == "/ns/test/services/frontend" && m == "DELETE":
|
||||
case p == "/namespaces/test/services/frontend" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -155,9 +155,9 @@ func TestDeleteMultipleObjectIgnoreMissing(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods/redis-master" && m == "DELETE":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 404, Body: stringBody("")}, nil
|
||||
case p == "/ns/test/services/frontend" && m == "DELETE":
|
||||
case p == "/namespaces/test/services/frontend" && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -187,11 +187,11 @@ func TestDeleteDirectory(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case strings.HasPrefix(p, "/ns/test/pods/") && m == "DELETE":
|
||||
case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case strings.HasPrefix(p, "/ns/test/services/") && m == "DELETE":
|
||||
case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
case strings.HasPrefix(p, "/ns/test/replicationcontrollers/") && m == "DELETE":
|
||||
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -220,19 +220,19 @@ func TestDeleteMultipleSelector(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods" && m == "GET":
|
||||
case p == "/namespaces/test/pods" && m == "GET":
|
||||
if req.URL.Query().Get("labels") != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case p == "/ns/test/services" && m == "GET":
|
||||
case p == "/namespaces/test/services" && m == "GET":
|
||||
if req.URL.Query().Get("labels") != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
case strings.HasPrefix(p, "/ns/test/pods/") && m == "DELETE":
|
||||
case strings.HasPrefix(p, "/namespaces/test/pods/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case strings.HasPrefix(p, "/ns/test/services/") && m == "DELETE":
|
||||
case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
|
@ -183,9 +183,9 @@ func TestGetMultipleTypeObjects(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
case "/namespaces/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case "/ns/test/services":
|
||||
case "/namespaces/test/services":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -219,9 +219,9 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
case "/namespaces/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case "/ns/test/services":
|
||||
case "/namespaces/test/services":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -271,9 +271,9 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
case "/namespaces/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case "/ns/test/services":
|
||||
case "/namespaces/test/services":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -359,9 +359,9 @@ func TestWatchSelector(t *testing.T) {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
case "/namespaces/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{Items: pods})}, nil
|
||||
case "/watch/ns/test/pods":
|
||||
case "/watch/namespaces/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -398,9 +398,9 @@ func TestWatchResource(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods/foo":
|
||||
case "/namespaces/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
||||
case "/watch/ns/test/pods/foo":
|
||||
case "/watch/namespaces/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -436,9 +436,9 @@ func TestWatchOnlyResource(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods/foo":
|
||||
case "/namespaces/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
||||
case "/watch/ns/test/pods/foo":
|
||||
case "/watch/namespaces/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
|
@ -49,9 +49,9 @@ func TestUpdateObject(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods/redis-master" && m == "GET":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case p == "/ns/test/pods/redis-master" && m == "PUT":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "PUT":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -81,14 +81,14 @@ func TestUpdateMultipleObject(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/ns/test/pods/redis-master" && m == "GET":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case p == "/ns/test/pods/redis-master" && m == "PUT":
|
||||
case p == "/namespaces/test/pods/redis-master" && m == "PUT":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
|
||||
case p == "/ns/test/services/frontend" && m == "GET":
|
||||
case p == "/namespaces/test/services/frontend" && m == "GET":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
case p == "/ns/test/services/frontend" && m == "PUT":
|
||||
case p == "/namespaces/test/services/frontend" && m == "PUT":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
@ -119,11 +119,11 @@ func TestUpdateDirectory(t *testing.T) {
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case strings.HasPrefix(p, "/ns/test/pods/") && (m == "GET" || m == "PUT"):
|
||||
case strings.HasPrefix(p, "/namespaces/test/pods/") && (m == "GET" || m == "PUT"):
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
|
||||
case strings.HasPrefix(p, "/ns/test/services/") && (m == "GET" || m == "PUT"):
|
||||
case strings.HasPrefix(p, "/namespaces/test/services/") && (m == "GET" || m == "PUT"):
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil
|
||||
case strings.HasPrefix(p, "/ns/test/replicationcontrollers/") && (m == "GET" || m == "PUT"):
|
||||
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == "GET" || m == "PUT"):
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
|
@ -304,7 +304,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
|
||||
func TestResourceByName(t *testing.T) {
|
||||
pods, _ := testData()
|
||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
||||
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
||||
})).
|
||||
NamespaceParam("test")
|
||||
|
||||
@ -337,7 +337,7 @@ func TestResourceByName(t *testing.T) {
|
||||
func TestResourceByNameAndEmptySelector(t *testing.T) {
|
||||
pods, _ := testData()
|
||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
||||
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
|
||||
})).
|
||||
NamespaceParam("test").
|
||||
SelectorParam("").
|
||||
@ -364,8 +364,8 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
|
||||
func TestSelector(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||
"/ns/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
||||
"/namespaces/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||
"/namespaces/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
||||
})).
|
||||
SelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
@ -494,7 +494,7 @@ func TestSingularObject(t *testing.T) {
|
||||
func TestListObject(t *testing.T) {
|
||||
pods, _ := testData()
|
||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||
"/namespaces/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||
})).
|
||||
SelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
@ -526,8 +526,8 @@ func TestListObject(t *testing.T) {
|
||||
func TestListObjectWithDifferentVersions(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||
"/ns/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
||||
"/namespaces/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
|
||||
"/namespaces/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
|
||||
})).
|
||||
SelectorParam("a=b").
|
||||
NamespaceParam("test").
|
||||
@ -552,7 +552,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) {
|
||||
func TestWatch(t *testing.T) {
|
||||
pods, _ := testData()
|
||||
w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/watch/ns/test/pods/redis-master?resourceVersion=10": watchBody(watch.Event{
|
||||
"/watch/namespaces/test/pods/redis-master?resourceVersion=10": watchBody(watch.Event{
|
||||
Type: watch.Added,
|
||||
Object: &pods.Items[0],
|
||||
}),
|
||||
@ -607,9 +607,9 @@ func TestLatest(t *testing.T) {
|
||||
}
|
||||
|
||||
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
|
||||
"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, newPod),
|
||||
"/ns/test/pods/bar": runtime.EncodeOrDie(latest.Codec, newPod2),
|
||||
"/ns/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),
|
||||
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, newPod),
|
||||
"/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, newPod2),
|
||||
"/namespaces/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),
|
||||
})).
|
||||
NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
|
||||
|
||||
|
@ -318,7 +318,7 @@ func TestHelperList(t *testing.T) {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != "/ns/bar" {
|
||||
if req.URL.Path != "/namespaces/bar" {
|
||||
t.Errorf("url doesn't contain name: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
|
@ -224,6 +224,7 @@ var statusColumns = []string{"STATUS"}
|
||||
var eventColumns = []string{"TIME", "NAME", "KIND", "SUBOBJECT", "REASON", "SOURCE", "MESSAGE"}
|
||||
var limitRangeColumns = []string{"NAME"}
|
||||
var resourceQuotaColumns = []string{"NAME"}
|
||||
var namespaceColumns = []string{"NAME", "LABELS"}
|
||||
|
||||
// addDefaultHandlers adds print handlers for default Kubernetes types.
|
||||
func (h *HumanReadablePrinter) addDefaultHandlers() {
|
||||
@ -243,6 +244,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
|
||||
h.Handler(limitRangeColumns, printLimitRangeList)
|
||||
h.Handler(resourceQuotaColumns, printResourceQuota)
|
||||
h.Handler(resourceQuotaColumns, printResourceQuotaList)
|
||||
h.Handler(namespaceColumns, printNamespace)
|
||||
h.Handler(namespaceColumns, printNamespaceList)
|
||||
}
|
||||
|
||||
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
|
||||
@ -366,6 +369,20 @@ func printEndpoints(endpoint *api.Endpoints, w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func printNamespace(item *api.Namespace, w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, "%s\t%s\n", item.Name, formatLabels(item.Labels))
|
||||
return err
|
||||
}
|
||||
|
||||
func printNamespaceList(list *api.NamespaceList, w io.Writer) error {
|
||||
for _, item := range list.Items {
|
||||
if err := printNamespace(&item, w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printMinion(minion *api.Node, w io.Writer) error {
|
||||
conditionMap := make(map[api.NodeConditionKind]*api.NodeCondition)
|
||||
NodeAllConditions := []api.NodeConditionKind{api.NodeReady, api.NodeReachable}
|
||||
|
@ -50,6 +50,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequotausage"
|
||||
@ -122,6 +123,7 @@ type Master struct {
|
||||
eventRegistry generic.Registry
|
||||
limitRangeRegistry generic.Registry
|
||||
resourceQuotaRegistry resourcequota.Registry
|
||||
namespaceRegistry generic.Registry
|
||||
storage map[string]apiserver.RESTStorage
|
||||
client *client.Client
|
||||
portalNet *net.IPNet
|
||||
@ -279,6 +281,7 @@ func New(c *Config) *Master {
|
||||
endpointRegistry: etcd.NewRegistry(c.EtcdHelper, nil),
|
||||
bindingRegistry: etcd.NewRegistry(c.EtcdHelper, boundPodFactory),
|
||||
eventRegistry: event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())),
|
||||
namespaceRegistry: namespace.NewEtcdRegistry(c.EtcdHelper),
|
||||
minionRegistry: minionRegistry,
|
||||
limitRangeRegistry: limitrange.NewEtcdRegistry(c.EtcdHelper),
|
||||
resourceQuotaRegistry: resourcequota.NewEtcdRegistry(c.EtcdHelper),
|
||||
@ -400,6 +403,7 @@ func (m *Master) init(c *Config) {
|
||||
"limitRanges": limitrange.NewREST(m.limitRangeRegistry),
|
||||
"resourceQuotas": resourcequota.NewREST(m.resourceQuotaRegistry),
|
||||
"resourceQuotaUsages": resourcequotausage.NewREST(m.resourceQuotaRegistry),
|
||||
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
||||
}
|
||||
|
||||
apiVersions := []string{"v1beta1", "v1beta2"}
|
||||
|
@ -34,6 +34,9 @@ func (m *Master) serviceWriterLoop(stop chan struct{}) {
|
||||
// TODO: when it becomes possible to change this stuff,
|
||||
// stop polling and start watching.
|
||||
// TODO: add endpoints of all replicas, not just the elected master.
|
||||
if err := m.createMasterNamespaceIfNeeded(api.NamespaceDefault); err != nil {
|
||||
glog.Errorf("Can't create master namespace: %v", err)
|
||||
}
|
||||
if m.serviceReadWriteIP != nil {
|
||||
if err := m.createMasterServiceIfNeeded("kubernetes", m.serviceReadWriteIP, m.serviceReadWritePort); err != nil {
|
||||
glog.Errorf("Can't create rw service: %v", err)
|
||||
@ -56,6 +59,9 @@ func (m *Master) roServiceWriterLoop(stop chan struct{}) {
|
||||
// Update service & endpoint records.
|
||||
// TODO: when it becomes possible to change this stuff,
|
||||
// stop polling and start watching.
|
||||
if err := m.createMasterNamespaceIfNeeded(api.NamespaceDefault); err != nil {
|
||||
glog.Errorf("Can't create master namespace: %v", err)
|
||||
}
|
||||
if m.serviceReadOnlyIP != nil {
|
||||
if err := m.createMasterServiceIfNeeded("kubernetes-ro", m.serviceReadOnlyIP, m.serviceReadOnlyPort); err != nil {
|
||||
glog.Errorf("Can't create ro service: %v", err)
|
||||
@ -73,6 +79,30 @@ func (m *Master) roServiceWriterLoop(stop chan struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// createMasterNamespaceIfNeeded will create the namespace that contains the master services if it doesn't already exist
|
||||
func (m *Master) createMasterNamespaceIfNeeded(ns string) error {
|
||||
ctx := api.NewContext()
|
||||
if _, err := m.namespaceRegistry.Get(ctx, api.NamespaceDefault); err == nil {
|
||||
// the namespace already exists
|
||||
return nil
|
||||
}
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: ns,
|
||||
Namespace: "",
|
||||
},
|
||||
}
|
||||
c, err := m.storage["namespaces"].(apiserver.RESTCreater).Create(ctx, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp := <-c
|
||||
if _, ok := resp.Object.(*api.Service); ok {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unexpected response %#v", resp)
|
||||
}
|
||||
|
||||
// createMasterServiceIfNeeded will create the specified service if it
|
||||
// doesn't already exist.
|
||||
func (m *Master) createMasterServiceIfNeeded(serviceName string, serviceIP net.IP, servicePort int) error {
|
||||
|
@ -31,6 +31,8 @@ import (
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/limitranger"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/autoprovision"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/exists"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcedefaults"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcequota"
|
||||
)
|
||||
|
@ -72,7 +72,7 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not an event object: %#v", obj)
|
||||
}
|
||||
if api.Namespace(ctx) != "" {
|
||||
if api.NamespaceValue(ctx) != "" {
|
||||
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
||||
return nil, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func TestRESTCreate(t *testing.T) {
|
||||
c, err := rest.Create(item.ctx, item.event)
|
||||
if !item.valid {
|
||||
if err == nil {
|
||||
ctxNS := api.Namespace(item.ctx)
|
||||
ctxNS := api.NamespaceValue(item.ctx)
|
||||
t.Errorf("unexpected non-error for %v (%v, %v)", item.event.Name, ctxNS, item.event.Namespace)
|
||||
}
|
||||
continue
|
||||
|
19
pkg/registry/namespace/doc.go
Normal file
19
pkg/registry/namespace/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 namespace provides Registry interface and it's REST
|
||||
// implementation for storing Namespace api objects.
|
||||
package namespace
|
48
pkg/registry/namespace/registry.go
Normal file
48
pkg/registry/namespace/registry.go
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 namespace
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||
)
|
||||
|
||||
// registry implements custom changes to generic.Etcd for Namespace storage
|
||||
type registry struct {
|
||||
*etcdgeneric.Etcd
|
||||
}
|
||||
|
||||
// NewEtcdRegistry returns a registry which will store Namespace objects in the given EtcdHelper.
|
||||
func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry {
|
||||
return registry{
|
||||
Etcd: &etcdgeneric.Etcd{
|
||||
NewFunc: func() runtime.Object { return &api.Namespace{} },
|
||||
NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
|
||||
EndpointName: "namespaces",
|
||||
KeyRootFunc: func(ctx api.Context) string {
|
||||
return "/registry/namespaces"
|
||||
},
|
||||
KeyFunc: func(ctx api.Context, id string) (string, error) {
|
||||
return "/registry/namespaces/" + id, nil
|
||||
},
|
||||
Helper: h,
|
||||
},
|
||||
}
|
||||
}
|
17
pkg/registry/namespace/registry_test.go
Normal file
17
pkg/registry/namespace/registry_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
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 namespace
|
134
pkg/registry/namespace/rest.go
Normal file
134
pkg/registry/namespace/rest.go
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
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 namespace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
// REST provides the RESTStorage access patterns to work with Namespace objects.
|
||||
type REST struct {
|
||||
registry generic.Registry
|
||||
}
|
||||
|
||||
// NewREST returns a new REST. You must use a registry created by
|
||||
// NewEtcdRegistry unless you're testing.
|
||||
func NewREST(registry generic.Registry) *REST {
|
||||
return &REST{
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a Namespace object
|
||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
||||
namespace := obj.(*api.Namespace)
|
||||
if err := rest.BeforeCreate(rest.Namespaces, ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||
if err := rs.registry.Create(ctx, namespace.Name, namespace); err != nil {
|
||||
err = rest.CheckGeneratedNameError(rest.Namespaces, err, namespace)
|
||||
return nil, err
|
||||
}
|
||||
return rs.registry.Get(ctx, namespace.Name)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Update updates a Namespace object.
|
||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
||||
namespace, ok := obj.(*api.Namespace)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not a namespace: %#v", obj)
|
||||
}
|
||||
|
||||
oldObj, err := rs.registry.Get(ctx, namespace.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldNamespace := oldObj.(*api.Namespace)
|
||||
if errs := validation.ValidateNamespaceUpdate(oldNamespace, namespace); len(errs) > 0 {
|
||||
return nil, kerrors.NewInvalid("namespace", namespace.Name, errs)
|
||||
}
|
||||
|
||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||
err := rs.registry.Update(ctx, oldNamespace.Name, oldNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs.registry.Get(ctx, oldNamespace.Name)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Delete deletes the Namespace with the specified name
|
||||
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
||||
obj, err := rs.registry.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ok := obj.(*api.Namespace)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
|
||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||
obj, err := rs.registry.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespace, ok := obj.(*api.Namespace)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid object type")
|
||||
}
|
||||
return namespace, err
|
||||
}
|
||||
|
||||
func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, err error) {
|
||||
return labels.Set{}, labels.Set{}, nil
|
||||
}
|
||||
|
||||
func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
|
||||
return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
|
||||
}
|
||||
|
||||
func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
|
||||
return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
|
||||
}
|
||||
|
||||
// New returns a new api.Namespace
|
||||
func (*REST) New() runtime.Object {
|
||||
return &api.Namespace{}
|
||||
}
|
||||
|
||||
func (*REST) NewList() runtime.Object {
|
||||
return &api.NamespaceList{}
|
||||
}
|
188
pkg/registry/namespace/rest_test.go
Normal file
188
pkg/registry/namespace/rest_test.go
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
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 namespace
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
type testRegistry struct {
|
||||
*registrytest.GenericRegistry
|
||||
}
|
||||
|
||||
func NewTestREST() (testRegistry, *REST) {
|
||||
reg := testRegistry{registrytest.NewGeneric(nil)}
|
||||
return reg, NewREST(reg)
|
||||
}
|
||||
|
||||
func testNamespace(name string) *api.Namespace {
|
||||
return &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTCreate(t *testing.T) {
|
||||
table := []struct {
|
||||
ctx api.Context
|
||||
namespace *api.Namespace
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
ctx: api.NewContext(),
|
||||
namespace: testNamespace("foo"),
|
||||
valid: true,
|
||||
}, {
|
||||
ctx: api.NewContext(),
|
||||
namespace: testNamespace("bar"),
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
_, rest := NewTestREST()
|
||||
c, err := rest.Create(item.ctx, item.namespace)
|
||||
if !item.valid {
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error for %v", item.namespace.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%v: Unexpected error %v", item.namespace.Name, err)
|
||||
continue
|
||||
}
|
||||
if !api.HasObjectMetaSystemFieldValues(&item.namespace.ObjectMeta) {
|
||||
t.Errorf("storage did not populate object meta field values")
|
||||
}
|
||||
if e, a := item.namespace, (<-c).Object; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
// Ensure we implement the interface
|
||||
_ = apiserver.ResourceWatcher(rest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTUpdate(t *testing.T) {
|
||||
_, rest := NewTestREST()
|
||||
namespaceA := testNamespace("foo")
|
||||
c, err := rest.Create(api.NewDefaultContext(), namespaceA)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
<-c
|
||||
got, err := rest.Get(api.NewDefaultContext(), namespaceA.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
if e, a := namespaceA, got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
namespaceB := testNamespace("foo")
|
||||
u, err := rest.Update(api.NewDefaultContext(), namespaceB)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
<-u
|
||||
got2, err := rest.Get(api.NewDefaultContext(), namespaceB.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
if e, a := namespaceB, got2; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRESTDelete(t *testing.T) {
|
||||
_, rest := NewTestREST()
|
||||
namespaceA := testNamespace("foo")
|
||||
c, err := rest.Create(api.NewContext(), namespaceA)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
<-c
|
||||
c, err = rest.Delete(api.NewContext(), namespaceA.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
if stat := (<-c).Object.(*api.Status); stat.Status != api.StatusSuccess {
|
||||
t.Errorf("unexpected status: %v", stat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTGet(t *testing.T) {
|
||||
_, rest := NewTestREST()
|
||||
namespaceA := testNamespace("foo")
|
||||
c, err := rest.Create(api.NewContext(), namespaceA)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
<-c
|
||||
got, err := rest.Get(api.NewContext(), namespaceA.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
if e, a := namespaceA, got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTList(t *testing.T) {
|
||||
reg, rest := NewTestREST()
|
||||
namespaceA := testNamespace("foo")
|
||||
namespaceB := testNamespace("bar")
|
||||
namespaceC := testNamespace("baz")
|
||||
reg.ObjectList = &api.NamespaceList{
|
||||
Items: []api.Namespace{*namespaceA, *namespaceB, *namespaceC},
|
||||
}
|
||||
got, err := rest.List(api.NewContext(), labels.Everything(), labels.Everything())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
expect := &api.NamespaceList{
|
||||
Items: []api.Namespace{*namespaceA, *namespaceB, *namespaceC},
|
||||
}
|
||||
if e, a := expect, got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRESTWatch(t *testing.T) {
|
||||
namespaceA := testNamespace("foo")
|
||||
reg, rest := NewTestREST()
|
||||
wi, err := rest.Watch(api.NewContext(), labels.Everything(), labels.Everything(), "0")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
go func() {
|
||||
reg.Broadcaster.Action(watch.Added, namespaceA)
|
||||
}()
|
||||
got := <-wi.ResultChan()
|
||||
if e, a := namespaceA, got.Object; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||
}
|
||||
}
|
94
plugin/pkg/admission/namespace/autoprovision/admission.go
Normal file
94
plugin/pkg/admission/namespace/autoprovision/admission.go
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 autoprovision
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("NamespaceAutoProvision", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewProvision(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// provision is an implementation of admission.Interface.
|
||||
// It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one.
|
||||
// It is useful in deployments that do not want to restrict creation of a namespace prior to its usage.
|
||||
type provision struct {
|
||||
client client.Interface
|
||||
store cache.Store
|
||||
}
|
||||
|
||||
func (p *provision) Admit(a admission.Attributes) (err error) {
|
||||
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
return nil
|
||||
}
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: a.GetNamespace(),
|
||||
Namespace: "",
|
||||
},
|
||||
Status: api.NamespaceStatus{},
|
||||
}
|
||||
_, exists, err := p.store.Get(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
_, err = p.client.Namespaces().Create(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewProvision(c client.Interface) admission.Interface {
|
||||
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||
reflector := cache.NewReflector(
|
||||
&cache.ListWatch{
|
||||
Client: c.(*client.Client),
|
||||
FieldSelector: labels.Everything(),
|
||||
Resource: "namespaces",
|
||||
},
|
||||
&api.Namespace{},
|
||||
store,
|
||||
)
|
||||
reflector.Run()
|
||||
return &provision{
|
||||
client: c,
|
||||
store: store,
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
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 autoprovision
|
98
plugin/pkg/admission/namespace/exists/admission.go
Normal file
98
plugin/pkg/admission/namespace/exists/admission.go
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 exists
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("NamespaceExists", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewExists(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// exists is an implementation of admission.Interface.
|
||||
// It rejects all incoming requests in a namespace context if the namespace does not exist.
|
||||
// It is useful in deployments that want to enforce pre-declaration of a Namespace resource.
|
||||
type exists struct {
|
||||
client client.Interface
|
||||
store cache.Store
|
||||
}
|
||||
|
||||
func (e *exists) Admit(a admission.Attributes) (err error) {
|
||||
defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
return nil
|
||||
}
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: a.GetNamespace(),
|
||||
Namespace: "",
|
||||
},
|
||||
Status: api.NamespaceStatus{},
|
||||
}
|
||||
_, exists, err := e.store.Get(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
obj := a.GetObject()
|
||||
name := "Unknown"
|
||||
if obj != nil {
|
||||
name, _ = meta.NewAccessor().Name(obj)
|
||||
}
|
||||
return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
|
||||
}
|
||||
|
||||
func NewExists(c client.Interface) admission.Interface {
|
||||
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||
// TODO: look into a list/watch that can work with client.Interface, maybe pass it a ListFunc and a WatchFunc
|
||||
reflector := cache.NewReflector(
|
||||
&cache.ListWatch{
|
||||
Client: c.(*client.Client),
|
||||
FieldSelector: labels.Everything(),
|
||||
Resource: "namespaces",
|
||||
},
|
||||
&api.Namespace{},
|
||||
store,
|
||||
)
|
||||
reflector.Run()
|
||||
return &exists{
|
||||
client: c,
|
||||
store: store,
|
||||
}
|
||||
}
|
17
plugin/pkg/admission/namespace/exists/admission_test.go
Normal file
17
plugin/pkg/admission/namespace/exists/admission_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
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 exists
|
Loading…
Reference in New Issue
Block a user