deployers/projectreader.go (316 lines of code) (raw):

/* * 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 ( "net/http" "strings" "github.com/apache/openwhisk-client-go/whisk" "github.com/apache/openwhisk-wskdeploy/parsers" "github.com/apache/openwhisk-wskdeploy/utils" "github.com/apache/openwhisk-wskdeploy/wskderrors" "github.com/apache/openwhisk-wskdeploy/wskenv" "github.com/apache/openwhisk-wskdeploy/wski18n" "github.com/apache/openwhisk-wskdeploy/wskprint" ) func (deployer *ServiceDeployer) UpdatePackageInputs() error { var paramsCLI interface{} var err error var inputsWithoutValue []string // check if any inputs/parameters are specified in CLI using --param or --param-file // store params in Key/value pairs if len(utils.Flags.Param) > 0 { paramsCLI, err = utils.GetJSONFromStrings(utils.Flags.Param, false) if err != nil { return err } } if paramsCLI != nil { // iterate over each package to update its set of inputs with CLI for _, pkg := range deployer.Deployment.Packages { // iterate over each input of type Parameter for name, param := range pkg.Inputs.Inputs { inputValue := param.Value // check if this particular input is specified on CLI if v, ok := paramsCLI.(map[string]interface{})[name]; ok { inputValue = wskenv.InterpolateStringWithEnvVar(v) } param.Value = inputValue pkg.Inputs.Inputs[name] = param } } } for _, pkg := range deployer.Deployment.Packages { keyValArr := make([]whisk.KeyValue, 0) if pkg.Inputs.Inputs != nil || len(pkg.Inputs.Inputs) != 0 { for k, v := range pkg.Inputs.Inputs { if v.Required { if parsers.IsTypeDefaultValue(v.Type, v.Value) { inputsWithoutValue = append(inputsWithoutValue, k) } } if _, ok := deployer.ProjectInputs[k]; !ok { keyVal := whisk.KeyValue{ Key: k, Value: v.Value, } keyValArr = append(keyValArr, keyVal) } } } pkg.Package.Parameters = keyValArr } if len(inputsWithoutValue) > 0 { errMessage := wski18n.T(wski18n.ID_ERR_REQUIRED_INPUTS_MISSING_VALUE_X_inputs_X, map[string]interface{}{ wski18n.KEY_INPUTS: strings.Join(inputsWithoutValue, ", ")}) if utils.Flags.Report { wskprint.PrintOpenWhiskError(errMessage) } else { return wskderrors.NewYAMLFileFormatError(deployer.ManifestPath, errMessage) } } return nil } func (deployer *ServiceDeployer) UnDeployProjectAssets() error { // calculate all the project entities such as packages, actions, sequences, // triggers, and rules based on the project name in "whisk-managed" annotation deployer.SetProjectAssets(utils.Flags.ProjectName) // calculate all the dependencies based on the project name projectDeps, err := deployer.SetProjectDependencies(utils.Flags.ProjectName) if err != nil { return err } // show preview of which all OpenWhisk entities will be deployed if utils.Flags.Preview { deployer.printDeploymentAssets(deployer.Deployment) for _, deps := range projectDeps { deployer.printDeploymentAssets(deps) } return nil } // now, undeploy all those project dependencies if not used by // any other project or packages for _, deps := range projectDeps { if err := deployer.unDeployAssets(deps); err != nil { return err } } // undeploy all the project entities return deployer.unDeployAssets(deployer.Deployment) return nil } // based on the project name set in "whisk-managed" annotation // calculate and determine list of packages, actions, sequences, rules and triggers func (deployer *ServiceDeployer) SetProjectAssets(projectName string) error { if err := deployer.SetProjectPackages(projectName); err != nil { return err } if err := deployer.SetPackageActionsAndSequences(projectName); err != nil { return err } if err := deployer.SetProjectTriggers(projectName); err != nil { return err } if err := deployer.SetProjectRules(projectName); err != nil { return err } if err := deployer.SetProjectApis(projectName); err != nil { return err } return nil } // check if project name matches with the one in "whisk-managed" annotation func (deployer *ServiceDeployer) isManagedEntity(a interface{}, projectName string) bool { if a != nil { ta := a.(map[string]interface{}) if ta[utils.OW_PROJECT_NAME] == projectName { return true } } return false } // get an instance of *whisk.Package for the specified package name func (deployer *ServiceDeployer) getPackage(packageName string) (*DeploymentPackage, error) { var err error var p *whisk.Package var response *http.Response err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { p, response, err = deployer.Client.Packages.Get(packageName) return err }) if err != nil { return nil, createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_PACKAGE, false) } newPack := NewDeploymentPackage() newPack.Package = p return newPack, nil } // capture all the packages with "whisk-managed" annotations and matching project name func (deployer *ServiceDeployer) SetProjectPackages(projectName string) error { // retrieve a list of all the packages available under the namespace listOfPackages, _, err := deployer.Client.Packages.List(&whisk.PackageListOptions{}) if err != nil { return nil } for _, pkg := range listOfPackages { if deployer.isManagedEntity(pkg.Annotations.GetValue(utils.MANAGED), projectName) { p, err := deployer.getPackage(pkg.Name) if err != nil { return err } deployer.Deployment.Packages[pkg.Name] = p } } return nil } // get a list of actions/sequences of a given package name func (deployer *ServiceDeployer) getPackageActionsAndSequences(packageName string, projectName string) (map[string]utils.ActionRecord, map[string]utils.ActionRecord, error) { listOfActions := make(map[string]utils.ActionRecord, 0) listOfSequences := make(map[string]utils.ActionRecord, 0) actions, _, err := deployer.Client.Actions.List(packageName, &whisk.ActionListOptions{}) if err != nil { return listOfActions, listOfSequences, err } for _, action := range actions { if deployer.isManagedEntity(action.Annotations.GetValue(utils.MANAGED), projectName) { var a *whisk.Action var response *http.Response err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { a, response, err = deployer.Client.Actions.Get(packageName+parsers.PATH_SEPARATOR+action.Name, false) return err }) if err != nil { return listOfActions, listOfSequences, createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_ACTION, false) } ar := utils.ActionRecord{Action: a, Packagename: packageName} if a.Exec.Kind == parsers.YAML_KEY_SEQUENCE { listOfSequences[action.Name] = ar } else { listOfActions[action.Name] = ar } } } return listOfActions, listOfSequences, err } // capture all the actions/sequences with "whisk-managed" annotations and matching project name func (deployer *ServiceDeployer) SetPackageActionsAndSequences(projectName string) error { for _, pkg := range deployer.Deployment.Packages { a, s, err := deployer.getPackageActionsAndSequences(pkg.Package.Name, projectName) if err != nil { return err } deployer.Deployment.Packages[pkg.Package.Name].Actions = a deployer.Deployment.Packages[pkg.Package.Name].Sequences = s } return nil } // get a list of triggers from a given project name func (deployer *ServiceDeployer) getProjectTriggers(projectName string) (map[string]*whisk.Trigger, error) { triggers := make(map[string]*whisk.Trigger, 0) listOfTriggers, _, err := deployer.Client.Triggers.List(&whisk.TriggerListOptions{}) if err != nil { return triggers, nil } for _, trigger := range listOfTriggers { if deployer.isManagedEntity(trigger.Annotations.GetValue(utils.MANAGED), projectName) { var t *whisk.Trigger var response *http.Response err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { t, response, err = deployer.Client.Triggers.Get(trigger.Name) return err }) if err != nil { return triggers, createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_TRIGGER, false) } triggers[trigger.Name] = t } } return triggers, nil } // capture all the triggers with "whisk-managed" annotations and matching project name func (deployer *ServiceDeployer) SetProjectTriggers(projectName string) error { t, err := deployer.getProjectTriggers(projectName) if err != nil { return err } deployer.Deployment.Triggers = t return nil } // get a list of rules from a given project name func (deployer *ServiceDeployer) getProjectRules(projectName string) (map[string]*whisk.Rule, error) { rules := make(map[string]*whisk.Rule, 0) listOfRules, _, err := deployer.Client.Rules.List(&whisk.RuleListOptions{}) if err != nil { return rules, nil } for _, rule := range listOfRules { if deployer.isManagedEntity(rule.Annotations.GetValue(utils.MANAGED), projectName) { var r *whisk.Rule var response *http.Response err = retry(DEFAULT_ATTEMPTS, DEFAULT_INTERVAL, func() error { r, response, err = deployer.Client.Rules.Get(rule.Name) return err }) if err != nil { return rules, createWhiskClientError(err.(*whisk.WskError), response, parsers.YAML_KEY_RULE, false) } rules[rule.Name] = r } } return rules, nil } // capture all the rules with "whisk-managed" annotations and matching project name func (deployer *ServiceDeployer) SetProjectRules(projectName string) error { r, err := deployer.getProjectRules(projectName) if err != nil { return err } deployer.Deployment.Rules = r return nil } // "whisk-manged" annotation stores package name with namespace such as // /<namespace>/<package name> // parse this kind of structure to determine package name func (deployer *ServiceDeployer) filterPackageName(name string) string { s := strings.SplitAfterN(name, "/", 3) if len(s) == 3 && len(s[2]) != 0 { return s[2] } return "" } // determine if any other package on the server is using the dependent package func (deployer *ServiceDeployer) isPackageUsedByOtherPackages(projectName string, depPackageName string) bool { // retrieve a list of packages on the server listOfPackages, _, err := deployer.Client.Packages.List(&whisk.PackageListOptions{}) if err != nil { return false } for _, pkg := range listOfPackages { if a := pkg.Annotations.GetValue(utils.MANAGED); a != nil { ta := a.(map[string]interface{}) // compare project names of the given package and other packages from server // we want to skip comparing packages from the same project if ta[utils.OW_PROJECT_NAME] != projectName { d := a.(map[string]interface{})[utils.OW_PROJECT_DEPS] listOfDeps := d.([]interface{}) // iterate over a list of dependencies of a package // to determine whether it has listed the dependent package as its dependency as well // in such case, we dont want to undeploy dependent package if its used by any other package for _, dep := range listOfDeps { name := deployer.filterPackageName(dep.(map[string]interface{})[wski18n.KEY_KEY].(string)) if name == depPackageName { return true } } } } } return false } // derive a map of dependent packages using "whisk-managed" annotation // "whisk-managed" annotation has a list of dependent packages in "projectDeps" // projectDeps is a list of key value pairs with its own "whisk-managed" annotation // for a given package, get the list of dependent packages // for each dependent package, determine whether any other package is using it or not // if not, collect a list of actions, sequences, triggers, and rules of the dependent package // and delete them in order following by deleting the package itself func (deployer *ServiceDeployer) SetProjectDependencies(projectName string) ([]*DeploymentProject, error) { projectDependencies := make([]*DeploymentProject, 0) // iterate over each package in a given project for _, pkg := range deployer.Deployment.Packages { // get the "whisk-managed" annotation if a := pkg.Package.Annotations.GetValue(utils.MANAGED); a != nil { // read the list of dependencies from "projectDeps" d := a.(map[string]interface{})[utils.OW_PROJECT_DEPS] listOfDeps := d.([]interface{}) // iterate over a list of dependencies for _, dep := range listOfDeps { // dependent package name is in form of "/<namespace>/<package-name> // filter it to derive the package name name := deployer.filterPackageName(dep.(map[string]interface{})[wski18n.KEY_KEY].(string)) // undeploy dependent package if its not used by any other package if !deployer.isPackageUsedByOtherPackages(projectName, name) { // get the *whisk.Package object for the given dependent package p, err := deployer.getPackage(name) if err != nil { return projectDependencies, err } // construct a new DeploymentProject for each dependency depProject := NewDeploymentProject() depProject.Packages[p.Package.Name] = p // Now, get the project name of dependent package so that // we can get other entities from that project pa := p.Package.Annotations.GetValue(utils.MANAGED) depProjectName := (pa.(map[string]interface{})[utils.OW_PROJECT_NAME]).(string) // get a list of actions and sequences of a dependent package actions, sequences, err := deployer.getPackageActionsAndSequences(p.Package.Name, depProjectName) if err != nil { return projectDependencies, err } depProject.Packages[p.Package.Name].Actions = actions depProject.Packages[p.Package.Name].Sequences = sequences // get a list of triggers of a dependent project t, err := deployer.getProjectTriggers(depProjectName) if err != nil { return projectDependencies, err } depProject.Triggers = t // get a list of rules of a dependent project r, err := deployer.getProjectRules(depProjectName) if err != nil { return projectDependencies, err } depProject.Rules = r projectDependencies = append(projectDependencies, depProject) } } } } return projectDependencies, nil } func (deployer *ServiceDeployer) SetProjectApis(projectName string) error { return nil }