Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow namespace specification via parameter in templates #12918

Merged
merged 1 commit into from
Feb 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/swagger-spec/oapi-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -28593,7 +28593,7 @@
"items": {
"$ref": "runtime.RawExtension"
},
"description": "objects is an array of resources to include in this template."
"description": "objects is an array of resources to include in this template. If a namespace value is hardcoded in the object, it will be removed during template instantiation, however if the namespace value is, or contains, a ${PARAMETER_REFERENCE}, the resolved value after parameter substitution will be respected and the object will be created in that namespace."
},
"parameters": {
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion api/swagger-spec/openshift-openapi-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -56428,7 +56428,7 @@
"$ref": "#/definitions/v1.ObjectMeta"
},
"objects": {
"description": "objects is an array of resources to include in this template.",
"description": "objects is an array of resources to include in this template. If a namespace value is hardcoded in the object, it will be removed during template instantiation, however if the namespace value is, or contains, a ${PARAMETER_REFERENCE}, the resolved value after parameter substitution will be respected and the object will be created in that namespace.",
"type": "array",
"items": {
"$ref": "#/definitions/runtime.RawExtension"
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ func HaltOnError(fn AfterFunc) AfterFunc {

// Create is the default create operation for a generic resource.
func Create(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) {
if len(info.Namespace) > 0 {
namespace = info.Namespace
}
return resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, obj)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/openapi/zz_generated.openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -23696,7 +23696,7 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
},
"objects": {
SchemaProps: spec.SchemaProps{
Description: "objects is an array of resources to include in this template.",
Description: "objects is an array of resources to include in this template. If a namespace value is hardcoded in the object, it will be removed during template instantiation, however if the namespace value is, or contains, a ${PARAMETER_REFERENCE}, the resolved value after parameter substitution will be respected and the object will be created in that namespace.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
Expand Down
5 changes: 5 additions & 0 deletions pkg/template/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ type Template struct {
Parameters []Parameter

// objects is an array of resources to include in this template.
// If a namespace value is hardcoded in the object, it will be removed
// during template instantiation, however if the namespace value
// is, or contains, a ${PARAMETER_REFERENCE}, the resolved
// value after parameter substitution will be respected and the object
// will be created in that namespace.
Objects []runtime.Object

// objectLabels is an optional set of labels that are applied to every
Expand Down
5 changes: 5 additions & 0 deletions pkg/template/api/v1/generated.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/template/api/v1/swagger_doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var map_Template = map[string]string{
"": "Template contains the inputs needed to produce a Config.",
"metadata": "Standard object's metadata.",
"message": "message is an optional instructional message that will be displayed when this template is instantiated. This field should inform the user how to utilize the newly created resources. Parameter substitution will be performed on the message before being displayed so that generated credentials and other parameters can be included in the output.",
"objects": "objects is an array of resources to include in this template.",
"objects": "objects is an array of resources to include in this template. If a namespace value is hardcoded in the object, it will be removed during template instantiation, however if the namespace value is, or contains, a ${PARAMETER_REFERENCE}, the resolved value after parameter substitution will be respected and the object will be created in that namespace.",
"parameters": "parameters is an optional array of Parameters used during the Template to Config transformation.",
"labels": "labels is a optional set of labels that are applied to every object during the Template to Config transformation.",
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/template/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ type Template struct {
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`

// objects is an array of resources to include in this template.
// If a namespace value is hardcoded in the object, it will be removed
// during template instantiation, however if the namespace value
// is, or contains, a ${PARAMETER_REFERENCE}, the resolved
// value after parameter substitution will be respected and the object
// will be created in that namespace.
Objects []runtime.RawExtension `json:"objects" protobuf:"bytes,3,rep,name=objects"`

// parameters is an optional array of Parameters used during the
Expand Down
19 changes: 10 additions & 9 deletions pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@ func (p *Processor) Process(template *api.Template) field.ErrorList {
item = decodedObj
}

// If an object definition's metadata includes a hardcoded namespace field, the field will be stripped out of
// the definition during template instantiation. Namespace fields that contain a ${PARAMETER_REFERENCE}
// will be left in place, resolved during parameter substition, and the object will be created in the
// referenced namespace.
stripNamespace(item)

newItem, err := p.SubstituteParameters(paramMap, item)
if err != nil {
templateErrors = append(templateErrors, field.Invalid(idxPath.Child("parameters"), template.Parameters, err.Error()))
}
// If an object definition's metadata includes a namespace field, the field will be stripped out of
// the definition during template instantiation. This is necessary because all objects created during
// instantiation are placed into the target namespace, so it would be invalid for the object to declare
//a different namespace.
stripNamespace(newItem)
if err := util.AddObjectLabels(newItem, template.ObjectLabels); err != nil {
templateErrors = append(templateErrors, field.Invalid(idxPath.Child("labels"),
template.ObjectLabels, fmt.Sprintf("label could not be applied: %v", err)))
Expand All @@ -85,22 +86,22 @@ func (p *Processor) Process(template *api.Template) field.ErrorList {
}

func stripNamespace(obj runtime.Object) {
// Remove namespace from the item
if itemMeta, err := meta.Accessor(obj); err == nil && len(itemMeta.GetNamespace()) > 0 {
// Remove namespace from the item unless it contains a ${PARAMETER_REFERENCE}
if itemMeta, err := meta.Accessor(obj); err == nil && len(itemMeta.GetNamespace()) > 0 && !stringParameterExp.MatchString(itemMeta.GetNamespace()) {
itemMeta.SetNamespace("")
return
}
// TODO: allow meta.Accessor to handle runtime.Unstructured
if unstruct, ok := obj.(*runtime.Unstructured); ok && unstruct.Object != nil {
if obj, ok := unstruct.Object["metadata"]; ok {
if m, ok := obj.(map[string]interface{}); ok {
if _, ok := m["namespace"]; ok {
if _, ok := m["namespace"]; ok && !stringParameterExp.MatchString(m["namespace"].(string)) {
m["namespace"] = ""
}
}
return
}
if _, ok := unstruct.Object["namespace"]; ok {
if _, ok := unstruct.Object["namespace"]; ok && stringParameterExp.MatchString(unstruct.Object["namespace"].(string)) {
unstruct.Object["namespace"] = ""
return
}
Expand Down
19 changes: 19 additions & 0 deletions test/cmd/newapp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ trap os::test::junit::reconcile_output EXIT
(
set +e
# oc delete all,templates --all
oc delete-project template-substitute
oc delete-project prefix-template-substitute
exit 0
) &>/dev/null

Expand Down Expand Up @@ -50,6 +52,23 @@ os::cmd::expect_failure 'oc get dc/mysql'
os::cmd::expect_failure 'oc get dc/php'
os::cmd::expect_success_and_text 'oc new-app -f test/testdata/template-without-app-label.json -o yaml' 'app: ruby-helloworld-sample'

# check object namespace handling
# hardcoded values should be stripped
os::cmd::expect_success_and_not_text 'oc new-app -f test/testdata/template-with-namespaces.json -o jsonpath="{.items[?(@.metadata.name==\"stripped\")].metadata.namespace}"' 'STRIPPED'
# normal parameterized values should be substituted and retained
os::cmd::expect_success_and_text 'oc new-app -f test/testdata/template-with-namespaces.json -o jsonpath="{.items[?(@.metadata.name==\"route-edge-substituted\")].metadata.namespace}"' 'substituted'
os::cmd::expect_success_and_text 'oc new-app -f test/testdata/template-with-namespaces.json -o jsonpath="{.items[?(@.metadata.name==\"route-edge-prefix-substituted\")].metadata.namespace}"' 'prefix-substituted'
# non-string parameterized values should be stripped
os::cmd::expect_failure_and_text 'oc new-app -f test/testdata/template-with-namespaces.json -o jsonpath="{.items[?(@.metadata.name==\"route-edge-refstripped\")].metadata.namespace}"' 'namespace is not found'
os::cmd::expect_failure_and_text 'oc new-app -f test/testdata/template-with-namespaces.json -o jsonpath="{.items[?(@.metadata.name==\"route-edge-prefix-refstripped\")].metadata.namespace}"' 'namespace is not found'
# ensure the objects can actually get created with a namespace specified
project=$(oc project -q)
os::cmd::expect_success 'oc new-project template-substitute'
os::cmd::expect_success 'oc new-project prefix-template-substitute'
os::cmd::expect_success 'oc project ${project}'
os::cmd::expect_success 'oc new-app -f test/testdata/template-with-namespaces.json -p SUBSTITUTED=template-substitute'
os::cmd::expect_success 'oc delete all -l app=ruby-helloworld-sample'

# ensure non-duplicate invalid label errors show up
os::cmd::expect_failure_and_text 'oc new-app nginx -l qwer1345%$$#=self' 'error: ImageStream "nginx" is invalid'
os::cmd::expect_failure_and_text 'oc new-app nginx -l qwer1345%$$#=self' 'DeploymentConfig "nginx" is invalid'
Expand Down
121 changes: 121 additions & 0 deletions test/testdata/template-with-namespaces.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{
"kind": "Template",
"apiVersion": "v1",
"metadata": {
"name": "ruby-helloworld-sample",
"creationTimestamp": null,
"annotations": {
"description": "some objects in this template declare their own namespace via a parameter confirm new-app will tolerate it",
"iconClass": "icon-ruby",
"tags": "instant-app,ruby,mysql"
}
},
"objects": [
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "route-edge-stripped",
"namespace": "STRIPPED"
},
"spec": {
"host": "www.example.com",
"to": {
"kind": "Service",
"name": "frontend"
},
"tls": {
"termination": "edge"
}
},
"status": {}
},
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "route-edge-substituted",
"namespace": "${SUBSTITUTED}"
},
"spec": {
"host": "www.example.com",
"to": {
"kind": "Service",
"name": "frontend"
},
"tls": {
"termination": "edge"
}
},
"status": {}
},
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "route-edge-prefix-substituted",
"namespace": "prefix-${SUBSTITUTED}"
},
"spec": {
"host": "www.example.com",
"to": {
"kind": "Service",
"name": "frontend"
},
"tls": {
"termination": "edge"
}
},
"status": {}
},
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "route-edge-refstripped",
"namespace": "${{SUBSTITUTED}}"
},
"spec": {
"host": "www.example.com",
"to": {
"kind": "Service",
"name": "frontend"
},
"tls": {
"termination": "edge"
}
},
"status": {}
},
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "route-edge-prefix-refstripped",
"namespace": "prefix-${{SUBSTITUTED}}"
},
"spec": {
"host": "www.example.com",
"to": {
"kind": "Service",
"name": "frontend"
},
"tls": {
"termination": "edge"
}
},
"status": {}
}
],
"parameters": [
{
"name": "SUBSTITUTED",
"description": "namespace value",
"value": "substituted",
"required": true
}
],
"labels": {
"template": "application-template-stibuild"
}
}