diff --git a/api/definitions/v1.deploymentlog/description.adoc b/api/definitions/v1.deploymentlog/description.adoc new file mode 100644 index 000000000000..c4ed999bc662 --- /dev/null +++ b/api/definitions/v1.deploymentlog/description.adoc @@ -0,0 +1 @@ +A deployment log is a virtual resource used by the OpenShift client tool for retrieving the logs for a deployment. \ No newline at end of file diff --git a/api/swagger-spec/oapi-v1.json b/api/swagger-spec/oapi-v1.json index 6d478756b69a..75ee6359176b 100644 --- a/api/swagger-spec/oapi-v1.json +++ b/api/swagger-spec/oapi-v1.json @@ -4212,6 +4212,81 @@ } ] }, + { + "path": "/oapi/v1/namespaces/{namespace}/deploymentconfigs/{name}/log", + "description": "OpenShift REST API, version v1", + "operations": [ + { + "type": "v1.DeploymentLog", + "method": "GET", + "summary": "read log of the specified DeploymentLog", + "nickname": "readNamespacedDeploymentLogLog", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "follow", + "description": "", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "nowait", + "description": "", + "required": false, + "allowMultiple": false + }, + { + "type": "*int", + "paramType": "query", + "name": "version", + "description": "", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespace", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the DeploymentLog", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1.DeploymentLog" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/oapi/v1/namespaces/{namespace}/generatedeploymentconfigs/{name}", "description": "OpenShift REST API, version v1", @@ -14556,6 +14631,7 @@ }, "v1.BuildLog": { "id": "v1.BuildLog", + "description": "TypeMeta describes an individual object in an API response or request with strings representing the type of the object and its API schema version. Structures that are versioned or persisted should inline TypeMeta.", "properties": { "kind": { "type": "string", @@ -14564,9 +14640,6 @@ "apiVersion": { "type": "string", "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources" - }, - "metadata": { - "$ref": "unversioned.ListMeta" } } }, @@ -16269,6 +16342,20 @@ } } }, + "v1.DeploymentLog": { + "id": "v1.DeploymentLog", + "description": "TypeMeta describes an individual object in an API response or request with strings representing the type of the object and its API schema version. Structures that are versioned or persisted should inline TypeMeta.", + "properties": { + "kind": { + "type": "string", + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources" + } + } + }, "v1.GroupList": { "id": "v1.GroupList", "required": [ diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index 3eb8d18a0c67..43a7cd31067f 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -684,7 +684,6 @@ _oc_describe() must_have_one_noun=() must_have_one_noun+=("build") must_have_one_noun+=("buildconfig") - must_have_one_noun+=("buildlog") must_have_one_noun+=("clusterpolicy") must_have_one_noun+=("clusterpolicybinding") must_have_one_noun+=("clusterrole") diff --git a/contrib/completions/bash/openshift b/contrib/completions/bash/openshift index 9dcbc6cacadb..8aa4234b57cb 100644 --- a/contrib/completions/bash/openshift +++ b/contrib/completions/bash/openshift @@ -2423,7 +2423,6 @@ _openshift_cli_describe() must_have_one_noun=() must_have_one_noun+=("build") must_have_one_noun+=("buildconfig") - must_have_one_noun+=("buildlog") must_have_one_noun+=("clusterpolicy") must_have_one_noun+=("clusterpolicybinding") must_have_one_noun+=("clusterrole") diff --git a/docs/generated/oc_by_example_content.adoc b/docs/generated/oc_by_example_content.adoc index fccc222f3f62..3be064db4ad5 100644 --- a/docs/generated/oc_by_example_content.adoc +++ b/docs/generated/oc_by_example_content.adoc @@ -502,14 +502,17 @@ Print the logs for a resource. [options="nowrap"] ---- - # Returns snapshot of ruby-container logs from pod 123456-7890. - $ openshift cli logs 123456-7890 -c ruby-container + # Returns snapshot of ruby-container logs from pod backend. + $ openshift cli logs backend -c ruby-container - # Starts streaming of ruby-container logs from pod 123456-7890. - $ openshift cli logs -f 123456-7890 -c ruby-container + # Starts streaming of ruby-container logs from pod backend. + $ openshift cli logs -f pod/backend -c ruby-container - # Starts streaming the logs of the most recent build of the openldap BuildConfig. + # Starts streaming the logs of the most recent build of the openldap buildConfig. $ openshift cli logs -f bc/openldap + + # Starts streaming the logs of the latest deployment of the mysql deploymentConfig + $ openshift cli logs -f dc/mysql ---- ==== diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index ab3115634405..087b6c9bc1d7 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -779,11 +779,6 @@ func deepCopy_api_BuildLog(in buildapi.BuildLog, out *buildapi.BuildLog, c *conv } else { out.TypeMeta = newVal.(unversioned.TypeMeta) } - if newVal, err := c.DeepCopy(in.ListMeta); err != nil { - return err - } else { - out.ListMeta = newVal.(unversioned.ListMeta) - } return nil } @@ -1349,6 +1344,32 @@ func deepCopy_api_DeploymentDetails(in deployapi.DeploymentDetails, out *deploya return nil } +func deepCopy_api_DeploymentLog(in deployapi.DeploymentLog, out *deployapi.DeploymentLog, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.TypeMeta); err != nil { + return err + } else { + out.TypeMeta = newVal.(unversioned.TypeMeta) + } + return nil +} + +func deepCopy_api_DeploymentLogOptions(in deployapi.DeploymentLogOptions, out *deployapi.DeploymentLogOptions, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.TypeMeta); err != nil { + return err + } else { + out.TypeMeta = newVal.(unversioned.TypeMeta) + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + func deepCopy_api_DeploymentStrategy(in deployapi.DeploymentStrategy, out *deployapi.DeploymentStrategy, c *conversion.Cloner) error { out.Type = in.Type if in.CustomParams != nil { @@ -2725,6 +2746,8 @@ func init() { deepCopy_api_DeploymentConfigRollback, deepCopy_api_DeploymentConfigRollbackSpec, deepCopy_api_DeploymentDetails, + deepCopy_api_DeploymentLog, + deepCopy_api_DeploymentLogOptions, deepCopy_api_DeploymentStrategy, deepCopy_api_DeploymentTemplate, deepCopy_api_DeploymentTriggerImageChangeParams, diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index ebc510e1b6ef..919163a31c16 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -1212,9 +1212,6 @@ func autoconvert_api_BuildLog_To_v1_BuildLog(in *buildapi.BuildLog, out *apiv1.B if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { return err } - if err := s.Convert(&in.ListMeta, &out.ListMeta, 0); err != nil { - return err - } return nil } @@ -1847,9 +1844,6 @@ func autoconvert_v1_BuildLog_To_api_BuildLog(in *apiv1.BuildLog, out *buildapi.B if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { return err } - if err := s.Convert(&in.ListMeta, &out.ListMeta, 0); err != nil { - return err - } return nil } @@ -2418,6 +2412,42 @@ func convert_api_DeploymentConfigRollbackSpec_To_v1_DeploymentConfigRollbackSpec return autoconvert_api_DeploymentConfigRollbackSpec_To_v1_DeploymentConfigRollbackSpec(in, out, s) } +func autoconvert_api_DeploymentLog_To_v1_DeploymentLog(in *deployapi.DeploymentLog, out *deployapiv1.DeploymentLog, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapi.DeploymentLog))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + return nil +} + +func convert_api_DeploymentLog_To_v1_DeploymentLog(in *deployapi.DeploymentLog, out *deployapiv1.DeploymentLog, s conversion.Scope) error { + return autoconvert_api_DeploymentLog_To_v1_DeploymentLog(in, out, s) +} + +func autoconvert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in *deployapi.DeploymentLogOptions, out *deployapiv1.DeploymentLogOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapi.DeploymentLogOptions))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + +func convert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in *deployapi.DeploymentLogOptions, out *deployapiv1.DeploymentLogOptions, s conversion.Scope) error { + return autoconvert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in, out, s) +} + func autoconvert_v1_DeploymentConfig_To_api_DeploymentConfig(in *deployapiv1.DeploymentConfig, out *deployapi.DeploymentConfig, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*deployapiv1.DeploymentConfig))(in) @@ -2495,6 +2525,42 @@ func convert_v1_DeploymentConfigRollbackSpec_To_api_DeploymentConfigRollbackSpec return autoconvert_v1_DeploymentConfigRollbackSpec_To_api_DeploymentConfigRollbackSpec(in, out, s) } +func autoconvert_v1_DeploymentLog_To_api_DeploymentLog(in *deployapiv1.DeploymentLog, out *deployapi.DeploymentLog, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapiv1.DeploymentLog))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + return nil +} + +func convert_v1_DeploymentLog_To_api_DeploymentLog(in *deployapiv1.DeploymentLog, out *deployapi.DeploymentLog, s conversion.Scope) error { + return autoconvert_v1_DeploymentLog_To_api_DeploymentLog(in, out, s) +} + +func autoconvert_v1_DeploymentLogOptions_To_api_DeploymentLogOptions(in *deployapiv1.DeploymentLogOptions, out *deployapi.DeploymentLogOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapiv1.DeploymentLogOptions))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + +func convert_v1_DeploymentLogOptions_To_api_DeploymentLogOptions(in *deployapiv1.DeploymentLogOptions, out *deployapi.DeploymentLogOptions, s conversion.Scope) error { + return autoconvert_v1_DeploymentLogOptions_To_api_DeploymentLogOptions(in, out, s) +} + func autoconvert_api_Image_To_v1_Image(in *imageapi.Image, out *imageapiv1.Image, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*imageapi.Image))(in) @@ -4929,6 +4995,8 @@ func init() { autoconvert_api_DeploymentConfigRollbackSpec_To_v1_DeploymentConfigRollbackSpec, autoconvert_api_DeploymentConfigRollback_To_v1_DeploymentConfigRollback, autoconvert_api_DeploymentConfig_To_v1_DeploymentConfig, + autoconvert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions, + autoconvert_api_DeploymentLog_To_v1_DeploymentLog, autoconvert_api_DockerBuildStrategy_To_v1_DockerBuildStrategy, autoconvert_api_EnvVarSource_To_v1_EnvVarSource, autoconvert_api_EnvVar_To_v1_EnvVar, @@ -5033,6 +5101,8 @@ func init() { autoconvert_v1_DeploymentConfigRollbackSpec_To_api_DeploymentConfigRollbackSpec, autoconvert_v1_DeploymentConfigRollback_To_api_DeploymentConfigRollback, autoconvert_v1_DeploymentConfig_To_api_DeploymentConfig, + autoconvert_v1_DeploymentLogOptions_To_api_DeploymentLogOptions, + autoconvert_v1_DeploymentLog_To_api_DeploymentLog, autoconvert_v1_DockerBuildStrategy_To_api_DockerBuildStrategy, autoconvert_v1_EnvVarSource_To_api_EnvVarSource, autoconvert_v1_EnvVar_To_api_EnvVar, diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index ea94776a8321..39ada03fb7ab 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -803,11 +803,6 @@ func deepCopy_v1_BuildLog(in apiv1.BuildLog, out *apiv1.BuildLog, c *conversion. } else { out.TypeMeta = newVal.(unversioned.TypeMeta) } - if newVal, err := c.DeepCopy(in.ListMeta); err != nil { - return err - } else { - out.ListMeta = newVal.(unversioned.ListMeta) - } return nil } @@ -1408,6 +1403,32 @@ func deepCopy_v1_DeploymentDetails(in deployapiv1.DeploymentDetails, out *deploy return nil } +func deepCopy_v1_DeploymentLog(in deployapiv1.DeploymentLog, out *deployapiv1.DeploymentLog, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.TypeMeta); err != nil { + return err + } else { + out.TypeMeta = newVal.(unversioned.TypeMeta) + } + return nil +} + +func deepCopy_v1_DeploymentLogOptions(in deployapiv1.DeploymentLogOptions, out *deployapiv1.DeploymentLogOptions, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.TypeMeta); err != nil { + return err + } else { + out.TypeMeta = newVal.(unversioned.TypeMeta) + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + func deepCopy_v1_DeploymentStrategy(in deployapiv1.DeploymentStrategy, out *deployapiv1.DeploymentStrategy, c *conversion.Cloner) error { out.Type = in.Type if in.CustomParams != nil { @@ -2639,6 +2660,8 @@ func init() { deepCopy_v1_DeploymentConfigSpec, deepCopy_v1_DeploymentConfigStatus, deepCopy_v1_DeploymentDetails, + deepCopy_v1_DeploymentLog, + deepCopy_v1_DeploymentLogOptions, deepCopy_v1_DeploymentStrategy, deepCopy_v1_DeploymentTriggerImageChangeParams, deepCopy_v1_DeploymentTriggerPolicy, diff --git a/pkg/api/v1beta3/conversion_generated.go b/pkg/api/v1beta3/conversion_generated.go index fbe828ca85f9..654b65b4abfd 100644 --- a/pkg/api/v1beta3/conversion_generated.go +++ b/pkg/api/v1beta3/conversion_generated.go @@ -1221,9 +1221,6 @@ func autoconvert_api_BuildLog_To_v1beta3_BuildLog(in *buildapi.BuildLog, out *ap if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { return err } - if err := s.Convert(&in.ListMeta, &out.ListMeta, 0); err != nil { - return err - } return nil } @@ -1856,9 +1853,6 @@ func autoconvert_v1beta3_BuildLog_To_api_BuildLog(in *apiv1beta3.BuildLog, out * if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { return err } - if err := s.Convert(&in.ListMeta, &out.ListMeta, 0); err != nil { - return err - } return nil } @@ -2427,6 +2421,42 @@ func convert_api_DeploymentConfigRollbackSpec_To_v1beta3_DeploymentConfigRollbac return autoconvert_api_DeploymentConfigRollbackSpec_To_v1beta3_DeploymentConfigRollbackSpec(in, out, s) } +func autoconvert_api_DeploymentLog_To_v1beta3_DeploymentLog(in *deployapi.DeploymentLog, out *deployapiv1beta3.DeploymentLog, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapi.DeploymentLog))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + return nil +} + +func convert_api_DeploymentLog_To_v1beta3_DeploymentLog(in *deployapi.DeploymentLog, out *deployapiv1beta3.DeploymentLog, s conversion.Scope) error { + return autoconvert_api_DeploymentLog_To_v1beta3_DeploymentLog(in, out, s) +} + +func autoconvert_api_DeploymentLogOptions_To_v1beta3_DeploymentLogOptions(in *deployapi.DeploymentLogOptions, out *deployapiv1beta3.DeploymentLogOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapi.DeploymentLogOptions))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + +func convert_api_DeploymentLogOptions_To_v1beta3_DeploymentLogOptions(in *deployapi.DeploymentLogOptions, out *deployapiv1beta3.DeploymentLogOptions, s conversion.Scope) error { + return autoconvert_api_DeploymentLogOptions_To_v1beta3_DeploymentLogOptions(in, out, s) +} + func autoconvert_v1beta3_DeploymentConfig_To_api_DeploymentConfig(in *deployapiv1beta3.DeploymentConfig, out *deployapi.DeploymentConfig, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*deployapiv1beta3.DeploymentConfig))(in) @@ -2504,6 +2534,42 @@ func convert_v1beta3_DeploymentConfigRollbackSpec_To_api_DeploymentConfigRollbac return autoconvert_v1beta3_DeploymentConfigRollbackSpec_To_api_DeploymentConfigRollbackSpec(in, out, s) } +func autoconvert_v1beta3_DeploymentLog_To_api_DeploymentLog(in *deployapiv1beta3.DeploymentLog, out *deployapi.DeploymentLog, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapiv1beta3.DeploymentLog))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + return nil +} + +func convert_v1beta3_DeploymentLog_To_api_DeploymentLog(in *deployapiv1beta3.DeploymentLog, out *deployapi.DeploymentLog, s conversion.Scope) error { + return autoconvert_v1beta3_DeploymentLog_To_api_DeploymentLog(in, out, s) +} + +func autoconvert_v1beta3_DeploymentLogOptions_To_api_DeploymentLogOptions(in *deployapiv1beta3.DeploymentLogOptions, out *deployapi.DeploymentLogOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*deployapiv1beta3.DeploymentLogOptions))(in) + } + if err := s.Convert(&in.TypeMeta, &out.TypeMeta, 0); err != nil { + return err + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + +func convert_v1beta3_DeploymentLogOptions_To_api_DeploymentLogOptions(in *deployapiv1beta3.DeploymentLogOptions, out *deployapi.DeploymentLogOptions, s conversion.Scope) error { + return autoconvert_v1beta3_DeploymentLogOptions_To_api_DeploymentLogOptions(in, out, s) +} + func autoconvert_api_Image_To_v1beta3_Image(in *imageapi.Image, out *imageapiv1beta3.Image, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*imageapi.Image))(in) @@ -4904,6 +4970,8 @@ func init() { autoconvert_api_DeploymentConfigRollbackSpec_To_v1beta3_DeploymentConfigRollbackSpec, autoconvert_api_DeploymentConfigRollback_To_v1beta3_DeploymentConfigRollback, autoconvert_api_DeploymentConfig_To_v1beta3_DeploymentConfig, + autoconvert_api_DeploymentLogOptions_To_v1beta3_DeploymentLogOptions, + autoconvert_api_DeploymentLog_To_v1beta3_DeploymentLog, autoconvert_api_DockerBuildStrategy_To_v1beta3_DockerBuildStrategy, autoconvert_api_EnvVarSource_To_v1beta3_EnvVarSource, autoconvert_api_EnvVar_To_v1beta3_EnvVar, @@ -5008,6 +5076,8 @@ func init() { autoconvert_v1beta3_DeploymentConfigRollbackSpec_To_api_DeploymentConfigRollbackSpec, autoconvert_v1beta3_DeploymentConfigRollback_To_api_DeploymentConfigRollback, autoconvert_v1beta3_DeploymentConfig_To_api_DeploymentConfig, + autoconvert_v1beta3_DeploymentLogOptions_To_api_DeploymentLogOptions, + autoconvert_v1beta3_DeploymentLog_To_api_DeploymentLog, autoconvert_v1beta3_DockerBuildStrategy_To_api_DockerBuildStrategy, autoconvert_v1beta3_EnvVarSource_To_api_EnvVarSource, autoconvert_v1beta3_EnvVar_To_api_EnvVar, diff --git a/pkg/api/v1beta3/deep_copy_generated.go b/pkg/api/v1beta3/deep_copy_generated.go index e1c0ef52187d..e1b63be4742a 100644 --- a/pkg/api/v1beta3/deep_copy_generated.go +++ b/pkg/api/v1beta3/deep_copy_generated.go @@ -811,11 +811,6 @@ func deepCopy_v1beta3_BuildLog(in apiv1beta3.BuildLog, out *apiv1beta3.BuildLog, } else { out.TypeMeta = newVal.(unversioned.TypeMeta) } - if newVal, err := c.DeepCopy(in.ListMeta); err != nil { - return err - } else { - out.ListMeta = newVal.(unversioned.ListMeta) - } return nil } @@ -1416,6 +1411,32 @@ func deepCopy_v1beta3_DeploymentDetails(in deployapiv1beta3.DeploymentDetails, o return nil } +func deepCopy_v1beta3_DeploymentLog(in deployapiv1beta3.DeploymentLog, out *deployapiv1beta3.DeploymentLog, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.TypeMeta); err != nil { + return err + } else { + out.TypeMeta = newVal.(unversioned.TypeMeta) + } + return nil +} + +func deepCopy_v1beta3_DeploymentLogOptions(in deployapiv1beta3.DeploymentLogOptions, out *deployapiv1beta3.DeploymentLogOptions, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.TypeMeta); err != nil { + return err + } else { + out.TypeMeta = newVal.(unversioned.TypeMeta) + } + out.Follow = in.Follow + out.NoWait = in.NoWait + if in.Version != nil { + out.Version = new(int) + *out.Version = *in.Version + } else { + out.Version = nil + } + return nil +} + func deepCopy_v1beta3_DeploymentStrategy(in deployapiv1beta3.DeploymentStrategy, out *deployapiv1beta3.DeploymentStrategy, c *conversion.Cloner) error { out.Type = in.Type if in.CustomParams != nil { @@ -2629,6 +2650,8 @@ func init() { deepCopy_v1beta3_DeploymentConfigSpec, deepCopy_v1beta3_DeploymentConfigStatus, deepCopy_v1beta3_DeploymentDetails, + deepCopy_v1beta3_DeploymentLog, + deepCopy_v1beta3_DeploymentLogOptions, deepCopy_v1beta3_DeploymentStrategy, deepCopy_v1beta3_DeploymentTriggerImageChangeParams, deepCopy_v1beta3_DeploymentTriggerPolicy, diff --git a/pkg/api/validation/coverage_test.go b/pkg/api/validation/coverage_test.go index af3bb696b64e..16a93c5c92ad 100644 --- a/pkg/api/validation/coverage_test.go +++ b/pkg/api/validation/coverage_test.go @@ -9,6 +9,7 @@ import ( authorizationapi "github.com/openshift/origin/pkg/authorization/api" buildapi "github.com/openshift/origin/pkg/build/api" + deployapi "github.com/openshift/origin/pkg/deploy/api" imageapi "github.com/openshift/origin/pkg/image/api" ) @@ -16,6 +17,8 @@ import ( // If you add something to this list, explain why it doesn't need validation. waaaa is not a valid // reason. var KnownValidationExceptions = []reflect.Type{ + reflect.TypeOf(&buildapi.BuildLog{}), // masks calls to a build subresource + reflect.TypeOf(&deployapi.DeploymentLog{}), // masks calls to a deploymentConfig subresource reflect.TypeOf(&imageapi.ImageStreamImage{}), // this object is only returned, never accepted reflect.TypeOf(&imageapi.ImageStreamTag{}), // this object is only returned, never accepted reflect.TypeOf(&authorizationapi.IsPersonalSubjectAccessReview{}), // only an api type for runtime.EmbeddedObject, never accepted @@ -27,7 +30,6 @@ var KnownValidationExceptions = []reflect.Type{ // You should never add to this list var MissingValidationExceptions = []reflect.Type{ reflect.TypeOf(&buildapi.BuildLogOptions{}), // TODO, looks like this one should have validation - reflect.TypeOf(&buildapi.BuildLog{}), // TODO, I have no idea what this is doing reflect.TypeOf(&imageapi.DockerImage{}), // TODO, I think this type is ok to skip validation (internal), but needs review } diff --git a/pkg/api/validation/register.go b/pkg/api/validation/register.go index b3941c2b8870..8265ead605ce 100644 --- a/pkg/api/validation/register.go +++ b/pkg/api/validation/register.go @@ -46,6 +46,7 @@ func init() { Validator.Register(&deployapi.DeploymentConfig{}, deployvalidation.ValidateDeploymentConfig, deployvalidation.ValidateDeploymentConfigUpdate) Validator.Register(&deployapi.DeploymentConfigRollback{}, deployvalidation.ValidateDeploymentConfigRollback, nil) + Validator.Register(&deployapi.DeploymentLogOptions{}, deployvalidation.ValidateDeploymentLogOptions, nil) Validator.Register(&imageapi.Image{}, imagevalidation.ValidateImage, imagevalidation.ValidateImageUpdate) Validator.Register(&imageapi.ImageStream{}, imagevalidation.ValidateImageStream, imagevalidation.ValidateImageStreamUpdate) diff --git a/pkg/authorization/api/types.go b/pkg/authorization/api/types.go index ea49765cbca5..1214d0a3e73b 100644 --- a/pkg/authorization/api/types.go +++ b/pkg/authorization/api/types.go @@ -74,7 +74,7 @@ var ( GroupsToResources = map[string][]string{ BuildGroupName: {"builds", "buildconfigs", "buildlogs", "buildconfigs/instantiate", "builds/log", "builds/clone", "buildconfigs/webhooks"}, ImageGroupName: {"imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages"}, - DeploymentGroupName: {"deployments", "deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks"}, + DeploymentGroupName: {"deployments", "deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/log"}, SDNGroupName: {"clusternetworks", "hostsubnets", "netnamespaces"}, TemplateGroupName: {"templates", "templateconfigs", "processedtemplates"}, UserGroupName: {"identities", "users", "useridentitymappings", "groups"}, diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go index 6b4a7b12b911..abdfae205e19 100644 --- a/pkg/build/api/types.go +++ b/pkg/build/api/types.go @@ -489,7 +489,6 @@ type GitRefInfo struct { // BuildLog is the (unused) resource associated with the build log redirector type BuildLog struct { unversioned.TypeMeta - unversioned.ListMeta } // BuildRequest is the resource used to pass parameters to build generator diff --git a/pkg/build/api/v1/types.go b/pkg/build/api/v1/types.go index 3c25b1e2cde6..eb873b82c95c 100644 --- a/pkg/build/api/v1/types.go +++ b/pkg/build/api/v1/types.go @@ -448,7 +448,6 @@ type GitInfo struct { // BuildLog is the (unused) resource associated with the build log redirector type BuildLog struct { unversioned.TypeMeta `json:",inline"` - unversioned.ListMeta `json:"metadata,omitempty"` } // BuildRequest is the resource used to pass parameters to build generator @@ -481,6 +480,7 @@ type BuildLogOptions struct { // NoWait if true causes the call to return immediately even if the build // is not available yet. Otherwise the server will wait until the build has started. + // TODO: Fix the tag to 'noWait' in v2 NoWait bool `json:"nowait,omitempty" description:"if true indicates that the server should not wait for a log to be available before returning; defaults to false"` } diff --git a/pkg/build/api/v1beta3/types.go b/pkg/build/api/v1beta3/types.go index 25842ea7dd67..93be3b5e1f33 100644 --- a/pkg/build/api/v1beta3/types.go +++ b/pkg/build/api/v1beta3/types.go @@ -429,7 +429,6 @@ type GitInfo struct { // BuildLog is the (unused) resource associated with the build log redirector type BuildLog struct { unversioned.TypeMeta `json:",inline"` - unversioned.ListMeta `json:"metadata,omitempty"` } // BuildRequest is the resource used to pass parameters to build generator diff --git a/pkg/client/client.go b/pkg/client/client.go index 3705592cc81b..c8ec2e334838 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -24,6 +24,7 @@ type Interface interface { ImageStreamTagsNamespacer ImageStreamImagesNamespacer DeploymentConfigsNamespacer + DeploymentLogsNamespacer RoutesNamespacer HostSubnetsInterface NetNamespacesInterface @@ -98,6 +99,11 @@ func (c *Client) DeploymentConfigs(namespace string) DeploymentConfigInterface { return newDeploymentConfigs(c, namespace) } +// DeploymentLogs provides a REST client for DeploymentLog +func (c *Client) DeploymentLogs(namespace string) DeploymentLogInterface { + return newDeploymentLogs(c, namespace) +} + // Routes provides a REST client for Route func (c *Client) Routes(namespace string) RouteInterface { return newRoutes(c, namespace) diff --git a/pkg/client/deploymentlogs.go b/pkg/client/deploymentlogs.go new file mode 100644 index 000000000000..863b7959799a --- /dev/null +++ b/pkg/client/deploymentlogs.go @@ -0,0 +1,48 @@ +package client + +import ( + "fmt" + + kclient "k8s.io/kubernetes/pkg/client/unversioned" + + "github.com/openshift/origin/pkg/deploy/api" +) + +// DeploymentLogsNamespacer has methods to work with DeploymentLogs resources in a namespace +type DeploymentLogsNamespacer interface { + DeploymentLogs(namespace string) DeploymentLogInterface +} + +// DeploymentLogInterface exposes methods on DeploymentLogs resources. +type DeploymentLogInterface interface { + Get(name string, opts api.DeploymentLogOptions) *kclient.Request +} + +// deploymentLogs implements DeploymentLogsNamespacer interface +type deploymentLogs struct { + r *Client + ns string +} + +// newDeploymentLogs returns a deploymentLogs +func newDeploymentLogs(c *Client, namespace string) *deploymentLogs { + return &deploymentLogs{ + r: c, + ns: namespace, + } +} + +// Get gets the deploymentlogs and return a deploymentLog request +func (c *deploymentLogs) Get(name string, opts api.DeploymentLogOptions) *kclient.Request { + req := c.r.Get().Namespace(c.ns).Resource("deploymentConfigs").Name(name).SubResource("log") + if opts.NoWait { + req.Param("nowait", "true") + } + if opts.Follow { + req.Param("follow", "true") + } + if opts.Version != nil { + req.Param("version", fmt.Sprintf("%d", *opts.Version)) + } + return req +} diff --git a/pkg/client/testclient/fake.go b/pkg/client/testclient/fake.go index 44f634b98303..a6db7f6443b0 100644 --- a/pkg/client/testclient/fake.go +++ b/pkg/client/testclient/fake.go @@ -165,6 +165,11 @@ func (c *Fake) DeploymentConfigs(namespace string) client.DeploymentConfigInterf return &FakeDeploymentConfigs{Fake: c, Namespace: namespace} } +// DeploymentLogs provides a fake REST client for DeploymentLogs +func (c *Fake) DeploymentLogs(namespace string) client.DeploymentLogInterface { + return &FakeDeploymentLogs{Fake: c, Namespace: namespace} +} + // Routes provides a fake REST client for Routes func (c *Fake) Routes(namespace string) client.RouteInterface { return &FakeRoutes{Fake: c, Namespace: namespace} diff --git a/pkg/client/testclient/fake_deploymentlogs.go b/pkg/client/testclient/fake_deploymentlogs.go new file mode 100644 index 000000000000..b36bc9526936 --- /dev/null +++ b/pkg/client/testclient/fake_deploymentlogs.go @@ -0,0 +1,28 @@ +package testclient + +import ( + kclient "k8s.io/kubernetes/pkg/client/unversioned" + ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient" + + "github.com/openshift/origin/pkg/deploy/api" +) + +// FakeDeploymentLogs implements DeploymentLogsInterface. 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 FakeDeploymentLogs struct { + Fake *Fake + Namespace string +} + +// Get builds and returns a buildLog request +func (c *FakeDeploymentLogs) Get(name string, opt api.DeploymentLogOptions) *kclient.Request { + action := ktestclient.GenericActionImpl{} + action.Verb = "get" + action.Namespace = c.Namespace + action.Resource = "deploymentconfigs" + action.Subresource = "logs" + action.Value = opt + + _, _ = c.Fake.Invokes(action, &api.DeploymentConfig{}) + return &kclient.Request{} +} diff --git a/pkg/cmd/cli/cmd/deploy.go b/pkg/cmd/cli/cmd/deploy.go index 1965d43b98ab..b7bdc080c8d8 100644 --- a/pkg/cmd/cli/cmd/deploy.go +++ b/pkg/cmd/cli/cmd/deploy.go @@ -27,6 +27,7 @@ import ( deployutil "github.com/openshift/origin/pkg/deploy/util" ) +// DeployOptions holds all the options for the `deploy` command type DeployOptions struct { out io.Writer osClient client.Interface @@ -243,7 +244,7 @@ func (o DeployOptions) retry(config *deployapi.DeploymentConfig, out io.Writer) deployment, err := o.kubeClient.ReplicationControllers(config.Namespace).Get(deploymentName) if err != nil { if kerrors.IsNotFound(err) { - return fmt.Errorf("Unable to find the latest deployment (#%d).\nYou can start a new deployment using the --latest option.", config.LatestVersion) + return fmt.Errorf("unable to find the latest deployment (#%d).\nYou can start a new deployment using the --latest option.", config.LatestVersion) } return err } diff --git a/pkg/cmd/cli/cmd/logs.go b/pkg/cmd/cli/cmd/logs.go index 6ab25da00323..0d995d025d4e 100644 --- a/pkg/cmd/cli/cmd/logs.go +++ b/pkg/cmd/cli/cmd/logs.go @@ -19,6 +19,8 @@ import ( buildapi "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/util/clientcmd" + deployapi "github.com/openshift/origin/pkg/deploy/api" + deployutil "github.com/openshift/origin/pkg/deploy/util" ) const ( @@ -27,14 +29,17 @@ Print the logs for a container in a pod If the pod has only one container, the container name is optional.` - logsExample = ` # Returns snapshot of ruby-container logs from pod 123456-7890. - $ %[1]s logs 123456-7890 -c ruby-container + logsExample = ` # Returns snapshot of ruby-container logs from pod backend. + $ %[1]s logs backend -c ruby-container - # Starts streaming of ruby-container logs from pod 123456-7890. - $ %[1]s logs -f 123456-7890 -c ruby-container + # Starts streaming of ruby-container logs from pod backend. + $ %[1]s logs -f pod/backend -c ruby-container - # Starts streaming the logs of the most recent build of the openldap BuildConfig. - $ %[1]s logs -f bc/openldap` + # Starts streaming the logs of the most recent build of the openldap buildConfig. + $ %[1]s logs -f bc/openldap + + # Starts streaming the logs of the latest deployment of the mysql deploymentConfig + $ %[1]s logs -f dc/mysql` ) type OpenShiftLogsOptions struct { @@ -153,6 +158,7 @@ func (o *OpenShiftLogsOptions) RunLog() error { return errors.New("container cannot be specified with anything besides a pod") } + // TODO: Use osutil.ResolveResource to resolve resource types switch resourceType { case "bc", "buildconfig", "buildconfigs": buildsForBCSelector := labels.SelectorFromSet(map[string]string{buildapi.BuildConfigLabel: resourceName}) @@ -175,6 +181,13 @@ func (o *OpenShiftLogsOptions) RunLog() error { } return o.runLogsForBuild(build) + case "dc", "deploymentconfig", "deploymentconfigs": + dc, err := o.OriginClient.DeploymentConfigs(o.Namespace).Get(resourceName) + if err != nil { + return err + } + return o.runLogsForDeployment(dc) + default: return fmt.Errorf("cannot display logs for resource type %v", resourceType) } @@ -197,3 +210,38 @@ func (o *OpenShiftLogsOptions) runLogsForBuild(build *buildapi.Build) error { _, err = io.Copy(o.KubeLogOptions.Out, readCloser) return err } + +func (o *OpenShiftLogsOptions) runLogsForDeployment(dc *deployapi.DeploymentConfig) error { + opts := deployapi.DeploymentLogOptions{ + Follow: o.KubeLogOptions.Follow, + NoWait: false, + } + + readCloser, err := o.OriginClient.DeploymentLogs(dc.Namespace).Get(dc.Name, opts).Stream() + if err != nil { + return err + } + defer readCloser.Close() + + written, err := io.Copy(o.KubeLogOptions.Out, readCloser) + if err != nil { + return err + } + + // If nothing is written, it means there are no logs returned. Normally + // in two cases we will get no logs: either if we don't want to wait + // for the deployer pod to be created (NoWait = true) or if the deployer + // pod has been deleted (successful deployment). + // + // Note that in case of manual pod deletion or deployment pruning, this may + // be inaccurate. + if written == 0 && !opts.NoWait { + if opts.Version == nil { + fmt.Fprintln(o.KubeLogOptions.Out, "Latest deployment successfully made active, no logs to show.") + return nil + } + fmt.Fprintf(o.KubeLogOptions.Out, "No logs exist for deployment %s.\n", deployutil.DeploymentNameForConfigVersion(dc.Name, *opts.Version)) + } + + return nil +} diff --git a/pkg/cmd/cli/describe/describer.go b/pkg/cmd/cli/describe/describer.go index c5630e6ef652..d6a0f4acf3ef 100644 --- a/pkg/cmd/cli/describe/describer.go +++ b/pkg/cmd/cli/describe/describer.go @@ -37,7 +37,6 @@ func describerMap(c *client.Client, kclient kclient.Interface, host string) map[ m := map[string]kctl.Describer{ "Build": &BuildDescriber{c, kclient}, "BuildConfig": &BuildConfigDescriber{c, host}, - "BuildLog": &BuildLogDescriber{c}, "DeploymentConfig": NewDeploymentConfigDescriber(c, kclient), "Identity": &IdentityDescriber{c}, "Image": &ImageDescriber{c}, @@ -408,16 +407,6 @@ func (d *BuildConfigDescriber) Describe(namespace, name string) (string, error) }) } -// BuildLogDescriber generates information about a BuildLog -type BuildLogDescriber struct { - client.Interface -} - -// Describe returns the description of a buildLog -func (d *BuildLogDescriber) Describe(namespace, name string) (string, error) { - return fmt.Sprintf("Name: %s/%s, Labels:", namespace, name), nil -} - // ImageDescriber generates information about a Image type ImageDescriber struct { client.Interface diff --git a/pkg/cmd/cli/describe/describer_test.go b/pkg/cmd/cli/describe/describer_test.go index e9e4c84b7c4f..00bbb59d93be 100644 --- a/pkg/cmd/cli/describe/describer_test.go +++ b/pkg/cmd/cli/describe/describer_test.go @@ -36,13 +36,16 @@ type describeClient struct { // If you add something to this list, explain why it doesn't need validation. waaaa is not a valid // reason. var DescriberCoverageExceptions = []reflect.Type{ + reflect.TypeOf(&buildapi.BuildLog{}), // normal users don't ever look at these reflect.TypeOf(&buildapi.BuildLogOptions{}), // normal users don't ever look at these reflect.TypeOf(&buildapi.BuildRequest{}), // normal users don't ever look at these + reflect.TypeOf(&deployapi.DeploymentConfigRollback{}), // normal users don't ever look at these + reflect.TypeOf(&deployapi.DeploymentLog{}), // normal users don't ever look at these + reflect.TypeOf(&deployapi.DeploymentLogOptions{}), // normal users don't ever look at these reflect.TypeOf(&imageapi.DockerImage{}), // not a top level resource reflect.TypeOf(&oauthapi.OAuthAccessToken{}), // normal users don't ever look at these reflect.TypeOf(&oauthapi.OAuthAuthorizeToken{}), // normal users don't ever look at these reflect.TypeOf(&oauthapi.OAuthClientAuthorization{}), // normal users don't ever look at these - reflect.TypeOf(&deployapi.DeploymentConfigRollback{}), // normal users don't ever look at these reflect.TypeOf(&projectapi.ProjectRequest{}), // normal users don't ever look at these reflect.TypeOf(&authorizationapi.IsPersonalSubjectAccessReview{}), // not a top level resource @@ -106,7 +109,6 @@ func TestDescribers(t *testing.T) { testDescriberList := []kubectl.Describer{ &BuildDescriber{c, fakeKube}, &BuildConfigDescriber{c, ""}, - &BuildLogDescriber{c}, &ImageDescriber{c}, &ImageStreamDescriber{c}, &ImageStreamTagDescriber{c}, diff --git a/pkg/cmd/cli/describe/printer_test.go b/pkg/cmd/cli/describe/printer_test.go index 4a76c066da22..571a6253d760 100644 --- a/pkg/cmd/cli/describe/printer_test.go +++ b/pkg/cmd/cli/describe/printer_test.go @@ -20,11 +20,14 @@ import ( ) // PrinterCoverageExceptions is the list of API types that do NOT have corresponding printers -// If you add something to this list, explain why it doesn't need validation. waaaa is not a valid +// If you add something to this list, explain why it doesn't need printing. waaaa is not a valid // reason. var PrinterCoverageExceptions = []reflect.Type{ - reflect.TypeOf(&imageapi.DockerImage{}), // not a top level resource - reflect.TypeOf(&buildapi.BuildLog{}), // just a marker type + reflect.TypeOf(&imageapi.DockerImage{}), // not a top level resource + reflect.TypeOf(&buildapi.BuildLog{}), // just a marker type + reflect.TypeOf(&buildapi.BuildLogOptions{}), // just a marker type + reflect.TypeOf(&deployapi.DeploymentLog{}), // just a marker type + reflect.TypeOf(&deployapi.DeploymentLogOptions{}), // just a marker type // these resources can't be "GET"ed, so we probably don't need a printer for them reflect.TypeOf(&authorizationapi.SubjectAccessReviewResponse{}), @@ -41,7 +44,6 @@ var PrinterCoverageExceptions = []reflect.Type{ var MissingPrinterCoverageExceptions = []reflect.Type{ reflect.TypeOf(&deployapi.DeploymentConfigRollback{}), reflect.TypeOf(&imageapi.ImageStreamMapping{}), - reflect.TypeOf(&buildapi.BuildLogOptions{}), reflect.TypeOf(&buildapi.BuildRequest{}), reflect.TypeOf(&projectapi.ProjectRequest{}), } diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index 746dd61f04d7..06cd8d04616d 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -40,6 +40,7 @@ import ( deployconfiggenerator "github.com/openshift/origin/pkg/deploy/generator" deployconfigregistry "github.com/openshift/origin/pkg/deploy/registry/deployconfig" deployconfigetcd "github.com/openshift/origin/pkg/deploy/registry/deployconfig/etcd" + deploylogregistry "github.com/openshift/origin/pkg/deploy/registry/deploylog" deployrollback "github.com/openshift/origin/pkg/deploy/registry/rollback" "github.com/openshift/origin/pkg/image/registry/image" imageetcd "github.com/openshift/origin/pkg/image/registry/image/etcd" @@ -401,7 +402,7 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage { LISFn2: imageStreamRegistry.ListImageStreams, }, } - _, kclient := c.DeploymentConfigControllerClients() + configClient, kclient := c.DeploymentConfigClients() deployRollback := &deployrollback.RollbackGenerator{} deployRollbackClient := deployrollback.Client{ DCFn: deployConfigRegistry.GetDeploymentConfig, @@ -439,6 +440,7 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage { "deploymentConfigs": deployConfigStorage, "generateDeploymentConfigs": deployconfiggenerator.NewREST(deployConfigGenerator, c.EtcdHelper.Codec()), "deploymentConfigRollbacks": deployrollback.NewREST(deployRollbackClient, c.EtcdHelper.Codec()), + "deploymentConfigs/log": deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient), "processedTemplates": templateregistry.NewREST(), "templates": templateetcd.NewREST(c.EtcdHelper), diff --git a/pkg/cmd/server/origin/master_config.go b/pkg/cmd/server/origin/master_config.go index d26b299da8d5..aa4a91131e48 100644 --- a/pkg/cmd/server/origin/master_config.go +++ b/pkg/cmd/server/origin/master_config.go @@ -88,7 +88,7 @@ type MasterConfig struct { ControllerPlug plug.Plug ControllerPlugStart func() - // a function that returns the appropriate image to use for a named component + // ImageFor is a function that returns the appropriate image to use for a named component ImageFor func(component string) string EtcdHelper storage.Interface @@ -127,6 +127,8 @@ type MasterConfig struct { ReplicationControllerServiceAccount string } +// BuildMasterConfig builds and returns the OpenShift master configuration based on the +// provided options func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) { client, err := etcd.EtcdClient(options.EtcdClientInfo) if err != nil { @@ -432,7 +434,7 @@ func (c *MasterConfig) ImageImportControllerClient() *osclient.Client { return c.PrivilegedLoopbackOpenShiftClient } -// DeploymentControllerClients returns the deployment controller client object +// DeploymentControllerClients returns the deployment controller client objects func (c *MasterConfig) DeploymentControllerClients() (*osclient.Client, *kclient.Client) { osClient, kClient, err := c.GetServiceAccountClients(c.DeploymentControllerServiceAccount) if err != nil { @@ -441,25 +443,47 @@ func (c *MasterConfig) DeploymentControllerClients() (*osclient.Client, *kclient return osClient, kClient } +// DeployerPodControllerClients returns the deployer pod controller client objects func (c *MasterConfig) DeployerPodControllerClients() (*osclient.Client, *kclient.Client) { return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient } + +// DeploymentConfigClients returns deploymentConfig and deployment client objects +func (c *MasterConfig) DeploymentConfigClients() (*osclient.Client, *kclient.Client) { + return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient +} + +// DeploymentConfigControllerClients returns the deploymentConfig controller client objects func (c *MasterConfig) DeploymentConfigControllerClients() (*osclient.Client, *kclient.Client) { return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient } + +// DeploymentConfigChangeControllerClients returns the deploymentConfig config change controller client objects func (c *MasterConfig) DeploymentConfigChangeControllerClients() (*osclient.Client, *kclient.Client) { return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient } + +// DeploymentImageChangeTriggerControllerClient returns the deploymentConfig image change controller client object func (c *MasterConfig) DeploymentImageChangeTriggerControllerClient() *osclient.Client { return c.PrivilegedLoopbackOpenShiftClient } +// DeploymentLogClient returns the deployment log client object +func (c *MasterConfig) DeploymentLogClient() *kclient.Client { + return c.PrivilegedLoopbackKubernetesClient +} + +// SecurityAllocationControllerClient returns the security allocation controller client object func (c *MasterConfig) SecurityAllocationControllerClient() *kclient.Client { return c.PrivilegedLoopbackKubernetesClient } + +// SDNControllerClients returns the SDN controller client objects func (c *MasterConfig) SDNControllerClients() (*osclient.Client, *kclient.Client) { return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient } + +// RouteAllocatorClients returns the route allocator client objects func (c *MasterConfig) RouteAllocatorClients() (*osclient.Client, *kclient.Client) { return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient } @@ -476,7 +500,7 @@ func (c *MasterConfig) OriginNamespaceControllerClients() (*osclient.Client, *kc return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient } -// AdmissionControlClient returns a client to be used for admission control. +// admissionControlClient returns a client to be used for admission control. // TODO: Refactor admission control to allow more than one client to be passed in to plugins func admissionControlClient(kClient *kclient.Client, osClient *osclient.Client) kclient.Interface { type kc struct{ *kclient.Client } diff --git a/pkg/deploy/api/register.go b/pkg/deploy/api/register.go index f38d73603988..6baff875982d 100644 --- a/pkg/deploy/api/register.go +++ b/pkg/deploy/api/register.go @@ -9,9 +9,13 @@ func init() { &DeploymentConfig{}, &DeploymentConfigList{}, &DeploymentConfigRollback{}, + &DeploymentLog{}, + &DeploymentLogOptions{}, ) } func (*DeploymentConfig) IsAnAPIObject() {} func (*DeploymentConfigList) IsAnAPIObject() {} func (*DeploymentConfigRollback) IsAnAPIObject() {} +func (*DeploymentLog) IsAnAPIObject() {} +func (*DeploymentLogOptions) IsAnAPIObject() {} diff --git a/pkg/deploy/api/types.go b/pkg/deploy/api/types.go index 415ab82e85bd..e86f398b42d3 100644 --- a/pkg/deploy/api/types.go +++ b/pkg/deploy/api/types.go @@ -357,3 +357,24 @@ type DeploymentConfigRollbackSpec struct { // IncludeStrategy specifies whether to include the deployment Strategy. IncludeStrategy bool } + +// DeploymentLog represents the logs for a deployment +type DeploymentLog struct { + unversioned.TypeMeta +} + +// DeploymentLogOptions is the REST options for a deployment log +type DeploymentLogOptions struct { + unversioned.TypeMeta + + // Follow if true indicates that the deployment log should be streamed until + // the deployment terminates. + Follow bool + + // NoWait if true causes the call to return immediately even if the deployment + // is not available yet. Otherwise the server will wait until the deployment has started. + NoWait bool + + // Version of the deploymentConfig for which to view logs. + Version *int +} diff --git a/pkg/deploy/api/v1/register.go b/pkg/deploy/api/v1/register.go index 399581d1d6f8..3dd51cdb98b3 100644 --- a/pkg/deploy/api/v1/register.go +++ b/pkg/deploy/api/v1/register.go @@ -9,9 +9,13 @@ func init() { &DeploymentConfig{}, &DeploymentConfigList{}, &DeploymentConfigRollback{}, + &DeploymentLog{}, + &DeploymentLogOptions{}, ) } func (*DeploymentConfig) IsAnAPIObject() {} func (*DeploymentConfigList) IsAnAPIObject() {} func (*DeploymentConfigRollback) IsAnAPIObject() {} +func (*DeploymentLog) IsAnAPIObject() {} +func (*DeploymentLogOptions) IsAnAPIObject() {} diff --git a/pkg/deploy/api/v1/types.go b/pkg/deploy/api/v1/types.go index 96a79b9cde36..1714ffcd41c0 100644 --- a/pkg/deploy/api/v1/types.go +++ b/pkg/deploy/api/v1/types.go @@ -348,3 +348,25 @@ type DeploymentConfigRollbackSpec struct { // IncludeStrategy specifies whether to include the deployment Strategy. IncludeStrategy bool `json:"includeStrategy" description:"whether to include the deployment strategy in the rollback"` } + +// DeploymentLog represents the logs for a deployment +type DeploymentLog struct { + unversioned.TypeMeta `json:",inline"` +} + +// DeploymentLogOptions is the REST options for a deployment log +type DeploymentLogOptions struct { + unversioned.TypeMeta `json:",inline"` + + // Follow if true indicates that the deployment log should be streamed until + // the deployment terminates. + Follow bool `json:"follow,omitempty" description:"if true indicates that the log should be streamed; defaults to false"` + + // NoWait if true causes the call to return immediately even if the deployment + // is not available yet. Otherwise the server will wait until the deployment has started. + // TODO: Fix the tag to 'noWait' in v2 + NoWait bool `json:"nowait,omitempty" description:"if true indicates that the server should not wait for a log to be available before returning; defaults to false"` + + // Version of the deploymentConfig for which to view logs. + Version *int `json:"version,omitempty" description:"the version of the deploymentConfig for which to view logs"` +} diff --git a/pkg/deploy/api/v1beta3/register.go b/pkg/deploy/api/v1beta3/register.go index d2e7b2b3e9c0..5ffafa47acff 100644 --- a/pkg/deploy/api/v1beta3/register.go +++ b/pkg/deploy/api/v1beta3/register.go @@ -9,9 +9,13 @@ func init() { &DeploymentConfig{}, &DeploymentConfigList{}, &DeploymentConfigRollback{}, + &DeploymentLog{}, + &DeploymentLogOptions{}, ) } func (*DeploymentConfig) IsAnAPIObject() {} func (*DeploymentConfigList) IsAnAPIObject() {} func (*DeploymentConfigRollback) IsAnAPIObject() {} +func (*DeploymentLog) IsAnAPIObject() {} +func (*DeploymentLogOptions) IsAnAPIObject() {} diff --git a/pkg/deploy/api/v1beta3/types.go b/pkg/deploy/api/v1beta3/types.go index 937259dde751..44071d5473b4 100644 --- a/pkg/deploy/api/v1beta3/types.go +++ b/pkg/deploy/api/v1beta3/types.go @@ -360,3 +360,24 @@ type DeploymentConfigRollbackSpec struct { // IncludeStrategy specifies whether to include the deployment Strategy. IncludeStrategy bool `json:"includeStrategy" description:"whether to include the deployment strategy in the rollback"` } + +// DeploymentLog represents the logs for a deployment +type DeploymentLog struct { + unversioned.TypeMeta `json:",inline"` +} + +// DeploymentLogOptions is the REST options for a deployment log +type DeploymentLogOptions struct { + unversioned.TypeMeta `json:",inline"` + + // Follow if true indicates that the deployment log should be streamed until + // the deployment terminates. + Follow bool `json:"follow,omitempty" description:"if true indicates that the log should be streamed; defaults to false"` + + // NoWait if true causes the call to return immediately even if the deployment + // is not available yet. Otherwise the server will wait until the deployment has started. + NoWait bool `json:"nowait,omitempty" description:"if true indicates that the server should not wait for a log to be available before returning; defaults to false"` + + // Version of the deploymentConfig for which to view logs. + Version *int `json:"version,omitempty" description:"the version of the deploymentConfig for which to view logs"` +} diff --git a/pkg/deploy/api/validation/validation.go b/pkg/deploy/api/validation/validation.go index 35621be3c366..8f6f99e48009 100644 --- a/pkg/deploy/api/validation/validation.go +++ b/pkg/deploy/api/validation/validation.go @@ -336,3 +336,13 @@ func IsValidPercent(percent string) bool { } const isNegativeErrorMsg string = `must be non-negative` + +func ValidateDeploymentLogOptions(opts *deployapi.DeploymentLogOptions) fielderrors.ValidationErrorList { + errs := fielderrors.ValidationErrorList{} + + if opts.Version != nil && *opts.Version <= 0 { + errs = append(errs, fielderrors.NewFieldInvalid("version", *opts.Version, "deployment version must be greater than 0")) + } + + return errs +} diff --git a/pkg/deploy/registry/deploylog/doc.go b/pkg/deploy/registry/deploylog/doc.go new file mode 100644 index 000000000000..6406df1edb82 --- /dev/null +++ b/pkg/deploy/registry/deploylog/doc.go @@ -0,0 +1,2 @@ +// Package deploylog provides a Registry interface for retrieving deployment logs +package deploylog diff --git a/pkg/deploy/registry/deploylog/rest.go b/pkg/deploy/registry/deploylog/rest.go new file mode 100644 index 000000000000..f8162c76dab3 --- /dev/null +++ b/pkg/deploy/registry/deploylog/rest.go @@ -0,0 +1,169 @@ +package deploylog + +import ( + "fmt" + "time" + + "github.com/golang/glog" + kapi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + kclient "k8s.io/kubernetes/pkg/client/unversioned" + genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" + "k8s.io/kubernetes/pkg/registry/pod" + "k8s.io/kubernetes/pkg/runtime" + + "github.com/openshift/origin/pkg/client" + deployapi "github.com/openshift/origin/pkg/deploy/api" + "github.com/openshift/origin/pkg/deploy/api/validation" + "github.com/openshift/origin/pkg/deploy/registry" + deployutil "github.com/openshift/origin/pkg/deploy/util" +) + +// defaultTimeout is the default time to wait for the logs of a deployment +const defaultTimeout time.Duration = 10 * time.Second + +// REST is an implementation of RESTStorage for the api server. +type REST struct { + ConfigGetter client.DeploymentConfigsNamespacer + DeploymentGetter kclient.ReplicationControllersNamespacer + PodGetter pod.ResourceGetter + ConnectionInfo kclient.ConnectionInfoGetter + Timeout time.Duration +} + +// REST implements GetterWithOptions +var _ = rest.GetterWithOptions(&REST{}) + +// NewREST creates a new REST for DeploymentLogs. It uses three clients: one for configs, +// one for deployments (replication controllers) and one for pods to get the necessary +// attributes to assemble the URL to which the request shall be redirected in order to +// get the deployment logs. +func NewREST(dn client.DeploymentConfigsNamespacer, rn kclient.ReplicationControllersNamespacer, pn kclient.PodsNamespacer, connectionInfo kclient.ConnectionInfoGetter) *REST { + return &REST{ + ConfigGetter: dn, + DeploymentGetter: rn, + PodGetter: &podGetter{pn}, + ConnectionInfo: connectionInfo, + Timeout: defaultTimeout, + } +} + +// NewGetOptions returns a new options object for deployment logs +func (r *REST) NewGetOptions() (runtime.Object, bool, string) { + return &deployapi.DeploymentLogOptions{}, false, "" +} + +// New creates an empty DeploymentLog resource +func (r *REST) New() runtime.Object { + return &deployapi.DeploymentLog{} +} + +// Get returns a streamer resource with the contents of the deployment log +func (r *REST) Get(ctx kapi.Context, name string, opts runtime.Object) (runtime.Object, error) { + // Ensure we have a namespace in the context + namespace, ok := kapi.NamespaceFrom(ctx) + if !ok { + return nil, errors.NewBadRequest("namespace parameter required.") + } + + // Validate DeploymentLogOptions + deployLogOpts, ok := opts.(*deployapi.DeploymentLogOptions) + if !ok { + return nil, errors.NewBadRequest("did not get an expected options.") + } + if errs := validation.ValidateDeploymentLogOptions(deployLogOpts); len(errs) > 0 { + return nil, errors.NewInvalid("deploymentLogOptions", "", errs) + } + + // Fetch deploymentConfig and check latest version; if 0, there are no deployments + // for this config + config, err := r.ConfigGetter.DeploymentConfigs(namespace).Get(name) + if err != nil { + return nil, errors.NewNotFound("deploymentConfig", name) + } + desiredVersion := config.LatestVersion + if desiredVersion == 0 { + return nil, errors.NewBadRequest(fmt.Sprintf("no deployment exists for deploymentConfig %q", config.Name)) + } + + // Support retrieving logs for older deployments + switch { + case deployLogOpts.Version == nil: + // Latest + case *deployLogOpts.Version <= 0 || *deployLogOpts.Version > config.LatestVersion: + // Invalid version + return nil, errors.NewBadRequest(fmt.Sprintf("invalid version for deploymentConfig %q: %d", config.Name, *deployLogOpts.Version)) + default: + desiredVersion = *deployLogOpts.Version + } + + // Get desired deployment + targetName := deployutil.DeploymentNameForConfigVersion(config.Name, desiredVersion) + target, err := r.DeploymentGetter.ReplicationControllers(namespace).Get(targetName) + if err != nil { + return nil, err + } + + // Check for deployment status; if it is new or pending, we will wait for it. If it is complete, + // the deployment completed successfully and the deployer pod will be deleted so we will return a + // success message. If it is running or failed, retrieve the log from the deployer pod. + status := deployutil.DeploymentStatusFor(target) + switch status { + case deployapi.DeploymentStatusNew, deployapi.DeploymentStatusPending: + if deployLogOpts.NoWait { + glog.V(4).Infof("Deployment %s is in %s state. No logs to retrieve yet.", deployutil.LabelForDeployment(target), status) + return &genericrest.LocationStreamer{}, nil + } + glog.V(4).Infof("Deployment %s is in %s state, waiting for it to start...", deployutil.LabelForDeployment(target), status) + + latest, ok, err := registry.WaitForRunningDeployment(r.DeploymentGetter, target, r.Timeout) + if err != nil { + return nil, errors.NewBadRequest(fmt.Sprintf("unable to wait for deployment %s to run: %v", deployutil.LabelForDeployment(target), err)) + } + if !ok { + return nil, errors.NewTimeoutError(fmt.Sprintf("timed out waiting for deployment %s to start after %s", deployutil.LabelForDeployment(target), r.Timeout), 1) + } + if deployutil.DeploymentStatusFor(latest) == deployapi.DeploymentStatusComplete { + // Deployer pod has been deleted, no logs to retrieve + glog.V(4).Infof("Deployment %s was successful so the deployer pod is deleted. No logs to retrieve.", deployutil.LabelForDeployment(target)) + return &genericrest.LocationStreamer{}, nil + } + case deployapi.DeploymentStatusComplete: + // Deployer pod has been deleted, no logs to retrieve + glog.V(4).Infof("Deployment %s was successful so the deployer pod is deleted. No logs to retrieve.", deployutil.LabelForDeployment(target)) + return &genericrest.LocationStreamer{}, nil + } + + // Setup url of the deployer pod + deployPodName := deployutil.DeployerPodNameForDeployment(target.Name) + logOpts := &kapi.PodLogOptions{ + Follow: deployLogOpts.Follow, + } + location, transport, err := pod.LogLocation(r.PodGetter, r.ConnectionInfo, ctx, deployPodName, logOpts) + if err != nil { + return nil, errors.NewBadRequest(err.Error()) + } + + return &genericrest.LocationStreamer{ + Location: location, + Transport: transport, + ContentType: "text/plain", + Flush: deployLogOpts.Follow, + }, nil +} + +// podGetter implements the ResourceGetter interface. Used by LogLocation to +// retrieve the deployer pod +type podGetter struct { + podsNamespacer kclient.PodsNamespacer +} + +// Get is responsible for retrieving the deployer pod +func (g *podGetter) Get(ctx kapi.Context, name string) (runtime.Object, error) { + namespace, ok := kapi.NamespaceFrom(ctx) + if !ok { + return nil, errors.NewBadRequest("namespace parameter required.") + } + return g.podsNamespacer.Pods(namespace).Get(name) +} diff --git a/pkg/deploy/registry/deploylog/rest_test.go b/pkg/deploy/registry/deploylog/rest_test.go new file mode 100644 index 000000000000..5c8d6f76713b --- /dev/null +++ b/pkg/deploy/registry/deploylog/rest_test.go @@ -0,0 +1,165 @@ +package deploylog + +import ( + "net/http" + "net/url" + "reflect" + "testing" + + kapi "k8s.io/kubernetes/pkg/api" + kclient "k8s.io/kubernetes/pkg/client/unversioned" + ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient" + genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + + "github.com/openshift/origin/pkg/client/testclient" + "github.com/openshift/origin/pkg/deploy/api" + deploytest "github.com/openshift/origin/pkg/deploy/api/test" + deployutil "github.com/openshift/origin/pkg/deploy/util" +) + +func makeDeployment(version int) kapi.ReplicationController { + deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(version), kapi.Codec) + return *deployment +} + +func makeDeploymentList(versions int) *kapi.ReplicationControllerList { + list := &kapi.ReplicationControllerList{} + for v := 1; v <= versions; v++ { + list.Items = append(list.Items, makeDeployment(v)) + } + return list +} + +// Mock pod resource getter +type deployerPodGetter struct{} + +func (p *deployerPodGetter) Get(ctx kapi.Context, name string) (runtime.Object, error) { + return &kapi.Pod{ + ObjectMeta: kapi.ObjectMeta{ + Name: name, + Namespace: kapi.NamespaceDefault, + }, + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + { + Name: name + "-container", + }, + }, + NodeName: name + "-host", + }, + }, nil +} + +// mockREST mocks a DeploymentLog REST +func mockREST(version, desired int, endStatus api.DeploymentStatus) *REST { + // Fake deploymentConfig + config := deploytest.OkDeploymentConfig(version) + fakeDn := testclient.NewSimpleFake(config) + fakeDn.PrependReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { + return true, config, nil + }) + // Fake deployments + fakeDeployments := makeDeploymentList(version) + fakeRn := ktestclient.NewSimpleFake(fakeDeployments) + fakeRn.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { + return true, &fakeDeployments.Items[desired-1], nil + }) + // Fake watcher for deployments + fakeWatch := watch.NewFake() + fakeRn.PrependWatchReactor("replicationcontrollers", ktestclient.DefaultWatchReactor(fakeWatch, nil)) + // Everything is fake + connectionInfo := &kclient.HTTPKubeletClient{Config: &kclient.KubeletConfig{EnableHttps: true, Port: 12345}, Client: &http.Client{}} + + obj := &fakeDeployments.Items[desired-1] + obj.Annotations[api.DeploymentStatusAnnotation] = string(endStatus) + go fakeWatch.Add(obj) + + return &REST{ + ConfigGetter: fakeDn, + DeploymentGetter: fakeRn, + PodGetter: &deployerPodGetter{}, + ConnectionInfo: connectionInfo, + Timeout: defaultTimeout, + } +} + +func TestRESTGet(t *testing.T) { + ctx := kapi.NewDefaultContext() + + tests := []struct { + testName string + rest *REST + name string + opts runtime.Object + expected runtime.Object + expectedErr error + }{ + { + testName: "running deployment", + rest: mockREST(1, 1, api.DeploymentStatusRunning), + name: "config", + opts: &api.DeploymentLogOptions{Follow: true, Version: intp(1)}, + expected: &genericrest.LocationStreamer{ + Location: &url.URL{ + Scheme: "https", + Host: "config-1-deploy-host:12345", + Path: "/containerLogs/default/config-1-deploy/config-1-deploy-container", + RawQuery: "follow=true", + }, + Transport: nil, + ContentType: "text/plain", + Flush: true, + }, + expectedErr: nil, + }, + { + testName: "complete deployment", + rest: mockREST(5, 5, api.DeploymentStatusComplete), + name: "config", + opts: &api.DeploymentLogOptions{Follow: true, Version: intp(5)}, + expected: &genericrest.LocationStreamer{}, + expectedErr: nil, + }, + { + testName: "previous failed deployment", + rest: mockREST(3, 2, api.DeploymentStatusFailed), + name: "config", + opts: &api.DeploymentLogOptions{Follow: false, Version: intp(2)}, + expected: &genericrest.LocationStreamer{ + Location: &url.URL{ + Scheme: "https", + Host: "config-2-deploy-host:12345", + Path: "/containerLogs/default/config-2-deploy/config-2-deploy-container", + }, + Transport: nil, + ContentType: "text/plain", + Flush: false, + }, + expectedErr: nil, + }, + } + + for _, test := range tests { + got, err := test.rest.Get(ctx, test.name, test.opts) + if err != test.expectedErr { + t.Errorf("%s: error mismatch: expected %v, got %v", test.testName, test.expectedErr, err) + continue + } + if !reflect.DeepEqual(got, test.expected) { + t.Errorf("%s: location streamer mismatch: expected\n%#v\ngot\n%#v\n", test.testName, test.expected, got) + if testing.Verbose() { + e := test.expected.(*genericrest.LocationStreamer) + a := got.(*genericrest.LocationStreamer) + t.Errorf("%s: expected url:\n%v\ngot:\n%v\n", test.testName, e.Location, a.Location) + } + } + } +} + +// TODO: These kind of functions seem to be used in lots of places +// We should move it in a common location +func intp(num int) *int { + return &num +} diff --git a/pkg/deploy/registry/rest.go b/pkg/deploy/registry/rest.go new file mode 100644 index 000000000000..78284d6134e3 --- /dev/null +++ b/pkg/deploy/registry/rest.go @@ -0,0 +1,56 @@ +package registry + +import ( + "errors" + "time" + + kapi "k8s.io/kubernetes/pkg/api" + kclient "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + + "github.com/openshift/origin/pkg/deploy/api" + deployutil "github.com/openshift/origin/pkg/deploy/util" +) + +var ( + // ErrUnknownDeploymentPhase is returned for WaitForRunningDeployment if an unknown phase is returned. + ErrUnknownDeploymentPhase = errors.New("unknown deployment phase") +) + +// WaitForRunningDeployment waits until the specified deployment is no longer New or Pending. Returns true if +// the deployment became running, complete, or failed within timeout, false if it did not, and an error if any +// other error state occurred. The last observed deployment state is returned. +func WaitForRunningDeployment(rn kclient.ReplicationControllersNamespacer, observed *kapi.ReplicationController, timeout time.Duration) (*kapi.ReplicationController, bool, error) { + fieldSelector := fields.Set{"metadata.name": observed.Name}.AsSelector() + w, err := rn.ReplicationControllers(observed.Namespace).Watch(labels.Everything(), fieldSelector, observed.ResourceVersion) + if err != nil { + return observed, false, err + } + defer w.Stop() + + ch := w.ResultChan() + // Passing time.After like this (vs receiving directly in a select) will trigger the channel + // and the timeout will have full effect here. + expire := time.After(timeout) + for { + select { + case event := <-ch: + obj, ok := event.Object.(*kapi.ReplicationController) + if !ok { + return observed, false, errors.New("received unknown object while watching for deployments") + } + observed = obj + + switch deployutil.DeploymentStatusFor(observed) { + case api.DeploymentStatusRunning, api.DeploymentStatusFailed, api.DeploymentStatusComplete: + return observed, true, nil + case api.DeploymentStatusNew, api.DeploymentStatusPending: + default: + return observed, false, ErrUnknownDeploymentPhase + } + case <-expire: + return observed, false, nil + } + } +} diff --git a/pkg/deploy/registry/rollback/rest.go b/pkg/deploy/registry/rollback/rest.go index 11a98022e6e2..e1959f03e5e9 100644 --- a/pkg/deploy/registry/rollback/rest.go +++ b/pkg/deploy/registry/rollback/rest.go @@ -33,12 +33,17 @@ type Client struct { DCFn func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) } +// GetDeployment returns the deploymentConfig with the provided context and name func (c Client) GetDeploymentConfig(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) { return c.DCFn(ctx, name) } + +// GetDeployment returns the deployment with the provided context and name func (c Client) GetDeployment(ctx kapi.Context, name string) (*kapi.ReplicationController, error) { return c.RCFn(ctx, name) } + +// GenerateRollback generates a new deploymentConfig by merging a pair of deploymentConfigs func (c Client) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) { return c.GRFn(from, to, spec) } @@ -51,6 +56,7 @@ func NewREST(generator GeneratorClient, codec runtime.Codec) *REST { } } +// New creates an empty DeploymentConfigRollback resource func (s *REST) New() runtime.Object { return &deployapi.DeploymentConfigRollback{} } diff --git a/pkg/deploy/util/util.go b/pkg/deploy/util/util.go index c74a9574cd6c..388122a37486 100644 --- a/pkg/deploy/util/util.go +++ b/pkg/deploy/util/util.go @@ -49,6 +49,12 @@ func LabelForDeploymentConfig(config *deployapi.DeploymentConfig) string { return fmt.Sprintf("%s/%s:%d", config.Namespace, config.Name, config.LatestVersion) } +// DeploymentNameForConfigVersion returns the name of the version-th deployment +// for the config that has the provided name +func DeploymentNameForConfigVersion(name string, version int) string { + return fmt.Sprintf("%s-%d", name, version) +} + // ConfigSelector returns a label Selector which can be used to find all // deployments for a DeploymentConfig. // diff --git a/test/end-to-end/core.sh b/test/end-to-end/core.sh index d98bf1854927..bf0aaf0563fa 100755 --- a/test/end-to-end/core.sh +++ b/test/end-to-end/core.sh @@ -19,6 +19,7 @@ function wait_for_app() { echo "[INFO] Waiting for app in namespace $1" echo "[INFO] Waiting for database pod to start" wait_for_command "oc get -n $1 pods -l name=database | grep -i Running" $((60*TIME_SEC)) + oc logs dc/database -n $1 --follow echo "[INFO] Waiting for database service to start" wait_for_command "oc get -n $1 services | grep database" $((20*TIME_SEC)) @@ -26,6 +27,7 @@ function wait_for_app() { echo "[INFO] Waiting for frontend pod to start" wait_for_command "oc get -n $1 pods | grep frontend | grep -i Running" $((120*TIME_SEC)) + oc logs dc/frontend -n $1 --follow echo "[INFO] Waiting for frontend service to start" wait_for_command "oc get -n $1 services | grep frontend" $((20*TIME_SEC))