blob: 8bc8b64478a128117cbcd6f2a698635fc3df7545 [file] [log] [blame]
/*
* 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 (
"bufio"
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
"github.com/apache/incubator-openwhisk-client-go/whisk"
"github.com/apache/incubator-openwhisk-wskdeploy/parsers"
"github.com/apache/incubator-openwhisk-wskdeploy/utils"
"github.com/apache/incubator-openwhisk-wskdeploy/wski18n"
"reflect"
)
type DeploymentProject struct {
Packages map[string]*DeploymentPackage
Triggers map[string]*whisk.Trigger
Rules map[string]*whisk.Rule
Apis map[string]*whisk.ApiCreateRequest
}
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)
return &dep
}
type DeploymentPackage struct {
Package *whisk.Package
Dependencies map[string]utils.DependencyRecord
Actions map[string]utils.ActionRecord
Sequences map[string]utils.ActionRecord
}
func NewDeploymentPackage() *DeploymentPackage {
var dep DeploymentPackage
dep.Dependencies = make(map[string]utils.DependencyRecord)
dep.Actions = make(map[string]utils.ActionRecord)
dep.Sequences = make(map[string]utils.ActionRecord)
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 {
Deployment *DeploymentProject
Client *whisk.Client
mt sync.RWMutex
RootPackageName string
IsInteractive bool
IsDefault bool
ManifestPath string
ProjectPath string
DeploymentPath string
// whether to deploy the action under the package
DeployActionInPackage bool
InteractiveChoice bool
ClientConfig *whisk.Config
DependencyMaster map[string]utils.DependencyRecord
}
// NewServiceDeployer is a Factory to create a new ServiceDeployer
func NewServiceDeployer() *ServiceDeployer {
var dep ServiceDeployer
dep.Deployment = NewDeploymentProject()
dep.IsInteractive = true
dep.DeployActionInPackage = true
dep.DependencyMaster = make(map[string]utils.DependencyRecord)
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) ConstructDeploymentPlan() error {
var manifestReader = NewManfiestReader(deployer)
manifestReader.IsUndeploy = false
var err error
manifest, manifestParser, err := manifestReader.ParseManifest()
if err != nil {
return err
}
deployer.RootPackageName = manifest.Package.Packagename
manifestReader.InitRootPackage(manifestParser, manifest)
if deployer.IsDefault == true {
fileReader := NewFileSystemReader(deployer)
fileActions, err := fileReader.ReadProjectDirectory(manifest)
if err != nil {
return err
}
fileReader.SetFileActions(fileActions)
}
// process manifest file
err = manifestReader.HandleYaml(deployer, manifestParser, manifest)
if err != nil {
return err
}
projectName := ""
if len(manifest.GetProject().Packages) != 0 {
projectName = manifest.GetProject().Name
}
// (TODO) delete this warning after deprecating application in manifest file
if manifest.Application.Name != "" {
warningString := wski18n.T("WARNING: application in manifest file will soon be deprecated, please use project instead.\n")
whisk.Debug(whisk.DbgWarn, warningString)
}
// process deployment file
if utils.FileExists(deployer.DeploymentPath) {
var deploymentReader = NewDeploymentReader(deployer)
err = deploymentReader.HandleYaml()
if err != nil {
return err
}
// (TODO) delete this warning after deprecating application in deployment file
if deploymentReader.DeploymentDescriptor.Application.Name != "" {
warningString := wski18n.T("WARNING: application in deployment file will soon be deprecated, please use project instead.\n")
whisk.Debug(whisk.DbgWarn, warningString)
}
// compare the name of the project/application
if len(deploymentReader.DeploymentDescriptor.GetProject().Packages) != 0 && len(projectName) != 0 {
projectNameDeploy := deploymentReader.DeploymentDescriptor.GetProject().Name
if projectNameDeploy != projectName {
errorString := wski18n.T("The name of the project/application {{.projectNameDeploy}} in deployment file at [{{.deploymentFile}}] does not match the name of the project/application {{.projectNameManifest}}} in manifest file at [{{.manifestFile}}].",
map[string]interface{}{"projectNameDeploy": projectNameDeploy, "deploymentFile": deployer.DeploymentPath,
"projectNameManifest": projectName, "manifestFile": deployer.ManifestPath})
return utils.NewInputYamlFormatError(errorString)
}
}
deploymentReader.BindAssets()
}
return err
}
func (deployer *ServiceDeployer) ConstructUnDeploymentPlan() (*DeploymentProject, error) {
var manifestReader = NewManfiestReader(deployer)
manifestReader.IsUndeploy = true
var err error
manifest, manifestParser, err := manifestReader.ParseManifest()
if err != nil {
return deployer.Deployment, err
}
deployer.RootPackageName = manifest.Package.Packagename
manifestReader.InitRootPackage(manifestParser, manifest)
// process file system
if deployer.IsDefault == true {
fileReader := NewFileSystemReader(deployer)
fileActions, err := fileReader.ReadProjectDirectory(manifest)
if err != nil {
return deployer.Deployment, err
}
err = fileReader.SetFileActions(fileActions)
if err != nil {
return deployer.Deployment, err
}
}
// process manifest file
err = manifestReader.HandleYaml(deployer, manifestParser, manifest)
if err != nil {
return deployer.Deployment, err
}
projectName := ""
if len(manifest.GetProject().Packages) != 0 {
projectName = manifest.GetProject().Name
}
// (TODO) delete this warning after deprecating application in manifest file
if manifest.Application.Name != "" {
warningString := wski18n.T("WARNING: application in manifest file will soon be deprecated, please use project instead.\n")
whisk.Debug(whisk.DbgWarn, warningString)
}
// process deployment file
if utils.FileExists(deployer.DeploymentPath) {
var deploymentReader = NewDeploymentReader(deployer)
err = deploymentReader.HandleYaml()
if err != nil {
return deployer.Deployment, err
}
// (TODO) delete this warning after deprecating application in deployment file
if deploymentReader.DeploymentDescriptor.Application.Name != "" {
warningString := wski18n.T("WARNING: application in deployment file will soon be deprecated, please use project instead.\n")
whisk.Debug(whisk.DbgWarn, warningString)
}
// compare the name of the application
if len(deploymentReader.DeploymentDescriptor.GetProject().Packages) != 0 && len(projectName) != 0 {
projectNameDeploy := deploymentReader.DeploymentDescriptor.GetProject().Name
if projectNameDeploy != projectName {
errorString := wski18n.T("The name of the project/application {{.projectNameDeploy}} in deployment file at [{{.deploymentFile}}] does not match the name of the application {{.projectNameManifest}}} in manifest file at [{{.manifestFile}}].",
map[string]interface{}{"projectNameDeploy": projectNameDeploy, "deploymentFile": deployer.DeploymentPath,
"projectNameManifest": projectName, "manifestFile": deployer.ManifestPath})
return deployer.Deployment, utils.NewInputYamlFormatError(errorString)
}
}
deploymentReader.BindAssets()
}
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.IsInteractive == true {
deployer.printDeploymentAssets(deployer.Deployment)
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you really want to deploy this? (y/N): ")
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
if text == "" {
text = "n"
}
if strings.EqualFold(text, "y") || strings.EqualFold(text, "yes") {
deployer.InteractiveChoice = true
if err := deployer.deployAssets(); err != nil {
errString := wski18n.T("Deployment did not complete sucessfully. Run `wskdeploy undeploy` to remove partially deployed assets.\n")
whisk.Debug(whisk.DbgError, errString)
return err
}
utils.PrintOpenWhiskOutput(wski18n.T("Deployment completed successfully.\n"))
return nil
} else {
deployer.InteractiveChoice = false
utils.PrintOpenWhiskOutput(wski18n.T("OK. Cancelling deployment.\n"))
return nil
}
}
// non-interactive
if err := deployer.deployAssets(); err != nil {
errString := wski18n.T("Deployment did not complete sucessfully. Run `wskdeploy undeploy` to remove partially deployed assets.\n")
whisk.Debug(whisk.DbgError, errString)
return err
}
utils.PrintOpenWhiskOutput(wski18n.T("Deployment completed successfully.\n"))
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
}
return nil
}
func (deployer *ServiceDeployer) DeployDependencies() error {
for _, pack := range deployer.Deployment.Packages {
for depName, depRecord := range pack.Dependencies {
output := wski18n.T("Deploying dependency {{.output}} ...",
map[string]interface{}{"output": 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("Dependency {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": 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
}
if err := depServiceDeployer.deployAssets(); err != nil {
errString := wski18n.T("Deployment of dependency {{.depName}} did not complete sucessfully. Run `wskdeploy undeploy` to remove partially deployed assets.\n",
map[string]interface{}{"depName": depName})
utils.PrintOpenWhiskErrorMessage(errString)
return err
}
// if the RootPackageName is different from depName
// create a binding to the origin package
if depServiceDeployer.RootPackageName != depName {
bindingPackage := new(whisk.BindingPackage)
bindingPackage.Namespace = pack.Package.Namespace
bindingPackage.Name = depName
pub := false
bindingPackage.Publish = &pub
qName, err := utils.ParseQualifiedName(depServiceDeployer.RootPackageName, depServiceDeployer.Deployment.Packages[depServiceDeployer.RootPackageName].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("Dependency {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": depName})
whisk.Debug(whisk.DbgInfo, output)
}
}
}
}
}
return nil
}
func (deployer *ServiceDeployer) DeployPackages() error {
for _, pack := range deployer.Deployment.Packages {
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 {
error := deployer.createAction(pack.Package.Name, action.Action)
if error != nil {
return error
}
}
}
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 {
error := deployer.createFeedAction(trigger, feedname)
if error != nil {
return error
}
} else {
error := deployer.createTrigger(trigger)
if error != nil {
return error
}
}
}
return nil
}
// Deploy Rules into OpenWhisk
func (deployer *ServiceDeployer) DeployRules() error {
for _, rule := range deployer.Deployment.Rules {
error := deployer.createRule(rule)
if error != nil {
return error
}
}
return nil
}
// Deploy Apis into OpenWhisk
func (deployer *ServiceDeployer) DeployApis() error {
for _, api := range deployer.Deployment.Apis {
error := deployer.createApi(api)
if error != nil {
return error
}
}
return nil
}
func (deployer *ServiceDeployer) createBinding(packa *whisk.BindingPackage) error {
output := wski18n.T("Deploying package binding {{.output}} ...",
map[string]interface{}{"output": packa.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Packages.Insert(packa, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating package binding with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
output := wski18n.T("Package binding {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": packa.Name})
whisk.Debug(whisk.DbgInfo, output)
}
return nil
}
func (deployer *ServiceDeployer) createPackage(packa *whisk.Package) error {
output := wski18n.T("Deploying package {{.output}} ...",
map[string]interface{}{"output": packa.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Packages.Insert(packa, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating package with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
output := wski18n.T("Package {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": packa.Name})
whisk.Debug(whisk.DbgInfo, output)
}
return nil
}
func (deployer *ServiceDeployer) createTrigger(trigger *whisk.Trigger) error {
output := wski18n.T("Deploying trigger {{.output}} ...",
map[string]interface{}{"output": trigger.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Triggers.Insert(trigger, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating trigger with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
output := wski18n.T("Trigger {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": trigger.Name})
whisk.Debug(whisk.DbgInfo, output)
}
return nil
}
func (deployer *ServiceDeployer) createFeedAction(trigger *whisk.Trigger, feedName string) error {
output := wski18n.T("Deploying trigger feed {{.output}} ...",
map[string]interface{}{"output": trigger.Name})
whisk.Debug(whisk.DbgInfo, output)
// 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
}
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)
}
_, _, err := deployer.Client.Triggers.Insert(t, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating trigger with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace)
if err != nil {
return err
}
namespace := deployer.Client.Namespace
deployer.Client.Namespace = qName.Namespace
_, _, err = deployer.Client.Actions.Invoke(qName.EntityName, params, true, false)
deployer.Client.Namespace = namespace
if err != nil {
// Remove the created trigger
deployer.Client.Triggers.Delete(trigger.Name)
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating trigger feed with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
}
output = wski18n.T("Trigger feed {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": trigger.Name})
whisk.Debug(whisk.DbgInfo, output)
return nil
}
func (deployer *ServiceDeployer) createRule(rule *whisk.Rule) error {
// The rule's trigger should include the namespace with pattern /namespace/trigger
rule.Trigger = deployer.getQualifiedName(rule.Trigger.(string), deployer.ClientConfig.Namespace)
// The rule's action should include the namespace and package
// with pattern /namespace/package/action
// TODO(TBD): please refer https://github.com/openwhisk/openwhisk/issues/1577
// if it contains a slash, then the action is qualified by a package name
if strings.Contains(rule.Action.(string), "/") {
rule.Action = deployer.getQualifiedName(rule.Action.(string), deployer.ClientConfig.Namespace)
} else {
// if not, we assume the action is inside the root package
rule.Action = deployer.getQualifiedName(strings.Join([]string{deployer.RootPackageName, rule.Action.(string)}, "/"), deployer.ClientConfig.Namespace)
}
output := wski18n.T("Deploying rule {{.output}} ...",
map[string]interface{}{"output": rule.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Rules.Insert(rule, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating rule with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
_, _, err = deployer.Client.Rules.SetState(rule.Name, "active")
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error setting the status of rule with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
output = wski18n.T("Rule {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": rule.Name})
whisk.Debug(whisk.DbgInfo, output)
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 deployer.DeployActionInPackage {
// the action will be created under package with pattern 'packagename/actionname'
action.Name = strings.Join([]string{pkgname, action.Name}, "/")
}
output := wski18n.T("Deploying action {{.output}} ...",
map[string]interface{}{"output": action.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Actions.Insert(action, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating action with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
output := wski18n.T("Action {{.output}} has been successfully deployed.\n",
map[string]interface{}{"output": action.Name})
whisk.Debug(whisk.DbgInfo, output)
}
return nil
}
// create api (API Gateway functionality)
func (deployer *ServiceDeployer) createApi(api *whisk.ApiCreateRequest) error {
_, _, err := deployer.Client.Apis.Insert(api, nil, true)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error creating api with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
return nil
}
func (deployer *ServiceDeployer) UnDeploy(verifiedPlan *DeploymentProject) error {
if deployer.IsInteractive == true {
deployer.printDeploymentAssets(verifiedPlan)
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you really want to undeploy this? (y/N): ")
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
if text == "" {
text = "n"
}
if strings.EqualFold(text, "y") || strings.EqualFold(text, "yes") {
deployer.InteractiveChoice = true
if err := deployer.unDeployAssets(verifiedPlan); err != nil {
utils.PrintOpenWhiskErrorMessage(wski18n.T("Undeployment did not complete sucessfully.\n"))
return err
}
utils.PrintOpenWhiskOutput(wski18n.T("Deployment removed successfully.\n"))
return nil
} else {
deployer.InteractiveChoice = false
utils.PrintOpenWhiskOutput(wski18n.T("OK. Canceling undeployment.\n"))
return nil
}
}
// non-interactive
if err := deployer.unDeployAssets(verifiedPlan); err != nil {
errString := wski18n.T("Undeployment did not complete sucessfully.\n")
whisk.Debug(whisk.DbgError, errString)
return err
}
utils.PrintOpenWhiskOutput(wski18n.T("Deployment removed successfully.\n"))
return nil
}
func (deployer *ServiceDeployer) unDeployAssets(verifiedPlan *DeploymentProject) error {
if err := deployer.UnDeployActions(verifiedPlan); err != nil {
return err
}
if err := deployer.UnDeploySequences(verifiedPlan); err != nil {
return err
}
if err := deployer.UnDeployTriggers(verifiedPlan); err != nil {
return err
}
if err := deployer.UnDeployRules(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("Undeploying dependency {{.depName}} ...",
map[string]interface{}{"depName": depName})
whisk.Debug(whisk.DbgInfo, output)
if depRecord.IsBinding {
_, err := deployer.Client.Packages.Delete(depName)
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
}
// delete binding pkg if the origin package name is different
if depServiceDeployer.RootPackageName != depName {
if _, _, ok := deployer.Client.Packages.Get(depName); ok == nil {
_, err := deployer.Client.Packages.Delete(depName)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error deleting binding package with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
}
}
if err := depServiceDeployer.unDeployAssets(plan); err != nil {
errString := wski18n.T("Undeployment of dependency {{.depName}} did not complete sucessfully.\n",
map[string]interface{}{"depName": depName})
whisk.Debug(whisk.DbgError, errString)
return err
}
}
output = wski18n.T("Dependency {{.depName}} has been successfully undeployed.\n",
map[string]interface{}{"depName": depName})
whisk.Debug(whisk.DbgInfo, output)
}
}
return nil
}
func (deployer *ServiceDeployer) UnDeployPackages(deployment *DeploymentProject) error {
for _, pack := range deployment.Packages {
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
}
} else {
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) deletePackage(packa *whisk.Package) error {
output := wski18n.T("Removing package {{.package}} ...",
map[string]interface{}{"package": packa.Name})
whisk.Debug(whisk.DbgInfo, output)
if _, _, ok := deployer.Client.Packages.Get(packa.Name); ok == nil {
_, err := deployer.Client.Packages.Delete(packa.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error deleting package with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
}
return nil
}
func (deployer *ServiceDeployer) deleteTrigger(trigger *whisk.Trigger) error {
output := wski18n.T("Removing trigger {{.trigger}} ...",
map[string]interface{}{"trigger": trigger.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Triggers.Delete(trigger.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error deleting trigger with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
output := wski18n.T("Trigger {{.trigger}} has been removed.\n",
map[string]interface{}{"trigger": trigger.Name})
whisk.Debug(whisk.DbgInfo, output)
}
return nil
}
func (deployer *ServiceDeployer) deleteFeedAction(trigger *whisk.Trigger, feedName string) error {
params := make(whisk.KeyValueArr, 0)
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
}
namespace := deployer.Client.Namespace
deployer.Client.Namespace = qName.Namespace
_, _, err = deployer.Client.Actions.Invoke(qName.EntityName, parameters, true, true)
deployer.Client.Namespace = namespace
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Failed to invoke the feed when deleting trigger feed with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
trigger.Parameters = nil
_, _, err := deployer.Client.Triggers.Delete(trigger.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error deleting trigger with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
}
return nil
}
func (deployer *ServiceDeployer) deleteRule(rule *whisk.Rule) error {
output := wski18n.T("Removing rule {{.rule}} ...",
map[string]interface{}{"rule": rule.Name})
whisk.Debug(whisk.DbgInfo, output)
_, _, err := deployer.Client.Rules.SetState(rule.Name, "inactive")
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error setting the status of rule with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
} else {
_, err = deployer.Client.Rules.Delete(rule.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error deleting rule with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
}
output = wski18n.T("Rule {{.rule}} has been removed.\n",
map[string]interface{}{"rule": rule.Name})
whisk.Debug(whisk.DbgInfo, output)
return nil
}
// Utility function to call go-whisk framework to make action
func (deployer *ServiceDeployer) deleteAction(pkgname string, action *whisk.Action) error {
// call ActionService Thru Client
if deployer.DeployActionInPackage {
// the action will be deleted under package with pattern 'packagename/actionname'
action.Name = strings.Join([]string{pkgname, action.Name}, "/")
}
output := wski18n.T("Removing action {{.action}} ...",
map[string]interface{}{"action": action.Name})
whisk.Debug(whisk.DbgInfo, output)
if _, _, ok := deployer.Client.Actions.Get(action.Name); ok == nil {
_, err := deployer.Client.Actions.Delete(action.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
errString := wski18n.T("Got error deleting action with error message: {{.err}} and error code: {{.code}}.\n",
map[string]interface{}{"err": wskErr.Error(), "code": strconv.Itoa(wskErr.ExitCode)})
whisk.Debug(whisk.DbgError, errString)
return utils.NewWhiskClientError(wskErr.Error(), wskErr.ExitCode)
}
output = wski18n.T("Action {{.action}} has been removed.\n",
map[string]interface{}{"action": action.Name})
whisk.Debug(whisk.DbgInfo, output)
}
return nil
}
// from whisk go client
func (deployer *ServiceDeployer) getQualifiedName(name string, namespace string) string {
if strings.HasPrefix(name, "/") {
return name
} else if strings.HasPrefix(namespace, "/") {
return fmt.Sprintf("%s/%s", namespace, name)
} else {
if len(namespace) == 0 {
namespace = deployer.ClientConfig.Namespace
}
return fmt.Sprintf("/%s/%s", namespace, name)
}
}
func (deployer *ServiceDeployer) printDeploymentAssets(assets *DeploymentProject) {
// pretty ASCII OpenWhisk graphic
utils.PrintOpenWhiskOutputln(" ____ ___ _ _ _ _ _\n /\\ \\ / _ \\ _ __ ___ _ __ | | | | |__ (_)___| | __\n /\\ /__\\ \\ | | | | '_ \\ / _ \\ '_ \\| | | | '_ \\| / __| |/ /\n / \\____ \\ / | |_| | |_) | __/ | | | |/\\| | | | | \\__ \\ <\n \\ \\ / \\/ \\___/| .__/ \\___|_| |_|__/\\__|_| |_|_|___/_|\\_\\ \n \\___\\/ |_|\n")
utils.PrintOpenWhiskOutputln("Packages:")
for _, pack := range assets.Packages {
utils.PrintOpenWhiskOutputln("Name: " + pack.Package.Name)
utils.PrintOpenWhiskOutputln(" bindings: ")
for _, p := range pack.Package.Parameters {
jsonValue, err := utils.PrettyJSON(p.Value)
if err != nil {
fmt.Printf(" - %s : %s\n", p.Key, utils.UNKNOWN_VALUE)
} else {
fmt.Printf(" - %s : %v\n", p.Key, jsonValue)
}
}
for key, dep := range pack.Dependencies {
utils.PrintOpenWhiskOutputln(" * dependency: " + key)
utils.PrintOpenWhiskOutputln(" location: " + dep.Location)
if !dep.IsBinding {
utils.PrintOpenWhiskOutputln(" local path: " + dep.ProjectPath)
}
}
utils.PrintOpenWhiskOutputln("")
for _, action := range pack.Actions {
utils.PrintOpenWhiskOutputln(" * action: " + action.Action.Name)
utils.PrintOpenWhiskOutputln(" 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, utils.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, utils.UNKNOWN_VALUE)
} else {
fmt.Printf(" - %s : %v\n", p.Key, jsonValue)
}
}
}
utils.PrintOpenWhiskOutputln(" annotations: ")
for _, p := range action.Action.Annotations {
fmt.Printf(" - %s : %v\n", p.Key, p.Value)
}
}
utils.PrintOpenWhiskOutputln("")
for _, action := range pack.Sequences {
utils.PrintOpenWhiskOutputln(" * sequence: " + action.Action.Name)
}
utils.PrintOpenWhiskOutputln("")
}
utils.PrintOpenWhiskOutputln("Triggers:")
for _, trigger := range assets.Triggers {
utils.PrintOpenWhiskOutputln("* trigger: " + trigger.Name)
utils.PrintOpenWhiskOutputln(" bindings: ")
for _, p := range trigger.Parameters {
jsonValue, err := utils.PrettyJSON(p.Value)
if err != nil {
fmt.Printf(" - %s : %s\n", p.Key, utils.UNKNOWN_VALUE)
} else {
fmt.Printf(" - %s : %v\n", p.Key, jsonValue)
}
}
utils.PrintOpenWhiskOutputln(" annotations: ")
for _, p := range trigger.Annotations {
value := "?"
if str, ok := p.Value.(string); ok {
value = str
}
utils.PrintOpenWhiskOutputln(" - name: " + p.Key + " value: " + value)
}
}
utils.PrintOpenWhiskOutputln("\n Rules")
for _, rule := range assets.Rules {
utils.PrintOpenWhiskOutputln("* rule: " + rule.Name)
utils.PrintOpenWhiskOutputln(" - trigger: " + rule.Trigger.(string) + "\n - action: " + rule.Action.(string))
}
utils.PrintOpenWhiskOutputln("")
}
func (deployer *ServiceDeployer) getDependentDeployer(depName string, depRecord utils.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.IsInteractive = true
depServiceDeployer.Client = deployer.Client
depServiceDeployer.ClientConfig = deployer.ClientConfig
depServiceDeployer.DependencyMaster = deployer.DependencyMaster
// share the master dependency list
depServiceDeployer.DependencyMaster = deployer.DependencyMaster
return depServiceDeployer, nil
}