cli/pkg/standalone/run.go

334 lines
7.9 KiB
Go

// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package standalone
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"runtime"
"strings"
"gopkg.in/yaml.v2"
"github.com/Pallinder/sillyname-go"
"github.com/phayes/freeport"
"github.com/dapr/dapr/pkg/components"
modes "github.com/dapr/dapr/pkg/config/modes"
)
const (
componentsDirName = "components"
redisMessageBusYamlFileName = "redis_messagebus.yaml"
redisStateStoreYamlFileName = "redis.yaml"
)
// RunConfig to represent application configuration parameters
type RunConfig struct {
AppID string
AppPort int
HTTPPort int
GRPCPort int
ConfigFile string
Protocol string
Arguments []string
EnableProfiling bool
ProfilePort int
LogLevel string
MaxConcurrency int
RedisHost string
PlacementHost string
}
// RunOutput to represent the run output
type RunOutput struct {
DaprCMD *exec.Cmd
DaprHTTPPort int
DaprGRPCPort int
AppID string
AppCMD *exec.Cmd
}
type component struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Metadata struct {
Name string `yaml:"name"`
} `yaml:"metadata"`
Spec struct {
Type string `yaml:"type"`
Metadata []componentMetadataItem `yaml:"metadata"`
} `yaml:"spec"`
}
type componentMetadataItem struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}
func getDaprCommand(appID string, daprHTTPPort int, daprGRPCPort int, appPort int, configFile, protocol string, enableProfiling bool, profilePort int, logLevel string, maxConcurrency int, placementHost string) (*exec.Cmd, int, int, error) {
if daprHTTPPort < 0 {
port, err := freeport.GetFreePort()
if err != nil {
return nil, -1, -1, err
}
daprHTTPPort = port
}
if daprGRPCPort < 0 {
grpcPort, err := freeport.GetFreePort()
if err != nil {
return nil, -1, -1, err
}
daprGRPCPort = grpcPort
}
if maxConcurrency < 1 {
maxConcurrency = -1
}
daprCMD := "daprd"
if runtime.GOOS == "windows" {
daprCMD = fmt.Sprintf("%s.exe", daprCMD)
}
args := []string{"--dapr-id", appID, "--dapr-http-port", fmt.Sprintf("%v", daprHTTPPort), "--dapr-grpc-port", fmt.Sprintf("%v", daprGRPCPort), "--log-level", logLevel, "--max-concurrency", fmt.Sprintf("%v", maxConcurrency), "--protocol", protocol}
if appPort > -1 {
args = append(args, "--app-port")
args = append(args, fmt.Sprintf("%v", appPort))
}
args = append(args, "--placement-address")
if runtime.GOOS == "windows" {
args = append(args, fmt.Sprintf("%s:6050", placementHost))
} else {
args = append(args, fmt.Sprintf("%s:50005", placementHost))
}
if configFile != "" {
args = append(args, "--config")
args = append(args, configFile)
}
if enableProfiling {
if profilePort == -1 {
pp, err := freeport.GetFreePort()
if err != nil {
return nil, -1, -1, err
}
profilePort = pp
}
args = append(args, "--enable-profiling")
args = append(args, "true")
args = append(args, "--profile-port")
args = append(args, fmt.Sprintf("%v", profilePort))
}
cmd := exec.Command(daprCMD, args...)
return cmd, daprHTTPPort, daprGRPCPort, nil
}
func getAppCommand(httpPort, grpcPort int, command string, args []string) (*exec.Cmd, error) {
cmd := exec.Command(command, args...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("DAPR_HTTP_PORT=%v", httpPort))
cmd.Env = append(cmd.Env, fmt.Sprintf("DAPR_GRPC_PORT=%v", grpcPort))
return cmd, nil
}
func absoluteComponentsDir() (string, error) {
wd, err := os.Getwd()
if err != nil {
return "", err
}
return path.Join(wd, componentsDirName), nil
}
func createRedisStateStore(redisHost string) error {
redisStore := component{
APIVersion: "dapr.io/v1alpha1",
Kind: "Component",
}
redisStore.Metadata.Name = "statestore"
redisStore.Spec.Type = "state.redis"
redisStore.Spec.Metadata = []componentMetadataItem{}
redisStore.Spec.Metadata = append(redisStore.Spec.Metadata, componentMetadataItem{
Name: "redisHost",
Value: fmt.Sprintf("%s:6379", redisHost),
})
redisStore.Spec.Metadata = append(redisStore.Spec.Metadata, componentMetadataItem{
Name: "redisPassword",
Value: "",
})
b, err := yaml.Marshal(&redisStore)
if err != nil {
return err
}
componentsDir, err := absoluteComponentsDir()
if err != nil {
return err
}
err = ioutil.WriteFile(path.Join(componentsDir, redisStateStoreYamlFileName), b, 0644)
if err != nil {
return err
}
return nil
}
func createDirectory(dir string) error {
if _, err := os.Stat(dir); !os.IsNotExist(err) {
return nil
}
return os.Mkdir(dir, 0777)
}
func createRedisPubSub(redisHost string) error {
redisMessageBus := component{
APIVersion: "dapr.io/v1alpha1",
Kind: "Component",
}
redisMessageBus.Metadata.Name = "messagebus"
redisMessageBus.Spec.Type = "pubsub.redis"
redisMessageBus.Spec.Metadata = []componentMetadataItem{}
redisMessageBus.Spec.Metadata = append(redisMessageBus.Spec.Metadata, componentMetadataItem{
Name: "redisHost",
Value: fmt.Sprintf("%s:6379", redisHost),
})
redisMessageBus.Spec.Metadata = append(redisMessageBus.Spec.Metadata, componentMetadataItem{
Name: "redisPassword",
Value: "",
})
b, err := yaml.Marshal(&redisMessageBus)
if err != nil {
return err
}
componentsDir, err := absoluteComponentsDir()
if err != nil {
return err
}
err = ioutil.WriteFile(path.Join(componentsDir, redisMessageBusYamlFileName), b, 0644)
if err != nil {
return err
}
return nil
}
func Run(config *RunConfig) (*RunOutput, error) {
appID := config.AppID
if appID == "" {
appID = strings.Replace(sillyname.GenerateStupidName(), " ", "-", -1)
}
dapr, err := List()
if err != nil {
return nil, err
}
for _, a := range dapr {
if appID == a.AppID {
return nil, fmt.Errorf("Dapr with ID %s is already running", appID)
}
}
componentsDir, err := absoluteComponentsDir()
if err != nil {
return nil, err
}
err = createDirectory(componentsDir)
if err != nil {
return nil, err
}
componentsLoader := components.NewStandaloneComponents(modes.StandaloneConfig{ComponentsPath: componentsDir})
components, err := componentsLoader.LoadComponents()
if err != nil {
return nil, err
}
var stateStore, pubSub string
for _, component := range components {
if strings.HasPrefix(component.Spec.Type, "state") {
stateStore = component.Spec.Type
}
if strings.HasPrefix(component.Spec.Type, "pubsub") {
pubSub = component.Spec.Type
}
}
if stateStore == "" {
err = createRedisStateStore(config.RedisHost)
if err != nil {
return nil, err
}
}
if pubSub == "" {
err = createRedisPubSub(config.RedisHost)
if err != nil {
return nil, err
}
}
daprCMD, daprHTTPPort, daprGRPCPort, err := getDaprCommand(appID, config.HTTPPort, config.GRPCPort, config.AppPort, config.ConfigFile, config.Protocol, config.EnableProfiling, config.ProfilePort, config.LogLevel, config.MaxConcurrency, config.PlacementHost)
if err != nil {
return nil, err
}
for _, a := range dapr {
if daprHTTPPort == a.HTTPPort {
return nil, fmt.Errorf("there's already a Dapr instance running with http port %v", daprHTTPPort)
} else if daprGRPCPort == a.GRPCPort {
return nil, fmt.Errorf("there's already a Dapr instance running with gRPC port %v", daprGRPCPort)
}
}
runArgs := []string{}
argCount := len(config.Arguments)
if argCount == 0 {
return nil, errors.New("No app entrypoint given")
}
cmd := config.Arguments[0]
if len(config.Arguments) > 1 {
runArgs = config.Arguments[1:]
}
appCMD, err := getAppCommand(daprHTTPPort, daprGRPCPort, cmd, runArgs)
if err != nil {
return nil, err
}
return &RunOutput{
DaprCMD: daprCMD,
AppCMD: appCMD,
AppID: appID,
DaprHTTPPort: daprHTTPPort,
DaprGRPCPort: daprGRPCPort,
}, nil
}