diff --git a/api/swagger-spec/v1beta1.json b/api/swagger-spec/v1beta1.json index cef4299e613..08c608ec158 100644 --- a/api/swagger-spec/v1beta1.json +++ b/api/swagger-spec/v1beta1.json @@ -4742,21 +4742,40 @@ } ] }, + { + "path": "/api/v1beta1/pods/{name}/exec", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta1/pods/{name}/log", "description": "API at /api/v1beta1 version v1beta1", "operations": [ { - "type": "v1beta1.PodLogOptions", + "type": "v1beta1.Pod", "method": "GET", - "summary": "read the specified PodLogOptions", - "nickname": "readPodLogOptions", + "summary": "read the specified Pod", + "nickname": "readPod", "parameters": [ { "type": "string", "paramType": "path", "name": "name", - "description": "name of the PodLogOptions", + "description": "name of the Pod", "required": true, "allowMultiple": false }, @@ -4773,7 +4792,7 @@ { "code": 200, "message": "OK", - "responseModel": "v1beta1.PodLogOptions" + "responseModel": "v1beta1.Pod" } ], "produces": [ @@ -4785,6 +4804,193 @@ } ] }, + { + "path": "/api/v1beta1/pods/{name}/portforward", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta1/pods/{name}/proxy", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta1/pods/{name}/proxy/{path:*}", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta1/pods/{name}/status", "description": "API at /api/v1beta1 version v1beta1", @@ -6914,6 +7120,31 @@ "id": "", "properties": null }, + "v1beta1.AWSElasticBlockStoreVolumeSource": { + "id": "v1beta1.AWSElasticBlockStoreVolumeSource", + "required": [ + "volumeID" + ], + "properties": { + "fsType": { + "type": "string", + "description": "file system type to mount, such as ext4, xfs, ntfs" + }, + "partition": { + "type": "integer", + "format": "int32", + "description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted" + }, + "readOnly": { + "type": "boolean", + "description": "read-only if true, read-write otherwise (false or unspecified)" + }, + "volumeID": { + "type": "string", + "description": "unique id of the PD resource in AWS" + } + } + }, "v1beta1.AccessModeType": { "id": "v1beta1.AccessModeType", "properties": {} @@ -8668,6 +8899,19 @@ } } }, + "v1beta1.PersistentVolumeClaimVolumeSource": { + "id": "v1beta1.PersistentVolumeClaimVolumeSource", + "properties": { + "claimName": { + "type": "string", + "description": "the name of the claim in the same namespace to be mounted as a volume" + }, + "readOnly": { + "type": "boolean", + "description": "mount volume as read-only when true; default false" + } + } + }, "v1beta1.PersistentVolumeList": { "id": "v1beta1.PersistentVolumeList", "properties": { @@ -8727,9 +8971,10 @@ "v1beta1.PersistentVolumeSpec": { "id": "v1beta1.PersistentVolumeSpec", "required": [ - "persistentDisk", "hostPath", - "glusterfs" + "glusterfs", + "persistentDisk", + "awsElasticBlockStore" ], "properties": { "accessModes": { @@ -8739,6 +8984,10 @@ }, "description": "all ways the volume can be mounted" }, + "awsElasticBlockStore": { + "$ref": "v1beta1.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource provisioned by an admin" + }, "capacity": { "type": "any", "description": "a description of the persistent volume's resources and capacity" @@ -8911,63 +9160,6 @@ } } }, - "v1beta1.PodLogOptions": { - "id": "v1beta1.PodLogOptions", - "properties": { - "annotations": { - "type": "any", - "description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about the object" - }, - "apiVersion": { - "type": "string", - "description": "version of the schema the object should have" - }, - "container": { - "type": "string", - "description": "the container for which to stream logs; defaults to only container if there is one container in the pod" - }, - "creationTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists" - }, - "deletionTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested" - }, - "follow": { - "type": "boolean", - "description": "follow the log stream of the pod; defaults to false" - }, - "generateName": { - "type": "string", - "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified" - }, - "id": { - "type": "string", - "description": "name of the object; must be a DNS_SUBDOMAIN and unique among all objects of the same kind within the same namespace; used in resource URLs; cannot be updated" - }, - "kind": { - "type": "string", - "description": "kind of object, in CamelCase; cannot be updated" - }, - "namespace": { - "type": "string", - "description": "namespace to which the object belongs; must be a DNS_SUBDOMAIN; 'default' by default; cannot be updated" - }, - "resourceVersion": { - "$ref": "uint64", - "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" - }, - "selfLink": { - "type": "string", - "description": "URL for the object; populated by the system, read-only" - }, - "uid": { - "type": "string", - "description": "unique UUID across space and time; populated by the system, read-only; cannot be updated" - } - } - }, "v1beta1.PodState": { "id": "v1beta1.PodState", "properties": { @@ -9812,7 +10004,7 @@ }, "source": { "$ref": "v1beta1.VolumeSource", - "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir" + "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, AWSElasticBlockStore, or GitRepo; default is EmptyDir" } } }, @@ -9851,6 +10043,7 @@ "hostDir", "emptyDir", "persistentDisk", + "awsElasticBlockStore", "gitRepo", "secret", "nfs", @@ -9858,6 +10051,10 @@ "glusterfs" ], "properties": { + "awsElasticBlockStore": { + "$ref": "v1beta1.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource attached to the host machine on demand" + }, "emptyDir": { "$ref": "v1beta1.EmptyDirVolumeSource", "description": "temporary directory that shares a pod's lifetime" @@ -9886,6 +10083,10 @@ "$ref": "v1beta1.GCEPersistentDiskVolumeSource", "description": "GCE disk resource attached to the host machine on demand" }, + "persistentVolumeClaim": { + "$ref": "v1beta1.PersistentVolumeClaimVolumeSource", + "description": "a reference to a PersistentVolumeClaim in the same namespace" + }, "secret": { "$ref": "v1beta1.SecretVolumeSource", "description": "secret to populate volume with" diff --git a/api/swagger-spec/v1beta2.json b/api/swagger-spec/v1beta2.json index 56625e84621..58a626423da 100644 --- a/api/swagger-spec/v1beta2.json +++ b/api/swagger-spec/v1beta2.json @@ -4742,21 +4742,40 @@ } ] }, + { + "path": "/api/v1beta2/pods/{name}/exec", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta2/pods/{name}/log", "description": "API at /api/v1beta2 version v1beta2", "operations": [ { - "type": "v1beta2.PodLogOptions", + "type": "v1beta2.Pod", "method": "GET", - "summary": "read the specified PodLogOptions", - "nickname": "readPodLogOptions", + "summary": "read the specified Pod", + "nickname": "readPod", "parameters": [ { "type": "string", "paramType": "path", "name": "name", - "description": "name of the PodLogOptions", + "description": "name of the Pod", "required": true, "allowMultiple": false }, @@ -4773,7 +4792,7 @@ { "code": 200, "message": "OK", - "responseModel": "v1beta2.PodLogOptions" + "responseModel": "v1beta2.Pod" } ], "produces": [ @@ -4785,6 +4804,193 @@ } ] }, + { + "path": "/api/v1beta2/pods/{name}/portforward", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta2/pods/{name}/proxy", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta2/pods/{name}/proxy/{path:*}", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta2/pods/{name}/status", "description": "API at /api/v1beta2 version v1beta2", @@ -6914,6 +7120,31 @@ "id": "", "properties": null }, + "v1beta2.AWSElasticBlockStoreVolumeSource": { + "id": "v1beta2.AWSElasticBlockStoreVolumeSource", + "required": [ + "volumeID" + ], + "properties": { + "fsType": { + "type": "string", + "description": "file system type to mount, such as ext4, xfs, ntfs" + }, + "partition": { + "type": "integer", + "format": "int32", + "description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted" + }, + "readOnly": { + "type": "boolean", + "description": "read-only if true, read-write otherwise (false or unspecified)" + }, + "volumeID": { + "type": "string", + "description": "unique id of the PD resource in AWS" + } + } + }, "v1beta2.AccessModeType": { "id": "v1beta2.AccessModeType", "properties": {} @@ -8657,6 +8888,19 @@ } } }, + "v1beta2.PersistentVolumeClaimVolumeSource": { + "id": "v1beta2.PersistentVolumeClaimVolumeSource", + "properties": { + "claimName": { + "type": "string", + "description": "the name of the claim in the same namespace to be mounted as a volume" + }, + "readOnly": { + "type": "boolean", + "description": "mount volume as read-only when true; default false" + } + } + }, "v1beta2.PersistentVolumeList": { "id": "v1beta2.PersistentVolumeList", "properties": { @@ -8717,6 +8961,7 @@ "id": "v1beta2.PersistentVolumeSpec", "required": [ "persistentDisk", + "awsElasticBlockStore", "hostPath", "glusterfs" ], @@ -8728,6 +8973,10 @@ }, "description": "all ways the volume can be mounted" }, + "awsElasticBlockStore": { + "$ref": "v1beta2.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource provisioned by an admin" + }, "capacity": { "type": "any", "description": "a description of the persistent volume's resources and capacity" @@ -8900,63 +9149,6 @@ } } }, - "v1beta2.PodLogOptions": { - "id": "v1beta2.PodLogOptions", - "properties": { - "annotations": { - "type": "any", - "description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about the object" - }, - "apiVersion": { - "type": "string", - "description": "version of the schema the object should have" - }, - "container": { - "type": "string", - "description": "the container for which to stream logs; defaults to only container if there is one container in the pod" - }, - "creationTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists" - }, - "deletionTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested" - }, - "follow": { - "type": "boolean", - "description": "follow the log stream of the pod; defaults to false" - }, - "generateName": { - "type": "string", - "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified" - }, - "id": { - "type": "string", - "description": "name of the object; must be a DNS_SUBDOMAIN and unique among all objects of the same kind within the same namespace; used in resource URLs; cannot be updated" - }, - "kind": { - "type": "string", - "description": "kind of object, in CamelCase; cannot be updated" - }, - "namespace": { - "type": "string", - "description": "namespace to which the object belongs; must be a DNS_SUBDOMAIN; 'default' by default; cannot be updated" - }, - "resourceVersion": { - "$ref": "uint64", - "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" - }, - "selfLink": { - "type": "string", - "description": "URL for the object; populated by the system, read-only" - }, - "uid": { - "type": "string", - "description": "unique UUID across space and time; populated by the system, read-only" - } - } - }, "v1beta2.PodState": { "id": "v1beta2.PodState", "properties": { @@ -9801,7 +9993,7 @@ }, "source": { "$ref": "v1beta2.VolumeSource", - "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir" + "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, AWSElasticBlockStore, or GitRepo; default is EmptyDir" } } }, @@ -9832,6 +10024,7 @@ "hostDir", "emptyDir", "persistentDisk", + "awsElasticBlockStore", "gitRepo", "secret", "nfs", @@ -9839,6 +10032,10 @@ "glusterfs" ], "properties": { + "awsElasticBlockStore": { + "$ref": "v1beta2.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource attached to the host machine on demand" + }, "emptyDir": { "$ref": "v1beta2.EmptyDirVolumeSource", "description": "temporary directory that shares a pod's lifetime" @@ -9867,6 +10064,10 @@ "$ref": "v1beta2.GCEPersistentDiskVolumeSource", "description": "GCE disk resource attached to the host machine on demand" }, + "persistentVolumeClaim": { + "$ref": "v1beta2.PersistentVolumeClaimVolumeSource", + "description": "a reference to a PersistentVolumeClaim in the same namespace" + }, "secret": { "$ref": "v1beta2.SecretVolumeSource", "description": "secret to populate volume" diff --git a/api/swagger-spec/v1beta3.json b/api/swagger-spec/v1beta3.json index b989fcf0a0c..332703f42cf 100644 --- a/api/swagger-spec/v1beta3.json +++ b/api/swagger-spec/v1beta3.json @@ -4722,21 +4722,40 @@ } ] }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/exec", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/log", "description": "API at /api/v1beta3 version v1beta3", "operations": [ { - "type": "v1beta3.PodLogOptions", + "type": "v1beta3.Pod", "method": "GET", - "summary": "read the specified PodLogOptions", - "nickname": "readPodLogOptions", + "summary": "read the specified Pod", + "nickname": "readPod", "parameters": [ { "type": "string", "paramType": "path", "name": "name", - "description": "name of the PodLogOptions", + "description": "name of the Pod", "required": true, "allowMultiple": false }, @@ -4753,7 +4772,7 @@ { "code": 200, "message": "OK", - "responseModel": "v1beta3.PodLogOptions" + "responseModel": "v1beta3.Pod" } ], "produces": [ @@ -4765,6 +4784,193 @@ } ] }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/portforward", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/proxy", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/proxy/{path:*}", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/status", "description": "API at /api/v1beta3 version v1beta3", @@ -4816,6 +5022,550 @@ } ] }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "v1beta3.PodTemplateList", + "method": "GET", + "summary": "list or watch objects of kind PodTemplate", + "nickname": "listPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplateList" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta3.PodTemplate", + "method": "POST", + "summary": "create a PodTemplate", + "nickname": "createPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "v1beta3.PodTemplate", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplate" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/watch/namespaces/{namespaces}/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "json.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of PodTemplate", + "nickname": "watchPodTemplatelist", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "json.WatchEvent" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/podtemplates/{name}", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "v1beta3.PodTemplate", + "method": "GET", + "summary": "read the specified PodTemplate", + "nickname": "readPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplate" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta3.PodTemplate", + "method": "PUT", + "summary": "replace the specified PodTemplate", + "nickname": "replacePodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "v1beta3.PodTemplate", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplate" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta3.PodTemplate", + "method": "PATCH", + "summary": "partially update the specified PodTemplate", + "nickname": "patchPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "string" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1beta3.Status", + "method": "DELETE", + "summary": "delete a PodTemplate", + "nickname": "deletePodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "v1beta3.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.Status" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/watch/namespaces/{namespaces}/podtemplates/{name}", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "json.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind PodTemplate", + "nickname": "watchPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "json.WatchEvent" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "v1beta3.PodTemplateList", + "method": "GET", + "summary": "list or watch objects of kind PodTemplate", + "nickname": "listPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplateList" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/watch/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "json.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of PodTemplate", + "nickname": "watchPodTemplatelist", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "json.WatchEvent" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta3/namespaces/{namespaces}/replicationcontrollers", "description": "API at /api/v1beta3 version v1beta3", @@ -7362,6 +8112,31 @@ } } }, + "v1beta3.AWSElasticBlockStoreVolumeSource": { + "id": "v1beta3.AWSElasticBlockStoreVolumeSource", + "required": [ + "volumeID" + ], + "properties": { + "fsType": { + "type": "string", + "description": "file system type to mount, such as ext4, xfs, ntfs" + }, + "partition": { + "type": "integer", + "format": "int32", + "description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted" + }, + "readOnly": { + "type": "boolean", + "description": "read-only if true, read-write otherwise (false or unspecified)" + }, + "volumeID": { + "type": "string", + "description": "unique id of the PD resource in AWS" + } + } + }, "v1beta3.AccessModeType": { "id": "v1beta3.AccessModeType", "properties": {} @@ -8885,6 +9660,19 @@ } } }, + "v1beta3.PersistentVolumeClaimVolumeSource": { + "id": "v1beta3.PersistentVolumeClaimVolumeSource", + "properties": { + "claimName": { + "type": "string", + "description": "the name of the claim in the same namespace to be mounted as a volume" + }, + "readOnly": { + "type": "boolean", + "description": "mount volume as read-only when true; default false" + } + } + }, "v1beta3.PersistentVolumeList": { "id": "v1beta3.PersistentVolumeList", "properties": { @@ -8917,6 +9705,7 @@ "id": "v1beta3.PersistentVolumeSpec", "required": [ "gcePersistentDisk", + "awsElasticBlockStore", "hostPath", "glusterfs" ], @@ -8928,6 +9717,10 @@ }, "description": "all ways the volume can be mounted" }, + "awsElasticBlockStore": { + "$ref": "v1beta3.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource provisioned by an admin" + }, "capacity": { "type": "any", "description": "a description of the persistent volume's resources and capacity" @@ -9068,27 +9861,6 @@ } } }, - "v1beta3.PodLogOptions": { - "id": "v1beta3.PodLogOptions", - "properties": { - "apiVersion": { - "type": "string", - "description": "version of the schema the object should have" - }, - "container": { - "type": "string", - "description": "the container for which to stream logs; defaults to only container if there is one container in the pod" - }, - "follow": { - "type": "boolean", - "description": "follow the log stream of the pod; defaults to false" - }, - "kind": { - "type": "string", - "description": "kind of object, in CamelCase; cannot be updated" - } - } - }, "v1beta3.PodSpec": { "id": "v1beta3.PodSpec", "required": [ @@ -9167,6 +9939,94 @@ } } }, + "v1beta3.PodTemplate": { + "id": "v1beta3.PodTemplate", + "properties": { + "annotations": { + "type": "any", + "description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about objects" + }, + "apiVersion": { + "type": "string", + "description": "version of the schema the object should have" + }, + "creationTimestamp": { + "type": "string", + "description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists" + }, + "deletionTimestamp": { + "type": "string", + "description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested" + }, + "generateName": { + "type": "string", + "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified" + }, + "kind": { + "type": "string", + "description": "kind of object, in CamelCase; cannot be updated" + }, + "labels": { + "type": "any", + "description": "map of string keys and values that can be used to organize and categorize objects; may match selectors of replication controllers and services" + }, + "name": { + "type": "string", + "description": "string that identifies an object. Must be unique within a namespace; cannot be updated" + }, + "namespace": { + "type": "string", + "description": "namespace of the object; cannot be updated" + }, + "resourceVersion": { + "type": "string", + "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" + }, + "selfLink": { + "type": "string", + "description": "URL for the object; populated by the system, read-only" + }, + "template": { + "$ref": "v1beta3.PodTemplateSpec", + "description": "the template of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status" + }, + "uid": { + "type": "string", + "description": "unique UUID across space and time; populated by the system; read-only" + } + } + }, + "v1beta3.PodTemplateList": { + "id": "v1beta3.PodTemplateList", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "type": "string", + "description": "version of the schema the object should have" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1beta3.PodTemplate" + }, + "description": "list of pod templates" + }, + "kind": { + "type": "string", + "description": "kind of object, in CamelCase; cannot be updated" + }, + "resourceVersion": { + "type": "string", + "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" + }, + "selfLink": { + "type": "string", + "description": "URL for the object; populated by the system, read-only" + } + } + }, "v1beta3.PodTemplateSpec": { "id": "v1beta3.PodTemplateSpec", "properties": { @@ -9348,7 +10208,7 @@ }, "selector": { "type": "any", - "description": "label keys and values that must match in order to be controlled by this replication controller" + "description": "label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template" }, "template": { "$ref": "v1beta3.PodTemplateSpec", @@ -9864,16 +10724,21 @@ "id": "v1beta3.Volume", "required": [ "name", - "gcePersistentDisk", - "gitRepo", + "emptyDir", "secret", - "nfs", - "iscsi", "glusterfs", "hostPath", - "emptyDir" + "awsElasticBlockStore", + "gitRepo", + "nfs", + "iscsi", + "gcePersistentDisk" ], "properties": { + "awsElasticBlockStore": { + "$ref": "v1beta3.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource attached to the host machine on demand" + }, "emptyDir": { "$ref": "v1beta3.EmptyDirVolumeSource", "description": "temporary directory that shares a pod's lifetime" @@ -9906,6 +10771,10 @@ "$ref": "v1beta3.NFSVolumeSource", "description": "NFS volume that will be mounted in the host machine" }, + "persistentVolumeClaim": { + "$ref": "v1beta3.PersistentVolumeClaimVolumeSource", + "description": "a reference to a PersistentVolumeClaim in the same namespace" + }, "secret": { "$ref": "v1beta3.SecretVolumeSource", "description": "secret to populate volume" diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 5bf6c2c46e6..cc061afcd4a 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -251,6 +251,7 @@ _kubectl_get() must_have_one_noun+=("persistentvolume") must_have_one_noun+=("persistentvolumeclaim") must_have_one_noun+=("pod") + must_have_one_noun+=("podtemplate") must_have_one_noun+=("replicationcontroller") must_have_one_noun+=("resourcequota") must_have_one_noun+=("secret") diff --git a/examples/examples_test.go b/examples/examples_test.go index d21c1eea150..dbf19b025a8 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -34,7 +34,6 @@ import ( ) func validateObject(obj runtime.Object) (errors []error) { - ctx := api.NewDefaultContext() switch t := obj.(type) { case *api.ReplicationController: if t.Namespace == "" { @@ -49,7 +48,6 @@ func validateObject(obj runtime.Object) (errors []error) { if t.Namespace == "" { t.Namespace = api.NamespaceDefault } - api.ValidNamespace(ctx, &t.ObjectMeta) errors = validation.ValidateService(t) case *api.ServiceList: for i := range t.Items { @@ -59,7 +57,6 @@ func validateObject(obj runtime.Object) (errors []error) { if t.Namespace == "" { t.Namespace = api.NamespaceDefault } - api.ValidNamespace(ctx, &t.ObjectMeta) errors = validation.ValidatePod(t) case *api.PodList: for i := range t.Items { @@ -68,8 +65,15 @@ func validateObject(obj runtime.Object) (errors []error) { case *api.PersistentVolume: errors = validation.ValidatePersistentVolume(t) case *api.PersistentVolumeClaim: - api.ValidNamespace(ctx, &t.ObjectMeta) + if t.Namespace == "" { + t.Namespace = api.NamespaceDefault + } errors = validation.ValidatePersistentVolumeClaim(t) + case *api.PodTemplate: + if t.Namespace == "" { + t.Namespace = api.NamespaceDefault + } + errors = validation.ValidatePodTemplate(t) default: return []error{fmt.Errorf("no validation defined for %#v", obj)} } @@ -156,6 +160,7 @@ func TestExampleObjectSchemas(t *testing.T) { "pod-with-http-healthcheck": &api.Pod{}, "service": &api.Service{}, "replication-controller": &api.ReplicationController{}, + "podtemplate": &api.PodTemplate{}, }, "../examples/update-demo": { "kitten-rc": &api.ReplicationController{}, diff --git a/examples/walkthrough/podtemplate.json b/examples/walkthrough/podtemplate.json new file mode 100644 index 00000000000..5732a113584 --- /dev/null +++ b/examples/walkthrough/podtemplate.json @@ -0,0 +1,22 @@ + { + "apiVersion": "v1beta3", + "kind": "PodTemplate", + "metadata": { + "name": "nginx" + }, + "template": { + "metadata": { + "labels": { + "name": "nginx" + }, + "generateName": "nginx-" + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "dockerfile/nginx", + "ports": [{"containerPort": 80}] + }] + } + } + } diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index b86756a9a02..3e534329de2 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -358,6 +358,34 @@ for version in "${kube_api_versions[@]}"; do kube::test::get_object_assert 'pods --namespace=other' "{{range.items}}{{$id_field}}:{{end}}" '' + ################# + # Pod templates # + ################# + + # Note: pod templates exist only in v1beta3 and above, so output will always be in that form + + ### Create PODTEMPLATE + # Pre-condition: no PODTEMPLATE + kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" '' + # Command + kubectl create -f examples/walkthrough/podtemplate.json "${kube_flags[@]}" + # Post-condition: nginx PODTEMPLATE is available + kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" 'nginx:' + + ### Printing pod templates works + kubectl get podtemplates "${kube_flags[@]}" + ### Display of an object which doesn't existing in v1beta1 and v1beta2 works + [[ "$(kubectl get podtemplates -o yaml "${kube_flags[@]}" | grep nginx)" ]] + + ### Delete nginx pod template by name + # Pre-condition: nginx pod template is available + kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" 'nginx:' + # Command + kubectl delete podtemplate nginx "${kube_flags[@]}" + # Post-condition: No templates exist + kube::test::get_object_assert podtemplate "{{range.items}}{{.metadata.name}}:{{end}}" '' + + ############ # Services # ############ diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index 724f5ee59d9..6b1591b5117 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -80,6 +80,10 @@ func TestRESTMapper(t *testing.T) { t.Errorf("unexpected version mapping: %s %s %v", v, k, err) } + if m, err := RESTMapper.RESTMapping("PodTemplate", ""); err != nil || m.APIVersion != "v1beta3" || m.Resource != "podtemplates" { + t.Errorf("unexpected version mapping: %#v %v", m, err) + } + for _, version := range Versions { mapping, err := RESTMapper.RESTMapping("ReplicationController", version) if err != nil { diff --git a/pkg/api/register.go b/pkg/api/register.go index 83fc40e23af..823f8bb7685 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -28,6 +28,8 @@ func init() { &Pod{}, &PodList{}, &PodStatusResult{}, + &PodTemplate{}, + &PodTemplateList{}, &ReplicationControllerList{}, &ReplicationController{}, &ServiceList{}, @@ -71,6 +73,8 @@ func init() { func (*Pod) IsAnAPIObject() {} func (*PodList) IsAnAPIObject() {} func (*PodStatusResult) IsAnAPIObject() {} +func (*PodTemplate) IsAnAPIObject() {} +func (*PodTemplateList) IsAnAPIObject() {} func (*ReplicationController) IsAnAPIObject() {} func (*ReplicationControllerList) IsAnAPIObject() {} func (*Service) IsAnAPIObject() {} diff --git a/pkg/api/rest/resttest/resttest.go b/pkg/api/rest/resttest/resttest.go index 29585d7dc7e..2732a0e563f 100644 --- a/pkg/api/rest/resttest/resttest.go +++ b/pkg/api/rest/resttest/resttest.go @@ -179,7 +179,7 @@ func (t *Tester) TestCreateRejectsMismatchedNamespace(valid runtime.Object) { if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") { - t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error()) + t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err) } } @@ -195,7 +195,30 @@ func (t *Tester) TestCreateRejectsNamespace(valid runtime.Object) { if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") { - t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error()) + t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err) + } +} + +func (t *Tester) TestUpdate(valid runtime.Object, existing, older runtime.Object) { + t.TestUpdateFailsOnNotFound(copyOrDie(valid)) + t.TestUpdateFailsOnVersion(copyOrDie(older)) +} + +func (t *Tester) TestUpdateFailsOnNotFound(valid runtime.Object) { + _, _, err := t.storage.(rest.Updater).Update(api.NewDefaultContext(), valid) + if err == nil { + t.Errorf("Expected an error, but we didn't get one") + } else if !errors.IsNotFound(err) { + t.Errorf("Expected NotFound error, got '%v'", err) + } +} + +func (t *Tester) TestUpdateFailsOnVersion(older runtime.Object) { + _, _, err := t.storage.(rest.Updater).Update(api.NewDefaultContext(), older) + if err == nil { + t.Errorf("Expected an error, but we didn't get one") + } else if !errors.IsConflict(err) { + t.Errorf("Expected Conflict error, got '%v'", err) } } diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index ca79e6f9408..e9f65233307 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -85,13 +85,20 @@ func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { } // roundTripSame verifies the same source object is tested in all API versions. -func roundTripSame(t *testing.T, item runtime.Object) { +func roundTripSame(t *testing.T, item runtime.Object, except ...string) { + set := util.NewStringSet(except...) seed := rand.Int63() fuzzInternalObject(t, "", item, seed) - roundTrip(t, v1beta1.Codec, item) - roundTrip(t, v1beta2.Codec, item) - fuzzInternalObject(t, "v1beta3", item, seed) - roundTrip(t, v1beta3.Codec, item) + if !set.Has("v1beta1") { + roundTrip(t, v1beta1.Codec, item) + } + if !set.Has("v1beta2") { + roundTrip(t, v1beta2.Codec, item) + } + if !set.Has("v1beta3") { + fuzzInternalObject(t, "v1beta3", item, seed) + roundTrip(t, v1beta3.Codec, item) + } } func roundTripAll(t *testing.T, item runtime.Object) { @@ -130,6 +137,10 @@ func TestList(t *testing.T) { var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList") var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions", "PodExecOptions") +var nonRoundTrippableTypesByVersion = map[string][]string{ + "PodTemplate": {"v1beta1", "v1beta2"}, + "PodTemplateList": {"v1beta1", "v1beta2"}, +} func TestRoundTripTypes(t *testing.T) { // api.Scheme.Log(t) @@ -148,7 +159,7 @@ func TestRoundTripTypes(t *testing.T) { if _, err := meta.TypeAccessor(item); err != nil { t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err) } - roundTripSame(t, item) + roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...) if !nonInternalRoundTrippableTypes.Has(kind) { roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63())) } diff --git a/pkg/api/types.go b/pkg/api/types.go index 6dc455f7bb3..1aeb63f9f43 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -848,8 +848,8 @@ type PodTemplate struct { TypeMeta `json:",inline"` ObjectMeta `json:"metadata,omitempty"` - // Spec defines the pods that will be created from this template - Spec PodTemplateSpec `json:"spec,omitempty"` + // Template defines the pods that will be created from this pod template + Template PodTemplateSpec `json:"template,omitempty"` } // PodTemplateList is a list of PodTemplates. diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index f9ecda57586..065fe3bb37d 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -854,8 +854,8 @@ type PodTemplate struct { TypeMeta `json:",inline"` ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#metadata"` - // Spec defines the behavior of a pod. - Spec PodTemplateSpec `json:"spec,omitempty" description:"specification of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"` + // Template defines the pods that will be created from this pod template + Template PodTemplateSpec `json:"template,omitempty" description:"the template of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"` } // PodTemplateList is a list of PodTemplates. diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index a1445e71292..d2535dc1676 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -888,6 +888,24 @@ func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { return allErrs } +// ValidatePodTemplate tests if required fields in the pod template are set. +func ValidatePodTemplate(pod *api.PodTemplate) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName).Prefix("metadata")...) + allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, 0).Prefix("template")...) + return allErrs +} + +// ValidatePodTemplateUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields +// that cannot be changed. +func ValidatePodTemplateUpdate(newPod, oldPod *api.PodTemplate) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + + allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta).Prefix("metadata")...) + allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, 0).Prefix("template")...) + return allErrs +} + var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone)) // ValidateService tests if required fields in the service are set. diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 3b740095670..ca10ea7b83a 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1676,7 +1676,7 @@ func TestValidateService(t *testing.T) { func TestValidateReplicationControllerUpdate(t *testing.T) { validSelector := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1688,7 +1688,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { }, } readWriteVolumePodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1702,7 +1702,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { } invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, @@ -1722,7 +1722,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1730,7 +1730,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 3, Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, }, @@ -1739,7 +1739,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1747,7 +1747,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 1, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, }, @@ -1765,7 +1765,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1773,7 +1773,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, }, @@ -1782,7 +1782,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1790,7 +1790,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: invalidSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, }, @@ -1799,7 +1799,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1807,7 +1807,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: validSelector, - Template: &invalidPodTemplate.Spec, + Template: &invalidPodTemplate.Template, }, }, }, @@ -1816,7 +1816,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1824,7 +1824,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: -1, Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, }, @@ -1840,7 +1840,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { func TestValidateReplicationController(t *testing.T) { validSelector := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1852,7 +1852,7 @@ func TestValidateReplicationController(t *testing.T) { }, } readWriteVolumePodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1866,7 +1866,7 @@ func TestValidateReplicationController(t *testing.T) { } invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, @@ -1881,14 +1881,14 @@ func TestValidateReplicationController(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, { ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, { @@ -1896,7 +1896,7 @@ func TestValidateReplicationController(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 1, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, } @@ -1911,27 +1911,27 @@ func TestValidateReplicationController(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "missing-namespace": { ObjectMeta: api.ObjectMeta{Name: "abc-123"}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "empty selector": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "selector_doesnt_match": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: map[string]string{"foo": "bar"}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "invalid manifest": { @@ -1945,7 +1945,7 @@ func TestValidateReplicationController(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, "negative_replicas": { @@ -1965,7 +1965,7 @@ func TestValidateReplicationController(t *testing.T) { }, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "invalid_label 2": { @@ -1977,7 +1977,20 @@ func TestValidateReplicationController(t *testing.T) { }, }, Spec: api.ReplicationControllerSpec{ - Template: &invalidPodTemplate.Spec, + Template: &invalidPodTemplate.Template, + }, + }, + "invalid_annotation": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + Annotations: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: api.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, }, }, "invalid restart policy 1": { diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 31aa674efd3..d760ac86250 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -245,6 +245,7 @@ func (h *HumanReadablePrinter) HandledResources() []string { } var podColumns = []string{"POD", "IP", "CONTAINER(S)", "IMAGE(S)", "HOST", "LABELS", "STATUS", "CREATED", "MESSAGE"} +var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"} var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS"} var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT(S)"} var endpointColumns = []string{"NAME", "ENDPOINTS"} @@ -263,6 +264,8 @@ var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"} func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(podColumns, printPod) h.Handler(podColumns, printPodList) + h.Handler(podTemplateColumns, printPodTemplate) + h.Handler(podTemplateColumns, printPodTemplateList) h.Handler(replicationControllerColumns, printReplicationController) h.Handler(replicationControllerColumns, printReplicationControllerList) h.Handler(serviceColumns, printService) @@ -383,11 +386,6 @@ func interpretContainerStatus(status *api.ContainerStatus) (string, string, stri } func printPod(pod *api.Pod, w io.Writer) error { - // TODO: remove me when pods are converted - spec := &api.PodSpec{} - if err := api.Scheme.Convert(&pod.Spec, spec); err != nil { - glog.Errorf("Unable to convert pod manifest: %v", err) - } _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", pod.Name, pod.Status.PodIP, @@ -447,6 +445,40 @@ func printPodList(podList *api.PodList, w io.Writer) error { return nil } +func printPodTemplate(pod *api.PodTemplate, w io.Writer) error { + containers := pod.Template.Spec.Containers + var firstContainer api.Container + if len(containers) > 0 { + firstContainer, containers = containers[0], containers[1:] + } + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", + pod.Name, + firstContainer.Name, + firstContainer.Image, + formatLabels(pod.Template.Labels), + ) + if err != nil { + return err + } + // Lay out all the other containers on separate lines. + for _, container := range containers { + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "", container.Name, container.Image, "") + if err != nil { + return err + } + } + return nil +} + +func printPodTemplateList(podList *api.PodTemplateList, w io.Writer) error { + for _, pod := range podList.Items { + if err := printPodTemplate(&pod, w); err != nil { + return err + } + } + return nil +} + func printReplicationController(controller *api.ReplicationController, w io.Writer) error { containers := controller.Spec.Template.Spec.Containers var firstContainer api.Container diff --git a/pkg/master/master.go b/pkg/master/master.go index 0b4c582f978..b8440e52d03 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -58,6 +58,7 @@ import ( pvcetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolumeclaim/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod" podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd" + podtemplateetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate/etcd" resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service" @@ -360,9 +361,18 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) // init initializes master. func (m *Master) init(c *Config) { + // TODO: make initialization of the helper part of the Master, and allow some storage + // objects to have a newer storage version than the user's default. + newerHelper, err := NewEtcdHelper(c.EtcdHelper.Client, "v1beta3") + if err != nil { + glog.Fatalf("Unable to setup storage for v1beta3: %v", err) + } + podStorage := podetcd.NewStorage(c.EtcdHelper, c.KubeletClient) podRegistry := pod.NewRegistry(podStorage.Pod) + podTemplateStorage := podtemplateetcd.NewREST(newerHelper) + eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())) limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper) @@ -397,6 +407,8 @@ func (m *Master) init(c *Config) { "pods/binding": podStorage.Binding, "bindings": podStorage.Binding, + "podTemplates": podTemplateStorage, + "replicationControllers": controllerStorage, "services": service.NewStorage(m.serviceRegistry, m.nodeRegistry, m.endpointRegistry, m.portalNet, c.ClusterName), "endpoints": endpointsStorage, @@ -606,6 +618,9 @@ func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion { func (m *Master) api_v1beta1() *apiserver.APIGroupVersion { storage := make(map[string]rest.Storage) for k, v := range m.storage { + if k == "podTemplates" { + continue + } storage[k] = v } version := m.defaultAPIGroupVersion() @@ -619,6 +634,9 @@ func (m *Master) api_v1beta1() *apiserver.APIGroupVersion { func (m *Master) api_v1beta2() *apiserver.APIGroupVersion { storage := make(map[string]rest.Storage) for k, v := range m.storage { + if k == "podTemplates" { + continue + } storage[k] = v } version := m.defaultAPIGroupVersion() diff --git a/pkg/registry/controller/etcd/etcd_test.go b/pkg/registry/controller/etcd/etcd_test.go index f1f3b48bd30..c6c0d630294 100644 --- a/pkg/registry/controller/etcd/etcd_test.go +++ b/pkg/registry/controller/etcd/etcd_test.go @@ -60,7 +60,7 @@ func createController(storage *REST, rc api.ReplicationController, t *testing.T) } var validPodTemplate = api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, @@ -79,8 +79,8 @@ var validPodTemplate = api.PodTemplate{ } var validControllerSpec = api.ReplicationControllerSpec{ - Selector: validPodTemplate.Spec.Labels, - Template: &validPodTemplate.Spec, + Selector: validPodTemplate.Template.Labels, + Template: &validPodTemplate.Template, } var validController = api.ReplicationController{ @@ -161,7 +161,7 @@ func TestCreateControllerWithGeneratedName(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: map[string]string{"a": "b"}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, } @@ -663,7 +663,7 @@ func TestCreate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: map[string]string{"a": "b"}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, // invalid @@ -671,7 +671,7 @@ func TestCreate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: map[string]string{}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, ) diff --git a/pkg/registry/pod/rest.go b/pkg/registry/pod/rest.go index 33738073cd8..1cab2036331 100644 --- a/pkg/registry/pod/rest.go +++ b/pkg/registry/pod/rest.go @@ -35,7 +35,6 @@ import ( ) // podStrategy implements behavior for Pods -// TODO: move to a pod specific package. type podStrategy struct { runtime.ObjectTyper api.NameGenerator diff --git a/pkg/registry/podtemplate/doc.go b/pkg/registry/podtemplate/doc.go new file mode 100644 index 00000000000..3be9642c4d0 --- /dev/null +++ b/pkg/registry/podtemplate/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package podtemplate provides RESTStorage implementations for storing PodTemplate API objects. +package podtemplate diff --git a/pkg/registry/podtemplate/etcd/etcd.go b/pkg/registry/podtemplate/etcd/etcd.go new file mode 100644 index 00000000000..421ddb8cbb9 --- /dev/null +++ b/pkg/registry/podtemplate/etcd/etcd.go @@ -0,0 +1,63 @@ +/* +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 etcd + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" + etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" +) + +// rest implements a RESTStorage for pod templates against etcd +type REST struct { + etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against pod templates. +func NewREST(h tools.EtcdHelper) *REST { + prefix := "/registry/podtemplates" + store := etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.PodTemplate{} }, + NewListFunc: func() runtime.Object { return &api.PodTemplateList{} }, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.PodTemplate).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return podtemplate.MatchPodTemplate(label, field) + }, + EndpointName: "podtemplates", + + CreateStrategy: podtemplate.Strategy, + UpdateStrategy: podtemplate.Strategy, + ReturnDeletedObject: true, + + Helper: h, + } + + return &REST{store} +} diff --git a/pkg/registry/podtemplate/etcd/etcd_test.go b/pkg/registry/podtemplate/etcd/etcd_test.go new file mode 100644 index 00000000000..9400032331c --- /dev/null +++ b/pkg/registry/podtemplate/etcd/etcd_test.go @@ -0,0 +1,99 @@ +/* +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 etcd + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" + "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" +) + +func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) { + fakeEtcdClient := tools.NewFakeEtcdClient(t) + fakeEtcdClient.TestIndex = true + helper := tools.NewEtcdHelper(fakeEtcdClient, v1beta3.Codec) + return fakeEtcdClient, helper +} + +func validNewPodTemplate(name string) *api.PodTemplate { + return &api.PodTemplate{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"test": "foo"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{ + { + Name: "foo", + Image: "test", + ImagePullPolicy: api.PullAlways, + + TerminationMessagePath: api.TerminationMessagePathDefault, + }, + }, + }, + }, + } +} + +func TestCreate(t *testing.T) { + fakeEtcdClient, helper := newHelper(t) + storage := NewREST(helper) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + pod := validNewPodTemplate("foo") + pod.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + pod, + // invalid + &api.PodTemplate{ + Template: api.PodTemplateSpec{}, + }, + ) +} + +func TestUpdate(t *testing.T) { + fakeEtcdClient, helper := newHelper(t) + storage := NewREST(helper) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + + fakeEtcdClient.ExpectNotFoundGet("/registry/podtemplates/default/foo") + fakeEtcdClient.ChangeIndex = 2 + pod := validNewPodTemplate("foo") + existing := validNewPodTemplate("exists") + obj, err := storage.Create(api.NewDefaultContext(), existing) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + older := obj.(*api.PodTemplate) + older.ResourceVersion = "1" + + test.TestUpdate( + pod, + existing, + older, + ) +} diff --git a/pkg/registry/podtemplate/rest.go b/pkg/registry/podtemplate/rest.go new file mode 100644 index 00000000000..9204c098e75 --- /dev/null +++ b/pkg/registry/podtemplate/rest.go @@ -0,0 +1,81 @@ +/* +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 podtemplate + +import ( + "fmt" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + errs "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" +) + +// podTemplateStrategy implements behavior for PodTemplates +type podTemplateStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating PodTemplate +// objects via the REST API. +var Strategy = podTemplateStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for pod templates. +func (podTemplateStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (podTemplateStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*api.PodTemplate) +} + +// Validate validates a new pod template. +func (podTemplateStrategy) Validate(ctx api.Context, obj runtime.Object) errs.ValidationErrorList { + pod := obj.(*api.PodTemplate) + return validation.ValidatePodTemplate(pod) +} + +// AllowCreateOnUpdate is false for pod templates. +func (podTemplateStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (podTemplateStrategy) PrepareForUpdate(obj, old runtime.Object) { + _ = obj.(*api.PodTemplate) +} + +// ValidateUpdate is the default update validation for an end user. +func (podTemplateStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList { + return validation.ValidatePodTemplateUpdate(obj.(*api.PodTemplate), old.(*api.PodTemplate)) +} + +// MatchPodTemplate returns a generic matcher for a given label and field selector. +func MatchPodTemplate(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + podObj, ok := obj.(*api.PodTemplate) + if !ok { + return false, fmt.Errorf("not a pod template") + } + return label.Matches(labels.Set(podObj.Labels)), nil + }) +}