blob: ef40e0715e80b354cb36245a1df3315d9b041e49 [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 (
"encoding/json"
"fmt"
"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
}
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, &param, 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 {
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
}
// 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]
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
}
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.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) 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
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
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
}
// 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
}