parsers/manifest_parser.go (1,121 lines of code) (raw):

/* * 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. */ package parsers import ( "encoding/base64" "encoding/json" "errors" "io/ioutil" "os" "path" "path/filepath" "strings" "gopkg.in/yaml.v2" "net/url" "github.com/apache/openwhisk-client-go/whisk" "github.com/apache/openwhisk-wskdeploy/conductor" "github.com/apache/openwhisk-wskdeploy/dependencies" "github.com/apache/openwhisk-wskdeploy/runtimes" "github.com/apache/openwhisk-wskdeploy/utils" "github.com/apache/openwhisk-wskdeploy/webaction" "github.com/apache/openwhisk-wskdeploy/wskderrors" "github.com/apache/openwhisk-wskdeploy/wskenv" "github.com/apache/openwhisk-wskdeploy/wski18n" "github.com/apache/openwhisk-wskdeploy/wskprint" yamlHelper "github.com/ghodss/yaml" ) const ( API = "API" HTTPS = "https://" HTTP = "http://" API_VERSION = "v1" WEB = "web" PATH_SEPARATOR = "/" DEFAULT_PACKAGE = "default" NATIVE_DOCKER_IMAGE = "openwhisk/dockerskeleton" PARAM_OPENING_BRACKET = "{" PARAM_CLOSING_BRACKET = "}" DUMMY_APIGW_ACCESS_TOKEN = "DUMMY TOKEN" YAML_FILE_EXTENSION = "yaml" YML_FILE_EXTENSION = "yml" ) // 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) composeInputs(inputs map[string]Parameter, packageInputs PackageInputs, manifestFilePath string) (whisk.KeyValueArr, error) { var errorParser error keyValArr := make(whisk.KeyValueArr, 0) var inputsWithoutValue []string var paramsCLI interface{} if len(utils.Flags.Param) > 0 { paramsCLI, errorParser = utils.GetJSONFromStrings(utils.Flags.Param, false) if errorParser != nil { return nil, errorParser } } for name, param := range inputs { var keyVal whisk.KeyValue // keyvalue key is set to parameter name keyVal.Key = name // parameter on CLI takes the highest precedence such that // input variables gets values from CLI first if paramsCLI != nil { // check if this particular input is specified on CLI if v, ok := paramsCLI.(map[string]interface{})[name]; ok { keyVal.Value = wskenv.ConvertSingleName(v.(string)) } } // if those inputs are not specified on CLI, // read their values from the manifest file if keyVal.Value == nil { keyVal.Value, errorParser = ResolveParameter(name, &param, manifestFilePath) if errorParser != nil { return nil, errorParser } if param.Type == STRING && param.Value != nil { if keyVal.Value == getTypeDefaultValue(param.Type) { if packageInputs.Inputs != nil { n := wskenv.GetEnvVarName(param.Value.(string)) if v, ok := packageInputs.Inputs[n]; ok { keyVal.Value = v.Value.(string) } } } } } if keyVal.Value != nil { keyValArr = append(keyValArr, keyVal) } if param.Required && keyVal.Value == getTypeDefaultValue(param.Type) { inputsWithoutValue = append(inputsWithoutValue, name) } } if len(inputsWithoutValue) > 0 { errMessage := wski18n.T(wski18n.ID_ERR_REQUIRED_INPUTS_MISSING_VALUE_X_inputs_X, map[string]interface{}{ wski18n.KEY_INPUTS: strings.Join(inputsWithoutValue, ", ")}) return nil, wskderrors.NewYAMLFileFormatError(manifestFilePath, errMessage) } return keyValArr, nil } func (dm *YAMLParser) composeAnnotations(annotations map[string]interface{}) whisk.KeyValueArr { listOfAnnotations := make(whisk.KeyValueArr, 0) for name, value := range annotations { var keyVal whisk.KeyValue keyVal.Key = name value = wskenv.InterpolateStringWithEnvVar(value) keyVal.Value = utils.ConvertInterfaceValue(value) listOfAnnotations = append(listOfAnnotations, keyVal) } return listOfAnnotations } func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, projectPath string, filePath string, managedAnnotations whisk.KeyValue, packageInputs map[string]PackageInputs) (map[string]dependencies.DependencyRecord, error) { dependencies := make(map[string]dependencies.DependencyRecord) packages := make(map[string]Package) 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, managedAnnotations, packageInputs[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, managedAnnotations whisk.KeyValue, packageInputs PackageInputs) (map[string]dependencies.DependencyRecord, error) { depMap := make(map[string]dependencies.DependencyRecord) for key, dependency := range pkg.Dependencies { version := dependency.Version if len(version) == 0 { version = YAML_VALUE_BRANCH_MASTER } location := dependency.Location isBinding := false if dependencies.LocationIsBinding(location) { if !strings.HasPrefix(location, PATH_SEPARATOR) { location = PATH_SEPARATOR + dependency.Location } isBinding = true } else if dependencies.LocationIsGithub(location) { // TODO() define const for the protocol prefix, etc. _, err := url.ParseRequestURI(location) if err != nil { location = HTTPS + dependency.Location location = wskenv.InterpolateStringWithEnvVar(location).(string) } isBinding = false } else { // TODO() create new named error in wskerrors package return nil, errors.New(wski18n.T(wski18n.ID_ERR_DEPENDENCY_UNKNOWN_TYPE)) } inputs, err := dm.composeInputs(dependency.Inputs, packageInputs, filePath) if err != nil { return nil, err } annotations := dm.composeAnnotations(dependency.Annotations) if utils.Flags.Managed || utils.Flags.Sync { annotations = append(annotations, managedAnnotations) } packDir := path.Join(projectPath, strings.Title(YAML_KEY_PACKAGES)) depName := packageName + ":" + key depMap[depName] = dependencies.NewDependencyRecord(packDir, packageName, location, version, inputs, annotations, isBinding) } return depMap, nil } func (dm *YAMLParser) ComposeAllPackages(projectInputs map[string]Parameter, manifest *YAML, filePath string, managedAnnotations whisk.KeyValue) (map[string]*whisk.Package, map[string]PackageInputs, error) { packages := map[string]*whisk.Package{} manifestPackages := make(map[string]Package) inputs := make(map[string]PackageInputs, 0) if len(manifest.Packages) != 0 { manifestPackages = manifest.Packages } else { manifestPackages = manifest.GetProject().Packages } if len(manifestPackages) == 0 { warningString := wski18n.T( wski18n.ID_WARN_PACKAGES_NOT_FOUND_X_path_X, map[string]interface{}{ wski18n.KEY_PATH: manifest.Filepath}) wskprint.PrintOpenWhiskWarning(warningString) } // Compose each package found in manifest for n, p := range manifestPackages { s, params, err := dm.ComposePackage(p, n, filePath, managedAnnotations, projectInputs) if err != nil { return nil, inputs, err } packages[n] = s inputs[n] = PackageInputs{PackageName: n, Inputs: params} } return packages, inputs, nil } func (dm *YAMLParser) composePackageInputs(projectInputs map[string]Parameter, rawInputs map[string]Parameter, filepath string) (map[string]Parameter, whisk.KeyValueArr, error) { inputs := make(map[string]Parameter, 0) // package inherits all project inputs for n, param := range projectInputs { inputs[n] = param } // iterate over package inputs for name, i := range rawInputs { value, err := ResolveParameter(name, &i, filepath) if err != nil { return nil, nil, err } // if value is set to default value for its type, // check for input key being an env. variable itself if value == getTypeDefaultValue(i.Type) { value = wskenv.InterpolateStringWithEnvVar("${" + name + "}") } // if at this point, still value is set to default value of its type // check if input key is defined under Project Inputs if value == getTypeDefaultValue(i.Type) { if i.Type == STRING && i.Value != nil { n := wskenv.GetEnvVarName(i.Value.(string)) if v, ok := projectInputs[n]; ok { value = v.Value.(string) } } } // create a Parameter object based on the package inputs // resolve the value using env. variables // if value is not specified, treat input key as an env. variable // check if input key is defined in environment // else set it to its default value based on the type for now // the input value will be updated if its specified in deployment // or on CLI using --param or --param-file i.Value = value inputs[name] = i } // create an array of Key/Value pair with inputs // inputs name as key and with its value if its not nil keyValArr := make(whisk.KeyValueArr, 0) for name, param := range inputs { var keyVal whisk.KeyValue keyVal.Key = name keyVal.Value = param.Value if keyVal.Value != nil { keyValArr = append(keyValArr, keyVal) } } return inputs, keyValArr, nil } func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath string, managedAnnotations whisk.KeyValue, projectInputs map[string]Parameter) (*whisk.Package, map[string]Parameter, error) { pag := &whisk.Package{} pag.Name = packageName //The namespace for this package is absent, so we use default guest here. pag.Namespace = pkg.Namespace pub := false pag.Publish = &pub //Version is a mandatory value //If it is an empty string, it will be set to default value //And print an warning message // TODO(#673) implement STRICT flag if pkg.Version == "" { warningString := wski18n.T( wski18n.ID_WARN_KEY_MISSING_X_key_X_value_X, map[string]interface{}{ wski18n.KEY_KEY: wski18n.PACKAGE_VERSION, wski18n.KEY_VALUE: DEFAULT_PACKAGE_VERSION}) wskprint.PrintOpenWhiskWarning(warningString) warningString = wski18n.T( wski18n.ID_WARN_KEYVALUE_NOT_SAVED_X_key_X, map[string]interface{}{wski18n.KEY_KEY: wski18n.PACKAGE_VERSION}) wskprint.PrintOpenWhiskWarning(warningString) pkg.Version = DEFAULT_PACKAGE_VERSION } pag.Version = wskenv.ConvertSingleName(pkg.Version) //License is a mandatory value //set license to unknown if it is an empty string //And print an warning message // TODO(#673) implement STRICT flag if pkg.License == "" { warningString := wski18n.T( wski18n.ID_WARN_KEY_MISSING_X_key_X_value_X, map[string]interface{}{ wski18n.KEY_KEY: wski18n.PACKAGE_LICENSE, wski18n.KEY_VALUE: DEFAULT_PACKAGE_LICENSE}) wskprint.PrintOpenWhiskWarning(warningString) warningString = wski18n.T( wski18n.ID_WARN_KEYVALUE_NOT_SAVED_X_key_X, map[string]interface{}{wski18n.KEY_KEY: wski18n.PACKAGE_VERSION}) wskprint.PrintOpenWhiskWarning(warningString) pkg.License = DEFAULT_PACKAGE_LICENSE } else { utils.CheckLicense(pkg.License) } // package inputs are set as package inputs of type Parameter{} // read all package inputs, interpolate their values using env. variables // check if input variable itself is an env. variable packageInputs, inputs, err := dm.composePackageInputs(projectInputs, pkg.Inputs, filePath) if err != nil { return nil, nil, err } if len(inputs) > 0 { pag.Parameters = inputs } // set Package Annotations listOfAnnotations := dm.composeAnnotations(pkg.Annotations) if len(listOfAnnotations) > 0 { pag.Annotations = append(pag.Annotations, listOfAnnotations...) } // add Managed Annotations if this is Managed Deployment if utils.Flags.Managed || utils.Flags.Sync { pag.Annotations = append(pag.Annotations, managedAnnotations) } // "default" package is a reserved package name // and in this case wskdeploy deploys openwhisk entities under // /namespace instead of /namespace/package if strings.ToLower(pag.Name) == DEFAULT_PACKAGE { wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, wski18n.T(wski18n.ID_MSG_DEFAULT_PACKAGE)) // when a package is marked public with "Public: true" in manifest file // the package is visible to anyone and created with publish state // set to shared otherwise publish state is set to private } else if pkg.Public { warningMsg := wski18n.T(wski18n.ID_WARN_PACKAGE_IS_PUBLIC_X_package_X, map[string]interface{}{ wski18n.KEY_PACKAGE: pag.Name}) wskprint.PrintlnOpenWhiskWarning(warningMsg) pag.Publish = &(pkg.Public) } return pag, packageInputs, nil } func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *YAML, manifestFilePath string, managedAnnotations whisk.KeyValue, packageInputs map[string]PackageInputs) ([]utils.ActionRecord, error) { var sequences []utils.ActionRecord = make([]utils.ActionRecord, 0) manifestPackages := make(map[string]Package) if len(mani.Packages) != 0 { manifestPackages = mani.Packages } else { manifestPackages = mani.GetProject().Packages } for n, p := range manifestPackages { s, err := dm.ComposeSequences(namespace, p.Sequences, n, manifestFilePath, managedAnnotations, packageInputs[n]) if err == nil { sequences = append(sequences, s...) } else { return nil, err } } return sequences, nil } func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Sequence, packageName string, manifestFilePath string, managedAnnotations whisk.KeyValue, packageInputs PackageInputs) ([]utils.ActionRecord, error) { var listOfSequences []utils.ActionRecord = make([]utils.ActionRecord, 0) var errorParser error for key, sequence := range sequences { wskaction := new(whisk.Action) wskaction.Exec = new(whisk.Exec) wskaction.Exec.Kind = YAML_KEY_SEQUENCE actionList := strings.Split(sequence.Actions, ",") var components []string for _, a := range actionList { act := strings.TrimSpace(a) if !strings.ContainsRune(act, []rune(PATH_SEPARATOR)[0]) && !strings.HasPrefix(act, packageName+PATH_SEPARATOR) && strings.ToLower(packageName) != DEFAULT_PACKAGE { act = path.Join(packageName, act) } components = append(components, path.Join(PATH_SEPARATOR+namespace, act)) } wskaction.Exec.Components = components if i, ok := packageInputs.Inputs[wskenv.GetEnvVarName(key)]; ok { wskaction.Name = i.Value.(string) } else { wskaction.Name = wskenv.ConvertSingleName(key) } pub := false wskaction.Publish = &pub wskaction.Namespace = namespace annotations := dm.composeAnnotations(sequence.Annotations) if len(annotations) > 0 { wskaction.Annotations = annotations } // appending managed annotations if its a managed deployment if utils.Flags.Managed || utils.Flags.Sync { wskaction.Annotations = append(wskaction.Annotations, managedAnnotations) } // Web Export // Treat sequence as a web action, a raw HTTP web action, or as a standard action based on web-export; // when web-export is set to yes | true, treat sequence as a web action, // when web-export is set to raw, treat sequence as a raw HTTP web action, // when web-export is set to no | false, treat sequence as a standard action if len(sequence.Web) != 0 { wskaction.Annotations, errorParser = webaction.SetWebActionAnnotations(manifestFilePath, wskaction.Name, sequence.Web, wskaction.Annotations, false) if errorParser != nil { return nil, errorParser } } // TODO Add web-secure support for sequences? record := utils.ActionRecord{Action: wskaction, Packagename: packageName, Filepath: key} listOfSequences = append(listOfSequences, record) } return listOfSequences, nil } func (dm *YAMLParser) ComposeActionsFromAllPackages(manifest *YAML, filePath string, managedAnnotations whisk.KeyValue, packageInputs map[string]PackageInputs) ([]utils.ActionRecord, error) { var actions []utils.ActionRecord = make([]utils.ActionRecord, 0) manifestPackages := make(map[string]Package) if len(manifest.Packages) != 0 { manifestPackages = manifest.Packages } else { manifestPackages = manifest.GetProject().Packages } for n, p := range manifestPackages { a, err := dm.ComposeActions(filePath, p.Actions, n, managedAnnotations, packageInputs[n]) if err == nil { actions = append(actions, a...) } else { return nil, err } } return actions, nil } func (dm *YAMLParser) validateActionCode(manifestFilePath string, action Action) error { // Check if action.Function is specified with action.Code // with action.Code, action.Function is not allowed // with action.Code, action.Runtime should be specified if len(action.Function) != 0 { err := wski18n.T(wski18n.ID_ERR_ACTION_INVALID_X_action_X, map[string]interface{}{ wski18n.KEY_ACTION: action.Name}) return wskderrors.NewYAMLFileFormatError(manifestFilePath, err) } if len(action.Runtime) == 0 { err := wski18n.T(wski18n.ID_ERR_ACTION_MISSING_RUNTIME_WITH_CODE_X_action_X, map[string]interface{}{ wski18n.KEY_ACTION: action.Name}) return wskderrors.NewYAMLFileFormatError(manifestFilePath, err) } return nil } func (dm *YAMLParser) readActionCode(manifestFilePath string, action Action) (*whisk.Exec, error) { exec := new(whisk.Exec) if err := dm.validateActionCode(manifestFilePath, action); err != nil { return nil, err } // validate runtime from the manifest file // error out if the specified runtime is not valid or not supported // even if runtime is invalid, deploy action with specified runtime in strict mode if utils.Flags.Strict { exec.Kind = action.Runtime } else if runtimes.CheckExistRuntime(action.Runtime, runtimes.SupportedRunTimes) { exec.Kind = action.Runtime } else if len(runtimes.DefaultRunTimes[action.Runtime]) != 0 { exec.Kind = runtimes.DefaultRunTimes[action.Runtime] } else { err := wski18n.T(wski18n.ID_ERR_RUNTIME_INVALID_X_runtime_X_action_X, map[string]interface{}{ wski18n.KEY_RUNTIME: action.Runtime, wski18n.KEY_ACTION: action.Name}) return nil, wskderrors.NewYAMLFileFormatError(manifestFilePath, err) } exec.Code = &(action.Code) // we can specify the name of the action entry point using main if len(action.Main) != 0 { exec.Main = action.Main } return exec, nil } func (dm *YAMLParser) validateActionFunction(manifestFileName string, action Action, ext string, kind string) error { // produce an error when a runtime could not be derived from the action file extension // and its not explicitly specified in the manifest YAML file // and action source is not a zip file if len(action.Runtime) == 0 && len(action.Docker) == 0 && !action.Native { if ext == runtimes.ZIP_FILE_EXTENSION { errMessage := wski18n.T(wski18n.ID_ERR_RUNTIME_INVALID_X_runtime_X_action_X, map[string]interface{}{ wski18n.KEY_RUNTIME: runtimes.RUNTIME_NOT_SPECIFIED, wski18n.KEY_ACTION: action.Name}) return wskderrors.NewInvalidRuntimeError(errMessage, manifestFileName, action.Name, runtimes.RUNTIME_NOT_SPECIFIED, runtimes.ListOfSupportedRuntimes(runtimes.SupportedRunTimes)) } else if len(kind) == 0 { errMessage := wski18n.T(wski18n.ID_ERR_RUNTIME_ACTION_SOURCE_NOT_SUPPORTED_X_ext_X_action_X, map[string]interface{}{ wski18n.KEY_EXTENSION: ext, wski18n.KEY_ACTION: action.Name}) return wskderrors.NewInvalidRuntimeError(errMessage, manifestFileName, action.Name, runtimes.RUNTIME_NOT_SPECIFIED, runtimes.ListOfSupportedRuntimes(runtimes.SupportedRunTimes)) } } return nil } func (dm *YAMLParser) readActionFunction(manifestFilePath string, manifestFileName string, action Action) (string, *whisk.Exec, error) { var actionFilePath string var zipFileName string exec := new(whisk.Exec) f := wskenv.InterpolateStringWithEnvVar(action.Function) interpolatedActionFunction := f.(string) // check if action function is pointing to an URL // we do not support if function is pointing to remote directory // therefore error out if there is a combination of http/https ending in a directory if strings.HasPrefix(interpolatedActionFunction, HTTP) || strings.HasPrefix(interpolatedActionFunction, HTTPS) { if len(path.Ext(interpolatedActionFunction)) == 0 { err := wski18n.T(wski18n.ID_ERR_ACTION_FUNCTION_REMOTE_DIR_NOT_SUPPORTED_X_action_X_url_X, map[string]interface{}{ wski18n.KEY_ACTION: action.Name, wski18n.KEY_URL: interpolatedActionFunction}) return actionFilePath, nil, wskderrors.NewYAMLFileFormatError(manifestFilePath, err) } actionFilePath = interpolatedActionFunction } else { actionFilePath = strings.TrimRight(manifestFilePath, manifestFileName) + interpolatedActionFunction } if utils.IsDirectory(actionFilePath) { zipFileName = actionFilePath + "." + runtimes.ZIP_FILE_EXTENSION err := utils.NewZipWriter(actionFilePath, zipFileName, action.Include, action.Exclude, filepath.Dir(manifestFilePath)).Zip() if err != nil { return actionFilePath, nil, err } defer os.Remove(zipFileName) actionFilePath = zipFileName } action.Function = actionFilePath // determine extension of the given action source file ext := filepath.Ext(actionFilePath) // drop the "." from file extension if len(ext) > 0 && ext[0] == '.' { ext = ext[1:] } // determine default runtime for the given file extension var kind string r := runtimes.FileExtensionRuntimeKindMap[ext] kind = runtimes.DefaultRunTimes[r] if err := dm.validateActionFunction(manifestFileName, action, ext, kind); err != nil { return actionFilePath, nil, err } exec.Kind = kind dat, err := utils.Read(actionFilePath) if err != nil { return actionFilePath, nil, err } code := string(dat) if ext == runtimes.ZIP_FILE_EXTENSION || ext == runtimes.JAR_FILE_EXTENSION { code = base64.StdEncoding.EncodeToString([]byte(dat)) } exec.Code = &code /* * Action.Runtime * Perform few checks if action runtime is specified in manifest YAML file * (1) Check if specified runtime is one of the supported runtimes by OpenWhisk server * (2) Check if specified runtime is consistent with action source file extensions * Set the action runtime to match with the source file extension, if wskdeploy is not invoked in strict mode */ if len(action.Runtime) != 0 { if runtimes.CheckExistRuntime(action.Runtime, runtimes.SupportedRunTimes) { // for zip actions, rely on the runtimes from the manifest file as it can not be derived from the action source file extension // pick runtime from manifest file if its supported by OpenWhisk server if ext == runtimes.ZIP_FILE_EXTENSION { exec.Kind = action.Runtime } else { if runtimes.CheckRuntimeConsistencyWithFileExtension(ext, action.Runtime) { exec.Kind = action.Runtime } else { warnStr := wski18n.T(wski18n.ID_ERR_RUNTIME_MISMATCH_X_runtime_X_ext_X_action_X, map[string]interface{}{ wski18n.KEY_RUNTIME: action.Runtime, wski18n.KEY_EXTENSION: ext, wski18n.KEY_ACTION: action.Name}) wskprint.PrintOpenWhiskWarning(warnStr) // even if runtime is not consistent with file extension, deploy action with specified runtime in strict mode if utils.Flags.Strict { exec.Kind = action.Runtime } else { warnStr := wski18n.T(wski18n.ID_WARN_RUNTIME_CHANGED_X_runtime_X_action_X, map[string]interface{}{ wski18n.KEY_RUNTIME: exec.Kind, wski18n.KEY_ACTION: action.Name}) wskprint.PrintOpenWhiskWarning(warnStr) } } } } else { warnStr := wski18n.T(wski18n.ID_ERR_RUNTIME_INVALID_X_runtime_X_action_X, map[string]interface{}{ wski18n.KEY_RUNTIME: action.Runtime, wski18n.KEY_ACTION: action.Name}) wskprint.PrintOpenWhiskWarning(warnStr) if ext == runtimes.ZIP_FILE_EXTENSION { // for zip action, error out if specified runtime is not supported by // OpenWhisk server return actionFilePath, nil, wskderrors.NewInvalidRuntimeError(warnStr, manifestFileName, action.Name, action.Runtime, runtimes.ListOfSupportedRuntimes(runtimes.SupportedRunTimes)) } else { if utils.Flags.Strict { exec.Kind = action.Runtime } else { warnStr := wski18n.T(wski18n.ID_WARN_RUNTIME_CHANGED_X_runtime_X_action_X, map[string]interface{}{ wski18n.KEY_RUNTIME: exec.Kind, wski18n.KEY_ACTION: action.Name}) wskprint.PrintOpenWhiskWarning(warnStr) } } } } // we can specify the name of the action entry point using main if len(action.Main) != 0 { exec.Main = action.Main } return actionFilePath, exec, nil } func (dm *YAMLParser) composeActionExec(manifestFilePath string, manifestFileName string, action Action) (string, *whisk.Exec, error) { var actionFilePath string exec := new(whisk.Exec) var err error if len(action.Code) != 0 { exec, err = dm.readActionCode(manifestFilePath, action) if err != nil { return actionFilePath, nil, err } } if len(action.Function) != 0 { actionFilePath, exec, err = dm.readActionFunction(manifestFilePath, manifestFileName, action) if err != nil { return actionFilePath, nil, err } } // when an action has Docker image specified, // set exec.Kind to "blackbox" and // set exec.Image to specified image e.g. dockerhub/image // when an action Native is set to true, // set exec.Image to openwhisk/skeleton if len(action.Docker) != 0 || action.Native { exec.Kind = runtimes.BLACKBOX if action.Native { exec.Image = NATIVE_DOCKER_IMAGE } else { exec.Image = wskenv.InterpolateStringWithEnvVar(action.Docker).(string) } } return actionFilePath, exec, err } func (dm *YAMLParser) validateActionLimits(limits Limits) { // TODO() use LIMITS_UNSUPPORTED in yamlparser to enumerate through instead of hardcoding // emit warning errors if these limits are not nil utils.NotSupportLimits(limits.ConcurrentActivations, LIMIT_VALUE_CONCURRENT_ACTIVATIONS) utils.NotSupportLimits(limits.UserInvocationRate, LIMIT_VALUE_USER_INVOCATION_RATE) utils.NotSupportLimits(limits.CodeSize, LIMIT_VALUE_CODE_SIZE) utils.NotSupportLimits(limits.ParameterSize, LIMIT_VALUE_PARAMETER_SIZE) } func (dm *YAMLParser) composeActionLimits(limits Limits) *whisk.Limits { dm.validateActionLimits(limits) wsklimits := new(whisk.Limits) for _, t := range LIMITS_SUPPORTED { switch t { case LIMIT_VALUE_TIMEOUT: if utils.LimitsTimeoutValidation(limits.Timeout) { wsklimits.Timeout = limits.Timeout } else { warningString := wski18n.T(wski18n.ID_WARN_LIMIT_IGNORED_X_limit_X, map[string]interface{}{wski18n.KEY_LIMIT: LIMIT_VALUE_TIMEOUT}) wskprint.PrintOpenWhiskWarning(warningString) } case LIMIT_VALUE_MEMORY_SIZE: if utils.LimitsMemoryValidation(limits.Memory) { wsklimits.Memory = limits.Memory } else { warningString := wski18n.T(wski18n.ID_WARN_LIMIT_IGNORED_X_limit_X, map[string]interface{}{wski18n.KEY_LIMIT: LIMIT_VALUE_MEMORY_SIZE}) wskprint.PrintOpenWhiskWarning(warningString) } case LIMIT_VALUE_LOG_SIZE: if utils.LimitsLogsizeValidation(limits.Logsize) { wsklimits.Logsize = limits.Logsize } else { warningString := wski18n.T(wski18n.ID_WARN_LIMIT_IGNORED_X_limit_X, map[string]interface{}{wski18n.KEY_LIMIT: LIMIT_VALUE_LOG_SIZE}) wskprint.PrintOpenWhiskWarning(warningString) } } } if wsklimits.Timeout != nil || wsklimits.Memory != nil || wsklimits.Logsize != nil { return wsklimits } return nil } func (dm *YAMLParser) warnIfRedundantWebActionFlags(action Action) { // Warn user if BOTH web and web-export specified, // as they are redundant; defer to "web" flag and its value if len(action.Web) != 0 && len(action.WebExport) != 0 { warningString := wski18n.T(wski18n.ID_WARN_ACTION_WEB_X_action_X, map[string]interface{}{wski18n.KEY_ACTION: action.Name}) wskprint.PrintOpenWhiskWarning(warningString) } } func (dm *YAMLParser) ComposeActions(manifestFilePath string, actions map[string]Action, packageName string, managedAnnotations whisk.KeyValue, packageInputs PackageInputs) ([]utils.ActionRecord, error) { var errorParser error var listOfActions []utils.ActionRecord = make([]utils.ActionRecord, 0) splitManifestFilePath := strings.Split(manifestFilePath, string(PATH_SEPARATOR)) manifestFileName := splitManifestFilePath[len(splitManifestFilePath)-1] for actionName, action := range actions { var actionFilePath string // update the action (of type Action) to set its name // here key name is the action name action.Name = actionName // Create action data object from client library wskaction := new(whisk.Action) //set action.Function to action.Location //because Location is deprecated in Action entity if len(action.Function) == 0 && len(action.Location) != 0 { action.Function = action.Location } actionFilePath, wskaction.Exec, errorParser = dm.composeActionExec(manifestFilePath, manifestFileName, action) if errorParser != nil { return nil, errorParser } // Action.Inputs listOfInputs, err := dm.composeInputs(action.Inputs, packageInputs, manifestFilePath) if err != nil { return nil, err } if len(listOfInputs) > 0 { wskaction.Parameters = listOfInputs } // Action.Outputs // TODO{} add outputs as annotations (work to discuss officially supporting for compositions) //listOfOutputs, err := dm.composeOutputs(action.Outputs, manifestFilePath) //if err != nil { // return nil, err //} //if len(listOfOutputs) > 0 { // wskaction.Annotations = listOfOutputs //} // Action.Annotations // ================== // WARNING! Processing of explicit Annotations MUST occur before handling of Action keys, as these // keys often need to check for inconsistencies (and raise errors). if listOfAnnotations := dm.composeAnnotations(action.Annotations); len(listOfAnnotations) > 0 { wskaction.Annotations = append(wskaction.Annotations, listOfAnnotations...) } // add managed annotations if its marked as managed deployment if utils.Flags.Managed || utils.Flags.Sync { wskaction.Annotations = append(wskaction.Annotations, managedAnnotations) } // Web Export (i.e., "web-export" annotation) // ========== // Treat ACTION as a web action, a raw HTTP web action, or as a standard action based on web-export; // when web-export is set to yes | true, treat action as a web action, // when web-export is set to raw, treat action as a raw HTTP web action, // when web-export is set to no | false, treat action as a standard action dm.warnIfRedundantWebActionFlags(action) if len(action.GetWeb()) != 0 { wskaction.Annotations, errorParser = webaction.SetWebActionAnnotations( manifestFilePath, action.Name, action.GetWeb(), wskaction.Annotations, false) if errorParser != nil { return listOfActions, errorParser } } // validate special action annotations such as "require-whisk-auth" // TODO: the Manifest parser will validate any declared APIs that ref. this action if wskaction.Annotations != nil { if webaction.HasAnnotation(&wskaction.Annotations, webaction.REQUIRE_WHISK_AUTH) { _, errorParser = webaction.ValidateRequireWhiskAuthAnnotationValue( actionName, wskaction.Annotations.GetValue(webaction.REQUIRE_WHISK_AUTH)) } if errorParser != nil { return listOfActions, errorParser } } // Action.Limits if action.Limits != nil { if wsklimits := dm.composeActionLimits(*(action.Limits)); wsklimits != nil { wskaction.Limits = wsklimits } } // Conductor Action if action.Conductor { wskaction.Annotations = append(wskaction.Annotations, conductor.ConductorAction()) } // Set other top-level values for the action (e.g., name, version, publish, etc.) wskaction.Name = actionName pub := false wskaction.Publish = &pub wskaction.Version = wskenv.ConvertSingleName(action.Version) // create a "record" of the Action relative to its package and function filepath // which will be used to compose the REST API calls record := utils.ActionRecord{Action: wskaction, Packagename: packageName, Filepath: actionFilePath} listOfActions = append(listOfActions, record) } return listOfActions, nil } func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *YAML, filePath string, managedAnnotations whisk.KeyValue, inputs map[string]PackageInputs) ([]*whisk.Trigger, error) { var triggers []*whisk.Trigger = make([]*whisk.Trigger, 0) manifestPackages := make(map[string]Package) if len(manifest.Packages) != 0 { manifestPackages = manifest.Packages } else { manifestPackages = manifest.GetProject().Packages } for packageName, pkg := range manifestPackages { t, err := dm.ComposeTriggers(filePath, pkg, managedAnnotations, inputs[packageName]) if err == nil { triggers = append(triggers, t...) } else { return nil, err } } return triggers, nil } func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package, managedAnnotations whisk.KeyValue, packageInputs PackageInputs) ([]*whisk.Trigger, error) { var errorParser error var listOfTriggers []*whisk.Trigger = make([]*whisk.Trigger, 0) for _, trigger := range pkg.GetTriggerList() { wsktrigger := new(whisk.Trigger) if i, ok := packageInputs.Inputs[wskenv.GetEnvVarName(trigger.Name)]; ok { wsktrigger.Name = i.Value.(string) } else { wsktrigger.Name = wskenv.ConvertSingleName(trigger.Name) } wsktrigger.Namespace = trigger.Namespace pub := false wsktrigger.Publish = &pub // print warning information when .Source key's value is not empty if len(trigger.Source) != 0 { warningString := wski18n.T( wski18n.ID_WARN_KEY_DEPRECATED_X_oldkey_X_filetype_X_newkey_X, map[string]interface{}{ wski18n.KEY_OLD: YAML_KEY_SOURCE, wski18n.KEY_NEW: YAML_KEY_FEED, wski18n.KEY_FILE_TYPE: wski18n.MANIFEST_FILE}) wskprint.PrintOpenWhiskWarning(warningString) } if len(trigger.Feed) == 0 { trigger.Feed = trigger.Source } // replacing env. variables here in the trigger feed name // to support trigger feed with $READ_FROM_ENV_TRIGGER_FEED trigger.Feed = wskenv.ConvertSingleName(trigger.Feed) keyValArr := make(whisk.KeyValueArr, 0) if len(trigger.Feed) != 0 { var keyVal whisk.KeyValue keyVal.Key = YAML_KEY_FEED keyVal.Value = trigger.Feed keyValArr = append(keyValArr, keyVal) wsktrigger.Annotations = keyValArr } inputs, err := dm.composeInputs(trigger.Inputs, packageInputs, filePath) if err != nil { return nil, errorParser } if len(inputs) > 0 { wsktrigger.Parameters = inputs } listOfAnnotations := dm.composeAnnotations(trigger.Annotations) if len(listOfAnnotations) > 0 { wsktrigger.Annotations = append(wsktrigger.Annotations, listOfAnnotations...) } // add managed annotations if its a managed deployment if utils.Flags.Managed || utils.Flags.Sync { wsktrigger.Annotations = append(wsktrigger.Annotations, managedAnnotations) } listOfTriggers = append(listOfTriggers, wsktrigger) } return listOfTriggers, nil } func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *YAML, managedAnnotations whisk.KeyValue, packageInputs map[string]PackageInputs) ([]*whisk.Rule, error) { var rules []*whisk.Rule = make([]*whisk.Rule, 0) manifestPackages := make(map[string]Package) if len(manifest.Packages) != 0 { manifestPackages = manifest.Packages } else { manifestPackages = manifest.GetProject().Packages } for n, p := range manifestPackages { r, err := dm.ComposeRules(p, n, managedAnnotations, packageInputs[n]) if err == nil { rules = append(rules, r...) } else { return nil, err } } return rules, nil } func (dm *YAMLParser) ComposeRules(pkg Package, packageName string, managedAnnotations whisk.KeyValue, packageInputs PackageInputs) ([]*whisk.Rule, error) { var rules []*whisk.Rule = make([]*whisk.Rule, 0) for _, rule := range pkg.GetRuleList() { wskrule := new(whisk.Rule) if i, ok := packageInputs.Inputs[wskenv.GetEnvVarName(rule.Name)]; ok { wskrule.Name = i.Value.(string) } else { wskrule.Name = wskenv.ConvertSingleName(rule.Name) } //wskrule.Namespace = rule.Namespace pub := false wskrule.Publish = &pub if i, ok := packageInputs.Inputs[wskenv.GetEnvVarName(rule.Trigger)]; ok { wskrule.Trigger = i.Value.(string) } else { wskrule.Trigger = wskenv.ConvertSingleName(rule.Trigger) } if i, ok := packageInputs.Inputs[wskenv.GetEnvVarName(rule.Action)]; ok { wskrule.Action = i.Value.(string) } else { wskrule.Action = wskenv.ConvertSingleName(rule.Action) } act := strings.TrimSpace(wskrule.Action.(string)) if !strings.ContainsRune(act, []rune(PATH_SEPARATOR)[0]) && !strings.HasPrefix(act, packageName+PATH_SEPARATOR) && strings.ToLower(packageName) != DEFAULT_PACKAGE { act = path.Join(packageName, act) } wskrule.Action = act listOfAnnotations := dm.composeAnnotations(rule.Annotations) if len(listOfAnnotations) > 0 { wskrule.Annotations = append(wskrule.Annotations, listOfAnnotations...) } // add managed annotations if its a managed deployment if utils.Flags.Managed || utils.Flags.Sync { wskrule.Annotations = append(wskrule.Annotations, managedAnnotations) } rules = append(rules, wskrule) } return rules, nil } func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(client *whisk.Config, manifest *YAML, actionrecords []utils.ActionRecord, sequencerecords []utils.ActionRecord) ([]*whisk.ApiCreateRequest, map[string]*whisk.ApiCreateRequestOptions, error) { var requests = make([]*whisk.ApiCreateRequest, 0) var responses = make(map[string]*whisk.ApiCreateRequestOptions, 0) manifestPackages := make(map[string]Package) if len(manifest.Packages) != 0 { manifestPackages = manifest.Packages } else { manifestPackages = manifest.GetProject().Packages } for packageName, p := range manifestPackages { r, response, err := dm.ComposeApiRecords(client, packageName, p, manifest.Filepath, actionrecords, sequencerecords) if err == nil { requests = append(requests, r...) for k, v := range response { responses[k] = v } } else { return nil, nil, err } } return requests, responses, nil } /* * 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, actionrecords []utils.ActionRecord, sequencerecords []utils.ActionRecord) ([]*whisk.ApiCreateRequest, map[string]*whisk.ApiCreateRequestOptions, error) { var requests = make([]*whisk.ApiCreateRequest, 0) // supply a dummy API GW token as it is optional if pkg.Apis != nil && len(pkg.Apis) != 0 { if len(client.ApigwAccessToken) == 0 { warningString := wski18n.T(wski18n.ID_MSG_CONFIG_PROVIDE_DEFAULT_APIGW_ACCESS_TOKEN, map[string]interface{}{wski18n.KEY_DUMMY_TOKEN: DUMMY_APIGW_ACCESS_TOKEN}) wskprint.PrintOpenWhiskWarning(warningString) client.ApigwAccessToken = DUMMY_APIGW_ACCESS_TOKEN } } requestOptions := make(map[string]*whisk.ApiCreateRequestOptions, 0) for apiName, apiDoc := range pkg.Apis { for gatewayBasePath, gatewayBasePathMap := range apiDoc { // Base Path // validate base path should not have any path parameters if !isGatewayBasePathValid(gatewayBasePath) { err := wskderrors.NewYAMLParserErr(manifestPath, wski18n.T(wski18n.ID_ERR_API_GATEWAY_BASE_PATH_INVALID_X_api_X, map[string]interface{}{wski18n.KEY_API_BASE_PATH: gatewayBasePath})) return requests, requestOptions, err } // append "/" to the gateway base path if its missing if !strings.HasPrefix(gatewayBasePath, PATH_SEPARATOR) { gatewayBasePath = PATH_SEPARATOR + gatewayBasePath } for gatewayRelPath, gatewayRelPathMap := range gatewayBasePathMap { // Relative Path // append "/" to the gateway relative path if its missing if !strings.HasPrefix(gatewayRelPath, PATH_SEPARATOR) { gatewayRelPath = PATH_SEPARATOR + gatewayRelPath } for actionName, gatewayMethodResponse := range gatewayRelPathMap { // verify that the action is defined under action records if _, ok := pkg.Actions[actionName]; ok { // verify that the action is defined as web action; // web or web-export set to any of [true, yes, raw]; if not, // we will try to add it (if no strict" flag) and warn user that we did so if err := webaction.TryUpdateAPIsActionToWebAction(actionrecords, packageName, apiName, actionName, false); err != nil { return requests, requestOptions, err } // verify that the sequence action is defined under sequence records } else if _, ok := pkg.Sequences[actionName]; ok { // verify that the sequence action is defined as web sequence // web or web-export set to any of [true, yes, raw]; if not, // we will try to add it (if no strict" flag) and warn user that we did so if err := webaction.TryUpdateAPIsActionToWebAction(sequencerecords, packageName, apiName, actionName, true); err != nil { return requests, requestOptions, err } } else { return nil, nil, wskderrors.NewYAMLFileFormatError(manifestPath, wski18n.T(wski18n.ID_ERR_API_MISSING_ACTION_OR_SEQUENCE_X_action_or_sequence_X_api_X, map[string]interface{}{ wski18n.KEY_ACTION: actionName, wski18n.KEY_API: apiName})) } // get the list of path parameters from relative path pathParameters := generatePathParameters(gatewayRelPath) // Check if response type is set to http for API using path parameters if strings.ToLower(gatewayMethodResponse.Method) != utils.HTTP_FILE_EXTENSION && len(pathParameters) > 0 { warningString := wski18n.T(wski18n.ID_WARN_API_INVALID_RESPONSE_TYPE, map[string]interface{}{ wski18n.KEY_API: apiName, wski18n.KEY_API_RELATIVE_PATH: gatewayRelPath, wski18n.KEY_RESPONSE: gatewayMethodResponse.Response}) wskprint.PrintlnOpenWhiskWarning(warningString) gatewayMethodResponse.Response = utils.HTTP_FILE_EXTENSION } // Check if API verb is valid, it must be one of (GET, PUT, POST, DELETE) if _, ok := whisk.ApiVerbs[strings.ToUpper(gatewayMethodResponse.Method)]; !ok { return nil, nil, wskderrors.NewInvalidAPIGatewayMethodError(manifestPath, gatewayBasePath+gatewayRelPath, gatewayMethodResponse.Method, dm.getGatewayMethods()) } apiDocActionName := actionName if strings.ToLower(packageName) != DEFAULT_PACKAGE { apiDocActionName = packageName + PATH_SEPARATOR + actionName } // set action of an API Doc apiDocAction := whisk.ApiAction{ Name: apiDocActionName, Namespace: client.Namespace, BackendUrl: strings.Join([]string{HTTPS + client.Host, strings.ToLower(API), API_VERSION, WEB, client.Namespace, packageName, actionName + "." + utils.HTTP_FILE_EXTENSION}, PATH_SEPARATOR), BackendMethod: gatewayMethodResponse.Method, Auth: client.AuthToken, } requestApiDoc := whisk.Api{ GatewayBasePath: gatewayBasePath, PathParameters: pathParameters, GatewayRelPath: gatewayRelPath, GatewayMethod: strings.ToUpper(gatewayMethodResponse.Method), Namespace: client.Namespace, ApiName: apiName, Id: strings.Join([]string{API, client.Namespace, gatewayRelPath}, ":"), Action: &apiDocAction, } request := whisk.ApiCreateRequest{ ApiDoc: &requestApiDoc, } // add a newly created ApiCreateRequest object to a list of requests requests = append(requests, &request) // Create an instance of ApiCreateRequestOptions options := whisk.ApiCreateRequestOptions{ ResponseType: gatewayMethodResponse.Response, } apiPath := request.ApiDoc.ApiName + " " + request.ApiDoc.GatewayBasePath + request.ApiDoc.GatewayRelPath + " " + request.ApiDoc.GatewayMethod requestOptions[apiPath] = &options } } } } return requests, requestOptions, nil } func (dm *YAMLParser) ComposeApiRecordsFromSwagger(client *whisk.Config, manifest *YAML) (*whisk.ApiCreateRequest, *whisk.ApiCreateRequestOptions, error) { var api *whisk.Api var err error var config string = manifest.Project.Config // Check to make sure the manifest has a config under project if config == "" { return nil, nil, nil } api, err = dm.parseSwaggerApi(config, client.Namespace) if err != nil { whisk.Debug(whisk.DbgError, "parseSwaggerApi() error: %s\n", err) errMsg := wski18n.T("Unable to parse swagger file: {{.err}}", map[string]interface{}{"err": err}) whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return nil, nil, whiskErr } apiCreateReq := new(whisk.ApiCreateRequest) apiCreateReq.ApiDoc = api apiCreateReqOptions := new(whisk.ApiCreateRequestOptions) return apiCreateReq, apiCreateReqOptions, nil } /* * Read the swagger config provided by under * Project: * config: swagger_filename.[yaml|yml] * * NOTE: This was lifted almost verbatim from openwhisk-cli/commands/api.go * and as a follow up should probably be moved and updated to live in whiskclient-go * NOTE: This does notb verify that actions used in swagger api definition are defined in * the openwhisk server or manifest. */ func (dm *YAMLParser) parseSwaggerApi(configfile, namespace string) (*whisk.Api, error) { if len(configfile) == 0 { whisk.Debug(whisk.DbgError, "No swagger file is specified\n") errMsg := wski18n.T("A swagger configuration file was not specified.") whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return nil, whiskErr } swagger, err := ioutil.ReadFile(configfile) if err != nil { whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", configfile, err) errMsg := wski18n.T("Error reading swagger file '{{.name}}': {{.err}}", map[string]interface{}{"name": configfile, "err": err}) whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return nil, whiskErr } // Check if this swagger is in JSON or YAML format isYaml := strings.HasSuffix(configfile, YAML_FILE_EXTENSION) || strings.HasSuffix(configfile, YML_FILE_EXTENSION) if isYaml { whisk.Debug(whisk.DbgInfo, "Converting YAML formated API configuration into JSON\n") jsonbytes, err := yamlHelper.YAMLToJSON([]byte(swagger)) if err != nil { whisk.Debug(whisk.DbgError, "yaml.YAMLToJSON() error: %s\n", err) errMsg := wski18n.T("Unable to parse YAML configuration file: {{.err}}", map[string]interface{}{"err": err}) whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) return nil, whiskErr } swagger = jsonbytes } // Parse the JSON into a swagger object swaggerObj := new(whisk.ApiSwagger) err = json.Unmarshal([]byte(swagger), swaggerObj) if err != nil { whisk.Debug(whisk.DbgError, "JSON parse of '%s' error: %s\n", configfile, err) errMsg := wski18n.T("Error parsing swagger file '{{.name}}': {{.err}}", map[string]interface{}{"name": configfile, "err": err}) whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return nil, whiskErr } if swaggerObj.BasePath == "" || swaggerObj.SwaggerName == "" || swaggerObj.Info == nil || swaggerObj.Paths == nil { whisk.Debug(whisk.DbgError, "Swagger file is invalid.\n") errMsg := wski18n.T("Swagger file is invalid (missing basePath, info, paths, or swagger fields)") whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return nil, whiskErr } if !strings.HasPrefix(swaggerObj.BasePath, "/") { whisk.Debug(whisk.DbgError, "Swagger file basePath is invalid.\n") errMsg := wski18n.T("Swagger file basePath must start with a leading slash (/)") whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) return nil, whiskErr } api := new(whisk.Api) api.Namespace = namespace api.Swagger = string(swagger) return api, nil } func (dm *YAMLParser) getGatewayMethods() []string { methods := []string{} for k := range whisk.ApiVerbs { methods = append(methods, k) } return methods } func doesPathParamExist(params []string, param string) bool { for _, e := range params { if e == param { return true } } return false } func getPathParameterNames(path string) []string { var pathParameters []string pathElements := strings.Split(path, PATH_SEPARATOR) for _, e := range pathElements { paramName := getParamName(e) if len(paramName) > 0 { if !doesPathParamExist(pathParameters, paramName) { pathParameters = append(pathParameters, paramName) } } } return pathParameters } func generatePathParameters(relativePath string) []whisk.ApiParameter { pathParams := []whisk.ApiParameter{} pathParamNames := getPathParameterNames(relativePath) for _, paramName := range pathParamNames { param := whisk.ApiParameter{Name: paramName, In: "path", Required: true, Type: "string", Description: wski18n.T("Default description for '{{.name}}'", map[string]interface{}{"name": paramName})} pathParams = append(pathParams, param) } return pathParams } func isParam(param string) bool { if strings.HasPrefix(param, PARAM_OPENING_BRACKET) && strings.HasSuffix(param, PARAM_CLOSING_BRACKET) { return true } return false } func getParamName(param string) string { paramName := "" if isParam(param) { paramName = param[1 : len(param)-1] } return paramName } func isGatewayBasePathValid(basePath string) bool { // return false if base path is empty string if len(basePath) == 0 { return false } // drop preceding "/" if exists if strings.HasPrefix(basePath, PATH_SEPARATOR) { basePath = basePath[1:] } // slice base path into substrings seperated by "/" // if there are more than one substrings, basePath has path parameters // basePath will have path parameters if substrings count is more than 1 basePathElements := strings.Split(basePath, PATH_SEPARATOR) if len(basePathElements) > 1 { return false } return true }