| /* |
| * 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/runtimes" |
| "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 + "." + runtimes.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) |
| } |