You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/02/07 21:05:27 UTC

[GitHub] pritidesai closed pull request #703: WIP: adding support for API Gateway - needs rebase

pritidesai closed pull request #703: WIP: adding support for API Gateway - needs rebase
URL: https://github.com/apache/incubator-openwhisk-wskdeploy/pull/703
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/deployers/manifestreader.go b/deployers/manifestreader.go
index a8df3851..55c2b145 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,12 +373,11 @@ 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 {
+	for _, api := range ar {
 		existApi, exist := dep.Deployment.Apis[api.ApiDoc.ApiName]
 		if exist {
 			existApi.ApiDoc.ApiName = api.ApiDoc.ApiName
diff --git a/deployers/servicedeployer.go b/deployers/servicedeployer.go
index 72f3540f..9784da41 100644
--- a/deployers/servicedeployer.go
+++ b/deployers/servicedeployer.go
@@ -932,9 +932,15 @@ func (deployer *ServiceDeployer) createApi(api *whisk.ApiCreateRequest) error {
 	var err error
 	var response *http.Response
 
+	apiCreateReqOptions := new(whisk.ApiCreateRequestOptions)
+	apiCreateReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0]
+	// TODO add APIGW_ACCESS_TOKEN
+	apiCreateReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken
+	apiCreateReqOptions.ResponseType = "json"
+
 	// TODO() Is there an api delete function? could not find it
 	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
 	})
 
diff --git a/deployers/whiskclient.go b/deployers/whiskclient.go
index 715750ce..c09a3398 100644
--- a/deployers/whiskclient.go
+++ b/deployers/whiskclient.go
@@ -63,8 +63,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) {
@@ -80,6 +80,8 @@ var CreateNewClient = func(config_input *whisk.Config) (*whisk.Client, error) {
 // (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 in a separate thread as APIGW_ACCESS_TOKEN only needed for APIs
 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{}
@@ -87,14 +89,16 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 	apiHost := PropertyValue{}
 	key := PropertyValue{}
 	cert := PropertyValue{}
+	apigwAccessToken := PropertyValue{}
 
 	// read credentials from command line
-	apihost, auth, ns, keyfile, certfile := GetCommandLineFlags()
+	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
@@ -119,6 +123,15 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 		}
 	}
 
+	// read APIGW_ACCESS_TOKEN if not found on command line
+	if  len(apigwAccessToken.Value) == 0 {
+		mm := parsers.NewYAMLParser()
+		deployment, _ := mm.ParseDeployment(deploymentPath)
+		apigwAccessToken = GetPropertyValue(apigwAccessToken,
+			deployment.GetProject().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
@@ -154,6 +167,25 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 		}
 	}
 
+	if len(apigwAccessToken.Value) == 0 {
+		if utils.FileExists(manifestPath) {
+			mm := parsers.NewYAMLParser()
+			manifest, _ := mm.ParseManifest(manifestPath)
+			if manifest.Package.Packagename != "" {
+				apigwAccessToken = GetPropertyValue(apigwAccessToken,
+					manifest.Package.ApigwAccessToken,
+					path.Base(manifestPath))
+			} else if manifest.Packages != nil {
+				if len(manifest.Packages) == 1 {
+					for _, pkg := range manifest.Packages {
+						apigwAccessToken = GetPropertyValue(apigwAccessToken,
+							pkg.ApigwAccessToken,
+							path.Base(manifestPath))
+					}
+				}
+		}	}
+	}
+
 	// Third, we need to look up the variables in .wskprops file.
 	pi := whisk.PropertiesImp{
 		OsPackage: whisk.OSPackageImp{},
@@ -166,6 +198,7 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 	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
 	// now, read credentials from whisk.properties but this is only acceptable within Travis
@@ -192,6 +225,12 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 			map[string]interface{}{wski18n.KEY_KEY: wski18n.API_HOST})
 		wskprint.PrintlnOpenWhiskWarning(warnMsg)
 	}
+	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)
+	}
 
 	// set namespace to default namespace if not yet found
 	if len(apiHost.Value) != 0 && len(credential.Value) != 0 && len(namespace.Value) == 0 {
@@ -235,6 +274,12 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 		}
 	}
 
+	if len(apigwAccessToken.Value) == 0 && isInteractive == true {
+		accessToken := promptForValue(wski18n.T(wski18n.APIGW_ACCESS_TOKEN))
+		apigwAccessToken.Value = accessToken
+		apigwAccessToken.Source = SOURCE_INTERACTIVE_INPUT
+	}
+
 	mode := true
 	if (len(cert.Value) != 0 && len(key.Value) != 0) {
 		mode = false
@@ -248,6 +293,7 @@ func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string,
 		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
diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go
index 37d318e6..7aa88e11 100644
--- a/parsers/manifest_parser.go
+++ b/parsers/manifest_parser.go
@@ -4,162 +4,6 @@
  * 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.
- */
-
-package parsers
-
-import (
-	"errors"
-	"io/ioutil"
-	"os"
-	"path"
-	"strings"
-	"encoding/base64"
-	"fmt"
-	"gopkg.in/yaml.v2"
-
-	"github.com/apache/incubator-openwhisk-client-go/whisk"
-	"github.com/apache/incubator-openwhisk-wskdeploy/utils"
-	"github.com/apache/incubator-openwhisk-wskdeploy/wski18n"
-	"github.com/apache/incubator-openwhisk-wskdeploy/wskderrors"
-	"github.com/apache/incubator-openwhisk-wskdeploy/wskenv"
-	"github.com/apache/incubator-openwhisk-wskdeploy/wskprint"
-)
-
-// Read existing manifest file or create new if none exists
-func ReadOrCreateManifest() (*YAML, error) {
-	maniyaml := YAML{}
-
-	if _, err := os.Stat(utils.ManifestFileNameYaml); err == nil {
-		dat, _ := ioutil.ReadFile(utils.ManifestFileNameYaml)
-		err := NewYAMLParser().Unmarshal(dat, &maniyaml)
-		if err != nil {
-			return &maniyaml, wskderrors.NewFileReadError(utils.ManifestFileNameYaml, err.Error())
-		}
-	}
-	return &maniyaml, nil
-}
-
-// Serialize manifest to local file
-func Write(manifest *YAML, filename string) error {
-	output, err := NewYAMLParser().marshal(manifest)
-	if err != nil {
-		return wskderrors.NewYAMLFileFormatError(filename, err.Error())
-	}
-
-	f, err := os.Create(filename)
-	if err != nil {
-		return wskderrors.NewFileReadError(filename, err.Error())
-	}
-	defer f.Close()
-
-	f.Write(output)
-	return nil
-}
-
-func (dm *YAMLParser) Unmarshal(input []byte, manifest *YAML) error {
-	err := yaml.UnmarshalStrict(input, manifest)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func (dm *YAMLParser) marshal(manifest *YAML) (output []byte, err error) {
-	data, err := yaml.Marshal(manifest)
-	if err != nil {
-		return nil, err
-	}
-	return data, nil
-}
-
-func (dm *YAMLParser) ParseManifest(manifestPath string) (*YAML, error) {
-	mm := NewYAMLParser()
-	maniyaml := YAML{}
-
-	content, err := utils.Read(manifestPath)
-	if err != nil {
-		return &maniyaml, wskderrors.NewFileReadError(manifestPath, err.Error())
-	}
-
-	err = mm.Unmarshal(content, &maniyaml)
-	if err != nil {
-		return &maniyaml, wskderrors.NewYAMLParserErr(manifestPath, err)
-	}
-	maniyaml.Filepath = manifestPath
-	manifest := ReadEnvVariable(&maniyaml)
-
-	return manifest, nil
-}
-
-func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, projectPath string, filePath string) (map[string]utils.DependencyRecord, error) {
-	dependencies := make(map[string]utils.DependencyRecord)
-	packages := make(map[string]Package)
-	if manifest.Package.Packagename != "" {
-		return dm.ComposeDependencies(manifest.Package, projectPath, filePath, manifest.Package.Packagename)
-	} else {
-		if len(manifest.Packages) != 0 {
-			packages = manifest.Packages
-		} else {
-			packages = manifest.GetProject().Packages
-		}
-	}
-
-	for n, p := range packages {
-		d, err := dm.ComposeDependencies(p, projectPath, filePath, n)
-		if err == nil {
-			for k, v := range d {
-				dependencies[k] = v
-			}
-		} else {
-			return nil, err
-		}
-	}
-	return dependencies, nil
-}
-
-func (dm *YAMLParser) ComposeDependencies(pkg Package, projectPath string, filePath string, packageName string) (map[string]utils.DependencyRecord, error) {
-
-	var errorParser error
-	depMap := make(map[string]utils.DependencyRecord)
-	for key, dependency := range pkg.Dependencies {
-		version := dependency.Version
-		if version == "" {
-			// TODO() interactive ask for branch, AND consider YAML specification to allow key for branch
-			version = YAML_VALUE_BRANCH_MASTER
-		}
-
-		location := dependency.Location
-
-		isBinding := false
-		if utils.LocationIsBinding(location) {
-
-			if !strings.HasPrefix(location, "/") {
-				location = "/" + dependency.Location
-			}
-
-			isBinding = true
-		} else if utils.LocationIsGithub(location) {
-
-			// TODO() define const for the protocol prefix, etc.
-			if !strings.HasPrefix(location, "https://") && !strings.HasPrefix(location, "http://") {
-				location = "https://" + dependency.Location
-			}
-
-			isBinding = false
-		} else {
-			// TODO() create new named error in wskerrors package
-			return nil, errors.New(wski18n.T(wski18n.ID_ERR_DEPENDENCY_UNKNOWN_TYPE))
-		}
 
 		keyValArrParams := make(whisk.KeyValueArr, 0)
 		for name, param := range dependency.Inputs {
@@ -856,12 +700,12 @@ 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)
 
 	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
@@ -870,8 +714,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 {
@@ -881,14 +725,94 @@ 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 {
+			// is gateway base path valid, it must begin with /
+			if !strings.HasPrefix(gatewayBasePath, "/") {
+				return nil, wskderrors.NewInvalidAPIPathError("Base path must begin with /", manifestPath, gatewayBasePath)
+			}
+			for gatewayRelPath, gatewayRelPathMap := range gatewayBasePathMap {
+				// is gateway relative path valid, it must begin with /
+				if !strings.HasPrefix(gatewayRelPath, "/") {
+					return nil, wskderrors.NewInvalidAPIPathError("Relative path must begin with /", manifestPath, gatewayRelPath)
+				}
+				for actionName, gatewayMethod := range gatewayRelPathMap {
+					// verify that the action is defined under actions sections
+					if _, ok := pkg.Actions[actionName]; ok {
+						// verify that the action is defined as web action
+						web := pkg.Actions[actionName].Webexport
+						// (TODO) Convert web export in Action struct from string to bool
+						if strings.ToUpper(web) == "TRUE" {
+							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 = "API:" + request.ApiDoc.Namespace + ":" + request.ApiDoc.GatewayRelPath
+
+							request.ApiDoc.Action = new(whisk.ApiAction)
+							request.ApiDoc.Action.Name = packageName + "/" + actionName
+							request.ApiDoc.Action.Namespace = client.Namespace
+							request.ApiDoc.Action.BackendUrl = "https://" + client.Host + "/api/v1/web/" +
+								client.Namespace + "/" + packageName + "/" + actionName + ".http"
+							request.ApiDoc.Action.BackendMethod = gatewayMethod
+							request.ApiDoc.Action.Auth = client.AuthToken
+
+							requests = append(requests, request)
+						} else {
+							//(TODO) exit with WARNING
+						}
+					} else {
+						//(TODO) exit with WARNING
+					}
+				}
+			}
+		}
+	}
+	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/yamlparser.go b/parsers/yamlparser.go
index 6bcee666..788441b5 100644
--- a/parsers/yamlparser.go
+++ b/parsers/yamlparser.go
@@ -194,6 +194,7 @@ type Package struct {
 	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"` //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
@@ -210,6 +211,7 @@ type Project struct {
 	Namespace  string             `yaml:"namespace"` //used in deployment.yaml
 	Credential string             `yaml:"credential"`
 	ApiHost    string             `yaml:"apiHost"`
+	ApigwAccessToken string		   `yaml:"apigwAccessToken"` //used in both manifest.yaml and deployment.yaml
 	Version    string             `yaml:"version"`
 	Packages   map[string]Package `yaml:"packages"` //used in deployment.yaml
 	Package    Package            `yaml:"package"`  // being deprecated, used in deployment.yaml
diff --git a/tests/dat/deployment_validate_credentials.yaml b/tests/dat/deployment_validate_credentials.yaml
index e44868d3..5fb12f96 100644
--- a/tests/dat/deployment_validate_credentials.yaml
+++ b/tests/dat/deployment_validate_credentials.yaml
@@ -1,4 +1,4 @@
-# do not change or delete this file without changing deployers/whiskclient_test.go
+
 # this is used for testing whiskclient functionality
 project:
   name: UnitTestCredentials
diff --git a/tests/src/integration/apigateway/manifest.yml b/tests/src/integration/apigateway/manifest.yml
index 56286e54..320fedda 100644
--- a/tests/src/integration/apigateway/manifest.yml
+++ b/tests/src/integration/apigateway/manifest.yml
@@ -1,34 +1,41 @@
 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/get-books.js b/tests/src/integration/apigateway/src/get-books.js
new file mode 100644
index 00000000..9b026a61
--- /dev/null
+++ b/tests/src/integration/apigateway/src/get-books.js
@@ -0,0 +1,13 @@
+/**
+ * Return a simple greeting message for someone.
+ *
+ * @param name A person's name.
+ * @param place Where the person is from.
+ */
+function main() {
+    return {
+      body: new Buffer(JSON.stringify({result:[{"name":"JavaScript: The Good Parts", "isbn":"978-0596517748"}]})).toString('base64'),
+      statusCode: 200,
+      headers:{ 'Content-Type': 'application/json'}
+    };
+}
diff --git a/tests/src/integration/apigateway/src/greeting.js b/tests/src/integration/apigateway/src/greeting.js
index 7c069f1e..c9e6edab 100644
--- a/tests/src/integration/apigateway/src/greeting.js
+++ b/tests/src/integration/apigateway/src/greeting.js
@@ -4,9 +4,10 @@
  * @param name A person's name.
  * @param place Where the person is from.
  */
-function main(params) {
-    var name = params.name || params.payload || 'stranger';
-    var place = params.place || 'somewhere';
-    return {payload:  'Hello, ' + name + ' from ' + place + '!'};
+function main({name:name='Serverless API Gateway'}) {
+    return {
+      body: new Buffer(JSON.stringify({payload:`Hello world ${name}`})).toString('base64'),
+      statusCode: 200,
+      headers:{ 'Content-Type': 'application/json'}
+    };
 }
-
diff --git a/utils/flags.go b/utils/flags.go
index 6cbc0d9e..226e6781 100644
--- a/utils/flags.go
+++ b/utils/flags.go
@@ -27,6 +27,7 @@ type WskDeployFlags struct {
 	ApiHost         string // OpenWhisk API host
 	Auth            string // OpenWhisk API key
 	Namespace       string
+	ApigwAccessToken string
 	ApiVersion      string // OpenWhisk version
 	CfgFile         string
 	CliVersion      string
diff --git a/wskderrors/wskdeployerror.go b/wskderrors/wskdeployerror.go
index c7d5e83a..4429def0 100644
--- a/wskderrors/wskdeployerror.go
+++ b/wskderrors/wskdeployerror.go
@@ -42,6 +42,9 @@ const (
 	STR_SUPPORTED_RUNTIMES = "Supported Runtimes"
 	STR_HTTP_STATUS = "HTTP Response Status"
 	STR_HTTP_BODY = "HTTP Response Body"
+	STR_API = "API"
+	STR_API_METHOD = "API gateway method"
+	STR_API_SUPPORTED_METHODS = "API gateway supported methods"
 
 	// Formatting
 	STR_INDENT_1 = "==>"
@@ -57,6 +60,8 @@ const (
 	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_API_GATEWAY_METHOD = "ERROR_YAML_INVALID_API_GATEWAY_METHOD"
+	ERROR_YAML_INVALID_API_PATH = "ERROR_YAML_INVALID_API_PATH"
 )
 
 /*
@@ -389,6 +394,48 @@ func NewInvalidRuntimeError(errMessage string, fpath string, action string, runt
 	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
+}
+
+/*
+ * Invalid API Path
+ */
+type InvalidAPIPathError struct {
+	FileError
+}
+
+func NewInvalidAPIPathError(errMessage string, fpath string, api string) *InvalidAPIPathError {
+	var err = &InvalidAPIPathError{}
+	err.SetErrorFilePath(fpath)
+	err.SetErrorType(ERROR_YAML_INVALID_API_PATH)
+	err.SetCallerByStackFrameSkip(2)
+	str := fmt.Sprintf("%s %s [%s]",
+		errMessage,
+		STR_API, api)
+	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 8f8b544e..1c5a6b21 100644
--- a/wski18n/i18n_ids.go
+++ b/wski18n/i18n_ids.go
@@ -48,6 +48,7 @@ const (
 	TRIGGER_FEED		= "trigger feed"
 	TRIGGERS		= "Triggers"
 	WHISK_PROPS		= "wskprops"
+	APIGW_ACCESS_TOKEN	= "API Gateway Access Token"
 )
 
 // i18n Identifiers


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services