blob: 741ec81102519e3f6cf58d4c82eaa24a48567ad1 [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 (
"net/http"
"os"
"path"
"strings"
"time"
"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/wski18n"
"github.com/apache/openwhisk-wskdeploy/wskprint"
)
// Possible sources for config info (e.g., API Host, Auth Key, Namespace)
const (
SOURCE_WSKPROPS = ".wskprops"
SOURCE_WHISK_PROPERTIES = "whisk.properties"
SOURCE_DEFAULT_VALUE = "wskdeploy default"
)
var (
credential = PropertyValue{}
namespace = PropertyValue{}
apiHost = PropertyValue{}
key = PropertyValue{}
cert = PropertyValue{}
apigwAccessToken = PropertyValue{}
apigwTenantId = PropertyValue{}
additionalHeaders = make(http.Header)
)
type PropertyValue struct {
Value string
Source string
}
// Note buyer beware this function returns the existing value, and only if there is not
// an existing value does it set it to newValue. We should perhaps rename this function
// as it implies that it is a getter, when this is not strictly true
var GetPropertyValue = func(prop PropertyValue, newValue string, source string) PropertyValue {
if len(prop.Value) == 0 && len(newValue) > 0 {
prop.Value = newValue
prop.Source = source
}
return prop
}
var GetWskPropFromWskprops = func(pi whisk.Properties, proppath string) (*whisk.Wskprops, error) {
return whisk.GetWskPropFromWskprops(pi, proppath)
}
var GetWskPropFromWhiskProperty = func(pi whisk.Properties) (*whisk.Wskprops, error) {
return whisk.GetWskPropFromWhiskProperty(pi)
}
// TODO implement a command line flag for APIGW_TENANT_ID
var GetCommandLineFlags = func() (string, string, string, string, string, string) {
return utils.Flags.ApiHost, utils.Flags.Auth, utils.Flags.Namespace, utils.Flags.Key, utils.Flags.Cert, utils.Flags.ApigwAccessToken
}
var CreateNewClient = func(config_input *whisk.Config) (*whisk.Client, error) {
var netClient = &http.Client{
Timeout: time.Second * utils.DEFAULT_HTTP_TIMEOUT,
}
return whisk.NewClient(netClient, config_input)
}
func AddAdditionalHeader(hdrName string, hdrValue string) {
additionalHeaders.Add(hdrName, hdrValue)
}
func resetWhiskConfig() {
credential = PropertyValue{}
namespace = PropertyValue{}
apiHost = PropertyValue{}
key = PropertyValue{}
cert = PropertyValue{}
apigwAccessToken = PropertyValue{}
apigwTenantId = PropertyValue{}
}
func readFromCLI() {
// read credentials, namespace, API host, key file, cert file, and APIGW access token from command line
apihost, auth, ns, keyfile, certfile, accessToken := GetCommandLineFlags()
credential = GetPropertyValue(credential, auth, wski18n.COMMAND_LINE)
namespace = GetPropertyValue(namespace, ns, wski18n.COMMAND_LINE)
apiHost = GetPropertyValue(apiHost, apihost, wski18n.COMMAND_LINE)
key = GetPropertyValue(key, keyfile, wski18n.COMMAND_LINE)
cert = GetPropertyValue(cert, certfile, wski18n.COMMAND_LINE)
apigwAccessToken = GetPropertyValue(apigwAccessToken, accessToken, wski18n.COMMAND_LINE)
// TODO optionally allow this value to be set from command line arg.
//apigwTenantId = GetPropertyValue(apigwTenantId, tenantId, wski18n.COMMAND_LINE)
}
func setWhiskConfig(cred string, ns string, host string, token string, source string) {
credential = GetPropertyValue(credential, cred, source)
namespace = GetPropertyValue(namespace, ns, source)
apiHost = GetPropertyValue(apiHost, host, source)
apigwAccessToken = GetPropertyValue(apigwAccessToken, token, source)
// TODO decide if we should allow APIGW_TENANT_ID in manifest
}
func readFromDeploymentFile(deploymentPath string) {
if len(credential.Value) == 0 || len(namespace.Value) == 0 || len(apiHost.Value) == 0 {
if utils.FileExists(deploymentPath) {
mm := parsers.NewYAMLParser()
deployment, _ := mm.ParseDeployment(deploymentPath)
p := deployment.GetProject()
setWhiskConfig(p.Credential, p.Namespace, p.ApiHost, p.ApigwAccessToken, path.Base(deploymentPath))
}
}
}
func readFromManifestFile(manifestPath string) {
if len(credential.Value) == 0 || len(namespace.Value) == 0 || len(apiHost.Value) == 0 {
if utils.FileExists(manifestPath) {
mm := parsers.NewYAMLParser()
manifest, _ := mm.ParseManifest(manifestPath)
p := manifest.GetProject()
// TODO look to deprecate reading Namespace, APIGW values from manifest or depl. YAML files
setWhiskConfig(p.Credential, p.Namespace, p.ApiHost, p.ApigwAccessToken, path.Base(manifestPath))
}
}
}
func readFromWskprops(pi whisk.PropertiesImp, proppath string) {
// The error raised here can be neglected, because we will handle it in the end of its calling function.
wskprops, _ := GetWskPropFromWskprops(pi, proppath)
credential = GetPropertyValue(credential, wskprops.AuthKey, SOURCE_WSKPROPS)
namespace = GetPropertyValue(namespace, wskprops.Namespace, SOURCE_WSKPROPS)
apiHost = GetPropertyValue(apiHost, wskprops.APIHost, SOURCE_WSKPROPS)
key = GetPropertyValue(key, wskprops.Key, SOURCE_WSKPROPS)
cert = GetPropertyValue(cert, wskprops.Cert, SOURCE_WSKPROPS)
apigwAccessToken = GetPropertyValue(apigwAccessToken, wskprops.AuthAPIGWKey, SOURCE_WSKPROPS)
apigwTenantId = GetPropertyValue(apigwTenantId, wskprops.APIGWTenantId, SOURCE_WSKPROPS)
}
func readFromWhiskProperty(pi whisk.PropertiesImp) {
// now, read credentials from whisk.properties but this is only acceptable within Travis
// whisk.properties will soon be deprecated and should not be used for any production deployment
whiskproperty, _ := GetWskPropFromWhiskProperty(pi)
var warnMsg string
credential = GetPropertyValue(credential, whiskproperty.AuthKey, SOURCE_WHISK_PROPERTIES)
if credential.Source == SOURCE_WHISK_PROPERTIES {
warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED,
map[string]interface{}{wski18n.KEY_KEY: wski18n.AUTH_KEY})
wskprint.PrintlnOpenWhiskWarning(warnMsg)
}
namespace = GetPropertyValue(namespace, whiskproperty.Namespace, SOURCE_WHISK_PROPERTIES)
if namespace.Source == SOURCE_WHISK_PROPERTIES {
warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED,
map[string]interface{}{wski18n.KEY_KEY: parsers.YAML_KEY_NAMESPACE})
wskprint.PrintlnOpenWhiskWarning(warnMsg)
}
apiHost = GetPropertyValue(apiHost, whiskproperty.APIHost, SOURCE_WHISK_PROPERTIES)
if apiHost.Source == SOURCE_WHISK_PROPERTIES {
warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED,
map[string]interface{}{wski18n.KEY_KEY: wski18n.API_HOST})
wskprint.PrintlnOpenWhiskWarning(warnMsg)
}
apigwAccessToken = GetPropertyValue(apigwAccessToken, whiskproperty.AuthAPIGWKey, SOURCE_WHISK_PROPERTIES)
if apigwAccessToken.Source == SOURCE_WHISK_PROPERTIES {
warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED,
map[string]interface{}{wski18n.KEY_KEY: wski18n.APIGW_ACCESS_TOKEN})
wskprint.PrintlnOpenWhiskWarning(warnMsg)
}
apigwTenantId = GetPropertyValue(apigwTenantId, whiskproperty.APIGWTenantId, SOURCE_WHISK_PROPERTIES)
if apigwTenantId.Source == SOURCE_WHISK_PROPERTIES {
warnMsg = wski18n.T(wski18n.ID_WARN_WHISK_PROPS_DEPRECATED,
map[string]interface{}{wski18n.KEY_KEY: wski18n.APIGW_TENANT_ID})
wskprint.PrintlnOpenWhiskWarning(warnMsg)
}
}
// we are reading openwhisk credentials (apihost, namespace, and auth) in the following precedence order:
// (1) wskdeploy command line `wskdeploy --apihost --namespace --auth`
// (2) deployment file
// (3) manifest file
// (4) .wskprops
// we are following the same precedence order for APIGW_ACCESS_TOKEN
// but as a separate thread as APIGW_ACCESS_TOKEN only needed for APIs
func NewWhiskConfig(proppath string, deploymentPath string, manifestPath string) (*whisk.Config, error) {
// reset credential, apiHost, namespace, etc to avoid any conflicts as they initialized globally
resetWhiskConfig()
// read from command line
readFromCLI()
// TODO() i18n
// Print all flags / values if verbose
//wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, wski18n.CONFIGURATION+":\n"+utils.Flags.Format())
// now, read them from deployment file if not found on command line
readFromDeploymentFile(deploymentPath)
// read credentials from manifest file as didn't find them on command line and in deployment file
readFromManifestFile(manifestPath)
// Third, we need to look up the variables in .wskprops file.
pi := whisk.PropertiesImp{
OsPackage: whisk.OSPackageImp{},
}
readFromWskprops(pi, proppath)
// TODO() whisk.properties should be deprecated
readFromWhiskProperty(pi)
// As a last resort initialize APIGW_ACCESS_TOKEN to "DUMMY TOKEN" for Travis builds
// The reason DUMMY TOKEN is not always true for Travis builds is that they may want
// to use Travis as a CD vehicle in which case we need to respect the other values
// that may be set before.
if strings.ToLower(os.Getenv("TRAVIS")) == "true" {
apigwAccessToken = GetPropertyValue(apigwAccessToken, "DUMMY TOKEN", SOURCE_DEFAULT_VALUE)
}
// set namespace to default namespace if not yet found
if len(apiHost.Value) != 0 && len(credential.Value) != 0 && len(namespace.Value) == 0 {
namespace.Value = whisk.DEFAULT_NAMESPACE
namespace.Source = SOURCE_DEFAULT_VALUE
}
mode := true
if len(cert.Value) != 0 && len(key.Value) != 0 {
mode = false
}
clientConfig = &whisk.Config{
AuthToken: credential.Value, //Authtoken
Namespace: namespace.Value, //Namespace
Host: apiHost.Value,
Version: "v1", // TODO() should not be hardcoded, should warn user of default
//Version: Apiversion
Cert: cert.Value,
Key: key.Value,
Insecure: mode, // true if you want to ignore certificate signing
ApigwAccessToken: apigwAccessToken.Value,
ApigwTenantId: apigwTenantId.Value,
AdditionalHeaders: additionalHeaders,
}
// Print all flags / values if verbose
wskprint.PrintlnOpenWhiskVerbose(utils.Flags.Verbose, wski18n.CLI_FLAGS+":\n"+utils.Flags.Format())
// validate we have credential, apihost and namespace
err := validateClientConfig(credential, apiHost, namespace)
return clientConfig, err
}
func validateClientConfig(credential PropertyValue, apiHost PropertyValue, namespace PropertyValue) error {
// Display error message for each config value found missing
if len(credential.Value) == 0 || len(apiHost.Value) == 0 || len(namespace.Value) == 0 {
var errorMsg string = ""
if len(credential.Value) == 0 {
errorMsg = wskderrors.AppendDetailToErrorMessage(
errorMsg, wski18n.T(wski18n.ID_MSG_CONFIG_MISSING_AUTHKEY), 1)
}
if len(apiHost.Value) == 0 {
errorMsg = wskderrors.AppendDetailToErrorMessage(
errorMsg, wski18n.T(wski18n.ID_MSG_CONFIG_MISSING_APIHOST), 1)
}
if len(namespace.Value) == 0 {
errorMsg = wskderrors.AppendDetailToErrorMessage(
errorMsg, wski18n.T(wski18n.ID_MSG_CONFIG_MISSING_NAMESPACE), 1)
}
if len(errorMsg) > 0 {
return wskderrors.NewWhiskClientInvalidConfigError(errorMsg)
}
}
// Show caller what final values we used for credential, apihost and namespace
stdout := wski18n.T(wski18n.ID_MSG_CONFIG_INFO_APIHOST_X_host_X_source_X,
map[string]interface{}{wski18n.KEY_HOST: apiHost.Value, wski18n.KEY_SOURCE: apiHost.Source})
wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, stdout)
stdout = wski18n.T(wski18n.ID_MSG_CONFIG_INFO_AUTHKEY_X_source_X,
map[string]interface{}{wski18n.KEY_SOURCE: credential.Source})
wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, stdout)
stdout = wski18n.T(wski18n.ID_MSG_CONFIG_INFO_NAMESPACE_X_namespace_X_source_X,
map[string]interface{}{wski18n.KEY_NAMESPACE: namespace.Value, wski18n.KEY_SOURCE: namespace.Source})
wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, stdout)
if len(apigwAccessToken.Value) != 0 {
stdout = wski18n.T(wski18n.ID_MSG_CONFIG_INFO_APIGE_ACCESS_TOKEN_X_source_X,
map[string]interface{}{wski18n.KEY_SOURCE: apigwAccessToken.Source})
wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, stdout)
}
if len(apigwTenantId.Value) != 0 {
stdout = wski18n.T(wski18n.ID_MSG_CONFIG_INFO_APIGW_TENANT_ID_X_source_X,
map[string]interface{}{wski18n.KEY_UUID: apigwTenantId, wski18n.KEY_SOURCE: apigwTenantId.Source})
wskprint.PrintOpenWhiskVerbose(utils.Flags.Verbose, stdout)
}
return nil
}