| /* |
| * 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 deployers |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "github.com/apache/openwhisk-wskdeploy/webaction" |
| "net/http" |
| "path" |
| "reflect" |
| "strconv" |
| "strings" |
| "sync" |
| "time" |
| |
| "github.com/apache/openwhisk-client-go/whisk" |
| "github.com/apache/openwhisk-wskdeploy/dependencies" |
| "github.com/apache/openwhisk-wskdeploy/parsers" |
| "github.com/apache/openwhisk-wskdeploy/utils" |
| "github.com/apache/openwhisk-wskdeploy/wskderrors" |
| "github.com/apache/openwhisk-wskdeploy/wski18n" |
| "github.com/apache/openwhisk-wskdeploy/wskprint" |
| ) |
| |
| const ( |
| CONFLICT_MESSAGE = "Concurrent modification to resource detected" |
| CONFLICT_CODE = 153 |
| DEFAULT_ATTEMPTS = 3 |
| DEFAULT_INTERVAL = 1 * time.Second |
| ) |
| |
| type DeploymentProject struct { |
| Packages map[string]*DeploymentPackage |
| Triggers map[string]*whisk.Trigger |
| Rules map[string]*whisk.Rule |
| Apis map[string]*whisk.ApiCreateRequest |
| ApiOptions map[string]*whisk.ApiCreateRequestOptions |
| SwaggerApi *whisk.ApiCreateRequest |
| SwaggerApiOptions *whisk.ApiCreateRequestOptions |
| } |
| |
| func NewDeploymentProject() *DeploymentProject { |
| var dep DeploymentProject |
| dep.Packages = make(map[string]*DeploymentPackage) |
| dep.Triggers = make(map[string]*whisk.Trigger) |
| dep.Rules = make(map[string]*whisk.Rule) |
| dep.Apis = make(map[string]*whisk.ApiCreateRequest) |
| dep.ApiOptions = make(map[string]*whisk.ApiCreateRequestOptions) |
| return &dep |
| } |
| |
| type DeploymentPackage struct { |
| Package *whisk.Package |
| Dependencies map[string]dependencies.DependencyRecord |
| Actions map[string]utils.ActionRecord |
| Sequences map[string]utils.ActionRecord |
| Inputs parsers.PackageInputs |
| } |
| |
| func NewDeploymentPackage() *DeploymentPackage { |
| var dep DeploymentPackage |
| dep.Dependencies = make(map[string]dependencies.DependencyRecord) |
| dep.Actions = make(map[string]utils.ActionRecord) |
| dep.Sequences = make(map[string]utils.ActionRecord) |
| dep.Inputs = parsers.PackageInputs{} |
| return &dep |
| } |
| |
| // ServiceDeployer defines a prototype service deployer. |
| // It should do the following: |
| // 1. Collect information from the manifest file (if any) |
| // 2. Collect information from the deployment file (if any) |
| // 3. Collect information about the source code files in the working directory |
| // 4. Create a deployment plan to create OpenWhisk service |
| type ServiceDeployer struct { |
| ProjectName string |
| ProjectInputs map[string]parsers.Parameter |
| Deployment *DeploymentProject |
| Client *whisk.Client |
| mt sync.RWMutex |
| Preview bool |
| Report bool |
| ManifestPath string |
| ProjectPath string |
| DeploymentPath string |
| ClientConfig *whisk.Config |
| DependencyMaster map[string]dependencies.DependencyRecord |
| ManagedAnnotation whisk.KeyValue |
| } |
| |
| // NewServiceDeployer is a Factory to create a new ServiceDeployer |
| func NewServiceDeployer() *ServiceDeployer { |
| var dep ServiceDeployer |
| dep.Deployment = NewDeploymentProject() |
| dep.Preview = true |
| dep.DependencyMaster = make(map[string]dependencies.DependencyRecord) |
| dep.ProjectInputs = make(map[string]parsers.Parameter, 0) |
| return &dep |
| } |
| |
| // Check if the manifest yaml could be parsed by Manifest Parser. |
| // Check if the deployment yaml could be parsed by Manifest Parser. |
| func (deployer *ServiceDeployer) Check() { |
| ps := parsers.NewYAMLParser() |
| if utils.FileExists(deployer.DeploymentPath) { |
| ps.ParseDeployment(deployer.DeploymentPath) |
| } |
| ps.ParseManifest(deployer.ManifestPath) |
| // add more schema check or manifest/deployment consistency checks here if |
| // necessary |
| } |
| |
| func (deployer *ServiceDeployer) setProjectInputs(manifest *parsers.YAML) error { |
| for parameterName, param := range manifest.Project.Inputs { |
| p, err := parsers.ResolveParameter(parameterName, ¶m, manifest.Filepath) |
| if err != nil { |
| return err |
| } |
| param.Value = p |
| deployer.ProjectInputs[parameterName] = param |
| } |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) ConstructDeploymentPlan() error { |
| |
| var manifestReader = NewManifestReader(deployer) |
| manifestReader.IsUndeploy = false |
| var err error |
| manifest, manifestParser, err := manifestReader.ParseManifest() |
| if err != nil { |
| return err |
| } |
| |
| deployer.ProjectName = utils.Flags.ProjectName |
| if deployer.ProjectName == "" { |
| deployer.ProjectName = manifest.GetProject().Name |
| } else { |
| warningString := wski18n.T( |
| wski18n.ID_WARN_PROJECT_NAME_OVERRIDDEN, |
| map[string]interface{}{ |
| wski18n.KEY_PROJECT: deployer.ProjectName}) |
| wskprint.PrintOpenWhiskWarning(warningString) |
| } |
| |
| err = deployer.setProjectInputs(manifest) |
| if err != nil { |
| return err |
| } |
| |
| // Generate Managed Annotations if its marked as a Managed Deployment |
| // Managed deployments are the ones when OpenWhisk entities are deployed with command line flag --managed. |
| // Which results in a hidden annotation in every OpenWhisk entity in manifest file. |
| if utils.Flags.Managed || utils.Flags.Sync { |
| // OpenWhisk entities are annotated with Project Name and therefore |
| // Project Name in manifest/deployment file is mandatory for managed deployments |
| if deployer.ProjectName == "" { |
| errmsg := wski18n.T(wski18n.ID_ERR_KEY_MISSING_X_key_X, |
| map[string]interface{}{wski18n.KEY_KEY: wski18n.NAME_PROJECT}) |
| |
| return wskderrors.NewYAMLFileFormatError(manifest.Filepath, errmsg) |
| } |
| // Every OpenWhisk entity in the manifest file will be annotated with: |
| //managed: '{"__OW__PROJECT__NAME": <name>, "__OW__PROJECT_HASH": <hash>, "__OW__FILE": <path>}' |
| deployer.ManagedAnnotation, err = utils.GenerateManagedAnnotation(deployer.ProjectName, manifest.Filepath) |
| if err != nil { |
| return wskderrors.NewYAMLFileFormatError(manifest.Filepath, err.Error()) |
| } |
| } |
| |
| err = manifestReader.InitPackages(manifestParser, manifest, deployer.ManagedAnnotation) |
| if err != nil { |
| return err |
| } |
| |
| projectName := "" |
| if len(manifest.GetProject().Packages) != 0 { |
| projectName = manifest.GetProject().Name |
| } |
| |
| // process deployment file |
| var deploymentReader = NewDeploymentReader(deployer) |
| if utils.FileExists(deployer.DeploymentPath) { |
| err = deploymentReader.HandleYaml() |
| if err != nil { |
| return err |
| } |
| |
| // compare the name of the project |
| if len(deploymentReader.DeploymentDescriptor.GetProject().Packages) != 0 && len(projectName) != 0 { |
| projectNameDeploy := deploymentReader.DeploymentDescriptor.GetProject().Name |
| if projectNameDeploy != projectName { |
| errorString := wski18n.T(wski18n.ID_ERR_NAME_MISMATCH_X_key_X_dname_X_dpath_X_mname_X_moath_X, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: parsers.YAML_KEY_PROJECT, |
| wski18n.KEY_DEPLOYMENT_NAME: projectNameDeploy, |
| wski18n.KEY_DEPLOYMENT_PATH: deployer.DeploymentPath, |
| wski18n.KEY_MANIFEST_NAME: projectName, |
| wski18n.KEY_MANIFEST_PATH: deployer.ManifestPath}) |
| return wskderrors.NewYAMLFileFormatError(manifest.Filepath, errorString) |
| } |
| } |
| } |
| |
| // overwrite package inputs based on command line parameters |
| // overwrite package inputs with the values from command line --param and/or --param-file in order |
| err = deployer.UpdatePackageInputs() |
| if err != nil { |
| return err |
| } |
| |
| // process manifest file |
| err = manifestReader.HandleYaml(manifestParser, manifest, deployer.ManagedAnnotation) |
| if err != nil { |
| return err |
| } |
| |
| // process deployment file |
| if utils.FileExists(deployer.DeploymentPath) { |
| if err := deploymentReader.BindAssets(); err != nil { |
| return err |
| } |
| } |
| |
| return err |
| } |
| |
| func (deployer *ServiceDeployer) ConstructUnDeploymentPlan() (*DeploymentProject, error) { |
| |
| var manifestReader = NewManifestReader(deployer) |
| manifestReader.IsUndeploy = true |
| var err error |
| manifest, manifestParser, err := manifestReader.ParseManifest() |
| if err != nil { |
| return deployer.Deployment, err |
| } |
| |
| manifestReader.InitPackages(manifestParser, manifest, whisk.KeyValue{}) |
| |
| // process manifest file |
| err = manifestReader.HandleYaml(manifestParser, manifest, whisk.KeyValue{}) |
| if err != nil { |
| return deployer.Deployment, err |
| } |
| |
| projectName := "" |
| if len(manifest.GetProject().Packages) != 0 { |
| projectName = manifest.GetProject().Name |
| } |
| |
| // process deployment file |
| if utils.FileExists(deployer.DeploymentPath) { |
| var deploymentReader = NewDeploymentReader(deployer) |
| err = deploymentReader.HandleYaml() |
| if err != nil { |
| return deployer.Deployment, err |
| } |
| |
| // compare the name of the project |
| if len(deploymentReader.DeploymentDescriptor.GetProject().Packages) != 0 && len(projectName) != 0 { |
| projectNameDeploy := deploymentReader.DeploymentDescriptor.GetProject().Name |
| if projectNameDeploy != projectName { |
| errorString := wski18n.T(wski18n.ID_ERR_NAME_MISMATCH_X_key_X_dname_X_dpath_X_mname_X_moath_X, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: parsers.YAML_KEY_PROJECT, |
| wski18n.KEY_DEPLOYMENT_NAME: projectNameDeploy, |
| wski18n.KEY_DEPLOYMENT_PATH: deployer.DeploymentPath, |
| wski18n.KEY_MANIFEST_NAME: projectName, |
| wski18n.KEY_MANIFEST_PATH: deployer.ManifestPath}) |
| return deployer.Deployment, wskderrors.NewYAMLFileFormatError(manifest.Filepath, errorString) |
| } |
| } |
| |
| if err := deploymentReader.BindAssets(); err != nil { |
| return deployer.Deployment, err |
| } |
| } |
| |
| verifiedPlan := deployer.Deployment |
| |
| return verifiedPlan, err |
| } |
| |
| // Use reflect util to deploy everything in this service deployer |
| // TODO(TBD): according to some planning? |
| func (deployer *ServiceDeployer) Deploy() error { |
| |
| if deployer.Preview { |
| deployer.printDeploymentAssets(deployer.Deployment) |
| return nil |
| } |
| |
| if deployer.Report { |
| deployer.reportInputs() |
| return nil |
| } |
| |
| if err := deployer.deployAssets(); err != nil { |
| wskprint.PrintOpenWhiskError(wski18n.T(wski18n.ID_MSG_DEPLOYMENT_FAILED)) |
| return err |
| } |
| |
| wskprint.PrintOpenWhiskSuccess(wski18n.T(wski18n.T(wski18n.ID_MSG_DEPLOYMENT_SUCCEEDED))) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) deployAssets() error { |
| |
| if err := deployer.DeployPackages(); err != nil { |
| return err |
| } |
| |
| if err := deployer.DeployDependencies(); err != nil { |
| return err |
| } |
| |
| if err := deployer.DeployActions(); err != nil { |
| return err |
| } |
| |
| if err := deployer.DeploySequences(); err != nil { |
| return err |
| } |
| |
| if err := deployer.DeployTriggers(); err != nil { |
| return err |
| } |
| |
| if err := deployer.DeployRules(); err != nil { |
| return err |
| } |
| |
| if err := deployer.DeployApis(); err != nil { |
| return err |
| } |
| |
| // During managed deployments, after deploying list of entities in a project |
| // refresh previously deployed project entities, delete the assets which is no longer part of the project |
| // i.e. in a subsequent managed deployment of the same project minus few OpenWhisk entities |
| // from the manifest file must result in undeployment of those deleted entities |
| if utils.Flags.Managed || utils.Flags.Sync { |
| if err := deployer.RefreshManagedEntities(deployer.ManagedAnnotation); err != nil { |
| errString := wski18n.T(wski18n.ID_MSG_MANAGED_UNDEPLOYMENT_FAILED) |
| whisk.Debug(whisk.DbgError, errString) |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) DeployDependencies() error { |
| for _, pack := range deployer.Deployment.Packages { |
| for depName, depRecord := range pack.Dependencies { |
| output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYING_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| whisk.Debug(whisk.DbgInfo, output) |
| |
| if depRecord.IsBinding { |
| bindingPackage := new(whisk.BindingPackage) |
| bindingPackage.Namespace = pack.Package.Namespace |
| bindingPackage.Name = depName |
| pub := false |
| bindingPackage.Publish = &pub |
| |
| qName, err := utils.ParseQualifiedName(depRecord.Location, pack.Package.Namespace) |
| if err != nil { |
| return err |
| } |
| bindingPackage.Binding = whisk.Binding{qName.Namespace, qName.EntityName} |
| |
| bindingPackage.Parameters = depRecord.Parameters |
| bindingPackage.Annotations = depRecord.Annotations |
| |
| error := deployer.createBinding(bindingPackage) |
| if error != nil { |
| return error |
| } else { |
| output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYMENT_SUCCESS_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| whisk.Debug(whisk.DbgInfo, output) |
| } |
| |
| } else { |
| depServiceDeployer, err := deployer.getDependentDeployer(depName, depRecord) |
| if err != nil { |
| return err |
| } |
| |
| err = depServiceDeployer.ConstructDeploymentPlan() |
| if err != nil { |
| return err |
| } |
| |
| dependentPackages := []string{} |
| for k := range depServiceDeployer.Deployment.Packages { |
| dependentPackages = append(dependentPackages, k) |
| } |
| |
| if len(dependentPackages) > 1 { |
| // TODO(799) i18n |
| errMessage := "GitHub dependency " + depName + " has multiple packages in manifest file: " + |
| strings.Join(dependentPackages, ", ") + ". " + |
| "One GitHub dependency can only be associated with single package in manifest file." + |
| "There is no way to reference actions from multiple packages of any GitHub dependencies." |
| return wskderrors.NewYAMLFileFormatError(deployer.ManifestPath, errMessage) |
| } |
| |
| if err := depServiceDeployer.deployAssets(); err != nil { |
| errString := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYMENT_FAILURE_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| wskprint.PrintOpenWhiskError(errString) |
| return err |
| } |
| |
| // if the dependency name in the original package |
| // is different from the package name in the manifest |
| // file of dependent github repo, create a binding to the origin package |
| if ok := depServiceDeployer.Deployment.Packages[depName]; ok == nil { |
| bindingPackage := new(whisk.BindingPackage) |
| bindingPackage.Namespace = pack.Package.Namespace |
| bindingPackage.Name = depName |
| pub := false |
| bindingPackage.Publish = &pub |
| |
| qName, err := utils.ParseQualifiedName(dependentPackages[0], depServiceDeployer.Deployment.Packages[dependentPackages[0]].Package.Namespace) |
| if err != nil { |
| return err |
| } |
| |
| bindingPackage.Binding = whisk.Binding{qName.Namespace, qName.EntityName} |
| |
| bindingPackage.Parameters = depRecord.Parameters |
| bindingPackage.Annotations = depRecord.Annotations |
| |
| err = deployer.createBinding(bindingPackage) |
| if err != nil { |
| return err |
| } else { |
| output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_DEPLOYMENT_SUCCESS_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| whisk.Debug(whisk.DbgInfo, output) |
| } |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // TODO() display "update" | "synced" messages pre/post |
| func (deployer *ServiceDeployer) RefreshManagedEntities(maValue whisk.KeyValue) error { |
| |
| ma := maValue.Value.(map[string]interface{}) |
| if err := deployer.RefreshManagedTriggers(ma); err != nil { |
| return err |
| } |
| |
| if err := deployer.RefreshManagedRules(ma); err != nil { |
| return err |
| } |
| |
| if err := deployer.RefreshManagedPackages(ma); err != nil { |
| return err |
| } |
| |
| if err := deployer.RefreshManagedPackagesWithDependencies(ma); err != nil { |
| return err |
| } |
| return nil |
| |
| } |
| |
| // TODO() display "update" | "synced" messages pre/post |
| func (deployer *ServiceDeployer) RefreshManagedActions(packageName string, ma map[string]interface{}) error { |
| options := whisk.ActionListOptions{} |
| // get a list of actions in your namespace |
| actions, _, err := deployer.Client.Actions.List(packageName, &options) |
| if err != nil { |
| return err |
| } |
| // iterate over list of actions to find an action with managed annotations |
| // check if "managed" annotation is attached to an action |
| for _, action := range actions { |
| // an annotation with "managed" key indicates that an action was deployed as part of managed deployment |
| // if such annotation exists, check if it belongs to the current managed deployment |
| // this action has attached managed annotations |
| if a := action.Annotations.GetValue(utils.MANAGED); a != nil { |
| // decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH |
| aa := a.(map[string]interface{}) |
| // we have found an action which was earlier part of the current project |
| // and this action was deployed as part of managed deployment and now |
| // must be undeployed as its not part of the project anymore |
| // The annotation with same project name but different project hash indicates |
| // that this action is deleted from the project in manifest file |
| if aa[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && aa[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] { |
| actionName := strings.Join([]string{packageName, action.Name}, "/") |
| |
| output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: parsers.YAML_KEY_ACTION, |
| wski18n.KEY_NAME: actionName, |
| wski18n.KEY_PROJECT: aa[utils.OW_PROJECT_NAME]}) |
| wskprint.PrintOpenWhiskWarning(output) |
| |
| var err error |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, err := deployer.Client.Actions.Delete(actionName) |
| return err |
| }) |
| |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| // TODO() display "update" | "synced" messages pre/post |
| func (deployer *ServiceDeployer) RefreshManagedTriggers(ma map[string]interface{}) error { |
| options := whisk.TriggerListOptions{} |
| // Get list of triggers in your namespace |
| triggers, _, err := deployer.Client.Triggers.List(&options) |
| if err != nil { |
| return err |
| } |
| // iterate over the list of triggers to determine whether any of them was part of managed project |
| // and now deleted from manifest file we can determine that from the managed annotation |
| // If a trigger has attached managed annotation with the project name equals to the current project name |
| // but the project hash is different (project hash differs since the trigger is deleted from the manifest file) |
| for _, trigger := range triggers { |
| // trigger has attached managed annotation |
| if a := trigger.Annotations.GetValue(utils.MANAGED); a != nil { |
| // decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH |
| ta := a.(map[string]interface{}) |
| if ta[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && ta[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] { |
| // we have found a trigger which was earlier part of the current project |
| output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: parsers.YAML_KEY_TRIGGER, |
| wski18n.KEY_NAME: trigger.Name, |
| wski18n.KEY_PROJECT: ma[utils.OW_PROJECT_NAME]}) |
| wskprint.PrintOpenWhiskWarning(output) |
| |
| var err error |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, _, err := deployer.Client.Triggers.Delete(trigger.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| // TODO() display "update" | "synced" messages pre/post |
| func (deployer *ServiceDeployer) RefreshManagedRules(ma map[string]interface{}) error { |
| options := whisk.RuleListOptions{} |
| // Get list of rules in your namespace |
| rules, _, err := deployer.Client.Rules.List(&options) |
| if err != nil { |
| return err |
| } |
| // iterate over the list of rules to determine whether any of them was part of managed project |
| // and now deleted from manifest file we can determine that from the managed annotation |
| // If a rule has attached managed annotation with the project name equals to the current project name |
| // but the project hash is different (project hash differs since the rule is deleted from the manifest file) |
| for _, rule := range rules { |
| // rule has attached managed annotation |
| if a := rule.Annotations.GetValue(utils.MANAGED); a != nil { |
| // decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH |
| ta := a.(map[string]interface{}) |
| if ta[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && ta[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] { |
| // we have found a trigger which was earlier part of the current project |
| output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: parsers.YAML_KEY_RULE, |
| wski18n.KEY_NAME: rule.Name, |
| wski18n.KEY_PROJECT: ma[utils.OW_PROJECT_NAME]}) |
| wskprint.PrintOpenWhiskWarning(output) |
| |
| var err error |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, err := deployer.Client.Rules.Delete(rule.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| // TODO() display "update" | "synced" messages pre/post |
| func (deployer *ServiceDeployer) RefreshManagedPackages(ma map[string]interface{}) error { |
| options := whisk.PackageListOptions{} |
| // Get the list of packages in your namespace |
| packages, _, err := deployer.Client.Packages.List(&options) |
| if err != nil { |
| return err |
| } |
| // iterate over each package to find managed annotations |
| // check if "managed" annotation is attached to a package |
| // when managed project name matches with the current project name and project |
| // hash differs, indicates that the package was part of the current project but |
| // now is deleted from the manifest file and should be undeployed. |
| for _, pkg := range packages { |
| if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil { |
| // decode the JSON blob and retrieve __OW_PROJECT_NAME and __OW_PROJECT_HASH |
| pa := a.(map[string]interface{}) |
| // perform the similar check on the list of actions from this package |
| // since package can not be deleted if its not empty (has any action or sequence) |
| if err := deployer.RefreshManagedActions(pkg.Name, ma); err != nil { |
| return err |
| } |
| // we have found a package which was earlier part of the current project |
| if pa[utils.OW_PROJECT_NAME] == ma[utils.OW_PROJECT_NAME] && pa[utils.OW_PROJECT_HASH] != ma[utils.OW_PROJECT_HASH] { |
| output := wski18n.T(wski18n.ID_MSG_MANAGED_FOUND_DELETED_X_key_X_name_X_project_X, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: parsers.YAML_KEY_PACKAGE, |
| wski18n.KEY_NAME: pkg.Name, |
| wski18n.KEY_PROJECT: pa[utils.OW_PROJECT_NAME]}) |
| wskprint.PrintOpenWhiskWarning(output) |
| |
| var err error |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, err := deployer.Client.Packages.Delete(pkg.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) appendDepAnnotation(list whisk.KeyValueArr, pkg *whisk.Package) whisk.KeyValueArr { |
| depExists := false |
| if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil { |
| //append annotations from this package to deps |
| pkgName := parsers.PATH_SEPARATOR + pkg.Namespace + parsers.PATH_SEPARATOR + pkg.Name |
| for _, dep := range list { |
| if dep.Key == pkgName { |
| depExists = true |
| } |
| } |
| if !depExists { |
| list = append(list, whisk.KeyValue{Key: pkgName, Value: a.(map[string]interface{})}) |
| } |
| } |
| return list |
| } |
| |
| func (deployer *ServiceDeployer) RefreshManagedPackagesWithDependencies(ma map[string]interface{}) error { |
| // iterate over each package from the given project |
| for _, p := range deployer.Deployment.Packages { |
| dependencyAnnotations := make(whisk.KeyValueArr, 0) |
| // iterate over the list of dependencies of the package |
| // dependencies could be labeled same as dependent package name |
| // for example, "helloworld" where the package it depends on is also called "helloworld" |
| // dependencies could be labeled different from the dependent package name |
| // for example, "custom-helloworld" where the package it depends on it called "helloworld" |
| for label, n := range p.Dependencies { |
| // we do not append dependencies in whisk-managed in case of a package binding |
| // for example when a package has dependencies on /whisk.system/ or any other /<namespace>/ |
| // the dependent packages are pre-installed and should not be managed by current project |
| if !n.IsBinding { |
| // find the package using dependency label |
| pkg, _, err := deployer.Client.Packages.Get(label) |
| if err != nil { |
| return err |
| } |
| // if dependency label (custom-helloworld) is different from the dependent package name, |
| // it must have binding set to the original package ("helloworld") |
| if len(pkg.Binding.Name) != 0 { |
| // having dependency on packages under /whisk.system is treated in a different way |
| // in which dependent package under /whisk.system are not modified to add managed annotation |
| // and parent package does not show this dependency in its managed annotation |
| // because whisk.system packages comes pre-packaged and deployed with OpenWhisk server and not |
| // deployed along with application deployments. |
| // get the original package to retrieve its managed annotations |
| pkg, _, err := deployer.Client.Packages.Get(pkg.Binding.Name) |
| if err != nil { |
| return err |
| } |
| dependencyAnnotations = deployer.appendDepAnnotation(dependencyAnnotations, pkg) |
| } else { |
| dependencyAnnotations = deployer.appendDepAnnotation(dependencyAnnotations, pkg) |
| } |
| } |
| } |
| updatedAnnotation, err := utils.AddDependentAnnotation(ma, dependencyAnnotations) |
| if err != nil { |
| return err |
| } |
| p.Package.Annotations.AddOrReplace(&updatedAnnotation) |
| } |
| if err := deployer.DeployPackages(); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) DeployPackages() error { |
| for _, pack := range deployer.Deployment.Packages { |
| // "default" package is a reserved package name |
| // all openwhisk entities will be deployed under |
| // /<namespace> instead of /<namespace>/<package> and |
| // therefore skip creating a new package and set |
| // deployer.DeployActionInPackage to false which is set to true by default |
| if strings.ToLower(pack.Package.Name) != parsers.DEFAULT_PACKAGE { |
| err := deployer.createPackage(pack.Package) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Deploy Sequences into OpenWhisk |
| func (deployer *ServiceDeployer) DeploySequences() error { |
| |
| for _, pack := range deployer.Deployment.Packages { |
| for _, action := range pack.Sequences { |
| err := deployer.createAction(pack.Package.Name, action.Action) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Deploy Actions into OpenWhisk |
| func (deployer *ServiceDeployer) DeployActions() error { |
| |
| for _, pack := range deployer.Deployment.Packages { |
| for _, action := range pack.Actions { |
| err := deployer.createAction(pack.Package.Name, action.Action) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Deploy Triggers into OpenWhisk |
| func (deployer *ServiceDeployer) DeployTriggers() error { |
| for _, trigger := range deployer.Deployment.Triggers { |
| |
| if feedname, isFeed := utils.IsFeedAction(trigger); isFeed { |
| err := deployer.createFeedAction(trigger, feedname) |
| if err != nil { |
| return err |
| } |
| } else { |
| err := deployer.createTrigger(trigger) |
| if err != nil { |
| return err |
| } |
| } |
| |
| } |
| return nil |
| |
| } |
| |
| // Deploy Rules into OpenWhisk |
| func (deployer *ServiceDeployer) DeployRules() error { |
| for _, rule := range deployer.Deployment.Rules { |
| err := deployer.createRule(rule) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // Deploy Apis into OpenWhisk |
| func (deployer *ServiceDeployer) DeployApis() error { |
| var err error |
| // NOTE: Only deploy either swagger or manifest defined api, but not both |
| // NOTE: Swagger API takes precedence |
| if deployer.Deployment.SwaggerApi != nil && deployer.Deployment.SwaggerApiOptions != nil { |
| err = deployer.createSwaggerApi(deployer.Deployment.SwaggerApi) |
| if err != nil { |
| return err |
| } |
| } else { |
| for _, api := range deployer.Deployment.Apis { |
| err = deployer.createApi(api) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) createBinding(packa *whisk.BindingPackage) error { |
| |
| displayPreprocessingInfo(wski18n.PACKAGE_BINDING, packa.Name, true) |
| |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Packages.Insert(packa, true) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, wski18n.PACKAGE_BINDING, true) |
| } |
| |
| displayPostprocessingInfo(wski18n.PACKAGE_BINDING, packa.Name, true) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) createPackage(packa *whisk.Package) error { |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, true) |
| |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Packages.Insert(packa, true) |
| return err |
| }) |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_PACKAGE, true) |
| } |
| |
| displayPostprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, true) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) createTrigger(trigger *whisk.Trigger) error { |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, true) |
| |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Triggers.Insert(trigger, true) |
| return err |
| }) |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_TRIGGER, true) |
| } |
| |
| displayPostprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, true) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) createFeedAction(trigger *whisk.Trigger, feedName string) error { |
| |
| displayPreprocessingInfo(wski18n.TRIGGER_FEED, trigger.Name, true) |
| |
| // to hold and modify trigger parameters, not passed by ref? |
| params := make(map[string]interface{}) |
| |
| // check for strings that are JSON |
| for _, keyVal := range trigger.Parameters { |
| params[keyVal.Key] = keyVal.Value |
| } |
| |
| // TODO() define keys and lifecycle operation names as const |
| params["authKey"] = deployer.ClientConfig.AuthToken |
| params["lifecycleEvent"] = "CREATE" |
| params["triggerName"] = "/" + deployer.Client.Namespace + "/" + trigger.Name |
| |
| pub := true |
| t := &whisk.Trigger{ |
| Name: trigger.Name, |
| Annotations: trigger.Annotations, |
| Publish: &pub, |
| } |
| |
| // triggers created using any of the feeds including cloudant, alarm, message hub etc |
| // does not honor UPDATE or overwrite=true with CREATE |
| // wskdeploy is designed such that, it updates trigger feeds if they exists |
| // or creates new in case they are missing |
| // To address trigger feed UPDATE issue, we are checking here if trigger feed |
| // exists, if so, delete it and recreate it |
| _, r, _ := deployer.Client.Triggers.Get(trigger.Name) |
| if r.StatusCode == 200 { |
| // trigger feed already exists so first lets delete it and then recreate it |
| deployer.deleteFeedAction(trigger, feedName) |
| } |
| |
| var err error |
| var response *http.Response |
| if err = deployer.createTrigger(t); err != nil { |
| return err |
| } |
| qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace) |
| if err != nil { |
| return err |
| } |
| |
| namespace := deployer.Client.Namespace |
| deployer.Client.Namespace = qName.Namespace |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Actions.Invoke(qName.EntityName, params, true, false) |
| return err |
| }) |
| deployer.Client.Namespace = namespace |
| |
| if err != nil { |
| // Remove the created trigger |
| deployer.Client.Triggers.Delete(trigger.Name) |
| |
| retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, _, err := deployer.Client.Triggers.Delete(trigger.Name) |
| return err |
| }) |
| |
| return createWhiskClientError(err.(*whisk.WskError), response, wski18n.TRIGGER_FEED, false) |
| } |
| |
| displayPostprocessingInfo(wski18n.TRIGGER_FEED, trigger.Name, true) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) createRule(rule *whisk.Rule) error { |
| displayPreprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, true) |
| |
| // The rule's trigger should include the namespace with pattern /namespace/trigger |
| rule.Trigger = deployer.getQualifiedName(rule.Trigger.(string)) |
| // The rule's action should include the namespace and package with pattern |
| // /namespace/package/action if that action was created under a package |
| // otherwise action should include the namespace with pattern /namespace/action |
| rule.Action = deployer.getQualifiedName(rule.Action.(string)) |
| |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Rules.Insert(rule, true) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_RULE, true) |
| } |
| |
| // Consecutive deployments of manifest containing trigger with feed action (and rule) result in inactive |
| // rule. The rule seems to become inactive when its trigger get deleted (part of the wskdeploy feed action update) |
| // Currently simply always setting rule status to active in case not specified implicitly |
| _, _, err = deployer.Client.Rules.SetState(rule.Name, "active") |
| if err != nil { |
| return err |
| } |
| |
| displayPostprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, true) |
| return nil |
| } |
| |
| // Utility function to call go-whisk framework to make action |
| func (deployer *ServiceDeployer) createAction(pkgname string, action *whisk.Action) error { |
| // call ActionService through the Client |
| if strings.ToLower(pkgname) != parsers.DEFAULT_PACKAGE { |
| // the action will be created under package with pattern 'packagename/actionname' |
| action.Name = strings.Join([]string{pkgname, action.Name}, "/") |
| } |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, true) |
| |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Actions.Insert(action, true) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_ACTION, true) |
| } |
| |
| displayPostprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, true) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) getAnnotationsFromPackageActionOrSequence(packageActionName string) *whisk.KeyValueArr { |
| |
| if len(packageActionName) != 0 { |
| // Split the package name and action name being searched for |
| aActionName := strings.Split(packageActionName, parsers.PATH_SEPARATOR) |
| |
| // Attempt to locate the named action (or sequence) to return its annotations |
| if pkg, found := deployer.Deployment.Packages[aActionName[0]]; found { |
| if atemp, found := pkg.Actions[aActionName[1]]; found { |
| return &(atemp.Action.Annotations) |
| } else if atemp, found := pkg.Sequences[aActionName[1]]; found { |
| return &(atemp.Action.Annotations) |
| } |
| } |
| } |
| return nil |
| } |
| |
| // create api (API Gateway functionality) |
| func (deployer *ServiceDeployer) createApi(api *whisk.ApiCreateRequest) error { |
| |
| 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 |
| |
| apiCreateReqOptions := deployer.Deployment.ApiOptions[apiPath] |
| |
| // Retrieve annotations on the action we are attempting to create an API for |
| var actionAnnotations *whisk.KeyValueArr |
| actionAnnotations = deployer.getAnnotationsFromPackageActionOrSequence(api.ApiDoc.Action.Name) |
| |
| // Process any special annotations (e.g., "require-whisk-auth") on the associated Action |
| // NOTE: we do not throw an error if annotations are NOT found (nil) since this is already done in |
| // the parsing phase and would be redundant. |
| if actionAnnotations != nil { |
| wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, fmt.Sprintf("Processing action annotations: %v", actionAnnotations)) |
| |
| // If the "require-whisk-auth" annotation is present on the referenced action, |
| // apply its user provided security key (i.e., the annotation's value) to the API |
| if webaction.HasAnnotation(actionAnnotations, webaction.REQUIRE_WHISK_AUTH) { |
| api.ApiDoc.Action.SecureKey = actionAnnotations.GetValue(webaction.REQUIRE_WHISK_AUTH) |
| } |
| } |
| |
| if len(deployer.Client.Config.ApigwTenantId) > 0 { |
| // Use it to identify the IAM namespace |
| apiCreateReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId |
| } else { |
| // assume a CF namespace (SpaceGuid) which is part of the authtoken |
| apiCreateReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0] |
| } |
| |
| apiCreateReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken |
| |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Apis.Insert(api, apiCreateReqOptions, true) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, true) |
| } |
| |
| displayPostprocessingInfo(parsers.YAML_KEY_API, apiPath, true) |
| return nil |
| } |
| |
| // create api (API Gateway functionality) from swagger file |
| func (deployer *ServiceDeployer) createSwaggerApi(api *whisk.ApiCreateRequest) error { |
| var err error |
| var response *http.Response |
| |
| apiCreateReqOptions := deployer.Deployment.SwaggerApiOptions |
| apiCreateReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken |
| // In the case of IAM namespaces, we must use the ApigwTenantId as the SpaceGuid |
| // IAM namespaces can be detected by seeing if the ApigwTenantId is populated |
| if len(deployer.Client.Config.ApigwTenantId) > 0 { |
| apiCreateReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId |
| } else { |
| apiCreateReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0] |
| } |
| |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Apis.Insert(api, apiCreateReqOptions, true) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_API, true) |
| } |
| |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) UnDeploy(verifiedPlan *DeploymentProject) error { |
| if deployer.Preview == true { |
| deployer.printDeploymentAssets(verifiedPlan) |
| return nil |
| } |
| |
| if err := deployer.unDeployAssets(verifiedPlan); err != nil { |
| wskprint.PrintOpenWhiskError(wski18n.T(wski18n.T(wski18n.ID_MSG_UNDEPLOYMENT_FAILED))) |
| return err |
| } |
| |
| wskprint.PrintOpenWhiskSuccess(wski18n.T(wski18n.T(wski18n.ID_MSG_UNDEPLOYMENT_SUCCEEDED))) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) UnDeployProject() error { |
| if err := deployer.UnDeployProjectAssets(); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) unDeployAssets(verifiedPlan *DeploymentProject) error { |
| if err := deployer.UndeploySwaggerApis(verifiedPlan); err != nil { |
| return err |
| } |
| if err := deployer.UnDeployApis(verifiedPlan); err != nil { |
| return err |
| } |
| |
| if err := deployer.UnDeployRules(verifiedPlan); err != nil { |
| return err |
| } |
| |
| if err := deployer.UnDeployTriggers(verifiedPlan); err != nil { |
| return err |
| } |
| |
| if err := deployer.UnDeploySequences(verifiedPlan); err != nil { |
| return err |
| } |
| |
| if err := deployer.UnDeployActions(verifiedPlan); err != nil { |
| return err |
| } |
| |
| if err := deployer.UnDeployPackages(verifiedPlan); err != nil { |
| return err |
| } |
| |
| if err := deployer.UnDeployDependencies(); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) UnDeployDependencies() error { |
| for _, pack := range deployer.Deployment.Packages { |
| for depName, depRecord := range pack.Dependencies { |
| output := wski18n.T(wski18n.ID_MSG_DEPENDENCY_UNDEPLOYING_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| whisk.Debug(whisk.DbgInfo, output) |
| |
| if depRecord.IsBinding { |
| var err error |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, err := deployer.Client.Packages.Delete(depName) |
| return err |
| }) |
| if err != nil { |
| return err |
| } |
| } else { |
| |
| depServiceDeployer, err := deployer.getDependentDeployer(depName, depRecord) |
| if err != nil { |
| return err |
| } |
| |
| plan, err := depServiceDeployer.ConstructUnDeploymentPlan() |
| if err != nil { |
| return err |
| } |
| |
| dependentPackages := []string{} |
| for k := range depServiceDeployer.Deployment.Packages { |
| dependentPackages = append(dependentPackages, k) |
| } |
| |
| // delete binding pkg if the origin package name is different |
| if ok := depServiceDeployer.Deployment.Packages[depName]; ok == nil { |
| if _, _, ok := deployer.Client.Packages.Get(depName); ok == nil { |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| response, err = deployer.Client.Packages.Delete(depName) |
| return err |
| }) |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, wski18n.PACKAGE_BINDING, false) |
| } |
| } |
| } |
| |
| if err := depServiceDeployer.unDeployAssets(plan); err != nil { |
| errString := wski18n.T(wski18n.ID_MSG_DEPENDENCY_UNDEPLOYMENT_FAILURE_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| whisk.Debug(whisk.DbgError, errString) |
| return err |
| } |
| } |
| output = wski18n.T(wski18n.ID_MSG_DEPENDENCY_UNDEPLOYMENT_SUCCESS_X_name_X, |
| map[string]interface{}{wski18n.KEY_NAME: depName}) |
| whisk.Debug(whisk.DbgInfo, output) |
| } |
| } |
| |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) UnDeployPackages(deployment *DeploymentProject) error { |
| for _, pack := range deployment.Packages { |
| // "default" package is a reserved package name |
| // all openwhisk entities were deployed under |
| // /<namespace> instead of /<namespace>/<package> and |
| // therefore skip deleting default package during undeployment |
| if strings.ToLower(pack.Package.Name) != parsers.DEFAULT_PACKAGE { |
| err := deployer.deletePackage(pack.Package) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) UnDeploySequences(deployment *DeploymentProject) error { |
| |
| for _, pack := range deployment.Packages { |
| for _, action := range pack.Sequences { |
| err := deployer.deleteAction(pack.Package.Name, action.Action) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // DeployActions into OpenWhisk |
| func (deployer *ServiceDeployer) UnDeployActions(deployment *DeploymentProject) error { |
| |
| for _, pack := range deployment.Packages { |
| for _, action := range pack.Actions { |
| err := deployer.deleteAction(pack.Package.Name, action.Action) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Deploy Triggers into OpenWhisk |
| func (deployer *ServiceDeployer) UnDeployTriggers(deployment *DeploymentProject) error { |
| |
| for _, trigger := range deployment.Triggers { |
| if feedname, isFeed := utils.IsFeedAction(trigger); isFeed { |
| err := deployer.deleteFeedAction(trigger, feedname) |
| if err != nil { |
| return err |
| } |
| } |
| err := deployer.deleteTrigger(trigger) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| |
| } |
| |
| // Deploy Rules into OpenWhisk |
| func (deployer *ServiceDeployer) UnDeployRules(deployment *DeploymentProject) error { |
| |
| for _, rule := range deployment.Rules { |
| err := deployer.deleteRule(rule) |
| if err != nil { |
| return err |
| } |
| } |
| 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) UndeploySwaggerApis(deployment *DeploymentProject) error { |
| api := deployment.SwaggerApi |
| err := deployer.deleteSwaggerApi(api) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) deletePackage(packa *whisk.Package) error { |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, false) |
| |
| if _, _, ok := deployer.Client.Packages.Get(packa.Name); ok == nil { |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| response, err = deployer.Client.Packages.Delete(packa.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_PACKAGE, false) |
| } |
| } |
| displayPostprocessingInfo(parsers.YAML_KEY_PACKAGE, packa.Name, false) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) deleteTrigger(trigger *whisk.Trigger) error { |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, false) |
| |
| if _, _, ok := deployer.Client.Triggers.Get(trigger.Name); ok == nil { |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Triggers.Delete(trigger.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_TRIGGER, false) |
| } |
| } |
| |
| displayPostprocessingInfo(parsers.YAML_KEY_TRIGGER, trigger.Name, false) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) deleteFeedAction(trigger *whisk.Trigger, feedName string) error { |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_FEED, trigger.Name, false) |
| |
| params := make(whisk.KeyValueArr, 0) |
| // TODO() define keys and operations as const |
| params = append(params, whisk.KeyValue{Key: "authKey", Value: deployer.ClientConfig.AuthToken}) |
| params = append(params, whisk.KeyValue{Key: "lifecycleEvent", Value: "DELETE"}) |
| params = append(params, whisk.KeyValue{Key: "triggerName", Value: "/" + deployer.Client.Namespace + "/" + trigger.Name}) |
| |
| parameters := make(map[string]interface{}) |
| for _, keyVal := range params { |
| parameters[keyVal.Key] = keyVal.Value |
| } |
| |
| qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace) |
| if err != nil { |
| return err |
| } |
| |
| if _, _, ok := deployer.Client.Triggers.Get(trigger.Name); ok != nil { |
| displayPostprocessingInfo(parsers.YAML_KEY_FEED, trigger.Name, false) |
| return nil |
| } |
| |
| namespace := deployer.Client.Namespace |
| deployer.Client.Namespace = qName.Namespace |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| _, response, err = deployer.Client.Actions.Invoke(qName.EntityName, parameters, true, false) |
| return err |
| }) |
| |
| deployer.Client.Namespace = namespace |
| |
| if err != nil { |
| wskErr := err.(*whisk.WskError) |
| errString := wski18n.T(wski18n.ID_ERR_FEED_INVOKE_X_err_X_code_X, |
| map[string]interface{}{wski18n.KEY_ERR: wskErr.Error(), wski18n.KEY_CODE: strconv.Itoa(wskErr.ExitCode)}) |
| whisk.Debug(whisk.DbgError, errString) |
| return wskderrors.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode, response) |
| |
| } |
| displayPostprocessingInfo(parsers.YAML_KEY_FEED, trigger.Name, false) |
| return nil |
| } |
| |
| func (deployer *ServiceDeployer) deleteRule(rule *whisk.Rule) error { |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, false) |
| |
| if _, _, ok := deployer.Client.Rules.Get(rule.Name); ok == nil { |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| response, err = deployer.Client.Rules.Delete(rule.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_RULE, false) |
| } |
| } |
| displayPostprocessingInfo(parsers.YAML_KEY_RULE, rule.Name, false) |
| 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 |
| if len(deployer.Client.Config.ApigwTenantId) > 0 { |
| // Use it to identify the IAM namespace |
| apiReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId |
| } else { |
| // assume a CF namespaces (SpaceGuid) which is part of the authtoken |
| 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 |
| if len(deployer.Client.Config.ApigwTenantId) > 0 { |
| // Use it to identify the IAM namespace |
| apiDeleteReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId |
| } else { |
| // assume a CF namespaces (SpaceGuid) which is part of the authtoken |
| 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 |
| } |
| |
| // delete api (API Gateway functionality) from swagger file |
| func (deployer *ServiceDeployer) deleteSwaggerApi(api *whisk.ApiCreateRequest) error { |
| var err error |
| var response *http.Response |
| |
| // If there is no swagger file do nothing |
| if api == nil { |
| return nil |
| } |
| swaggerString := api.ApiDoc.Swagger |
| swaggerObj := new(whisk.ApiSwagger) |
| err = json.Unmarshal([]byte(swaggerString), swaggerObj) |
| if err != nil { |
| return err |
| } |
| |
| apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions) |
| apiDeleteReqOptions.AccessToken = deployer.Client.Config.ApigwAccessToken |
| apiDeleteReqOptions.ApiBasePath = swaggerObj.BasePath |
| // In the case of IAM namespaces, we must use the ApigwTenantId as the SpaceGuid |
| // IAM namespaces can be detected by seeing if the ApigwTenantId is populated |
| if len(deployer.Client.Config.ApigwTenantId) > 0 { |
| apiDeleteReqOptions.SpaceGuid = deployer.Client.Config.ApigwTenantId |
| } else { |
| apiDeleteReqOptions.SpaceGuid = strings.Split(deployer.Client.Config.AuthToken, ":")[0] |
| } |
| |
| a := new(whisk.ApiDeleteRequest) |
| a.Swagger = swaggerString |
| |
| 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, true) |
| } |
| |
| return nil |
| } |
| |
| // Utility function to call go-whisk framework to delete action |
| func (deployer *ServiceDeployer) deleteAction(pkgname string, action *whisk.Action) error { |
| // call ActionService through Client |
| if pkgname != parsers.DEFAULT_PACKAGE { |
| // the action will be deleted under package with pattern 'packagename/actionname' |
| action.Name = strings.Join([]string{pkgname, action.Name}, "/") |
| } |
| |
| displayPreprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, false) |
| |
| if _, _, ok := deployer.Client.Actions.Get(action.Name, false); ok == nil { |
| var err error |
| var response *http.Response |
| err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { |
| response, err = deployer.Client.Actions.Delete(action.Name) |
| return err |
| }) |
| |
| if err != nil { |
| return createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_ACTION, false) |
| |
| } |
| } |
| displayPostprocessingInfo(parsers.YAML_KEY_ACTION, action.Name, false) |
| return nil |
| } |
| |
| func retry(attempts int, sleep time.Duration, callback func() error) error { |
| var err error |
| for i := 0; ; i++ { |
| err = callback() |
| if i >= (attempts - 1) { |
| break |
| } |
| if err != nil { |
| wskErr := err.(*whisk.WskError) |
| if wskErr.ExitCode == CONFLICT_CODE && strings.Contains(wskErr.Error(), CONFLICT_MESSAGE) { |
| time.Sleep(sleep) |
| warningMsg := wski18n.T(wski18n.ID_WARN_COMMAND_RETRY, |
| map[string]interface{}{ |
| wski18n.KEY_CMD: strconv.Itoa(i + 1), |
| wski18n.KEY_ERR: err.Error()}) |
| wskprint.PrintlnOpenWhiskWarning(warningMsg) |
| } else { |
| return err |
| } |
| } else { |
| return err |
| } |
| } |
| return err |
| } |
| |
| // getQualifiedName(name) returns a fully qualified name given a |
| // (possibly fully qualified) resource name. |
| // |
| // Examples: |
| // (foo) => /ns/foo |
| // (pkg/foo) => /ns/pkg/foo |
| // (/ns/pkg/foo) => /ns/pkg/foo |
| func (deployer *ServiceDeployer) getQualifiedName(name string) string { |
| namespace := deployer.ClientConfig.Namespace |
| if strings.HasPrefix(name, "/") { |
| return name |
| } else if strings.HasPrefix(namespace, "/") { |
| return fmt.Sprintf("%s/%s", namespace, name) |
| } |
| return fmt.Sprintf("/%s/%s", namespace, name) |
| } |
| |
| func (deployer *ServiceDeployer) printDeploymentAssets(assets *DeploymentProject) { |
| |
| // TODO() review format |
| wskprint.PrintlnOpenWhiskOutput(strings.Title(parsers.YAML_KEY_PACKAGES) + ":") |
| for _, pack := range assets.Packages { |
| wskprint.PrintlnOpenWhiskOutput(strings.Title(wski18n.KEY_NAME) + ": " + pack.Package.Name) |
| wskprint.PrintlnOpenWhiskOutput(" " + wski18n.KEY_BINDINGS + ": ") |
| for _, p := range pack.Package.Parameters { |
| jsonValue, err := utils.PrettyJSON(p.Value) |
| if err != nil { |
| fmt.Printf(" - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE) |
| } else { |
| fmt.Printf(" - %s : %v\n", p.Key, jsonValue) |
| } |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput(" " + parsers.YAML_KEY_ANNOTATION + ": ") |
| for _, p := range pack.Package.Annotations { |
| fmt.Printf(" - %s : %v\n", p.Key, p.Value) |
| |
| } |
| |
| for key, dep := range pack.Dependencies { |
| wskprint.PrintlnOpenWhiskOutput(" * " + wski18n.KEY_DEPENDENCY + ": " + key) |
| wskprint.PrintlnOpenWhiskOutput(" " + wski18n.KEY_LOCATION + ": " + dep.Location) |
| if !dep.IsBinding { |
| wskprint.PrintlnOpenWhiskOutput(" " + wski18n.KEY_PATH + ": " + dep.ProjectPath) |
| } |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput("") |
| |
| for _, action := range pack.Actions { |
| wskprint.PrintlnOpenWhiskOutput(" * " + parsers.YAML_KEY_ACTION + ": " + action.Action.Name) |
| wskprint.PrintlnOpenWhiskOutput(" " + wski18n.KEY_BINDINGS + ": ") |
| for _, p := range action.Action.Parameters { |
| |
| if reflect.TypeOf(p.Value).Kind() == reflect.Map { |
| if _, ok := p.Value.(map[interface{}]interface{}); ok { |
| var temp map[string]interface{} = utils.ConvertInterfaceMap(p.Value.(map[interface{}]interface{})) |
| fmt.Printf(" - %s : %v\n", p.Key, temp) |
| } else { |
| jsonValue, err := utils.PrettyJSON(p.Value) |
| if err != nil { |
| fmt.Printf(" - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE) |
| } else { |
| fmt.Printf(" - %s : %v\n", p.Key, jsonValue) |
| } |
| } |
| } else { |
| jsonValue, err := utils.PrettyJSON(p.Value) |
| if err != nil { |
| fmt.Printf(" - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE) |
| } else { |
| fmt.Printf(" - %s : %v\n", p.Key, jsonValue) |
| } |
| } |
| |
| } |
| wskprint.PrintlnOpenWhiskOutput(" " + parsers.YAML_KEY_ANNOTATION + ": ") |
| for _, p := range action.Action.Annotations { |
| fmt.Printf(" - %s : %v\n", p.Key, p.Value) |
| |
| } |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput("") |
| for _, action := range pack.Sequences { |
| wskprint.PrintlnOpenWhiskOutput(" * " + parsers.YAML_KEY_SEQUENCE + ": " + action.Action.Name) |
| wskprint.PrintlnOpenWhiskOutput(" " + parsers.YAML_KEY_ANNOTATION + ": ") |
| for _, p := range action.Action.Annotations { |
| fmt.Printf(" - %s : %v\n", p.Key, p.Value) |
| |
| } |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput("") |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput(wski18n.TRIGGERS + ":") |
| for _, trigger := range assets.Triggers { |
| wskprint.PrintlnOpenWhiskOutput("* " + parsers.YAML_KEY_TRIGGER + ": " + trigger.Name) |
| wskprint.PrintlnOpenWhiskOutput(" " + wski18n.KEY_BINDINGS + ": ") |
| |
| for _, p := range trigger.Parameters { |
| jsonValue, err := utils.PrettyJSON(p.Value) |
| if err != nil { |
| fmt.Printf(" - %s : %s\n", p.Key, wskderrors.STR_UNKNOWN_VALUE) |
| } else { |
| fmt.Printf(" - %s : %v\n", p.Key, jsonValue) |
| } |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput(" " + parsers.YAML_KEY_ANNOTATION + ": ") |
| for _, p := range trigger.Annotations { |
| fmt.Printf(" - %s : %v\n", p.Key, p.Value) |
| |
| } |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput("\n" + wski18n.RULES) |
| for _, rule := range assets.Rules { |
| wskprint.PrintlnOpenWhiskOutput("* " + parsers.YAML_KEY_RULE + ": " + rule.Name) |
| wskprint.PrintlnOpenWhiskOutput(" " + parsers.YAML_KEY_ANNOTATION + ": ") |
| for _, p := range rule.Annotations { |
| fmt.Printf(" - %s : %v\n", p.Key, p.Value) |
| |
| } |
| if reflect.TypeOf(rule.Trigger).Kind() == reflect.String { |
| wskprint.PrintlnOpenWhiskOutput(" - " + parsers.YAML_KEY_TRIGGER + ": " + rule.Trigger.(string) + "\n - " + parsers.YAML_KEY_ACTION + ": " + rule.Action.(string)) |
| } else if reflect.TypeOf(rule.Trigger).Kind() == reflect.Map { |
| trigger := rule.Trigger.(map[string]interface{}) |
| triggerName := trigger["path"].(string) + parsers.PATH_SEPARATOR + trigger["name"].(string) |
| action := rule.Action.(map[string]interface{}) |
| actionName := action["path"].(string) + parsers.PATH_SEPARATOR + action["name"].(string) |
| wskprint.PrintlnOpenWhiskOutput(" - " + parsers.YAML_KEY_TRIGGER + ": " + triggerName + "\n - " + parsers.YAML_KEY_ACTION + ": " + actionName) |
| } |
| |
| } |
| |
| wskprint.PrintlnOpenWhiskOutput("") |
| |
| } |
| |
| func (deployer *ServiceDeployer) getDependentDeployer(depName string, depRecord dependencies.DependencyRecord) (*ServiceDeployer, error) { |
| depServiceDeployer := NewServiceDeployer() |
| projectPath := path.Join(depRecord.ProjectPath, depName+"-"+depRecord.Version) |
| if len(depRecord.SubFolder) > 0 { |
| projectPath = path.Join(projectPath, depRecord.SubFolder) |
| } |
| manifestPath := utils.GetManifestFilePath(projectPath) |
| deploymentPath := utils.GetDeploymentFilePath(projectPath) |
| depServiceDeployer.ProjectPath = projectPath |
| depServiceDeployer.ManifestPath = manifestPath |
| depServiceDeployer.DeploymentPath = deploymentPath |
| depServiceDeployer.Preview = true |
| |
| depServiceDeployer.Client = deployer.Client |
| depServiceDeployer.ClientConfig = deployer.ClientConfig |
| |
| // share the master dependency list |
| depServiceDeployer.DependencyMaster = deployer.DependencyMaster |
| |
| return depServiceDeployer, nil |
| } |
| |
| func displayPreprocessingInfo(entity string, name string, onDeploy bool) { |
| |
| var msgKey string |
| if onDeploy { |
| msgKey = wski18n.ID_MSG_ENTITY_DEPLOYING_X_key_X_name_X |
| } else { |
| msgKey = wski18n.ID_MSG_ENTITY_UNDEPLOYING_X_key_X_name_X |
| } |
| msg := wski18n.T(msgKey, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: entity, |
| wski18n.KEY_NAME: name}) |
| wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, msg) |
| } |
| |
| func displayPostprocessingInfo(entity string, name string, onDeploy bool) { |
| |
| var msgKey string |
| if onDeploy { |
| msgKey = wski18n.ID_MSG_ENTITY_DEPLOYED_SUCCESS_X_key_X_name_X |
| } else { |
| msgKey = wski18n.ID_MSG_ENTITY_UNDEPLOYED_SUCCESS_X_key_X_name_X |
| } |
| msg := wski18n.T(msgKey, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: entity, |
| wski18n.KEY_NAME: name}) |
| wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, msg) |
| } |
| |
| func createWhiskClientError(err *whisk.WskError, response *http.Response, entity string, onCreate bool) *wskderrors.WhiskClientError { |
| |
| var msgKey string |
| if onCreate { |
| msgKey = wski18n.ID_ERR_ENTITY_CREATE_X_key_X_err_X_code_X |
| } else { |
| msgKey = wski18n.ID_ERR_ENTITY_DELETE_X_key_X_err_X_code_X |
| } |
| errString := wski18n.T(msgKey, |
| map[string]interface{}{ |
| wski18n.KEY_KEY: entity, |
| wski18n.KEY_ERR: err.Error(), |
| wski18n.KEY_CODE: strconv.Itoa(err.ExitCode)}) |
| wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, errString) |
| |
| // TODO() add errString as an AppendDetail() to WhiskClientError |
| return wskderrors.NewWhiskClientError(err.Error(), err.ExitCode, response) |
| } |
| |
| func (deployer *ServiceDeployer) reportInputs() error { |
| // display project level inputs |
| i := make(map[string]interface{}, 0) |
| for name, param := range deployer.ProjectInputs { |
| i[name] = param.Value |
| } |
| projectInputs := parsers.DisplayInputs{Name: deployer.ProjectName, Inputs: i} |
| j, err := json.MarshalIndent(projectInputs, "", " ") |
| if err != nil { |
| return err |
| } |
| wskprint.PrintlnOpenWhiskOutput(string(j)) |
| |
| // display package level inputs |
| // iterate over each package and print inputs section of each package |
| for _, pkg := range deployer.Deployment.Packages { |
| i := make(map[string]interface{}, 0) |
| for name, param := range pkg.Inputs.Inputs { |
| if _, ok := deployer.ProjectInputs[name]; !ok { |
| i[name] = param.Value |
| } |
| } |
| packageInputs := parsers.DisplayInputs{Name: pkg.Package.Name, Inputs: i} |
| j, err := json.MarshalIndent(packageInputs, "", " ") |
| if err != nil { |
| return err |
| } |
| wskprint.PrintlnOpenWhiskOutput(string(j)) |
| |
| for _, d := range pkg.Dependencies { |
| i := make(map[string]interface{}, 0) |
| for _, param := range d.Parameters { |
| i[param.Key] = param.Value |
| } |
| depInputs := parsers.DisplayInputs{Name: d.Location, Inputs: i} |
| j, err := json.MarshalIndent(depInputs, "", " ") |
| if err != nil { |
| return err |
| } |
| wskprint.PrintlnOpenWhiskOutput(string(j)) |
| } |
| |
| for _, a := range pkg.Actions { |
| i := make(map[string]interface{}, 0) |
| for _, param := range a.Action.Parameters { |
| i[param.Key] = param.Value |
| } |
| |
| actionInputs := parsers.DisplayInputs{Name: a.Action.Name, Inputs: i} |
| j, err := json.MarshalIndent(actionInputs, "", " ") |
| if err != nil { |
| return err |
| } |
| wskprint.PrintlnOpenWhiskOutput(string(j)) |
| } |
| |
| for _, s := range pkg.Sequences { |
| i := make(map[string]interface{}, 0) |
| for _, param := range s.Action.Parameters { |
| i[param.Key] = param.Value |
| } |
| seqInputs := parsers.DisplayInputs{Name: s.Action.Name, Inputs: i} |
| j, err := json.MarshalIndent(seqInputs, "", " ") |
| if err != nil { |
| return err |
| } |
| wskprint.PrintlnOpenWhiskOutput(string(j)) |
| } |
| } |
| |
| for _, trigger := range deployer.Deployment.Triggers { |
| i := make(map[string]interface{}, 0) |
| for _, param := range trigger.Parameters { |
| i[param.Key] = param.Value |
| } |
| triggerInputs := parsers.DisplayInputs{Name: trigger.Name, Inputs: i} |
| j, err := json.MarshalIndent(triggerInputs, "", " ") |
| if err != nil { |
| return err |
| } |
| wskprint.PrintlnOpenWhiskOutput(string(j)) |
| } |
| return nil |
| } |