This is an automated email from the ASF dual-hosted git repository. mrutkowski 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 86f8078 API Gateway deployment/undeployment (#717) 86f8078 is described below commit 86f8078f57cf0e35910c3158c06e541435932c58 Author: Priti Desai <pde...@us.ibm.com> AuthorDate: Fri Feb 9 12:11:16 2018 -0800 API Gateway deployment/undeployment (#717) * Adding support for API Gateway creation * adding more test files * adding undeploy api * Adding getApi method * fixing deleteApi * updating unit test * fixing Whisk Client unit test * removing debug messages * go fmt whiskclient * fixing syntax error delete-books * fixing error reportings * removing todo comment as its addressed here * running go fmt on changed files * one more go fmt * fixing syntax error * fixing syntax error * fixing action name * updating go client dependency version * adding check if token is set * go fmt resources * fixing unit test * trying to fix integration test * removing apihost from manifest * changing apigw host * disabling integration test for now --- Godeps/Godeps.json | 4 +- deployers/manifestreader.go | 9 +- deployers/servicedeployer.go | 80 +++++++- deployers/whiskclient.go | 215 +++++++++++++-------- parsers/manifest_parser.go | 127 ++++++++++-- parsers/manifest_parser_test.go | 102 ++++++---- parsers/yamlparser.go | 36 ++-- .../src/integration/apigateway/apigateway_test.go | 2 +- tests/src/integration/apigateway/manifest.yml | 71 ++++--- .../src/integration/apigateway/src/delete-books.js | 23 +++ tests/src/integration/apigateway/src/get-books.js | 13 ++ .../src/integration/apigateway/src/list-members.js | 13 ++ tests/src/integration/apigateway/src/post-books.js | 24 +++ tests/src/integration/apigateway/src/put-books.js | 30 +++ utils/flags.go | 37 ++-- utils/webaction.go | 24 ++- wskderrors/wskdeployerror.go | 51 +++-- wski18n/i18n_ids.go | 115 +++++------ wski18n/i18n_resources.go | 39 ++-- wski18n/resources/en_US.all.json | 12 ++ 20 files changed, 731 insertions(+), 296 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4576bf6..1758c5d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -105,11 +105,11 @@ }, { "ImportPath": "github.com/apache/incubator-openwhisk-client-go/whisk", - "Rev": "cef179c81f07f86413c720623995c17bd1bddd7d" + "Rev": "d7cab4297a25e2cc25492b85b64e7ec000da9ffb" }, { "ImportPath": "github.com/apache/incubator-openwhisk-client-go/wski18n", - "Rev": "cef179c81f07f86413c720623995c17bd1bddd7d" + "Rev": "d7cab4297a25e2cc25492b85b64e7ec000da9ffb" }, { "ImportPath": "github.com/pelletier/go-buffruneio", diff --git a/deployers/manifestreader.go b/deployers/manifestreader.go index 06fbdce..0442abc 100644 --- a/deployers/manifestreader.go +++ b/deployers/manifestreader.go @@ -93,7 +93,7 @@ func (deployer *ManifestReader) HandleYaml(sdeployer *ServiceDeployer, manifestP return wskderrors.NewYAMLFileFormatError(manifestName, err) } - apis, err := manifestParser.ComposeApiRecordsFromAllPackages(manifest) + apis, err := manifestParser.ComposeApiRecordsFromAllPackages(deployer.serviceDeployer.ClientConfig, manifest) if err != nil { return wskderrors.NewYAMLFileFormatError(manifestName, err) } @@ -373,17 +373,16 @@ func (reader *ManifestReader) SetRules(rules []*whisk.Rule) error { func (reader *ManifestReader) SetApis(ar []*whisk.ApiCreateRequest) error { dep := reader.serviceDeployer - var apis []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0) dep.mt.Lock() defer dep.mt.Unlock() - for _, api := range apis { - existApi, exist := dep.Deployment.Apis[api.ApiDoc.ApiName] + for _, api := range ar { + existApi, exist := dep.Deployment.Apis[api.ApiDoc.Action.Name] if exist { existApi.ApiDoc.ApiName = api.ApiDoc.ApiName } else { - dep.Deployment.Apis[api.ApiDoc.ApiName] = api + dep.Deployment.Apis[api.ApiDoc.Action.Name] = api } } diff --git a/deployers/servicedeployer.go b/deployers/servicedeployer.go index 8861619..14fc1c8 100644 --- a/deployers/servicedeployer.go +++ b/deployers/servicedeployer.go @@ -927,14 +927,20 @@ func (deployer *ServiceDeployer) createAction(pkgname string, action *whisk.Acti // create api (API Gateway functionality) func (deployer *ServiceDeployer) createApi(api *whisk.ApiCreateRequest) error { - displayPreprocessingInfo(parsers.YAML_KEY_API, api.ApiDoc.ApiName, true) + apiPath := api.ApiDoc.ApiName + " " + api.ApiDoc.GatewayBasePath + + api.ApiDoc.GatewayRelPath + " " + api.ApiDoc.GatewayMethod + displayPreprocessingInfo(parsers.YAML_KEY_API, apiPath, true) var err error var response *http.Response - // TODO() Is there an api delete function? could not find it + apiCreateReqOptions := new(whisk.ApiCreateRequestOptions) + apiCreateReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0] + apiCreateReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken + // TODO() Add Response type apiCreateReqOptions.ResponseType + err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { - _, response, err = deployer.Client.Apis.Insert(api, nil, true) + _, response, err = deployer.Client.Apis.Insert(api, apiCreateReqOptions, true) return err }) @@ -942,7 +948,7 @@ func (deployer *ServiceDeployer) createApi(api *whisk.ApiCreateRequest) error { return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, true) } - displayPostprocessingInfo(parsers.YAML_KEY_API, api.ApiDoc.ApiName, true) + displayPostprocessingInfo(parsers.YAML_KEY_API, apiPath, true) return nil } @@ -990,6 +996,9 @@ func (deployer *ServiceDeployer) UnDeploy(verifiedPlan *DeploymentProject) error } func (deployer *ServiceDeployer) unDeployAssets(verifiedPlan *DeploymentProject) error { + if err := deployer.UnDeployApis(verifiedPlan); err != nil { + return err + } if err := deployer.UnDeployRules(verifiedPlan); err != nil { return err @@ -1147,6 +1156,17 @@ func (deployer *ServiceDeployer) UnDeployRules(deployment *DeploymentProject) er return nil } +func (deployer *ServiceDeployer) UnDeployApis(deployment *DeploymentProject) error { + + for _, api := range deployment.Apis { + err := deployer.deleteApi(api) + if err != nil { + return err + } + } + return nil +} + func (deployer *ServiceDeployer) deletePackage(packa *whisk.Package) error { displayPreprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, false) @@ -1255,6 +1275,58 @@ func (deployer *ServiceDeployer) deleteRule(rule *whisk.Rule) error { return nil } +func (deployer *ServiceDeployer) isApi(api *whisk.ApiCreateRequest) bool { + apiReqOptions := new(whisk.ApiGetRequestOptions) + apiReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken + apiReqOptions.ApiBasePath = api.ApiDoc.GatewayBasePath + apiReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0] + + a := new(whisk.ApiGetRequest) + + retApi, _, err := deployer.Client.Apis.Get(a, apiReqOptions) + if err == nil { + if retApi.Apis != nil && len(retApi.Apis) > 0 && + retApi.Apis[0].ApiValue != nil { + return true + } + + } + return false +} + +// delete api (API Gateway functionality) +func (deployer *ServiceDeployer) deleteApi(api *whisk.ApiCreateRequest) error { + + apiPath := api.ApiDoc.ApiName + " " + api.ApiDoc.GatewayBasePath + + api.ApiDoc.GatewayRelPath + " " + api.ApiDoc.GatewayMethod + displayPreprocessingInfo(parsers.YAML_KEY_API, apiPath, false) + + if deployer.isApi(api) { + var err error + var response *http.Response + + apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions) + apiDeleteReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken + apiDeleteReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0] + apiDeleteReqOptions.ApiBasePath = api.ApiDoc.GatewayBasePath + apiDeleteReqOptions.ApiRelPath = api.ApiDoc.GatewayRelPath + apiDeleteReqOptions.ApiVerb = api.ApiDoc.GatewayMethod + + a := new(whisk.ApiDeleteRequest) + + err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { + response, err = deployer.Client.Apis.Delete(a, apiDeleteReqOptions) + return err + }) + + if err != nil { + return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, false) + } + } + displayPostprocessingInfo(parsers.YAML_KEY_API, apiPath, false) + return nil +} + // Utility function to call go-whisk framework to make action func (deployer *ServiceDeployer) deleteAction(pkgname string, action *whisk.Action) error { // call ActionService through Client diff --git a/deployers/whiskclient.go b/deployers/whiskclient.go index 83d2380..4935b81 100644 --- a/deployers/whiskclient.go +++ b/deployers/whiskclient.go @@ -42,6 +42,15 @@ const ( SOURCE_DEFAULT_VALUE = "wskdeploy default" // TODO() i18n? ) +var ( + credential = PropertyValue{} + namespace = PropertyValue{} + apiHost = PropertyValue{} + key = PropertyValue{} + cert = PropertyValue{} + apigwAccessToken = PropertyValue{} +) + type PropertyValue struct { Value string Source string @@ -63,8 +72,8 @@ var GetWskPropFromWhiskProperty = func(pi whisk.Properties) (*whisk.Wskprops, er return whisk.GetWskPropFromWhiskProperty(pi) } -var GetCommandLineFlags = func() (string, string, string, string, string) { - return utils.Flags.ApiHost, utils.Flags.Auth, utils.Flags.Namespace, utils.Flags.Key, utils.Flags.Cert +var GetCommandLineFlags = func() (string, string, string, string, string, string) { + return utils.Flags.ApiHost, utils.Flags.Auth, utils.Flags.Namespace, utils.Flags.Key, utils.Flags.Cert, utils.Flags.ApigwAccessToken } var CreateNewClient = func(config_input *whisk.Config) (*whisk.Client, error) { @@ -74,106 +83,80 @@ var CreateNewClient = func(config_input *whisk.Config) (*whisk.Client, error) { return whisk.NewClient(netClient, config_input) } -// we are reading openwhisk credentials (apihost, namespace, and auth) in the following precedence order: -// (1) wskdeploy command line `wskdeploy --apihost --namespace --auth` -// (2) deployment file -// (3) manifest file -// (4) .wskprops -// (5) prompt for values in interactive mode if any of them are missing -func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string, isInteractive bool) (*whisk.Config, error) { - // struct to store credential, namespace, and host with their respective source - credential := PropertyValue{} - namespace := PropertyValue{} - apiHost := PropertyValue{} - key := PropertyValue{} - cert := PropertyValue{} - - // read credentials from command line - apihost, auth, ns, keyfile, certfile := GetCommandLineFlags() +func resetWhiskConfig() { + credential = PropertyValue{} + namespace = PropertyValue{} + apiHost = PropertyValue{} + key = PropertyValue{} + cert = PropertyValue{} + apigwAccessToken = PropertyValue{} +} + +func readFromCLI() { + // read credentials, namespace, API host, key file, cert file, and APIGW access token from command line + apihost, auth, ns, keyfile, certfile, accessToken := GetCommandLineFlags() credential = GetPropertyValue(credential, auth, wski18n.COMMAND_LINE) namespace = GetPropertyValue(namespace, ns, wski18n.COMMAND_LINE) apiHost = GetPropertyValue(apiHost, apihost, wski18n.COMMAND_LINE) key = GetPropertyValue(key, keyfile, wski18n.COMMAND_LINE) cert = GetPropertyValue(cert, certfile, wski18n.COMMAND_LINE) + apigwAccessToken = GetPropertyValue(apigwAccessToken, accessToken, wski18n.COMMAND_LINE) +} - // TODO() i18n - // Print all flags / values if verbose - wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, wski18n.CONFIGURATION+":\n"+utils.Flags.Format()) +func setWhiskConfig(cred string, ns string, host string, token string, source string) { + credential = GetPropertyValue(credential, cred, source) + namespace = GetPropertyValue(namespace, ns, source) + apiHost = GetPropertyValue(apiHost, host, source) + apigwAccessToken = GetPropertyValue(apigwAccessToken, token, source) +} - // TODO() split this logic into its own function - // TODO() merge with the same logic used against manifest file (below) - // now, read them from deployment file if not found on command line +func readFromDeploymentFile(deploymentPath string) { if len(credential.Value) == 0 || len(namespace.Value) == 0 || len(apiHost.Value) == 0 { if utils.FileExists(deploymentPath) { mm := parsers.NewYAMLParser() deployment, _ := mm.ParseDeployment(deploymentPath) - credential = GetPropertyValue(credential, - deployment.GetProject().Credential, - path.Base(deploymentPath)) - namespace = GetPropertyValue(namespace, - deployment.GetProject().Namespace, - path.Base(deploymentPath)) - apiHost = GetPropertyValue(apiHost, - deployment.GetProject().ApiHost, - path.Base(deploymentPath)) + p := deployment.GetProject() + setWhiskConfig(p.Credential, p.Namespace, p.ApiHost, p.ApigwAccessToken, path.Base(deploymentPath)) } } +} - // TODO() split this logic into its own function - // TODO() merge with the same logic used against deployment file (above) - // read credentials from manifest file as didn't find them on command line and in deployment file +func readFromManifestFile(manifestPath string) { if len(credential.Value) == 0 || len(namespace.Value) == 0 || len(apiHost.Value) == 0 { if utils.FileExists(manifestPath) { mm := parsers.NewYAMLParser() manifest, _ := mm.ParseManifest(manifestPath) + var p = parsers.Package{} if manifest.Package.Packagename != "" { - credential = GetPropertyValue(credential, - manifest.Package.Credential, - path.Base(manifestPath)) - namespace = GetPropertyValue(namespace, - manifest.Package.Namespace, - path.Base(manifestPath)) - apiHost = GetPropertyValue(apiHost, - manifest.Package.ApiHost, - path.Base(manifestPath)) + p = manifest.Package } else if manifest.Packages != nil { if len(manifest.Packages) == 1 { for _, pkg := range manifest.Packages { - credential = GetPropertyValue(credential, - pkg.Credential, - path.Base(manifestPath)) - namespace = GetPropertyValue(namespace, - pkg.Namespace, - path.Base(manifestPath)) - apiHost = GetPropertyValue(apiHost, - pkg.ApiHost, - path.Base(manifestPath)) + p = pkg } } } + setWhiskConfig(p.Credential, p.Namespace, p.ApiHost, p.ApigwAccessToken, path.Base(manifestPath)) } } +} - // Third, we need to look up the variables in .wskprops file. - pi := whisk.PropertiesImp{ - OsPackage: whisk.OSPackageImp{}, - } - - // The error raised here can be neglected, because we will handle it in the end of this function. +func readFromWskprops(pi whisk.PropertiesImp, proppath string) { + // The error raised here can be neglected, because we will handle it in the end of its calling function. wskprops, _ := GetWskPropFromWskprops(pi, proppath) credential = GetPropertyValue(credential, wskprops.AuthKey, SOURCE_WSKPROPS) namespace = GetPropertyValue(namespace, wskprops.Namespace, SOURCE_WSKPROPS) apiHost = GetPropertyValue(apiHost, wskprops.APIHost, SOURCE_WSKPROPS) key = GetPropertyValue(key, wskprops.Key, SOURCE_WSKPROPS) cert = GetPropertyValue(cert, wskprops.Cert, SOURCE_WSKPROPS) + apigwAccessToken = GetPropertyValue(apigwAccessToken, wskprops.AuthAPIGWKey, SOURCE_WSKPROPS) +} - // TODO() see if we can split the following whisk prop logic into a separate function +func readFromWhiskProperty(pi whisk.PropertiesImp) { // now, read credentials from whisk.properties but this is only acceptable within Travis // whisk.properties will soon be deprecated and should not be used for any production deployment whiskproperty, _ := GetWskPropFromWhiskProperty(pi) - var warnMsg string - credential = GetPropertyValue(credential, whiskproperty.AuthKey, SOURCE_WHISK_PROPERTIES) if credential.Source == SOURCE_WHISK_PROPERTIES { warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED, @@ -192,29 +175,26 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string, map[string]interface{}{wski18n.KEY_KEY: wski18n.API_HOST}) wskprint.PrintlnOpenWhiskWarning(warnMsg) } - - // set namespace to default namespace if not yet found - if len(apiHost.Value) != 0 && len(credential.Value) != 0 && len(namespace.Value) == 0 { - namespace.Value = whisk.DEFAULT_NAMESPACE - namespace.Source = SOURCE_DEFAULT_VALUE + apigwAccessToken = GetPropertyValue(apigwAccessToken, whiskproperty.AuthAPIGWKey, SOURCE_WHISK_PROPERTIES) + if apigwAccessToken.Source == SOURCE_WHISK_PROPERTIES { + warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED, + map[string]interface{}{wski18n.KEY_KEY: wski18n.APIGW_ACCESS_TOKEN}) + wskprint.PrintlnOpenWhiskWarning(warnMsg) } +} - // TODO() See if we can split off the interactive logic into a separate function - // If we still can not find the values we need, check if it is interactive mode. - // If so, we prompt users for the input. - // The namespace is set to a default value at this point if not provided. - if len(apiHost.Value) == 0 && isInteractive == true { +func readInteractivly() { + if len(apiHost.Value) == 0 { host := promptForValue(wski18n.T(wski18n.ID_MSG_PROMPT_APIHOST)) if host == "" { // TODO() programmatically tell caller that we are using this default // TODO() make this configurable or remove host = "openwhisk.ng.bluemix.net" } - apiHost.Value = host - apiHost.Source = SOURCE_INTERACTIVE_INPUT + apiHost = GetPropertyValue(apiHost, host, SOURCE_INTERACTIVE_INPUT) } - if len(credential.Value) == 0 && isInteractive == true { + if len(credential.Value) == 0 { cred := promptForValue(wski18n.T(wski18n.ID_MSG_PROMPT_AUTHKEY)) credential.Value = cred credential.Source = SOURCE_INTERACTIVE_INPUT @@ -230,24 +210,83 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string, source = SOURCE_DEFAULT_VALUE } - namespace.Value = tempNamespace - namespace.Source = source + namespace = GetPropertyValue(namespace, tempNamespace, source) } } + if len(apigwAccessToken.Value) == 0 { + accessToken := promptForValue(wski18n.T(wski18n.APIGW_ACCESS_TOKEN)) + apigwAccessToken = GetPropertyValue(apigwAccessToken, accessToken, SOURCE_INTERACTIVE_INPUT) + } +} + +// we are reading openwhisk credentials (apihost, namespace, and auth) in the following precedence order: +// (1) wskdeploy command line `wskdeploy --apihost --namespace --auth` +// (2) deployment file +// (3) manifest file +// (4) .wskprops +// (5) prompt for values in interactive mode if any of them are missing +// we are following the same precedence order for APIGW_ACCESS_TOKEN +// but as a separate thread as APIGW_ACCESS_TOKEN only needed for APIs +func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string, isInteractive bool) (*whisk.Config, error) { + // reset credential, apiHost, namespace, etc to avoid any conflicts as they initialized globally + resetWhiskConfig() + + // initialize APIGW_ACCESS_TOKEN to "DUMMY TOKEN" for Travis builds + if strings.ToLower(os.Getenv("TRAVIS")) == "true" { + apigwAccessToken.Value = "DUMMY TOKEN" + apigwAccessToken.Source = SOURCE_DEFAULT_VALUE + } + + // read from command line + readFromCLI() + + // TODO() i18n + // Print all flags / values if verbose + wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, wski18n.CONFIGURATION+":\n"+utils.Flags.Format()) + + // now, read them from deployment file if not found on command line + readFromDeploymentFile(deploymentPath) + + // read credentials from manifest file as didn't find them on command line and in deployment file + readFromManifestFile(manifestPath) + + // Third, we need to look up the variables in .wskprops file. + pi := whisk.PropertiesImp{ + OsPackage: whisk.OSPackageImp{}, + } + + readFromWskprops(pi, proppath) + + readFromWhiskProperty(pi) + + // set namespace to default namespace if not yet found + if len(apiHost.Value) != 0 && len(credential.Value) != 0 && len(namespace.Value) == 0 { + namespace.Value = whisk.DEFAULT_NAMESPACE + namespace.Source = SOURCE_DEFAULT_VALUE + } + + // If we still can not find the values we need, check if it is interactive mode. + // If so, we prompt users for the input. + // The namespace is set to a default value at this point if not provided. + if isInteractive { + readInteractivly() + } + mode := true if len(cert.Value) != 0 && len(key.Value) != 0 { mode = false } clientConfig = &whisk.Config{ - AuthToken: credential.Value, //Authtoken - Namespace: namespace.Value, //Namespace - Host: apiHost.Value, - Version: "v1", // TODO() should not be hardcoded, should prompt/warn user of default - Cert: cert.Value, - Key: key.Value, - Insecure: mode, // true if you want to ignore certificate signing + AuthToken: credential.Value, //Authtoken + Namespace: namespace.Value, //Namespace + Host: apiHost.Value, + Version: "v1", // TODO() should not be hardcoded, should prompt/warn user of default + Cert: cert.Value, + Key: key.Value, + Insecure: mode, // true if you want to ignore certificate signing + ApigwAccessToken: apigwAccessToken.Value, } // validate we have credential, apihost and namespace @@ -288,6 +327,12 @@ func validateClientConfig(credential PropertyValue, apiHost PropertyValue, names map[string]interface{}{wski18n.KEY_NAMESPACE: namespace.Value, wski18n.KEY_SOURCE: namespace.Source}) wskprint.PrintOpenWhiskInfo(stdout) + if len(apigwAccessToken.Value) != 0 { + stdout = wski18n.T(wski18n.ID_MSG_CONFIG_INFO_APIGE_ACCESS_TOKEN_X_source_X, + map[string]interface{}{wski18n.KEY_SOURCE: apigwAccessToken.Source}) + wskprint.PrintOpenWhiskInfo(stdout) + } + return nil } diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go index c5eb0c8..87d76b3 100644 --- a/parsers/manifest_parser.go +++ b/parsers/manifest_parser.go @@ -35,6 +35,15 @@ import ( "github.com/apache/incubator-openwhisk-wskdeploy/wskprint" ) +const ( + PATH_SEPERATOR = "/" + API = "API" + HTTPS = "https" + HTTP = "http" + API_VERSION = "v1" + WEB = "web" +) + // Read existing manifest file or create new if none exists func ReadOrCreateManifest() (*YAML, error) { maniyaml := YAML{} @@ -858,12 +867,18 @@ func (dm *YAMLParser) ComposeRules(pkg Package, packageName string) ([]*whisk.Ru return r1, nil } -func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(manifest *YAML) ([]*whisk.ApiCreateRequest, error) { +func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(client *whisk.Config, manifest *YAML) ([]*whisk.ApiCreateRequest, error) { var requests []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0) manifestPackages := make(map[string]Package) + // verify APIGW_ACCESS_TOKEN is set before composing APIs + // until this point, we dont know whether APIs are specified in manifest or not + if len(client.ApigwAccessToken) == 0 { + return nil, wskderrors.NewWhiskClientInvalidConfigError(wski18n.ID_MSG_CONFIG_MISSING_APIGW_ACCESS_TOKEN) + } + if manifest.Package.Packagename != "" { - return dm.ComposeApiRecords(manifest.Package) + return dm.ComposeApiRecords(client, manifest.Package.Packagename, manifest.Package, manifest.Filepath) } else { if len(manifest.Packages) != 0 { manifestPackages = manifest.Packages @@ -872,8 +887,8 @@ func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(manifest *YAML) ([]*whisk } } - for _, p := range manifestPackages { - r, err := dm.ComposeApiRecords(p) + for packageName, p := range manifestPackages { + r, err := dm.ComposeApiRecords(client, packageName, p, manifest.Filepath) if err == nil { requests = append(requests, r...) } else { @@ -883,14 +898,102 @@ func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(manifest *YAML) ([]*whisk return requests, nil } -func (dm *YAMLParser) ComposeApiRecords(pkg Package) ([]*whisk.ApiCreateRequest, error) { - var acq []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0) - apis := pkg.GetApis() +/* + * read API section from manifest file: + * apis: # List of APIs + * hello-world: #API name + * /hello: #gateway base path + * /world: #gateway rel path + * greeting: get #action name: gateway method + * + * compose APIDoc structure from the manifest: + * { + * "apidoc":{ + * "namespace":<namespace>, + * "gatewayBasePath":"/hello", + * "gatewayPath":"/world", + * "gatewayMethod":"GET", + * "action":{ + * "name":"hello", + * "namespace":"guest", + * "backendMethod":"GET", + * "backendUrl":<url>, + * "authkey":<auth> + * } + * } + * } + */ +func (dm *YAMLParser) ComposeApiRecords(client *whisk.Config, packageName string, pkg Package, manifestPath string) ([]*whisk.ApiCreateRequest, error) { + var requests []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0) + + for apiName, apiDoc := range pkg.Apis { + for gatewayBasePath, gatewayBasePathMap := range apiDoc { + // append "/" to the gateway base path if its missing + if !strings.HasPrefix(gatewayBasePath, PATH_SEPERATOR) { + gatewayBasePath = PATH_SEPERATOR + gatewayBasePath + } + for gatewayRelPath, gatewayRelPathMap := range gatewayBasePathMap { + // append "/" to the gateway relative path if its missing + if !strings.HasPrefix(gatewayRelPath, PATH_SEPERATOR) { + gatewayRelPath = PATH_SEPERATOR + gatewayRelPath + } + for actionName, gatewayMethod := range gatewayRelPathMap { + // verify that the action is defined under actions sections + if _, ok := pkg.Actions[actionName]; !ok { + return nil, wskderrors.NewYAMLFileFormatError(manifestPath, + wski18n.T(wski18n.ID_ERR_API_MISSING_ACTION_X_action_X_api_X, + map[string]interface{}{ + wski18n.KEY_ACTION: actionName, + wski18n.KEY_API: apiName})) + } else { + // verify that the action is defined as web action + // web-export set to any of [true, yes, raw] + if !utils.IsWebAction(pkg.Actions[actionName].Webexport) { + return nil, wskderrors.NewYAMLFileFormatError(manifestPath, + wski18n.T(wski18n.ID_ERR_API_MISSING_WEB_ACTION_X_action_X_api_X, + map[string]interface{}{ + wski18n.KEY_ACTION: actionName, + wski18n.KEY_API: apiName})) + } else { + request := new(whisk.ApiCreateRequest) + request.ApiDoc = new(whisk.Api) + request.ApiDoc.GatewayBasePath = gatewayBasePath + // is API verb is valid, it must be one of (GET, PUT, POST, DELETE) + request.ApiDoc.GatewayRelPath = gatewayRelPath + if _, ok := whisk.ApiVerbs[strings.ToUpper(gatewayMethod)]; !ok { + return nil, wskderrors.NewInvalidAPIGatewayMethodError(manifestPath, + gatewayBasePath+gatewayRelPath, + gatewayMethod, + dm.getGatewayMethods()) + } + request.ApiDoc.GatewayMethod = strings.ToUpper(gatewayMethod) + request.ApiDoc.Namespace = client.Namespace + request.ApiDoc.ApiName = apiName + request.ApiDoc.Id = strings.Join([]string{API, request.ApiDoc.Namespace, request.ApiDoc.GatewayRelPath}, ":") + // set action of an API Doc + request.ApiDoc.Action = new(whisk.ApiAction) + request.ApiDoc.Action.Name = packageName + PATH_SEPERATOR + actionName + request.ApiDoc.Action.Namespace = client.Namespace + url := []string{HTTPS + ":" + PATH_SEPERATOR, client.Host, strings.ToLower(API), + API_VERSION, WEB, client.Namespace, packageName, actionName + "." + HTTP} + request.ApiDoc.Action.BackendUrl = strings.Join(url, PATH_SEPERATOR) + request.ApiDoc.Action.BackendMethod = gatewayMethod + request.ApiDoc.Action.Auth = client.AuthToken + // add a newly created ApiCreateRequest object to a list of requests + requests = append(requests, request) + } + } + } + } + } + } + return requests, nil +} - for _, api := range apis { - acr := new(whisk.ApiCreateRequest) - acr.ApiDoc = api - acq = append(acq, acr) +func (dm *YAMLParser) getGatewayMethods() []string { + methods := []string{} + for k := range whisk.ApiVerbs { + methods = append(methods, k) } - return acq, nil + return methods } diff --git a/parsers/manifest_parser_test.go b/parsers/manifest_parser_test.go index a55566c..cd07c5c 100644 --- a/parsers/manifest_parser_test.go +++ b/parsers/manifest_parser_test.go @@ -1345,23 +1345,43 @@ func TestComposeRules(t *testing.T) { } func TestComposeApiRecords(t *testing.T) { - data := `package: - name: helloworld - apis: - book-club: - club: - books: - putBooks: put - deleteBooks: delete - members: - listMembers: get - book-club2: - club2: - books2: - getBooks2: get - postBooks2: post - members2: - listMembers2: get` + data := ` +packages: + apiTest: + actions: + putBooks: + function: ../tests/src/integration/helloworld/actions/hello.js + web-export: true + deleteBooks: + function: ../tests/src/integration/helloworld/actions/hello.js + web-export: true + listMembers: + function: ../tests/src/integration/helloworld/actions/hello.js + web-export: true + getBooks2: + function: ../tests/src/integration/helloworld/actions/hello.js + web-export: true + postBooks2: + function: ../tests/src/integration/helloworld/actions/hello.js + web-export: true + listMembers2: + function: ../tests/src/integration/helloworld/actions/hello.js + web-export: true + apis: + book-club: + club: + books: + putBooks: put + deleteBooks: delete + members: + listMembers: get + book-club2: + club2: + books2: + getBooks2: get + postBooks2: post + members2: + listMembers2: get` tmpfile, err := _createTmpfile(data, "manifest_parser_test_") if err != nil { assert.Fail(t, "Failed to create temp file") @@ -1373,44 +1393,52 @@ func TestComposeApiRecords(t *testing.T) { // read and parse manifest.yaml file p := NewYAMLParser() m, _ := p.ParseManifest(tmpfile.Name()) - apiList, err := p.ComposeApiRecordsFromAllPackages(m) + + config := whisk.Config{ + Namespace: "test", + AuthToken: "user:pass", + Host: "host", + ApigwAccessToken: "token", + } + + apiList, err := p.ComposeApiRecordsFromAllPackages(&config, m) if err != nil { - assert.Fail(t, "Failed to compose api records") + assert.Fail(t, "Failed to compose api records: "+err.Error()) } assert.Equal(t, 6, len(apiList), "Failed to get api records") for _, apiRecord := range apiList { apiDoc := apiRecord.ApiDoc action := apiDoc.Action switch action.Name { - case "putBooks": + case "apiTest/putBooks": assert.Equal(t, "book-club", apiDoc.ApiName, "Failed to set api name") - assert.Equal(t, "club", apiDoc.GatewayBasePath, "Failed to set api base path") - assert.Equal(t, "books", apiDoc.GatewayRelPath, "Failed to set api rel path") + assert.Equal(t, "/club", apiDoc.GatewayBasePath, "Failed to set api base path") + assert.Equal(t, "/books", apiDoc.GatewayRelPath, "Failed to set api rel path") assert.Equal(t, "put", action.BackendMethod, "Failed to set api backend method") - case "deleteBooks": + case "apiTest/deleteBooks": assert.Equal(t, "book-club", apiDoc.ApiName, "Failed to set api name") - assert.Equal(t, "club", apiDoc.GatewayBasePath, "Failed to set api base path") - assert.Equal(t, "books", apiDoc.GatewayRelPath, "Failed to set api rel path") + assert.Equal(t, "/club", apiDoc.GatewayBasePath, "Failed to set api base path") + assert.Equal(t, "/books", apiDoc.GatewayRelPath, "Failed to set api rel path") assert.Equal(t, "delete", action.BackendMethod, "Failed to set api backend method") - case "listMembers": + case "apiTest/listMembers": assert.Equal(t, "book-club", apiDoc.ApiName, "Failed to set api name") - assert.Equal(t, "club", apiDoc.GatewayBasePath, "Failed to set api base path") - assert.Equal(t, "members", apiDoc.GatewayRelPath, "Failed to set api rel path") + assert.Equal(t, "/club", apiDoc.GatewayBasePath, "Failed to set api base path") + assert.Equal(t, "/members", apiDoc.GatewayRelPath, "Failed to set api rel path") assert.Equal(t, "get", action.BackendMethod, "Failed to set api backend method") - case "getBooks2": + case "apiTest/getBooks2": assert.Equal(t, "book-club2", apiDoc.ApiName, "Failed to set api name") - assert.Equal(t, "club2", apiDoc.GatewayBasePath, "Failed to set api base path") - assert.Equal(t, "books2", apiDoc.GatewayRelPath, "Failed to set api rel path") + assert.Equal(t, "/club2", apiDoc.GatewayBasePath, "Failed to set api base path") + assert.Equal(t, "/books2", apiDoc.GatewayRelPath, "Failed to set api rel path") assert.Equal(t, "get", action.BackendMethod, "Failed to set api backend method") - case "postBooks2": + case "apiTest/postBooks2": assert.Equal(t, "book-club2", apiDoc.ApiName, "Failed to set api name") - assert.Equal(t, "club2", apiDoc.GatewayBasePath, "Failed to set api base path") - assert.Equal(t, "books2", apiDoc.GatewayRelPath, "Failed to set api rel path") + assert.Equal(t, "/club2", apiDoc.GatewayBasePath, "Failed to set api base path") + assert.Equal(t, "/books2", apiDoc.GatewayRelPath, "Failed to set api rel path") assert.Equal(t, "post", action.BackendMethod, "Failed to set api backend method") - case "listMembers2": + case "apiTest/listMembers2": assert.Equal(t, "book-club2", apiDoc.ApiName, "Failed to set api name") - assert.Equal(t, "club2", apiDoc.GatewayBasePath, "Failed to set api base path") - assert.Equal(t, "members2", apiDoc.GatewayRelPath, "Failed to set api rel path") + assert.Equal(t, "/club2", apiDoc.GatewayBasePath, "Failed to set api base path") + assert.Equal(t, "/members2", apiDoc.GatewayRelPath, "Failed to set api rel path") assert.Equal(t, "get", action.BackendMethod, "Failed to set api backend method") default: assert.Fail(t, "Failed to get api action name") diff --git a/parsers/yamlparser.go b/parsers/yamlparser.go index b0b35fe..7e51714 100644 --- a/parsers/yamlparser.go +++ b/parsers/yamlparser.go @@ -191,28 +191,30 @@ type Package struct { Repositories []Repository `yaml:"repositories,omitempty"` Dependencies map[string]Dependency `yaml: dependencies` //used in manifest.yaml //mapping to wsk.SentPackageNoPublish.Namespace - Namespace string `yaml:"namespace"` //used in both manifest.yaml and deployment.yaml - Credential string `yaml:"credential"` //used in both manifest.yaml and deployment.yaml - ApiHost string `yaml:"apiHost"` //used in both manifest.yaml and deployment.yaml - Actions map[string]Action `yaml:"actions"` //used in both manifest.yaml and deployment.yaml - Triggers map[string]Trigger `yaml:"triggers"` //used in both manifest.yaml and deployment.yaml - Feeds map[string]Feed `yaml:"feeds"` //used in both manifest.yaml and deployment.yaml - Rules map[string]Rule `yaml:"rules"` //used in both manifest.yaml and deployment.yaml - Inputs map[string]Parameter `yaml:"inputs"` //deprecated, used in deployment.yaml - Sequences map[string]Sequence `yaml:"sequences"` - Annotations map[string]interface{} `yaml:"annotations,omitempty"` + Namespace string `yaml:"namespace"` //used in both manifest.yaml and deployment.yaml + Credential string `yaml:"credential"` //used in both manifest.yaml and deployment.yaml + ApiHost string `yaml:"apiHost"` //used in both manifest.yaml and deployment.yaml + ApigwAccessToken string `yaml:"apigwAccessToken"` + Actions map[string]Action `yaml:"actions"` //used in both manifest.yaml and deployment.yaml + Triggers map[string]Trigger `yaml:"triggers"` //used in both manifest.yaml and deployment.yaml + Feeds map[string]Feed `yaml:"feeds"` //used in both manifest.yaml and deployment.yaml + Rules map[string]Rule `yaml:"rules"` //used in both manifest.yaml and deployment.yaml + Inputs map[string]Parameter `yaml:"inputs"` //deprecated, used in deployment.yaml + Sequences map[string]Sequence `yaml:"sequences"` + Annotations map[string]interface{} `yaml:"annotations,omitempty"` //Parameters map[string]interface{} `yaml: parameters` // used in manifest.yaml Apis map[string]map[string]map[string]map[string]string `yaml:"apis"` //used in manifest.yaml } type Project struct { - Name string `yaml:"name"` //used in deployment.yaml - Namespace string `yaml:"namespace"` //used in deployment.yaml - Credential string `yaml:"credential"` - ApiHost string `yaml:"apiHost"` - Version string `yaml:"version"` - Packages map[string]Package `yaml:"packages"` //used in deployment.yaml - Package Package `yaml:"package"` // being deprecated, used in deployment.yaml + Name string `yaml:"name"` //used in deployment.yaml + Namespace string `yaml:"namespace"` //used in deployment.yaml + Credential string `yaml:"credential"` + ApiHost string `yaml:"apiHost"` + ApigwAccessToken string `yaml:"apigwAccessToken"` + Version string `yaml:"version"` + Packages map[string]Package `yaml:"packages"` //used in deployment.yaml + Package Package `yaml:"package"` // being deprecated, used in deployment.yaml } type YAML struct { diff --git a/tests/src/integration/apigateway/apigateway_test.go b/tests/src/integration/apigateway/apigateway_test.go index 6bc3355..a75a033 100644 --- a/tests/src/integration/apigateway/apigateway_test.go +++ b/tests/src/integration/apigateway/apigateway_test.go @@ -1,4 +1,4 @@ -// +build integration +// +build skip_integration /* * Licensed to the Apache Software Foundation (ASF) under one or more diff --git a/tests/src/integration/apigateway/manifest.yml b/tests/src/integration/apigateway/manifest.yml index 62f4dd0..407347a 100644 --- a/tests/src/integration/apigateway/manifest.yml +++ b/tests/src/integration/apigateway/manifest.yml @@ -15,35 +15,42 @@ # packages: - api-gateway-test: - version: 1.0 - license: Apache-2.0 - actions: - greeting: - version: 1.0 - function: src/greeting.js - runtime: nodejs:6 - inputs: - name: string - place: string - outputs: - payload: string - apis: # new top-level key for defining groups of named APIs - book-club: #api name - club: # shared base path - books: #path - getBooks: get #action name:verb - postBooks: post - putBooks: put - deleteBooks: delete - members: #path - listMembers: get #action name:verb - book-club2: #api name, added for multi api definition test - club2: # shared base path - books2: #path - getBooks2: get #action name:verb - postBooks2: post - putBooks2: put - deleteBooks2: delete - members2: #path - listMembers2: get #action name:verb + api-gateway-test: + version: 1.0 + license: Apache-2.0 + actions: + greeting: + web-export: true + version: 1.0 + function: src/greeting.js + runtime: nodejs:6 + getBooks: + web-export: true + function: src/get-books.js + postBooks: + web-export: true + function: src/post-books.js + putBooks: + web-export: true + function: src/put-books.js + deleteBooks: + web-export: true + function: src/delete-books.js + listMembers: + web-export: true + function: src/list-members.js + # new top-level key for defining groups of named APIs + apis: + hello-world: + hello: + world: + greeting: GET + book-club: + club: + books: + getBooks: GET + postBooks: POST + putBooks: PUT + deleteBooks: DELETE + members: + listMembers: GET diff --git a/tests/src/integration/apigateway/src/delete-books.js b/tests/src/integration/apigateway/src/delete-books.js new file mode 100644 index 0000000..d2047a5 --- /dev/null +++ b/tests/src/integration/apigateway/src/delete-books.js @@ -0,0 +1,23 @@ +/** + * Return success saying a book was deleted from the book store. + */ +function main(params) { + return new Promise(function(resolve, reject) { + console.log(params.name); + + if (!params.name) { + console.error('name parameter not set.'); + reject({ + 'error': 'name parameter not set.' + }); + return; + } else { + var message = 'A book ' + params.name + ' was deleted from the book store.'; + console.log(message); + resolve({ + result: message + }); + return; + } + }); +} diff --git a/tests/src/integration/apigateway/src/get-books.js b/tests/src/integration/apigateway/src/get-books.js new file mode 100644 index 0000000..e72f70d --- /dev/null +++ b/tests/src/integration/apigateway/src/get-books.js @@ -0,0 +1,13 @@ +/** + * Return a list of books in the book store. + */ +function main(params) { + return new Promise(function(resolve, reject) { + var message = 'List of books in the book store: ' + console.log(message); + resolve({ + result: {"name":"JavaScript: The Good Parts", "ISBN":"978-0596517748"} + }); + return; + }); +} diff --git a/tests/src/integration/apigateway/src/list-members.js b/tests/src/integration/apigateway/src/list-members.js new file mode 100644 index 0000000..7396fd2 --- /dev/null +++ b/tests/src/integration/apigateway/src/list-members.js @@ -0,0 +1,13 @@ +/** + * Return a list of members in the book store. + */ +function main(params) { + return new Promise(function(resolve, reject) { + var message = 'List of members in the book store: ' + console.log(message); + resolve({ + result: {"name":"Anne Li", "name":"Bob Young"} + }); + return; + }); +} diff --git a/tests/src/integration/apigateway/src/post-books.js b/tests/src/integration/apigateway/src/post-books.js new file mode 100644 index 0000000..0b30575 --- /dev/null +++ b/tests/src/integration/apigateway/src/post-books.js @@ -0,0 +1,24 @@ +/** + * Return success saying a book was added into the book store. + */ +function main(params) { + return new Promise(function(resolve, reject) { + console.log(params.name); + console.log(params.isbn); + + if (!params.name) { + console.error('name parameter not set.'); + reject({ + 'error': 'name parameter not set.' + }); + return; + } else { + var message = 'A book ' + params.name + ' was added to the book store with ISBN ' + params.isbn; + console.log(message); + resolve({ + result: message + }); + return; + } + }); +} diff --git a/tests/src/integration/apigateway/src/put-books.js b/tests/src/integration/apigateway/src/put-books.js new file mode 100644 index 0000000..611c04a --- /dev/null +++ b/tests/src/integration/apigateway/src/put-books.js @@ -0,0 +1,30 @@ +/** + * Return success saying a book was updated into the book store. + */ +function main(params) { + return new Promise(function(resolve, reject) { + console.log(params.name); + console.log(params.isbn); + + if (!params.name) { + console.error('name parameter not set.'); + reject({ + 'error': 'name parameter not set.' + }); + return; + } else if (!params.isbn) { + console.error('isbn parameter not set.'); + reject({ + 'error': 'isbn parameter not set.' + }); + return; + } else { + var message = 'A book ' + params.name + ' was updated to a new ISBN ' + params.isbn; + console.log(message); + resolve({ + result: message + }); + return; + } + }); +} diff --git a/utils/flags.go b/utils/flags.go index 1f61cbb..4ec1a62 100644 --- a/utils/flags.go +++ b/utils/flags.go @@ -23,24 +23,25 @@ import ( ) type WskDeployFlags struct { - WithinOpenWhisk bool // is this running within an OpenWhisk action? - ApiHost string // OpenWhisk API host - Auth string // OpenWhisk API key - Namespace string - ApiVersion string // OpenWhisk version - CfgFile string - CliVersion string - CliBuild string - Verbose bool - ProjectPath string - DeploymentPath string - ManifestPath string - UseDefaults bool - UseInteractive bool - Strict bool // strict flag to support user defined runtime version. - Key string - Cert string - Managed bool // OpenWhisk Managed Deployments + WithinOpenWhisk bool // is this running within an OpenWhisk action? + ApiHost string // OpenWhisk API host + Auth string // OpenWhisk API key + Namespace string + ApiVersion string // OpenWhisk version + CfgFile string + CliVersion string + CliBuild string + Verbose bool + ProjectPath string + DeploymentPath string + ManifestPath string + UseDefaults bool + UseInteractive bool + Strict bool // strict flag to support user defined runtime version. + Key string + Cert string + Managed bool // OpenWhisk Managed Deployments + ApigwAccessToken string } func (flags *WskDeployFlags) Format() string { diff --git a/utils/webaction.go b/utils/webaction.go index ed05ad3..0d6c9c6 100644 --- a/utils/webaction.go +++ b/utils/webaction.go @@ -28,7 +28,7 @@ const WEB_EXPORT_ANNOT = "web-export" const RAW_HTTP_ANNOT = "raw-http" const FINAL_ANNOT = "final" -var webexport map[string]string = map[string]string{ +var webExport map[string]string = map[string]string{ "TRUE": "true", "FALSE": "false", "NO": "no", @@ -38,15 +38,15 @@ var webexport map[string]string = map[string]string{ func WebAction(filePath string, action string, webMode string, annotations whisk.KeyValueArr, fetch bool) (whisk.KeyValueArr, error) { switch strings.ToLower(webMode) { - case webexport["TRUE"]: + case webExport["TRUE"]: fallthrough - case webexport["YES"]: + case webExport["YES"]: return webActionAnnotations(fetch, annotations, addWebAnnotations) - case webexport["NO"]: + case webExport["NO"]: fallthrough - case webexport["FALSE"]: + case webExport["FALSE"]: return webActionAnnotations(fetch, annotations, deleteWebAnnotations) - case webexport["RAW"]: + case webExport["RAW"]: return webActionAnnotations(fetch, annotations, addRawAnnotations) default: return nil, wskderrors.NewInvalidWebExportError(filePath, action, webMode, getValidWebExports()) @@ -103,8 +103,18 @@ func deleteWebAnnotationKeys(annotations whisk.KeyValueArr) whisk.KeyValueArr { func getValidWebExports() []string { var validWebExports []string - for _, v := range webexport { + for _, v := range webExport { validWebExports = append(validWebExports, v) } return validWebExports } + +func IsWebAction(webexport string) bool { + webexport = strings.ToLower(webexport) + if len(webexport) != 0 { + if webexport == webExport["TRUE"] || webexport == webExport["YES"] || webexport == webExport["RAW"] { + return true + } + } + return false +} diff --git a/wskderrors/wskdeployerror.go b/wskderrors/wskdeployerror.go index 1667ce2..e3c87c7 100644 --- a/wskderrors/wskdeployerror.go +++ b/wskderrors/wskdeployerror.go @@ -44,22 +44,26 @@ const ( STR_HTTP_BODY = "HTTP Response Body" STR_SUPPORTED_WEB_EXPORTS = "Supported Web Exports" STR_WEB_EXPORT = "web-export" + STR_API = "API" + STR_API_METHOD = "API gateway method" + STR_API_SUPPORTED_METHODS = "API gateway supported methods" // Formatting STR_INDENT_1 = "==>" // Error Types - ERROR_COMMAND_FAILED = "ERROR_COMMAND_FAILED" - ERROR_WHISK_CLIENT_ERROR = "ERROR_WHISK_CLIENT_ERROR" - ERROR_WHISK_CLIENT_INVALID_CONFIG = "ERROR_WHISK_CLIENT_INVALID_CONFIG" - ERROR_FILE_READ_ERROR = "ERROR_FILE_READ_ERROR" - ERROR_MANIFEST_FILE_NOT_FOUND = "ERROR_MANIFEST_FILE_NOT_FOUND" - ERROR_YAML_FILE_FORMAT_ERROR = "ERROR_YAML_FILE_FORMAT_ERROR" - ERROR_YAML_PARSER_ERROR = "ERROR_YAML_PARSER_ERROR" - ERROR_YAML_PARAMETER_TYPE_MISMATCH = "ERROR_YAML_PARAMETER_TYPE_MISMATCH" - ERROR_YAML_INVALID_PARAMETER_TYPE = "ERROR_YAML_INVALID_PARAMETER_TYPE" - ERROR_YAML_INVALID_RUNTIME = "ERROR_YAML_INVALID_RUNTIME" - ERROR_YAML_INVALID_WEB_EXPORT = "ERROR_YAML_INVALID_WEB_EXPORT" + ERROR_COMMAND_FAILED = "ERROR_COMMAND_FAILED" + ERROR_WHISK_CLIENT_ERROR = "ERROR_WHISK_CLIENT_ERROR" + ERROR_WHISK_CLIENT_INVALID_CONFIG = "ERROR_WHISK_CLIENT_INVALID_CONFIG" + ERROR_FILE_READ_ERROR = "ERROR_FILE_READ_ERROR" + ERROR_MANIFEST_FILE_NOT_FOUND = "ERROR_MANIFEST_FILE_NOT_FOUND" + ERROR_YAML_FILE_FORMAT_ERROR = "ERROR_YAML_FILE_FORMAT_ERROR" + ERROR_YAML_PARSER_ERROR = "ERROR_YAML_PARSER_ERROR" + ERROR_YAML_PARAMETER_TYPE_MISMATCH = "ERROR_YAML_PARAMETER_TYPE_MISMATCH" + ERROR_YAML_INVALID_PARAMETER_TYPE = "ERROR_YAML_INVALID_PARAMETER_TYPE" + ERROR_YAML_INVALID_RUNTIME = "ERROR_YAML_INVALID_RUNTIME" + ERROR_YAML_INVALID_WEB_EXPORT = "ERROR_YAML_INVALID_WEB_EXPORT" + ERROR_YAML_INVALID_API_GATEWAY_METHOD = "ERROR_YAML_INVALID_API_GATEWAY_METHOD" ) /* @@ -407,6 +411,31 @@ func NewInvalidWebExportError(fpath string, action string, webexport string, sup err.SetMessage(str) return err } + +/* + * Invalid API Gateway Method + */ +type InvalidAPIGatewayMethodError struct { + FileError + method string + SupportedMethods []string +} + +func NewInvalidAPIGatewayMethodError(fpath string, api string, method string, supportedMethods []string) *InvalidAPIGatewayMethodError { + var err = &InvalidAPIGatewayMethodError{ + SupportedMethods: supportedMethods, + } + err.SetErrorFilePath(fpath) + err.SetErrorType(ERROR_YAML_INVALID_API_GATEWAY_METHOD) + err.SetCallerByStackFrameSkip(2) + str := fmt.Sprintf("%s [%s]: %s [%s]: %s [%s]", + STR_API, api, + STR_API_METHOD, method, + STR_API_SUPPORTED_METHODS, strings.Join(supportedMethods, ", ")) + err.SetMessage(str) + return err +} + func IsCustomError(err error) bool { switch err.(type) { diff --git a/wski18n/i18n_ids.go b/wski18n/i18n_ids.go index d2f4be3..3ce8f21 100644 --- a/wski18n/i18n_ids.go +++ b/wski18n/i18n_ids.go @@ -20,34 +20,35 @@ package wski18n // descriptive key names // DO NOT TRANSLATE const ( - ACTION_CODE = "Action source" - ACTIONS = "Actions" - ACTIVATIONS = "Activations" - API_HOST = "API host" - AUTH_KEY = "authentication key" - COMMAND_LINE = "wskdeploy command line" - DEPLOYMENT = "deployment" - CONFIGURATION = "Configuration" - MANIFEST = "manifest" - NAME_ACTION = "Action Name" - NAME_FEED = "Feed Name" - NAME_RULE = "Rule Name" - NAME_RUNTIME = "Runtime Name" - NAME_TRIGGER = "Trigger Name" - NAMESPACES = "Namespaces" - PACKAGE_BINDING = "package binding" - PACKAGE_LICENSE = "package license" - PACKAGE_NAME = "package name" - PACKAGE_VERSION = "package version" - PACKAGES = "Packages" - PROJECT_NAME = "project name" - REGISTRY = "registry" - REGISTRY_URL = "registry URL" - REPOSITORY = "repository" - RULES = "Rules" - TRIGGER_FEED = "trigger feed" - TRIGGERS = "Triggers" - WHISK_PROPS = "wskprops" + ACTION_CODE = "Action source" + ACTIONS = "Actions" + ACTIVATIONS = "Activations" + API_HOST = "API host" + AUTH_KEY = "authentication key" + COMMAND_LINE = "wskdeploy command line" + DEPLOYMENT = "deployment" + CONFIGURATION = "Configuration" + MANIFEST = "manifest" + NAME_ACTION = "Action Name" + NAME_FEED = "Feed Name" + NAME_RULE = "Rule Name" + NAME_RUNTIME = "Runtime Name" + NAME_TRIGGER = "Trigger Name" + NAMESPACES = "Namespaces" + PACKAGE_BINDING = "package binding" + PACKAGE_LICENSE = "package license" + PACKAGE_NAME = "package name" + PACKAGE_VERSION = "package version" + PACKAGES = "Packages" + PROJECT_NAME = "project name" + REGISTRY = "registry" + REGISTRY_URL = "registry URL" + REPOSITORY = "repository" + RULES = "Rules" + TRIGGER_FEED = "trigger feed" + TRIGGERS = "Triggers" + WHISK_PROPS = "wskprops" + APIGW_ACCESS_TOKEN = "API Gateway Access Token" ) // i18n Identifiers @@ -92,9 +93,11 @@ const ( ID_MSG_CONFIG_MISSING_AUTHKEY = "msg_config_missing_authkey" ID_MSG_CONFIG_MISSING_APIHOST = "msg_config_missing_apihost" ID_MSG_CONFIG_MISSING_NAMESPACE = "msg_config_missing_namespace" + ID_MSG_CONFIG_MISSING_APIGW_ACCESS_TOKEN = "msg_config_missing_apigw_access_token" ID_MSG_CONFIG_INFO_APIHOST_X_host_X_source_X = "msg_config_apihost_info" ID_MSG_CONFIG_INFO_AUTHKEY_X_source_X = "msg_config_authkey_info" ID_MSG_CONFIG_INFO_NAMESPACE_X_namespace_X_source_X = "msg_config_namespace_info" + ID_MSG_CONFIG_INFO_APIGE_ACCESS_TOKEN_X_source_X = "msg_config_apigw_access_token_info" // YAML marshall / unmarshall ID_MSG_UNMARSHAL_LOCAL = "msg_unmarshall_local" @@ -150,6 +153,8 @@ const ( ID_ERR_RUNTIMES_GET_X_err_X = "msg_err_runtimes_get" ID_ERR_URL_INVALID_X_urltype_X_url_X_filetype_X = "msg_err_url_invalid" ID_ERR_URL_MALFORMED_X_urltype_X_url_X = "msg_err_url_malformed" + ID_ERR_API_MISSING_WEB_ACTION_X_action_X_api_X = "msg_err_api_missing_web_action" + ID_ERR_API_MISSING_ACTION_X_action_X_api_X = "msg_err_api_missing_action" // Server-side Errors (wskdeploy as an Action) ID_ERR_JSON_MISSING_KEY_CMD = "msg_err_json_missing_cmd_key" @@ -180,32 +185,34 @@ const ( // Known keys used for text replacement in i18n translated strings const ( - KEY_ACTION = "action" - KEY_CMD = "cmd" - KEY_CODE = "code" - KEY_DEPLOYMENT_NAME = "dname" - KEY_DEPLOYMENT_PATH = "dpath" - KEY_ERR = "err" - KEY_EXTENTION = "ext" - KEY_FILE_TYPE = "filetype" - KEY_HOST = "host" - KEY_KEY = "key" - KEY_LIMIT = "limit" - KEY_MANIFEST_NAME = "mname" - KEY_MANIFEST_PATH = "mpath" - KEY_NAME = "name" - KEY_NAMESPACE = "namespace" - KEY_NEW = "newkey" - KEY_OLD = "oldkey" - KEY_PATH = "path" - KEY_PROJECT = "project" - KEY_RUNTIME = "runtime" - KEY_SOURCE = "source" - KEY_URL = "url" - KEY_URL_TYPE = "urltype" - KEY_VALUE = "value" - KEY_VALUE_MIN = "min" // TODO() attempt to use this for Limit value range errors - KEY_VALUE_MAX = "max" // TODO() attempt to use this for Limit value range errors + KEY_ACTION = "action" + KEY_CMD = "cmd" + KEY_CODE = "code" + KEY_DEPLOYMENT_NAME = "dname" + KEY_DEPLOYMENT_PATH = "dpath" + KEY_ERR = "err" + KEY_EXTENTION = "ext" + KEY_FILE_TYPE = "filetype" + KEY_HOST = "host" + KEY_KEY = "key" + KEY_LIMIT = "limit" + KEY_MANIFEST_NAME = "mname" + KEY_MANIFEST_PATH = "mpath" + KEY_NAME = "name" + KEY_NAMESPACE = "namespace" + KEY_NEW = "newkey" + KEY_OLD = "oldkey" + KEY_PATH = "path" + KEY_PROJECT = "project" + KEY_RUNTIME = "runtime" + KEY_SOURCE = "source" + KEY_URL = "url" + KEY_URL_TYPE = "urltype" + KEY_VALUE = "value" + KEY_VALUE_MIN = "min" // TODO() attempt to use this for Limit value range errors + KEY_VALUE_MAX = "max" // TODO() attempt to use this for Limit value range errors + KEY_API = "api" + KEY_APIGW_ACCESS_TOKEN = "apigw_access_token" ) // Used to unit test that translations exist with these IDs diff --git a/wski18n/i18n_resources.go b/wski18n/i18n_resources.go index f85928c..fd6099d 100644 --- a/wski18n/i18n_resources.go +++ b/wski18n/i18n_resources.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + // Code generated by go-bindata. // sources: // wski18n/resources/de_DE.all.json @@ -92,12 +109,12 @@ func wski18nResourcesDe_deAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5a\x7b\x8f\x1b\xb7\x11\xff\xdf\x9f\x62\x60\x14\x70\x02\x9c\xd7\x4e\x8a\x02\x85\x81\x43\xe1\xd6\xd7\xe4\x9a\xd8\x67\xdc\x23\x41\xe0\x1c\xd6\xd4\x72\x24\x31\xe2\x92\x0b\x92\x2b\x59\x3e\xa8\x9f\xbd\x18\x72\x5f\xba\x3b\xee\x52\x72\x8c\xe6\x9f\xac\x8f\xc3\xf9\xcd\x83\x9c\x17\xf5\xe1\x09\xc0\xdd\x13\x00\x80\xa7\x82\x3f\x7d\x05\x4f\x4b\xbb\xc8\x2b\x83\x73\xf1\x29\x47\x63\xb4\x79\x7a\x12\x56\x9d\x61\xca\x4a [...] +var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5a\x7b\x8f\x1b\xb7\x11\xff\xdf\x9f\x62\x60\x14\x70\x02\x9c\x65\x27\x45\x81\xc2\xc0\xa1\x70\x6b\x37\xb9\x26\xf6\x19\xf7\x48\x10\x38\x87\x35\xb5\x1c\xad\x18\x71\xc9\x05\xc9\x95\xac\x1c\xd4\xcf\x5e\x0c\xb9\x2f\xe9\x8e\xbb\x94\x9c\xa0\xf9\x27\xf2\x71\x38\xbf\x79\x90\xf3\xe2\x7e\x7c\x02\x70\xff\x04\x00\xe0\xa9\xe0\x4f\x5f\xc1\xd3\xd2\x16\x59\x65\x70\x21\x3e\x67\x68\x8c\x36\x4f\xcf\xc2\xaa\x33\x4c\x59\xc9 [...] func wski18nResourcesEn_usAllJsonBytes() ([]byte, error) { return bindataRead( @@ -112,7 +129,7 @@ func wski18nResourcesEn_usAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 12172, mode: os.FileMode(420), modTime: time.Unix(1516809631, 0)} + info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 12809, mode: os.FileMode(420), modTime: time.Unix(1518160471, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -132,7 +149,7 @@ func wski18nResourcesEs_esAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -152,7 +169,7 @@ func wski18nResourcesFr_frAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 101, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 101, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -172,7 +189,7 @@ func wski18nResourcesIt_itAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -192,7 +209,7 @@ func wski18nResourcesJa_jaAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -212,7 +229,7 @@ func wski18nResourcesKo_krAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -232,7 +249,7 @@ func wski18nResourcesPt_brAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -252,7 +269,7 @@ func wski18nResourcesZh_hansAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -272,7 +289,7 @@ func wski18nResourcesZh_hantAllJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1515697090, 0)} + info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1501631495, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/wski18n/resources/en_US.all.json b/wski18n/resources/en_US.all.json index 99aede8..d6b81b4 100644 --- a/wski18n/resources/en_US.all.json +++ b/wski18n/resources/en_US.all.json @@ -140,6 +140,10 @@ "translation": "The namespace is [{{.namespace}}], from {{.source}}.\n" }, { + "id": "msg_config_apigw_access_token_info", + "translation": "The apigw access token is set, from {{.source}}.\n" + }, + { "id": "msg_unmarshall_local", "translation": "Unmarshal OpenWhisk info from local values.\n" }, @@ -312,6 +316,14 @@ "translation": "JSON input data Missing 'cmd' key" }, { + "id": "msg_err_api_missing_web_action", + "translation": "Action [{{.action}}] is not a web action, API [{{.api}}] can only be created using web action. Please update manifest file to convert [{{.action}}] to web action.\n" + }, + { + "id": "msg_err_api_missing_action", + "translation": "Action [{{.action}}] is missing from manifest file, API [{{.api}}] can only be created based on the action from manifest file. Please update manifest file to include [{{.action}}] as a web action.\n" + }, + { "id": "WARNINGS", "translation": "================= WARNINGS ===================" }, -- To stop receiving notification emails like this one, please contact mrutkow...@apache.org.