Skip to content

Commit

Permalink
Merge pull request #2586 from MaxMcAdam/anax-2476
Browse files Browse the repository at this point in the history
Issue 2476 - Update hzn dev service new/start/stop to support secrets
  • Loading branch information
dabooz authored Jul 9, 2021
2 parents 6b34890 + 1012e13 commit b2efe34
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 17 deletions.
64 changes: 64 additions & 0 deletions cli/dev/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dev

import (
"encoding/json"
"fmt"
"github.com/open-horizon/anax/common"
"github.com/open-horizon/anax/config"
"github.com/open-horizon/anax/containermessage"
)

const SECRETS_FILE = "servicesecret"

// Check for the existence of the user input config file in the project.
func SecretsFileExists(directory string) (bool, error) {
return FileExists(directory, SECRETS_FILE)
}

type ServiceSecret struct {
Key string `json:"key"`
Value string `json:"value"`
}

func CreateSecretsFile(directory string) error {
fileContents := ServiceSecret{Key: "My Secret Type", Value: "My Secret Value"}

return CreateFile(directory, SECRETS_FILE, fileContents)
}

// Add binds for the provided service secret files if the secret name is specified in a dependent service
func AddDependentServiceSecretBinds(deps []*common.ServiceFile, secretsFiles map[string]string) {
for _, dep := range deps {
typedDeploy := common.DeploymentConfig{}

if deployBytes, err := json.Marshal(dep.Deployment); err != nil {
fmt.Printf("Failed to marshal deps: %v", err)
} else if err = json.Unmarshal(deployBytes, &typedDeploy); err != nil {
fmt.Printf("Failed to unmarshal deps: %v", err)
} else {
for svcName, svcInfo := range typedDeploy.Services {
for secName, _ := range svcInfo.Secrets {
if svcPath, ok := secretsFiles[secName]; ok {
svcInfo.Binds = append(svcInfo.Binds, fmt.Sprintf("%v:%v/%v", svcPath, config.HZN_SECRETS_MOUNT, secName))
} else {
fmt.Printf("Warning: Secret %v for service %v not specified with %v command.\n", secName, svcName, SERVICE_START_COMMAND)
}
}
}
}
dep.Deployment = typedDeploy
}
}

// Add binds for the provided service secret files to the top-level service container if the secret name is in the service deployment
func AddTopLevelServiceSecretBinds(deployment *containermessage.DeploymentDescription, secretsFiles map[string]string) {
for svcName, svcInfo := range deployment.Services {
for secName, _ := range svcInfo.Secrets {
if svcPath, ok := secretsFiles[secName]; ok {
svcInfo.Binds = append(svcInfo.Binds, fmt.Sprintf("%v:%v/%v", svcPath, config.HZN_SECRETS_MOUNT, secName))
} else {
fmt.Printf("Warning: Secret %v for service %v not specified with %v command.\n", secName, svcName, SERVICE_START_COMMAND)
}
}
}
}
39 changes: 31 additions & 8 deletions cli/dev/service.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package dev

import (
"fmt"
"os"
"runtime"
"strings"

"errors"

"fmt"
"github.com/open-horizon/anax/cli/cliutils"
"github.com/open-horizon/anax/cli/plugin_registry"
"github.com/open-horizon/anax/common"
"github.com/open-horizon/anax/container"
"github.com/open-horizon/anax/cutil"
"github.com/open-horizon/anax/i18n"
"github.com/open-horizon/anax/semanticversion"
"os"
"path/filepath"
"runtime"
"strings"
)

// These constants define the hzn dev subcommands supported by this module.
Expand Down Expand Up @@ -66,6 +65,7 @@ func ServiceNew(homeDirectory string, org string, specRef string, version string
// If there are any horizon metadata files already in the directory then we wont create any files.
cmd := fmt.Sprintf("%v %v", SERVICE_COMMAND, SERVICE_CREATION_COMMAND)
FileNotExist(dir, cmd, USERINPUT_FILE, UserInputExists)
FileNotExist(dir, cmd, SECRETS_FILE, SecretsFileExists)
FileNotExist(dir, cmd, SERVICE_DEFINITION_FILE, ServiceDefinitionExists)
FileNotExist(dir, cmd, PATTERN_DEFINITION_FILE, PatternDefinitionExists)
FileNotExist(dir, cmd, PATTERN_DEFINITION_ALL_ARCHES_FILE, PatternDefinitionAllArchesExists)
Expand Down Expand Up @@ -93,6 +93,12 @@ func ServiceNew(homeDirectory string, org string, specRef string, version string
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, "'%v %v' %v", SERVICE_COMMAND, SERVICE_CREATION_COMMAND, err)
}

cliutils.Verbose(msgPrinter.Sprintf("Creating secrets file: %v/%v", dir, SECRETS_FILE))
err = CreateSecretsFile(dir)
if err != nil {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, "'%v %v' %v", SERVICE_COMMAND, SERVICE_CREATION_COMMAND, err)
}

cliutils.Verbose(msgPrinter.Sprintf("Creating service definition file: %v/%v", dir, SERVICE_DEFINITION_FILE))
err = CreateServiceDefinition(dir, specRef, imageInfo, noImageGen, dconfig)
if err != nil {
Expand Down Expand Up @@ -198,10 +204,27 @@ func verifyNewServiceInputs(homeDirectory string, org string, specRef string, ve
return dir, nil
}

func ServiceStartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string) {
// Take the filepaths given by --secrets flags
// map the filename which is the same as the secret name to the filepath
func mapSecNameToSecPath(secretPaths []string) map[string]string {
// get message printer
msgPrinter := i18n.GetMessagePrinter()

finalMap := make(map[string]string, len(secretPaths))
for _, secPath := range secretPaths {
if _, err := os.Stat(secPath); err != nil {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Error verifying filepath %v: %v", secPath, err))
}
finalMap[filepath.Base(secPath)] = secPath
}
return finalMap
}

func ServiceStartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string, secretsFilePaths []string) {
secretsFilePathsMap := mapSecNameToSecPath(secretsFilePaths)

// Allow the right plugin to start a test of this service.
startErr := plugin_registry.DeploymentConfigPlugins.StartTest(homeDirectory, userInputFile, configFiles, configType, noFSS, userCreds)
startErr := plugin_registry.DeploymentConfigPlugins.StartTest(homeDirectory, userInputFile, configFiles, configType, noFSS, userCreds, secretsFilePathsMap)
if startErr != nil {
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, "%v", startErr)
}
Expand Down
2 changes: 1 addition & 1 deletion cli/helm_deployment/helm_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (p *HelmDeploymentConfigPlugin) Validate(dep interface{}, cdep interface{})
}
}

func (p *HelmDeploymentConfigPlugin) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string) bool {
func (p *HelmDeploymentConfigPlugin) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string, secretsFiles map[string]string) bool {

// get message printer
msgPrinter := i18n.GetMessagePrinter()
Expand Down
3 changes: 2 additions & 1 deletion cli/hzn.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ Environment Variables:
devServiceConfigType := devServiceStartTestCmd.Flag("type", msgPrinter.Sprintf("The type of file to be made available through the sync service APIs. All config files are presumed to be of the same type. This flag is required if any configFiles are specified.")).Short('t').String()
devServiceNoFSS := devServiceStartTestCmd.Flag("noFSS", msgPrinter.Sprintf("Do not bring up file sync service (FSS) containers. They are brought up by default.")).Short('S').Bool()
devServiceStartCmdUserPw := devServiceStartTestCmd.Flag("user-pw", msgPrinter.Sprintf("Horizon Exchange user credentials to query exchange resources. Specify it when you want to automatically fetch the missing dependent services from the Exchange. The default is HZN_EXCHANGE_USER_AUTH environment variable. If you don't prepend it with the user's org, it will automatically be prepended with the value of the HZN_ORG_ID environment variable.")).Short('u').PlaceHolder("USER:PW").String()
devServiceStartSecretsFiles := devServiceStartTestCmd.Flag("secret", msgPrinter.Sprintf("Filepath of a file containing a secret that is required by the service or one of its dependent services. The filename must match a secret name in the service definition. The file is encoded in JSON as an object containing two keys both typed as a string; \"key\" is used to indicate the kind of secret, and \"value\" is the string form of the secret. This flag can be repeated.")).Strings()
devServiceStopTestCmd := devServiceCmd.Command("stop", msgPrinter.Sprintf("Stop a service that is running in a mocked Horizon Agent environment. This command is not supported for services using the %v deployment configuration.", kube_deployment.KUBE_DEPLOYMENT_CONFIG_TYPE))
devServiceValidateCmd := devServiceCmd.Command("verify | vf", msgPrinter.Sprintf("Validate the project for completeness and schema compliance.")).Alias("vf").Alias("verify")
devServiceVerifyUserInputFile := devServiceValidateCmd.Flag("userInputFile", msgPrinter.Sprintf("File containing user input values for verification of a project. If omitted, the userinput file for the project will be used.")).Short('f').String()
Expand Down Expand Up @@ -1095,7 +1096,7 @@ Environment Variables:
case devServiceNewCmd.FullCommand():
dev.ServiceNew(*devHomeDirectory, *devServiceNewCmdOrg, *devServiceNewCmdName, *devServiceNewCmdVer, *devServiceNewCmdImage, *devServiceNewCmdNoImageGen, *devServiceNewCmdCfg, *devServiceNewCmdNoPattern, *devServiceNewCmdNoPolicy)
case devServiceStartTestCmd.FullCommand():
dev.ServiceStartTest(*devHomeDirectory, *devServiceUserInputFile, *devServiceConfigFile, *devServiceConfigType, *devServiceNoFSS, *devServiceStartCmdUserPw)
dev.ServiceStartTest(*devHomeDirectory, *devServiceUserInputFile, *devServiceConfigFile, *devServiceConfigType, *devServiceNoFSS, *devServiceStartCmdUserPw, *devServiceStartSecretsFiles)
case devServiceStopTestCmd.FullCommand():
dev.ServiceStopTest(*devHomeDirectory)
case devServiceValidateCmd.FullCommand():
Expand Down
2 changes: 1 addition & 1 deletion cli/kube_deployment/kube_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (p *KubeDeploymentConfigPlugin) Validate(dep interface{}, cdep interface{})
}
}

func (p *KubeDeploymentConfigPlugin) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string) bool {
func (p *KubeDeploymentConfigPlugin) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string, secretsFiles map[string]string) bool {

// get message printer
msgPrinter := i18n.GetMessagePrinter()
Expand Down
10 changes: 8 additions & 2 deletions cli/native_deployment/native_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (p *NativeDeploymentConfigPlugin) DefaultConfig(imageInfo interface{}) inte
"services": map[string]*containermessage.Service{
"": &containermessage.Service{
Image: "",
Secrets: map[string]containermessage.Secret{},
},
},
}
Expand All @@ -117,6 +118,7 @@ func (p *NativeDeploymentConfigPlugin) DefaultConfig(imageInfo interface{}) inte
for image_name, image := range imageList {
serviceDep[image_name] = &containermessage.Service{
Image: image,
Secrets: map[string]containermessage.Secret{},
}
}
return map[string]interface{}{"services": serviceDep}
Expand Down Expand Up @@ -163,7 +165,7 @@ func (p *NativeDeploymentConfigPlugin) Validate(dep interface{}, cdep interface{
}

// This can't be a const because a map literal isn't a const in go
var VALID_DEPLOYMENT_FIELDS = map[string]int8{"image": 1, "privileged": 1, "cap_add": 1, "environment": 1, "devices": 1, "binds": 1, "specific_ports": 1, "command": 1, "ports": 1, "ephemeral_ports": 1, "tmpfs": 1, "network": 1, "entrypoint": 1, "max_memory_mb": 1, "max_cpus": 1, "log_driver": 1}
var VALID_DEPLOYMENT_FIELDS = map[string]int8{"image": 1, "privileged": 1, "cap_add": 1, "environment": 1, "devices": 1, "binds": 1, "specific_ports": 1, "command": 1, "ports": 1, "ephemeral_ports": 1, "tmpfs": 1, "network": 1, "entrypoint": 1, "max_memory_mb": 1, "max_cpus": 1, "log_driver": 1, "secrets": 1}

// CheckDeploymentService verifies it has the required 'image' key, and checks for keys we don't recognize.
// For now it only prints a warning for unrecognized keys, in case we recently added a key to anax and haven't updated hzn yet.
Expand Down Expand Up @@ -271,7 +273,7 @@ func SignImagesFromDeploymentMap(deployment map[string]interface{}, dontTouchIma
}

// Start the native deployment config in test mode. Only services are supported.
func (p *NativeDeploymentConfigPlugin) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string) bool {
func (p *NativeDeploymentConfigPlugin) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string, secretsFiles map[string]string) bool {
// get message printer
msgPrinter := i18n.GetMessagePrinter()

Expand Down Expand Up @@ -311,6 +313,8 @@ func (p *NativeDeploymentConfigPlugin) StartTest(homeDirectory string, userInput
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, msgPrinter.Sprintf("'%v %v' unable to get service dependencies, %v", dev.SERVICE_COMMAND, dev.SERVICE_START_COMMAND, derr))
}

dev.AddDependentServiceSecretBinds(deps, secretsFiles)

// Log the starting of dependencies if there are any.
if len(deps) != 0 {
cliutils.Verbose(msgPrinter.Sprintf("Starting dependencies."))
Expand Down Expand Up @@ -343,6 +347,8 @@ func (p *NativeDeploymentConfigPlugin) StartTest(homeDirectory string, userInput
cliutils.Fatal(cliutils.CLI_GENERAL_ERROR, "'%v %v' %v", dev.SERVICE_COMMAND, dev.SERVICE_START_COMMAND, cerr)
}

dev.AddTopLevelServiceSecretBinds(deployment, secretsFiles)

// Now we can start the service container.
_, err := dev.StartContainers(deployment, serviceDef.URL, userInputs.Global, serviceDef.UserInputs, userInputs.Services, serviceDef.Org, dc, cw, msNetworks, true, true, agreementId)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions cli/plugin_registry/plugin_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type DeploymentConfigPlugin interface {
DefaultConfig(imageInfo interface{}) interface{}
DefaultClusterConfig() interface{}
Validate(dep interface{}, cdep interface{}) (bool, error)
StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string) bool
StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string, secretsFiles map[string]string) bool
StopTest(homeDirectory string) bool
}

Expand Down Expand Up @@ -71,9 +71,9 @@ func (d DeploymentConfigRegistry) ValidatedByOne(dep interface{}, cdep interface
// Ask each plugin to attempt to start the project in test mode. Plugins are called
// until one of them claims ownership of the deployment config. If no error is
// returned, then one of the plugins has claimed the deployment config.
func (d DeploymentConfigRegistry) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string) error {
func (d DeploymentConfigRegistry) StartTest(homeDirectory string, userInputFile string, configFiles []string, configType string, noFSS bool, userCreds string, secretsFiles map[string]string) error {
for _, p := range d {
if owned := p.StartTest(homeDirectory, userInputFile, configFiles, configType, noFSS, userCreds); owned {
if owned := p.StartTest(homeDirectory, userInputFile, configFiles, configType, noFSS, userCreds, secretsFiles); owned {
return nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion containermessage/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ type Service struct {
MaxMemoryMb int64 `json:"max_memory_mb,omitempty"`
MaxCPUs float32 `json:"max_cpus,omitempty"`
LogDriver string `json:"log_driver,omitempty"` // Docker's log-driver. Syslog will be used as default driver
Secrets map[string]Secret `json:"secrets,omitempty"`
Secrets map[string]Secret `json:"secrets"`
}

func (s *Service) AddFilesystemBinding(bind string) {
Expand Down

0 comments on commit b2efe34

Please sign in to comment.