blob: 746854c06c7712e46ae49b63a3a0dcd3eff492dd [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 cmd
import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/apache/incubator-openwhisk-client-go/whisk"
"github.com/apache/incubator-openwhisk-wskdeploy/deployers"
"github.com/apache/incubator-openwhisk-wskdeploy/parsers"
"github.com/apache/incubator-openwhisk-wskdeploy/utils"
"github.com/apache/incubator-openwhisk-wskdeploy/wski18n"
"github.com/spf13/cobra"
)
var exportCmd = &cobra.Command{
Use: "export",
SuggestFor: []string{"capture"},
Short: "Export project assets from OpenWhisk",
Long: `Exports managed project assets from OpenWhisk to manifest and function files`,
RunE: ExportCmdImp,
}
var config *whisk.Config
var wskpropsPath string
var client *whisk.Client
func ExportAction(actionName string, packageName string, maniyaml *parsers.YAML, targetManifest string, projectName string) error {
pkg := maniyaml.Packages[packageName]
if pkg.Actions == nil {
pkg.Actions = make(map[string]parsers.Action)
maniyaml.Packages[packageName] = pkg
}
wskAction, _, err := client.Actions.Get(actionName, true)
if err != nil {
return err
}
if a := wskAction.Annotations.GetValue(utils.MANAGED); a != nil {
// decode the JSON blob and retrieve __OW_PROJECT_NAME
pa := a.(map[string]interface{})
// we have found a package which is part of the current project
if pa[utils.OW_PROJECT_NAME] != projectName {
return nil
}
} else {
return nil
}
if wskAction.Exec.Kind == "sequence" {
seq := new(parsers.Sequence)
for _, component := range wskAction.Exec.Components {
// must ommit namespace from seq component name
ExportAction(strings.SplitN(component, "/", 3)[2], packageName, maniyaml, targetManifest, projectName)
if len(seq.Actions) > 0 {
seq.Actions += ","
}
// save action in the seq list as package/action
seq.Actions += strings.SplitN(component, "/", 3)[2]
}
pkg = maniyaml.Packages[packageName]
if pkg.Sequences == nil {
pkg.Sequences = make(map[string]parsers.Sequence)
}
pkg.Sequences[wskAction.Name] = *seq
} else {
parsedAction := *maniyaml.ComposeParsersAction(*wskAction)
manifestDir := filepath.Dir(targetManifest)
// store function file under action package name subdirectory in the specified manifest folder
functionDir := filepath.Join(manifestDir, packageName)
// save the code in file
filename, err := saveCode(*wskAction, functionDir)
if err != nil {
return err
}
if filename != "" {
// store function in manifest if action has code section
parsedAction.Function = packageName + "/" + filename
}
pkg.Actions[wskAction.Name] = parsedAction
}
maniyaml.Packages[packageName] = pkg
return nil
}
func exportProject(projectName string, targetManifest string) error {
maniyaml := &parsers.YAML{}
maniyaml.Project.Name = projectName
// Get the list of packages in your namespace
packages, _, err := client.Packages.List(&whisk.PackageListOptions{})
if err != nil {
return err
}
var bindings = make(map[string]whisk.Binding)
// iterate over each package to find managed annotations
// check if "managed" annotation is attached to a package
// add to export when managed project name matches with the
// specified project name
for _, pkg := range packages {
if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil {
// decode the JSON blob and retrieve __OW_PROJECT_NAME
pa := a.(map[string]interface{})
// we have found a package which is part of the current project
if pa[utils.OW_PROJECT_NAME] == projectName {
// check if the package is dependency
if pkg.Annotations.GetValue(wski18n.BINDING) != nil {
bindings[pkg.Name] = *pkg.Binding
continue
}
if maniyaml.Packages == nil {
maniyaml.Packages = make(map[string]parsers.Package)
}
maniyaml.Packages[pkg.Name] = *maniyaml.ComposeParsersPackage(pkg)
// TODO: throw if there more than single package managed by project
// currently will be a mess because triggers and rules managed under packages
// instead of the top level (similar to OW model)
// if len(maniyaml.Packages) > 1 {
// return errors.New("currently can't work with more than one package managed by one project")
// }
// perform the similar check on the list of actions from this package
// get a list of actions in your namespace
actions, _, err := client.Actions.List(pkg.Name, &whisk.ActionListOptions{})
if err != nil {
return err
}
// iterate over list of managed package actions to find an action with managed annotations
// check if "managed" annotation is attached to an action
for _, action := range actions {
// TODO: consider to throw error when there unmanaged or "foreign" assets under managed package
// 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 {
aa := a.(map[string]interface{})
if aa[utils.OW_PROJECT_NAME] == projectName {
actionName := strings.Join([]string{pkg.Name, action.Name}, "/")
// export action to file system
err = ExportAction(actionName, pkg.Name, maniyaml, targetManifest, projectName)
if err != nil {
return err
}
}
}
}
}
}
}
// Get list of triggers in your namespace
triggers, _, err := client.Triggers.List(&whisk.TriggerListOptions{})
if err != nil {
return err
}
// iterate over the list of triggers to determine whether any of them part of specified managed project
for _, trg := range triggers {
// trigger has attached managed annotation
if a := trg.Annotations.GetValue(utils.MANAGED); a != nil {
// decode the JSON blob and retrieve __OW_PROJECT_NAME
ta := a.(map[string]interface{})
if ta[utils.OW_PROJECT_NAME] == projectName {
for pkgName := range maniyaml.Packages {
if maniyaml.Packages[pkgName].Namespace == trg.Namespace {
if maniyaml.Packages[pkgName].Triggers == nil {
pkg := maniyaml.Packages[pkgName]
pkg.Triggers = make(map[string]parsers.Trigger)
maniyaml.Packages[pkgName] = pkg
}
// export trigger to manifest
if feedname, isFeed := utils.IsFeedAction(&trg); isFeed {
// export feed input parameters
feedAction, _, _ := client.Actions.Get(feedname, true)
if err != nil {
return err
}
params := make(map[string]interface{})
params["authKey"] = client.Config.AuthToken
params["lifecycleEvent"] = "READ"
params["triggerName"] = "/" + client.Namespace + "/" + trg.Name
res, _, err := client.Actions.Invoke(feedname, params, true, true)
if err != nil {
return err
}
feedConfig := res["config"]
for key, val := range feedConfig.(map[string]interface{}) {
if i := feedAction.Parameters.FindKeyValue(key); i >= 0 {
trg.Parameters = trg.Parameters.AddOrReplace(&whisk.KeyValue{Key: key, Value: val})
}
}
}
maniyaml.Packages[pkgName].Triggers[trg.Name] = *maniyaml.ComposeParsersTrigger(trg)
}
}
}
}
}
// Get list of rules from OW
rules, _, err := client.Rules.List(&whisk.RuleListOptions{})
if err != nil {
return err
}
// iterate over the list of rules to determine whether any of them is part of the manage dproject
for _, rule := range rules {
// get rule from OW
wskRule, _, _ := client.Rules.Get(rule.Name)
// rule has attached managed annotation
if a := wskRule.Annotations.GetValue(utils.MANAGED); a != nil {
// decode the JSON blob and retrieve __OW_PROJECT_NAME
ta := a.(map[string]interface{})
if ta[utils.OW_PROJECT_NAME] == projectName {
for pkgName := range maniyaml.Packages {
if maniyaml.Packages[pkgName].Namespace == wskRule.Namespace {
if maniyaml.Packages[pkgName].Rules == nil {
pkg := maniyaml.Packages[pkgName]
pkg.Rules = make(map[string]parsers.Rule)
maniyaml.Packages[pkgName] = pkg
}
// export rule to manifest
maniyaml.Packages[pkgName].Rules[wskRule.Name] = *maniyaml.ComposeParsersRule(*wskRule)
}
}
}
}
}
// adding dependencies to the first package
for pkgName := range maniyaml.Packages {
for bPkg, binding := range bindings {
if maniyaml.Packages[pkgName].Dependencies == nil {
pkg := maniyaml.Packages[pkgName]
pkg.Dependencies = make(map[string]parsers.Dependency)
maniyaml.Packages[pkgName] = pkg
}
bPkgData, _, err := client.Packages.Get(bPkg)
if err != nil {
return err
}
maniyaml.Packages[pkgName].Dependencies[bPkg] = *maniyaml.ComposeParsersDependency(binding, *bPkgData)
}
break
}
// find exported manifest parent directory
manifestDir := filepath.Dir(utils.Flags.ManifestPath)
os.MkdirAll(manifestDir, os.ModePerm)
// export manifest to file
parsers.Write(maniyaml, targetManifest)
fmt.Println("Manifest exported to: " + targetManifest)
// create dependencies directory if not exists
depDir := filepath.Join(manifestDir, "dependencies")
if len(bindings) > 0 {
fmt.Println("Exporting project dependencies to " + depDir)
}
// now export dependencies to their own manifests
for _, binding := range bindings {
ns := client.Config.Namespace
client.Config.Namespace = binding.Namespace
pkg, _, err := client.Packages.Get(binding.Name)
if err != nil {
return err
}
if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil {
// decode the JSON blob and retrieve __OW_PROJECT_NAME
pa := a.(map[string]interface{})
os.MkdirAll(depDir, os.ModePerm)
depManifestPath := filepath.Join(depDir, pa[utils.OW_PROJECT_NAME].(string)+".yaml")
// export the whole project as dependency
err := exportProject(pa[utils.OW_PROJECT_NAME].(string), depManifestPath)
if err != nil {
return err
}
} else {
// showing warning to notify user that exported manifest dependent on unmanaged library which can't be exported
fmt.Println("Warning! Dependency package " + binding.Name + " currently unmanaged by any project. Unable to export this package")
}
client.Config.Namespace = ns
}
return nil
}
func ExportCmdImp(cmd *cobra.Command, args []string) error {
config, _ = deployers.NewWhiskConfig(wskpropsPath, utils.Flags.DeploymentPath, utils.Flags.ManifestPath)
client, _ = deployers.CreateNewClient(config)
// Init supported runtimes and action files extensions maps
setSupportedRuntimes(config.Host)
return exportProject(utils.Flags.ProjectName, utils.Flags.ManifestPath)
}
const (
JAVA_EXT = ".jar"
ZIP_EXT = ".zip"
JAVA = "java"
)
func getBinaryKindExtension(runtime string) (extension string) {
switch strings.ToLower(runtime) {
case JAVA:
extension = JAVA_EXT
default:
extension = ZIP_EXT
}
return extension
}
func saveCode(action whisk.Action, directory string) (string, error) {
var code string
var runtime string
var exec whisk.Exec
exec = *action.Exec
runtime = strings.Split(exec.Kind, ":")[0]
if exec.Code != nil {
code = *exec.Code
} else {
return "", nil
}
var filename = ""
if *exec.Binary {
decoded, _ := base64.StdEncoding.DecodeString(code)
code = string(decoded)
filename = action.Name + getBinaryKindExtension(runtime)
} else {
filename = action.Name + "." + utils.FileRuntimeExtensionsMap[action.Exec.Kind]
}
os.MkdirAll(directory, os.ModePerm)
path := filepath.Join(directory, filename)
if err := utils.WriteFile(path, code); err != nil {
return "", err
}
return filename, nil
}
func init() {
RootCmd.AddCommand(exportCmd)
}