This is an automated email from the ASF dual-hosted git repository. houshengbo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-wskdeploy.git
The following commit(s) were added to refs/heads/master by this push: new 2876555 WIP: JSON Output support (#607) 2876555 is described below commit 2876555455464970cc73a7b2532054d246a66b4d Author: Matt Rutkowski <mrutk...@us.ibm.com> AuthorDate: Mon Oct 23 14:29:21 2017 -0500 WIP: JSON Output support (#607) JSON Output support --- parsers/manifest_parser.go | 253 ++++++++++++++++----- parsers/manifest_parser_test.go | 195 +++++++--------- parsers/yamlparser.go | 2 +- tests/dat/manifest_bad_yaml_2.yaml | 8 - tests/dat/manifest_bad_yaml_3.yaml | 8 - tests/dat/manifest_bad_yaml_4.yaml | 6 - ...yaml => manifest_bad_yaml_invalid_comment.yaml} | 2 +- ...anifest_bad_yaml_invalid_key_mapping_value.yaml | 8 + .../dat/manifest_bad_yaml_invalid_package_key.yaml | 7 + tests/dat/manifest_bad_yaml_missing_root_key.yaml | 6 + tests/dat/manifest_data_compose_triggers.yaml | 12 + tests/dat/manifest_validate_params.yaml | 2 +- tests/src/integration/common/wskdeploy.go | 2 +- utils/flags.go | 4 +- utils/misc_test.go | 26 +-- utils/wskdeployerror.go | 39 +++- 16 files changed, 368 insertions(+), 212 deletions(-) diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go index 08c5c99..c6ce6d2 100644 --- a/parsers/manifest_parser.go +++ b/parsers/manifest_parser.go @@ -376,16 +376,20 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, // set the name of the action (which is the key) action.Name = key + // Create action data object with CLI + wskaction := new(whisk.Action) + wskaction.Exec = new(whisk.Exec) + + /* + * Action.Function + */ //set action.Function to action.Location //because Location is deprecated in Action entity if action.Function == "" && action.Location != "" { action.Function = action.Location } - wskaction := new(whisk.Action) //bind action, and exposed URL - - wskaction.Exec = new(whisk.Exec) if action.Function != "" { filePath := strings.TrimRight(filePath, splitFilePath[len(splitFilePath)-1]) + action.Function @@ -419,7 +423,7 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, kind = "nodejs:6" errStr := wski18n.T("Unsupported runtime type, set to nodejs") whisk.Debug(whisk.DbgWarn, errStr) - //add the user input kind here + // TODO() add the user input kind here if interactive } wskaction.Exec.Kind = kind @@ -441,6 +445,9 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, } + /* + * Action.Runtime + */ if action.Runtime != "" { if utils.CheckExistRuntime(action.Runtime, utils.Rts) { wskaction.Exec.Kind = action.Runtime @@ -458,11 +465,13 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, wskaction.Exec.Main = action.Main } + /* + * Action.Inputs + */ keyValArr := make(whisk.KeyValueArr, 0) for name, param := range action.Inputs { var keyVal whisk.KeyValue keyVal.Key = name - keyVal.Value, errorParser = ResolveParameter(name, ¶m, filePath) if errorParser != nil { @@ -474,19 +483,51 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, } } + // if we have successfully parser valid key/value parameters if len(keyValArr) > 0 { wskaction.Parameters = keyValArr } + /* + * Action.Outputs + */ + keyValArr = make(whisk.KeyValueArr, 0) + for name, param := range action.Outputs { + var keyVal whisk.KeyValue + keyVal.Key = name + keyVal.Value, errorParser = ResolveParameter(name, ¶m, filePath) + + // short circuit on error + if errorParser != nil { + return nil, errorParser + } + + if keyVal.Value != nil { + keyValArr = append(keyValArr, keyVal) + } + } + + // TODO{} add outputs as annotations (work to discuss officially supporting for compositions) + if len(keyValArr) > 0 { + //wskaction.Annotations // TBD + } + + /* + * Action.Annotations + */ keyValArr = make(whisk.KeyValueArr, 0) for name, value := range action.Annotations { var keyVal whisk.KeyValue keyVal.Key = name keyVal.Value = utils.GetEnvVar(value) - keyValArr = append(keyValArr, keyVal) + // TODO{} Fix Annottions; they are not added to Action if web-export key is not present + // Need to assure annotations are added/set even if web-export is not set on the action. } + /* + * Web Export + */ // only set the webaction when the annotations are not empty. if action.Webexport == "true" { // TODO() why is this commented out? we should now support annotations... @@ -497,7 +538,9 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, } } - //set limitations + /* + * Action.Limits + */ if action.Limits!=nil { wsklimits := new(whisk.Limits) if utils.LimitsTimeoutValidation(action.Limits.Timeout) { @@ -769,97 +812,192 @@ func ResolveParamTypeFromValue(name string, value interface{}, filePath string) return paramType, err } -// Resolve input parameter (i.e., type, value, default) -// Note: parameter values may set later (overriddNen) by an (optional) Deployment file -func ResolveParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { - var errorParser error - var tempType string - // default parameter value to empty string - var value interface{} = "" +/* + resolveSingleLineParameter assures that a Parameter's Type is correctly identified and set from its Value. - // Trace Parameter struct before any resolution - //dumpParameter(paramName, param, "BEFORE") + Additionally, this function: + + - detects if the parameter value contains the name of a valid OpenWhisk parameter types. if so, the + - param.Type is set to detected OpenWhisk parameter type. + - param.Value is set to the zero (default) value for that OpenWhisk parameter type. + */ +func resolveSingleLineParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { + var errorParser error - // Parameters can be single OR multi-line declarations which must be processed/validated differently if !param.multiline { - // we have a single-line parameter declaration // We need to identify parameter Type here for later validation param.Type, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath) // In single-line format, the param's <value> can be a "Type name" and NOT an actual value. // if this is the case, we must detect it and set the value to the default for that type name. - if param.Value!=nil && param.Type == "string" { + if param.Value != nil && param.Type == "string" { // The value is a <string>; now we must test if is the name of a known Type - var tempValue = param.Value.(string) - if isValidParameterType(tempValue) { + if isValidParameterType(param.Value.(string)) { // If the value is indeed the name of a Type, we must change BOTH its // Type to be that type and its value to that Type's default value - // (which happens later by setting it to nil here param.Type = param.Value.(string) - param.Value = nil + param.Value = getTypeDefaultValue(param.Type) + fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, param.Value) } } } else { - // we have a multi-line parameter declaration + msgs := []string{"Parameter [" + paramName + "] is not single-line format."} + return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + + return param.Value, errorParser +} + +/* + resolveMultiLineParameter assures that the values for Parameter Type and Value are properly set and are valid. + + Additionally, this function: + - uses param.Default as param.Value if param.Value is not provided + - uses the actual param.Value data type for param.type if param.Type is not provided + + */ +func resolveMultiLineParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { + var errorParser error + + if param.multiline { + var valueType string // if we do not have a value, but have a default, use it for the value if param.Value == nil && param.Default != nil { param.Value = param.Default } - // if we also have a type at this point, verify value (and/or default) matches type, if not error // Note: if either the value or default is in conflict with the type then this is an error - tempType, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath) - - // if we do not have a value or default, but have a type, find its default and use it for the value - if param.Type != "" && !isValidParameterType(param.Type) { - // TODO() - move string to i18n - msgs := []string{"Parameter [" + paramName + "] has an invalid Type. [" + param.Type + "]"} - return value, utils.NewParserErr(filePath, nil, msgs) - } else if param.Type == "" { - param.Type = tempType + valueType, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath) + + // if we have a declared parameter Type, assure that it is a known value + if param.Type != "" { + if !isValidParameterType(param.Type) { + // TODO() - move string to i18n + msgs := []string{"Parameter [" + paramName + "] has an invalid Type. [" + param.Type + "]"} + return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + } else { + // if we do not have a value for the Parameter Type, use the Parameter Value's Type + param.Type = valueType } + + // TODO{} if the declared and actual parameter type conflict, generate TypeMismatch error + //if param.Type != valueType{ + // errorParser = utils.NewParameterTypeMismatchError("", param.Type, valueType ) + //} + } else { + msgs := []string{"Parameter [" + paramName + "] is not multiline format."} + return param.Value, utils.NewParserErr(filePath, nil, msgs) } - // Make sure the parameter's value is a valid, non-empty string and startsWith '$" (dollar) sign - value = utils.GetEnvVar(param.Value) - // JSON - Handle both cases, where value 1) is a string containing JSON, 2) is a map of JSON + return param.Value, errorParser +} - // Case 1: if user set parameter type to 'json' and the value's type is a 'string' - if str, ok := value.(string); ok && param.Type == "json" { - var parsed interface{} - err := json.Unmarshal([]byte(str), &parsed) - if err == nil { - fmt.Printf("EXIT: Parameter type=[%v] value=[%v]\n", param.Type, parsed) - return parsed, err + +/* + resolveJSONParameter assure JSON data is converted to a map[string]{interface*} type. + + This function handles the forms JSON data appears in: + 1) a string containing JSON, which needs to be parsed into map[string]interface{} + 2) is a map of JSON (but not a map[string]interface{} + */ +func resolveJSONParameter(paramName string, param *Parameter, value interface{}, filePath string) (interface{}, error) { + var errorParser error + + if param.Type == "json" { + // Case 1: if user set parameter type to 'json' and the value's type is a 'string' + if str, ok := value.(string); ok { + var parsed interface{} + errParser := json.Unmarshal([]byte(str), &parsed) + if errParser == nil { + //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, parsed) + return parsed, errParser + } } + + // Case 2: value contains a map of JSON + // We must make sure the map type is map[string]interface{}; otherwise we cannot + // marshall it later on to serialize in the body of an HTTP request. + if( param.Value != nil && reflect.TypeOf(param.Value).Kind() == reflect.Map ) { + if _, ok := param.Value.(map[interface{}]interface{}); ok { + var temp map[string]interface{} = + utils.ConvertInterfaceMap(param.Value.(map[interface{}]interface{})) + //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, temp) + return temp, errorParser + } + } // else TODO{} + } else { + msgs := []string{"Parameter [" + paramName + "] is not JSON format."} + return param.Value, utils.NewParserErr(filePath, nil, msgs) } - // Case 2: value contains a map of JSON - // We must make sure the map type is map[string]interface{}; otherwise we cannot - // marshall it later on to serialize in the body of an HTTP request. - if( param.Value != nil && reflect.TypeOf(param.Value).Kind() == reflect.Map ) { - if _, ok := param.Value.(map[interface{}]interface{}); ok { - var temp map[string]interface{} = - utils.ConvertInterfaceMap(param.Value.(map[interface{}]interface{})) - fmt.Printf("EXIT: Parameter type=[%v] value=[%v]\n", param.Type, temp) - return temp, errorParser - } + return param.Value, errorParser +} + +/* + ResolveParameter assures that the Parameter structure's values are correctly filled out for + further processing. This includes special processing for + + - single-line format parameters + - deriving missing param.Type from param.Value + - resolving case where param.Value contains a valid Parameter type name + - multi-line format parameters: + - assures that param.Value is set while taking into account param.Default + - validating param.Type + + Note: parameter values may set later (overridden) by an (optional) Deployment file + + */ +func ResolveParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { + + var errorParser error + // default parameter value to empty string + var value interface{} = "" + + // Trace Parameter struct before any resolution + //dumpParameter(paramName, param, "BEFORE") + + // Parameters can be single OR multi-line declarations which must be processed/validated differently + if !param.multiline { + + // This function will assure that param.Value and param.Type are correctly set + value, errorParser = resolveSingleLineParameter(paramName, param, filePath) + + } else { + + value, errorParser = resolveMultiLineParameter(paramName, param, filePath) } - // Default to an empty string, do NOT error/terminate as Value may be provided later bu a Deployment file. + // String value pre-processing (interpolation) + // See if we have any Environment Variable replacement within the parameter's value + + // Make sure the parameter's value is a valid, non-empty string + if ( param.Value != nil && param.Type == "string") { + // perform $ notation replacement on string if any exist + value = utils.GetEnvVar(param.Value) + } + + // JSON - Handle both cases, where value 1) is a string containing JSON, 2) is a map of JSON + if param.Type == "json" { + value, errorParser = resolveJSONParameter(paramName, param, value, filePath) + } + + // Default value to zero value for the Type + // Do NOT error/terminate as Value may be provided later by a Deployment file. if value == nil { value = getTypeDefaultValue(param.Type) // @TODO(): Need warning message here to warn of default usage, support for warnings (non-fatal) + //msgs := []string{"Parameter [" + paramName + "] is not multiline format."} + //return param.Value, utils.NewParserErr(filePath, nil, msgs) } // Trace Parameter struct after resolution //dumpParameter(paramName, param, "AFTER") - //fmt.Printf("EXIT: Parameter type=[%v] value=[%v]\n", param.Type, value) - + //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, value) return value, errorParser } @@ -912,6 +1050,7 @@ func dumpParameter(paramName string, param *Parameter, separator string) { if param != nil { fmt.Printf("\t\tParameter.Description: [%s]\n", param.Description) fmt.Printf("\t\tParameter.Type: [%s]\n", param.Type) + fmt.Printf("\t\t--> Actual Type: [%T]\n", param.Value) fmt.Printf("\t\tParameter.Value: [%v]\n", param.Value) fmt.Printf("\t\tParameter.Default: [%v]\n", param.Default) } diff --git a/parsers/manifest_parser_test.go b/parsers/manifest_parser_test.go index 39c1a6a..0aef582 100644 --- a/parsers/manifest_parser_test.go +++ b/parsers/manifest_parser_test.go @@ -223,23 +223,8 @@ func TestUnmarshalForMissingPackage(t *testing.T) { /* Test 7: validate manifest_parser:ParseManifest() method for multiline parameters - manifest_parser should be able to parse all different mutliline combinations of - inputs section including: - - case 1: value only - param: - value: <value> - case 2: type only - param: - type: <type> - case 3: type and value only - param: - type: <type> - value: <value> - case 4: default value - param: - type: <type> - default: <default value> + manifest_parser should be able to parse all different multiline combinations of + inputs section. */ func TestParseManifestForMultiLineParams(t *testing.T) { // manifest file is located under ../tests folder @@ -337,16 +322,18 @@ func TestParseManifestForMultiLineParams(t *testing.T) { } } - // validate outputs - // output payload is of type string and has a description - if payload, ok := action.Outputs["payload"]; ok { - p := payload.(map[interface{}]interface{}) - expectedResult = "string" - actualResult = p["type"].(string) - assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) - expectedResult = "parameter dump" - actualResult = p["description"].(string) - assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) + // validate Outputs from this action + for output, param := range action.Outputs { + switch output { + case "payload": + expectedType := "string" + actualType := param.Type + assert.Equal(t, expectedType, actualType, "Expected Type: " + expectedType + ", but got: " + actualType) + expectedDesc := "parameter dump" + actualDesc := param.Description + assert.Equal(t, expectedDesc, actualDesc, "Expected " + expectedDesc + " but got " + actualDesc) + + } } } } @@ -387,7 +374,7 @@ func TestParseManifestForSingleLineParams(t *testing.T) { actualResult = strconv.FormatInt(int64(len(action.Inputs)), 10) assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) - // validate inputs to this action + // validate Inputs to this action for input, param := range action.Inputs { switch input { case "param_simple_string": @@ -445,16 +432,18 @@ func TestParseManifestForSingleLineParams(t *testing.T) { } } - // validate outputs - // output payload is of type string and has a description - if payload, ok := action.Outputs["payload"]; ok { - p := payload.(map[interface{}]interface{}) - expectedResult = "string" - actualResult = p["type"].(string) - assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) - expectedResult = "parameter dump" - actualResult = p["description"].(string) - assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) + // validate Outputs from this action + for output, param := range action.Outputs { + switch output { + case "payload": + expectedResult = "string" + actualResult = param.Type + assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) + + expectedResult = "parameter dump" + actualResult = param.Description + assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult) + } } } } @@ -530,6 +519,7 @@ func TestComposeActionsForInvalidRuntime(t *testing.T) { _, err := p.ComposeActionsFromAllPackages(m, tmpfile.Name()) // (TODO) uncomment the following test case after issue #307 is fixed // (TODO) its failing right now as we are lacking check on invalid runtime + // TODO() https://github.com/apache/incubator-openwhisk-wskdeploy/issues/608 // assert.NotNil(t, err, "Invalid runtime, ComposeActions should report an error") // (TODO) remove this print statement after uncommenting above test case fmt.Println(err) @@ -545,7 +535,12 @@ func TestComposeActionsForSingleLineParams(t *testing.T) { manifestFile := "../tests/dat/manifest_validate_singleline_params.yaml" // read and parse manifest.yaml file p := NewYAMLParser() - m, _ := p.ParseManifest(manifestFile) + m, err := p.ParseManifest(manifestFile) + + if err != nil { + assert.Fail(t, "Failed to parse manifest: " + manifestFile ) + } + actions, err := p.ComposeActionsFromAllPackages(m, manifestFile) if err == nil { @@ -699,7 +694,12 @@ func TestComposeActionsForMultiLineParams(t *testing.T) { manifestFile := "../tests/dat/manifest_validate_multiline_params.yaml" // read and parse manifest.yaml file p := NewYAMLParser() - m, _ := p.ParseManifest(manifestFile) + m, err := p.ParseManifest(manifestFile) + + if err != nil { + assert.Fail(t, "Failed to parse manifest: " + manifestFile ) + } + actions, err := p.ComposeActionsFromAllPackages(m, manifestFile) if err == nil { @@ -954,7 +954,11 @@ func TestParseManifestForJSONParams(t *testing.T) { // manifest file is located under ../tests folder manifestFile := "../tests/dat/manifest_validate_json_params.yaml" // read and parse manifest.yaml file - m, _ := NewYAMLParser().ParseManifest(manifestFile) + m, err := NewYAMLParser().ParseManifest(manifestFile) + + if err != nil { + assert.Fail(t, "Failed to parse manifest: " + manifestFile ) + } // validate package name should be "validate" packageName := "validate_json" @@ -1017,12 +1021,15 @@ func TestParseManifestForJSONParams(t *testing.T) { } } - // TODO{} We do not yet support json outputs - // validate outputs - // output payload is of type string and has a description - //if payload, ok := action.Outputs["fellowship"]; ok { - // p := payload.(map[interface{}]interface{}) - //} + // validate Outputs from this action + for output, param := range action.Outputs { + switch output { + case "fellowship": + expectedType := "json" + actualType := param.Type + assert.Equal(t, expectedType, actualType, "Expected Type: " + expectedType + ", but got: " + actualType) + } + } } } @@ -1108,30 +1115,15 @@ func TestComposeSequences(t *testing.T) { } func TestComposeTriggers(t *testing.T) { - data := `package: - name: helloworld - triggers: - trigger1: - inputs: - name: string - place: string - trigger2: - feed: myfeed - inputs: - name: myname - place: myplace` - tmpfile, err := _createTmpfile(data, "manifest_parser_test_") + // read and parse manifest.yaml file located under ../tests folder + manifestFile := "../tests/dat/manifest_data_compose_triggers.yaml" + p := NewYAMLParser() + m, err := p.ParseManifest(manifestFile) if err != nil { - assert.Fail(t, "Failed to create temp file") + assert.Fail(t, "Failed to parse manifest: " + manifestFile ) } - defer func() { - tmpfile.Close() - os.Remove(tmpfile.Name()) - }() - // read and parse manifest.yaml file - p := NewYAMLParser() - m, _ := p.ParseManifest(tmpfile.Name()) - triggerList, err := p.ComposeTriggersFromAllPackages(m, tmpfile.Name()) + + triggerList, err := p.ComposeTriggersFromAllPackages(m, manifestFile) if err != nil { assert.Fail(t, "Failed to compose trigger") } @@ -1315,65 +1307,42 @@ func TestComposeDependencies(t *testing.T) { } } -func TestInvalidKeyManifestYaml(t *testing.T) { - data := `package: - name: helloWorldTriggerRule - version: 1.0 - invalidKey: test - license: Apache-2.0` - tmpfile, err := _createTmpfile(data, "manifest_parser_test_") - if err != nil { - assert.Fail(t, "Failed to create temp file") - } - defer func() { - tmpfile.Close() - os.Remove(tmpfile.Name()) - }() +func TestBadYAMLInvalidPackageKeyInManifest(t *testing.T) { + // read and parse manifest.yaml file located under ../tests folder p := NewYAMLParser() - _, err = p.ParseManifest(tmpfile.Name()) + _, err := p.ParseManifest("../tests/dat/manifest_bad_yaml_invalid_package_key.yaml") + assert.NotNil(t, err) // go-yaml/yaml prints the wrong line number for mapping values. It should be 4. assert.Contains(t, err.Error(), "line 2: field invalidKey not found in struct parsers.Package") } -func TestMappingValueManifestYaml(t *testing.T) { - data := `package: - name: helloWorldTriggerRule - version: 1.0 - license: Apache-2.0 - actions: test` - tmpfile, err := _createTmpfile(data, "manifest_parser_test_") - if err != nil { - assert.Fail(t, "Failed to create temp file") - } - defer func() { - tmpfile.Close() - os.Remove(tmpfile.Name()) - }() +func TestBadYAMLInvalidKeyMappingValueInManifest(t *testing.T) { + // read and parse manifest.yaml file located under ../tests folder p := NewYAMLParser() - _, err = p.ParseManifest(tmpfile.Name()) + _, err := p.ParseManifest("../tests/dat/manifest_bad_yaml_invalid_key_mapping_value.yaml") + assert.NotNil(t, err) // go-yaml/yaml prints the wrong line number for mapping values. It should be 5. assert.Contains(t, err.Error(), "line 4: mapping values are not allowed in this context") } -func TestMissingRootValueManifestYaml(t *testing.T) { - data := `actions: - helloNodejs: - function: actions/hello.js` - tmpfile, err := _createTmpfile(data, "manifest_parser_test_") - if err != nil { - assert.Fail(t, "Failed to create temp file") - } - defer func() { - tmpfile.Close() - os.Remove(tmpfile.Name()) - }() +func TestBadYAMLMissingRootKeyInManifest(t *testing.T) { + // read and parse manifest.yaml file located under ../tests folder p := NewYAMLParser() - _, err = p.ParseManifest(tmpfile.Name()) + _, err := p.ParseManifest("../tests/dat/manifest_bad_yaml_missing_root_key.yaml") + assert.NotNil(t, err) assert.Contains(t, err.Error(), "line 1: field actions not found in struct parsers.YAML") +} + +func TestBadYAMLInvalidCommentInManifest(t *testing.T) { + // read and parse manifest.yaml file located under ../tests folder + p := NewYAMLParser() + _, err := p.ParseManifest("../tests/dat/manifest_bad_yaml_invalid_comment.yaml") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "line 13: could not find expected ':'") } // validate manifest_parser:Unmarshal() method for package in manifest YAML @@ -1552,7 +1521,7 @@ func TestParseYAML_param(t *testing.T) { panic(err) } - packageName := "manifest6" + packageName := "validateParams" assert.Equal(t, 1, len(manifest.Packages[packageName].Actions), "Get action list failed.") for action_name := range manifest.Packages[packageName].Actions { diff --git a/parsers/yamlparser.go b/parsers/yamlparser.go index 4b7a2db..974ce5b 100644 --- a/parsers/yamlparser.go +++ b/parsers/yamlparser.go @@ -58,7 +58,7 @@ type Action struct { Namespace string `yaml:"namespace"` //used in deployment.yaml Credential string `yaml:"credential"` //used in deployment.yaml Inputs map[string]Parameter `yaml:"inputs"` //used in both manifest.yaml and deployment.yaml - Outputs map[string]interface{} `yaml:"outputs"` //used in manifest.yaml + Outputs map[string]Parameter `yaml:"outputs"` //used in manifest.yaml //mapping to wsk.Action.Name Name string Annotations map[string]interface{} `yaml:"annotations,omitempty"` diff --git a/tests/dat/manifest_bad_yaml_2.yaml b/tests/dat/manifest_bad_yaml_2.yaml deleted file mode 100644 index bd4d895..0000000 --- a/tests/dat/manifest_bad_yaml_2.yaml +++ /dev/null @@ -1,8 +0,0 @@ -package: - name: helloWorldTriggerRule - version: 1.0 - invalidKey: test - license: Apache-2.0 - invalidKey2: test - -# go-yaml/yaml prints the wrong line number for mapping values. It should be 4. diff --git a/tests/dat/manifest_bad_yaml_3.yaml b/tests/dat/manifest_bad_yaml_3.yaml deleted file mode 100644 index bcffb6e..0000000 --- a/tests/dat/manifest_bad_yaml_3.yaml +++ /dev/null @@ -1,8 +0,0 @@ -packages: - helloWorldTriggerRule: - version: 1.0 - license: Apache-2.0 - actions: test - -# go-yaml/yaml prints the wrong line number for mapping values. It should be 5. - diff --git a/tests/dat/manifest_bad_yaml_4.yaml b/tests/dat/manifest_bad_yaml_4.yaml deleted file mode 100644 index da73f3e..0000000 --- a/tests/dat/manifest_bad_yaml_4.yaml +++ /dev/null @@ -1,6 +0,0 @@ -actions: - helloNodejs: - function: actions/hello.js - -# go-yaml/yaml prints the wrong line number for mapping values. It should be 5. - diff --git a/tests/dat/manifest_bad_yaml.yaml b/tests/dat/manifest_bad_yaml_invalid_comment.yaml similarity index 89% rename from tests/dat/manifest_bad_yaml.yaml rename to tests/dat/manifest_bad_yaml_invalid_comment.yaml index e4fcf70..e1330e5 100644 --- a/tests/dat/manifest_bad_yaml.yaml +++ b/tests/dat/manifest_bad_yaml_invalid_comment.yaml @@ -1,5 +1,5 @@ packages: - BadYAML: + testBadYAMLInvalidCommentInManifest: actions: # helloworld action in NodeJS helloNodejs: diff --git a/tests/dat/manifest_bad_yaml_invalid_key_mapping_value.yaml b/tests/dat/manifest_bad_yaml_invalid_key_mapping_value.yaml new file mode 100644 index 0000000..5d0cd70 --- /dev/null +++ b/tests/dat/manifest_bad_yaml_invalid_key_mapping_value.yaml @@ -0,0 +1,8 @@ +packages: + testBadYAMLInvalidKeyMappingValueInManifest: + version: 1.0 + license: Apache-2.0 + actions: test + +# go-yaml/yaml "line 4: mapping values are not allowed in this context". + diff --git a/tests/dat/manifest_bad_yaml_invalid_package_key.yaml b/tests/dat/manifest_bad_yaml_invalid_package_key.yaml new file mode 100644 index 0000000..2d74f7a --- /dev/null +++ b/tests/dat/manifest_bad_yaml_invalid_package_key.yaml @@ -0,0 +1,7 @@ +package: + name: testBadYAMLInvalidPackageKeyInManifest + version: 1.0 + invalidKey: test + license: Apache-2.0 + +# go-yaml/yaml "line 2: field invalidKey not found in struct parsers.Package". diff --git a/tests/dat/manifest_bad_yaml_missing_root_key.yaml b/tests/dat/manifest_bad_yaml_missing_root_key.yaml new file mode 100644 index 0000000..b8d3d0b --- /dev/null +++ b/tests/dat/manifest_bad_yaml_missing_root_key.yaml @@ -0,0 +1,6 @@ +actions: + testBadYAMLMissingRootKeyInManifest: + function: actions/hello.js + +# go-yaml/yaml "line 1: field actions not found in struct parsers.YAML". + diff --git a/tests/dat/manifest_data_compose_triggers.yaml b/tests/dat/manifest_data_compose_triggers.yaml new file mode 100644 index 0000000..1d99fe9 --- /dev/null +++ b/tests/dat/manifest_data_compose_triggers.yaml @@ -0,0 +1,12 @@ +package: + name: trigger_compose + triggers: + trigger1: + inputs: + name: string + place: string + trigger2: + feed: myfeed + inputs: + name: myname + place: myplace diff --git a/tests/dat/manifest_validate_params.yaml b/tests/dat/manifest_validate_params.yaml index 584b7ed..0bd226b 100644 --- a/tests/dat/manifest_validate_params.yaml +++ b/tests/dat/manifest_validate_params.yaml @@ -1,5 +1,5 @@ packages: - manifest6: + validateParams: actions: action1: inputs: diff --git a/tests/src/integration/common/wskdeploy.go b/tests/src/integration/common/wskdeploy.go index b142789..190353a 100644 --- a/tests/src/integration/common/wskdeploy.go +++ b/tests/src/integration/common/wskdeploy.go @@ -200,7 +200,7 @@ func (wskdeploy *Wskdeploy) GetDeploymentObjects(manifestPath string, deployment utils.Rts = utils.DefaultRts } - //invokce ConstructDeploymentPlan to create the in memory objects for deployment + //invoke ConstructDeploymentPlan to create the in memory objects for deployment err = deployer.ConstructDeploymentPlan() if err != nil { return nil,err diff --git a/utils/flags.go b/utils/flags.go index 03390ad..7b35856 100644 --- a/utils/flags.go +++ b/utils/flags.go @@ -33,8 +33,8 @@ var Flags struct { UseDefaults bool UseInteractive bool Strict bool // strict flag to support user defined runtime version. - Key string - Cert string + Key string + Cert string //action flag definition //from go cli diff --git a/utils/misc_test.go b/utils/misc_test.go index 638468c..94203ad 100644 --- a/utils/misc_test.go +++ b/utils/misc_test.go @@ -82,20 +82,20 @@ func TestDependencies(t *testing.T) { func TestParseOpenWhisk(t *testing.T) { openwhiskHost := "https://openwhisk.ng.bluemix.net" - openwhisk, err := ParseOpenWhisk(openwhiskHost) - assert.Equal(t, nil, err, "parse openwhisk info error happened.") - converted := ConvertToMap(openwhisk) - assert.Equal(t, 1, len(converted["nodejs"]), "not expected length") - assert.Equal(t, 1, len(converted["php"]), "not expected length") - assert.Equal(t, 1, len(converted["java"]), "not expected length") - assert.Equal(t, 3, len(converted["python"]), "not expected length") - assert.Equal(t, 2, len(converted["swift"]), "not expected length") + openwhisk, err := ParseOpenWhisk(openwhiskHost) + assert.Equal(t, nil, err, "parse openwhisk info error happened.") + converted := ConvertToMap(openwhisk) + assert.Equal(t, 1, len(converted["nodejs"]), "not expected length") + assert.Equal(t, 1, len(converted["php"]), "not expected length") + assert.Equal(t, 1, len(converted["java"]), "not expected length") + assert.Equal(t, 3, len(converted["python"]), "not expected length") + assert.Equal(t, 2, len(converted["swift"]), "not expected length") } func TestNewZipWritter(t *testing.T) { - filePath := "../tests/src/integration/zipaction/actions/cat" - zipName := filePath + ".zip" - err := NewZipWritter(filePath, zipName).Zip() - defer os.Remove(zipName) - assert.Equal(t, nil, err, "zip folder error happened.") + filePath := "../tests/src/integration/zipaction/actions/cat" + zipName := filePath + ".zip" + err := NewZipWritter(filePath, zipName).Zip() + defer os.Remove(zipName) + assert.Equal(t, nil, err, "zip folder error happened.") } diff --git a/utils/wskdeployerror.go b/utils/wskdeployerror.go index d699184..6613384 100644 --- a/utils/wskdeployerror.go +++ b/utils/wskdeployerror.go @@ -29,6 +29,7 @@ const ( INVALID_YAML_INPUT = "Invalid input of Yaml file" INVALID_YAML_FORMAT = "Invalid input of Yaml format" OPENWHISK_CLIENT_ERROR = "OpenWhisk Client Error" + PARAMETER_TYPE_MISMATCH = "Parameter type mismatch error" MANIFEST_NOT_FOUND = INVALID_YAML_INPUT // TODO{} This should be a unique message. UNKNOWN = "Unknown" UNKNOWN_VALUE = "Unknown value" @@ -89,7 +90,10 @@ func NewErrorManifestFileNotFound(errMessage string) *ErrorManifestFileNotFound } func (e *ErrorManifestFileNotFound) Error() string { - return fmt.Sprintf("%s [%d]: %s =====> %s\n", e.FileName, e.LineNum, e.errorType, e.Message) + if e.errorType == "" { + return fmt.Sprintf("%s [%d]: %s\n", e.FileName, e.LineNum, e.Message) + } + return fmt.Sprintf("%s [%d]: %s ==> %s\n", e.FileName, e.LineNum, e.errorType, e.Message) } type InputYamlFileError struct { @@ -113,6 +117,9 @@ func (e *InputYamlFileError) SetErrorType(errorType string) { } func (e *InputYamlFileError) Error() string { + if e.errorType == "" { + return fmt.Sprintf("%s [%d]: %s\n", e.FileName, e.LineNum, e.Message) + } return fmt.Sprintf("%s [%d]: %s %s\n", e.FileName, e.LineNum, e.errorType, e.Message) } @@ -199,3 +206,33 @@ func (e *ParserErr) Error() string { } return fmt.Sprintf("\n==> %s [%d]: Failed to parse the yaml file: %s: \n%s", fn, e.LineNum, e.YamlFile, strings.Join(result, "\n")) } + +type ParameterTypeMismatchError struct { + BaseErr + errorType string + expectedType string + actualType string +} + +func (e *ParameterTypeMismatchError) Error() string { + if e.errorType == "" { + return fmt.Sprintf("%s [%d]: %s\n", e.FileName, e.LineNum, e.Message) + } + return fmt.Sprintf("%s [%d]: %s ==> %s\n", e.FileName, e.LineNum, e.errorType, e.Message) +} + +func NewParameterTypeMismatchError(errMessage string, expectedType string, actualType string) *ParameterTypeMismatchError { + _, fn, lineNum, _ := runtime.Caller(1) + var err = &ParameterTypeMismatchError{ + // TODO{} add i18n + //errorType: wski18n.T(PARAMETER_TYPE_MISMATCH), + errorType: PARAMETER_TYPE_MISMATCH, + expectedType: expectedType, + actualType: actualType, + } + + err.SetFileName(filepath.Base(fn)) + err.SetLineNum(lineNum) + err.SetMessage(errMessage) + return err +} -- To stop receiving notification emails like this one, please contact ['"commits@openwhisk.apache.org" <commits@openwhisk.apache.org>'].