blob: af68e115ba00c37abad718d8d01a321b9cdb6662 [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"
"strings"
"sync"
"github.com/openwhisk/openwhisk-client-go/whisk"
"github.com/openwhisk/openwhisk-wskdeploy/parsers"
"github.com/openwhisk/openwhisk-wskdeploy/utils"
"log"
)
type DeploymentApplication struct {
Packages map[string]*DeploymentPackage
Triggers map[string]*whisk.Trigger
Rules map[string]*whisk.Rule
}
func NewDeploymentApplication() *DeploymentApplication {
var dep DeploymentApplication
dep.Packages = make(map[string]*DeploymentPackage)
dep.Triggers = make(map[string]*whisk.Trigger)
dep.Rules = make(map[string]*whisk.Rule)
return &dep
}
type DeploymentPackage struct {
Package *whisk.Package
Actions map[string]utils.ActionRecord
Sequences map[string]utils.ActionRecord
}
func NewDeploymentPackage() *DeploymentPackage {
var dep DeploymentPackage
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 *DeploymentApplication
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
}
// NewServiceDeployer is a Factory to create a new ServiceDeployer
func NewServiceDeployer() *ServiceDeployer {
var dep ServiceDeployer
dep.Deployment = NewDeploymentApplication()
dep.IsInteractive = true
dep.DeployActionInPackage = true
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)
manifest, manifestParser, err := manifestReader.ParseManifest()
utils.Check(err)
deployer.RootPackageName = manifest.Package.Packagename
manifestReader.InitRootPackage(manifestParser, manifest)
if deployer.IsDefault == true && !utils.Flags.WithinOpenWhisk {
fileReader := NewFileSystemReader(deployer)
fileActions, err := fileReader.ReadProjectDirectory(manifest)
utils.Check(err)
fileReader.SetFileActions(fileActions)
}
// process manifest file
err = manifestReader.HandleYaml(manifestParser, manifest)
utils.Check(err)
// process deploymet file
if utils.FileExists(deployer.DeploymentPath) {
var deploymentReader = NewDeploymentReader(deployer)
deploymentReader.HandleYaml()
deploymentReader.BindAssets()
}
return nil
}
func (deployer *ServiceDeployer) ConstructUnDeploymentPlan() (*DeploymentApplication, error) {
var manifestReader = NewManfiestReader(deployer)
manifest, manifestParser, err := manifestReader.ParseManifest()
utils.Check(err)
manifestReader.InitRootPackage(manifestParser, manifest)
// process file system
if deployer.IsDefault == true {
fileReader := NewFileSystemReader(deployer)
fileActions, err := fileReader.ReadProjectDirectory(manifest)
utils.Check(err)
err = fileReader.SetFileActions(fileActions)
utils.Check(err)
}
// process manifest file
err = manifestReader.HandleYaml(manifestParser, manifest)
utils.Check(err)
// process deploymet file
if utils.FileExists(deployer.DeploymentPath) {
var deploymentReader = NewDeploymentReader(deployer)
deploymentReader.HandleYaml()
deploymentReader.BindAssets()
}
verifiedPlan := deployer.Deployment
return verifiedPlan, nil
}
// Use relfect util to deploy everything in this service deployer
// according some planning?
func (deployer *ServiceDeployer) Deploy() error {
if deployer.IsInteractive == true && !utils.Flags.WithinOpenWhisk {
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 {
log.Println("\nDeployment did not complete sucessfully. Run `wskdeploy undeploy` to remove partially deployed assets")
return err
}
fmt.Println("\nDeployment completed successfully.")
return nil
} else {
deployer.InteractiveChoice = false
fmt.Println("OK. Cancelling deployment")
return nil
}
}
// non-interactive
if err := deployer.deployAssets(); err != nil {
log.Println("\nDeployment did not complete sucessfully. Run `wskdeploy undeploy` to remove partially deployed assets")
return err
}
log.Println("\nDeployment completed successfully.")
return nil
}
func (deployer *ServiceDeployer) deployAssets() error {
if err := deployer.DeployPackages(); 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
}
return nil
}
func (deployer *ServiceDeployer) DeployPackages() error {
for _, pack := range deployer.Deployment.Packages {
deployer.createPackage(pack.Package)
}
return nil
}
// DeployActions into OpenWhisk
func (deployer *ServiceDeployer) DeploySequences() error {
for _, pack := range deployer.Deployment.Packages {
for _, action := range pack.Sequences {
deployer.createAction(pack.Package.Name, action.Action)
}
}
return nil
}
// DeployActions 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 {
deployer.createFeedAction(trigger, feedname)
} else {
deployer.createTrigger(trigger)
}
}
return nil
}
// Deploy Rules into OpenWhisk
func (deployer *ServiceDeployer) DeployRules() error {
for _, rule := range deployer.Deployment.Rules {
deployer.createRule(rule)
}
return nil
}
func (deployer *ServiceDeployer) createPackage(packa *whisk.Package) {
log.Print("Deploying package " + packa.Name + " ... ")
_, _, err := deployer.Client.Packages.Insert(packa, true)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error creating package with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
log.Println("Done!")
}
func (deployer *ServiceDeployer) createTrigger(trigger *whisk.Trigger) {
_, _, err := deployer.Client.Triggers.Insert(trigger, true)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error creating trigger with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
fmt.Println("Done!")
}
func (deployer *ServiceDeployer) createFeedAction(trigger *whisk.Trigger, feedName string) {
log.Println("Deploying trigger feed " + trigger.Name + " ... ")
// 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 {
if b, isJson := utils.IsJSON(keyVal.Value.(string)); isJson {
fmt.Println(keyVal.Key + " is JSON " + keyVal.Value.(string))
params[keyVal.Key] = b
} else {
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,
}
_, _, err := deployer.Client.Triggers.Insert(t, false)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error creating trigger with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
} else {
qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace)
utils.Check(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 {
wskErr := err.(*whisk.WskError)
log.Printf("Got error creating trigger feed with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
}
fmt.Println("Done!")
}
func (deployer *ServiceDeployer) createRule(rule *whisk.Rule) {
// 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
// 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)
}
fmt.Print("Deploying rule " + rule.Name + " ... ")
_, _, err := deployer.Client.Rules.Insert(rule, true)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error creating rule with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
_, _, err = deployer.Client.Rules.SetState(rule.Name, "active")
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error activating rule with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
fmt.Println("Done!")
}
// Utility function to call go-whisk framework to make action
func (deployer *ServiceDeployer) createAction(pkgname string, action *whisk.Action) error {
// call ActionService Thru Client
if deployer.DeployActionInPackage {
// the action will be created under package with pattern 'packagename/actionname'
action.Name = strings.Join([]string{pkgname, action.Name}, "/")
}
log.Print("Deploying action " + action.Name + " ... ")
_, _, err := deployer.Client.Actions.Insert(action, true)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error creating action with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
return err
}
log.Println("Done!")
return nil
}
func (deployer *ServiceDeployer) UnDeploy(verifiedPlan *DeploymentApplication) 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 {
fmt.Println("\nUndeployment did not complete sucessfully.")
return err
}
fmt.Println("\nDeployment removed successfully.")
return nil
} else {
deployer.InteractiveChoice = false
fmt.Println("OK. Cancelling undeployment")
return nil
}
}
// non-interactive
if err := deployer.unDeployAssets(verifiedPlan); err != nil {
fmt.Println("\nUndeployment did not complete sucessfully.")
return err
}
fmt.Println("\nDeployment removed successfully.")
return nil
}
func (deployer *ServiceDeployer) unDeployAssets(verifiedPlan *DeploymentApplication) 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
}
return nil
}
func (deployer *ServiceDeployer) UnDeployPackages(deployment *DeploymentApplication) error {
for _, pack := range deployment.Packages {
deployer.deletePackage(pack.Package)
}
return nil
}
func (deployer *ServiceDeployer) UnDeploySequences(deployment *DeploymentApplication) error {
for _, pack := range deployment.Packages {
for _, action := range pack.Sequences {
deployer.deleteAction(pack.Package.Name, action.Action)
}
}
return nil
}
// DeployActions into OpenWhisk
func (deployer *ServiceDeployer) UnDeployActions(deployment *DeploymentApplication) 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 *DeploymentApplication) error {
for _, trigger := range deployment.Triggers {
if feedname, isFeed := utils.IsFeedAction(trigger); isFeed {
deployer.deleteFeedAction(trigger, feedname)
} else {
deployer.deleteTrigger(trigger)
}
}
return nil
}
// Deploy Rules into OpenWhisk
func (deployer *ServiceDeployer) UnDeployRules(deployment *DeploymentApplication) error {
for _, rule := range deployment.Rules {
deployer.deleteRule(rule)
}
return nil
}
func (deployer *ServiceDeployer) deletePackage(packa *whisk.Package) {
fmt.Print("Removing package " + packa.Name + " ... ")
_, err := deployer.Client.Packages.Delete(packa.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error deleteing package with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
fmt.Println("Done!")
}
func (deployer *ServiceDeployer) deleteTrigger(trigger *whisk.Trigger) {
fmt.Print("Removing trigger " + trigger.Name + " ... ")
_, _, err := deployer.Client.Triggers.Delete(trigger.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error deleting trigger with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
fmt.Println("Done!")
}
func (deployer *ServiceDeployer) deleteFeedAction(trigger *whisk.Trigger, feedName string) {
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})
trigger.Parameters = nil
_, _, err := deployer.Client.Triggers.Delete(trigger.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error deleting trigger with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
} else {
parameters := make(map[string]interface{})
for _, keyVal := range params {
parameters[keyVal.Key] = keyVal.Value
}
qName, err := utils.ParseQualifiedName(feedName, deployer.ClientConfig.Namespace)
utils.Check(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)
log.Printf("Got error deleting trigger feed with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
}
fmt.Println("Done!")
}
func (deployer *ServiceDeployer) deleteRule(rule *whisk.Rule) {
fmt.Print("Removing rule " + rule.Name + " ... ")
_, _, err := deployer.Client.Rules.SetState(rule.Name, "inactive")
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error deleting rule with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
} else {
_, err = deployer.Client.Rules.Delete(rule.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error deleting rule with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
}
fmt.Println("Done!")
}
}
// 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}, "/")
}
fmt.Print("Removing action " + action.Name + " ... ")
_, err := deployer.Client.Actions.Delete(action.Name)
if err != nil {
wskErr := err.(*whisk.WskError)
log.Printf("Got error deleting action with error message: %v and error code: %v.\n", wskErr.Error(), wskErr.ExitCode)
return err
}
fmt.Println("Done!")
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 *DeploymentApplication) {
// pretty ASCII OpenWhisk graphic
fmt.Println(" ____ ___ _ _ _ _ _\n /\\ \\ / _ \\ _ __ ___ _ __ | | | | |__ (_)___| | __\n /\\ /__\\ \\ | | | | '_ \\ / _ \\ '_ \\| | | | '_ \\| / __| |/ /\n / \\____ \\ / | |_| | |_) | __/ | | | |/\\| | | | | \\__ \\ <\n \\ \\ / \\/ \\___/| .__/ \\___|_| |_|__/\\__|_| |_|_|___/_|\\_\\ \n \\___\\/ |_|\n")
fmt.Println("Packages:")
for _, pack := range assets.Packages {
fmt.Println("Name: " + pack.Package.Name)
fmt.Println(" bindings: ")
for _, p := range pack.Package.Parameters {
fmt.Printf(" - %s : %v\n", p.Key, utils.PrettyJSON(p.Value))
}
for _, action := range pack.Actions {
fmt.Println(" * action: " + action.Action.Name)
fmt.Println(" bindings: ")
for _, p := range action.Action.Parameters {
fmt.Printf(" - %s : %v\n", p.Key, utils.PrettyJSON(p.Value))
}
fmt.Println(" annotations: ")
for _, p := range action.Action.Annotations {
fmt.Printf(" - %s : %v\n", p.Key, p.Value)
}
}
fmt.Println("")
for _, action := range pack.Sequences {
fmt.Println(" * sequence: " + action.Action.Name)
}
fmt.Println("")
}
fmt.Println("Triggers:")
for _, trigger := range assets.Triggers {
fmt.Println("* trigger: " + trigger.Name)
fmt.Println(" bindings: ")
for _, p := range trigger.Parameters {
fmt.Printf(" - %s : %v\n", p.Key, utils.PrettyJSON(p.Value))
}
fmt.Println(" annotations: ")
for _, p := range trigger.Annotations {
value := "?"
if str, ok := p.Value.(string); ok {
value = str
}
fmt.Println(" - name: " + p.Key + " value: " + value)
}
}
fmt.Println("\n Rules")
for _, rule := range assets.Rules {
fmt.Println("* rule: " + rule.Name)
fmt.Println(" - trigger: " + rule.Trigger.(string) + "\n - action: " + rule.Action.(string))
}
fmt.Println("")
}