Skip to content

Commit c1f9627

Browse files
Apps run config template parsing (#1152)
* initial commit for run template parsing Signed-off-by: Pravin Pushkar <[email protected]> * some code refactor. Signed-off-by: Pravin Pushkar <[email protected]> * add some UTs Signed-off-by: Pravin Pushkar <[email protected]> * call validate Signed-off-by: Pravin Pushkar <[email protected]> * add tests and correct err message Signed-off-by: Pravin Pushkar <[email protected]> * refactor Runconfig into 2 structs Signed-off-by: Pravin Pushkar <[email protected]> * Fix Arg parsing Signed-off-by: Pravin Pushkar <[email protected]> * fix UTs. make app-id be inferred from app-dir if empty Signed-off-by: Pravin Pushkar <[email protected]> * fix tests Signed-off-by: Pravin Pushkar <[email protected]> * put config reltd code into separate folder Signed-off-by: Pravin Pushkar <[email protected]> * merge logic for common and apps section of flags. Signed-off-by: Pravin Pushkar <[email protected]> * adding tests and few refactoring Signed-off-by: Pravin Pushkar <[email protected]> * fix tests Signed-off-by: Pravin Pushkar <[email protected]> * file close Signed-off-by: Pravin Pushkar <[email protected]> * review comments Signed-off-by: Pravin Pushkar <[email protected]> * fix review comments Signed-off-by: Pravin Pushkar <[email protected]> * refactor file names Signed-off-by: Pravin Pushkar <[email protected]> * resolve path to abs and add more e2e for path resolving Signed-off-by: Pravin Pushkar <[email protected]> * address review comments Signed-off-by: Pravin Pushkar <[email protected]> * remove getAppskeysmapping Signed-off-by: Pravin Pushkar <[email protected]> * add tests, and fix issue for setitng app id when empty. Signed-off-by: Pravin Pushkar <[email protected]> * fix tests Signed-off-by: Pravin Pushkar <[email protected]> Signed-off-by: Pravin Pushkar <[email protected]> Co-authored-by: Mukundan Sundararajan <[email protected]>
1 parent d4654c0 commit c1f9627

File tree

15 files changed

+669
-65
lines changed

15 files changed

+669
-65
lines changed

cmd/run.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/dapr/cli/pkg/metadata"
2929
"github.com/dapr/cli/pkg/print"
3030
"github.com/dapr/cli/pkg/standalone"
31+
"github.com/dapr/cli/pkg/standalone/runfileconfig"
3132
"github.com/dapr/cli/utils"
3233
)
3334

@@ -57,6 +58,7 @@ var (
5758
appHealthThreshold int
5859
enableAPILogging bool
5960
apiListenAddresses string
61+
runFilePath string
6062
)
6163

6264
const (
@@ -93,6 +95,10 @@ dapr run --app-id myapp --dapr-path /usr/local/dapr
9395
viper.BindPFlag("placement-host-address", cmd.Flags().Lookup("placement-host-address"))
9496
},
9597
Run: func(cmd *cobra.Command, args []string) {
98+
if len(runFilePath) > 0 {
99+
executeRunWithAppsConfigFile(runFilePath)
100+
return
101+
}
96102
if len(args) == 0 {
97103
fmt.Println(print.WhiteBold("WARNING: no application command found."))
98104
}
@@ -126,35 +132,38 @@ dapr run --app-id myapp --dapr-path /usr/local/dapr
126132
}
127133
}
128134

129-
output, err := standalone.Run(&standalone.RunConfig{
130-
AppID: appID,
131-
AppPort: appPort,
132-
HTTPPort: port,
133-
GRPCPort: grpcPort,
135+
sharedRunConfig := &standalone.SharedRunConfig{
134136
ConfigFile: configFile,
135-
Arguments: args,
136137
EnableProfiling: enableProfiling,
137-
ProfilePort: profilePort,
138138
LogLevel: logLevel,
139139
MaxConcurrency: maxConcurrency,
140-
Protocol: protocol,
140+
AppProtocol: protocol,
141141
PlacementHostAddr: viper.GetString("placement-host-address"),
142142
ComponentsPath: componentsPath,
143143
ResourcesPath: resourcesPath,
144144
AppSSL: appSSL,
145-
MetricsPort: metricsPort,
146145
MaxRequestBodySize: maxRequestBodySize,
147146
HTTPReadBufferSize: readBufferSize,
148-
UnixDomainSocket: unixDomainSocket,
149147
EnableAppHealth: enableAppHealth,
150148
AppHealthPath: appHealthPath,
151149
AppHealthInterval: appHealthInterval,
152150
AppHealthTimeout: appHealthTimeout,
153151
AppHealthThreshold: appHealthThreshold,
154152
EnableAPILogging: enableAPILogging,
155-
InternalGRPCPort: internalGRPCPort,
156-
DaprPathCmdFlag: daprPath,
157153
APIListenAddresses: apiListenAddresses,
154+
}
155+
output, err := standalone.Run(&standalone.RunConfig{
156+
AppID: appID,
157+
AppPort: appPort,
158+
HTTPPort: port,
159+
GRPCPort: grpcPort,
160+
ProfilePort: profilePort,
161+
Command: args,
162+
MetricsPort: metricsPort,
163+
UnixDomainSocket: unixDomainSocket,
164+
InternalGRPCPort: internalGRPCPort,
165+
DaprPathCmdFlag: daprPath,
166+
SharedRunConfig: *sharedRunConfig,
158167
})
159168
if err != nil {
160169
print.FailureStatusEvent(os.Stderr, err.Error())
@@ -416,5 +425,19 @@ func init() {
416425
RunCmd.Flags().IntVar(&appHealthThreshold, "app-health-threshold", 0, "Number of consecutive failures for the app to be considered unhealthy")
417426
RunCmd.Flags().BoolVar(&enableAPILogging, "enable-api-logging", false, "Log API calls at INFO verbosity. Valid values are: true or false")
418427
RunCmd.Flags().StringVar(&apiListenAddresses, "dapr-listen-addresses", "", "Comma separated list of IP addresses that sidecar will listen to")
428+
RunCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the configuration file for the apps to run")
419429
RootCmd.AddCommand(RunCmd)
420430
}
431+
432+
func executeRunWithAppsConfigFile(runFilePath string) {
433+
config := runfileconfig.RunFileConfig{}
434+
apps, err := config.GetApps(runFilePath)
435+
if err != nil {
436+
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Error getting apps from config file: %s", err))
437+
os.Exit(1)
438+
}
439+
if len(apps) == 0 {
440+
print.FailureStatusEvent(os.Stdout, "No apps to run")
441+
os.Exit(1)
442+
}
443+
}

pkg/standalone/run.go

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,39 @@ const sentryDefaultAddress = "localhost:50001"
3535

3636
// RunConfig represents the application configuration parameters.
3737
type RunConfig struct {
38-
AppID string `env:"APP_ID" arg:"app-id"`
39-
AppPort int `env:"APP_PORT" arg:"app-port"`
40-
HTTPPort int `env:"DAPR_HTTP_PORT" arg:"dapr-http-port"`
41-
GRPCPort int `env:"DAPR_GRPC_PORT" arg:"dapr-grpc-port"`
42-
ConfigFile string `arg:"config"`
43-
Protocol string `arg:"app-protocol"`
44-
Arguments []string
45-
APIListenAddresses string `arg:"dapr-listen-addresses"`
46-
EnableProfiling bool `arg:"enable-profiling"`
47-
ProfilePort int `arg:"profile-port"`
48-
LogLevel string `arg:"log-level"`
49-
MaxConcurrency int `arg:"app-max-concurrency"`
50-
PlacementHostAddr string `arg:"placement-host-address"`
38+
AppID string `env:"APP_ID" arg:"app-id" yaml:"app_id"`
39+
AppPort int `env:"APP_PORT" arg:"app-port" yaml:"app_port"`
40+
HTTPPort int `env:"DAPR_HTTP_PORT" arg:"dapr-http-port" yaml:"dapr_http_port"`
41+
GRPCPort int `env:"DAPR_GRPC_PORT" arg:"dapr-grpc-port" yaml:"dapr_grpc_port"`
42+
ProfilePort int `arg:"profile-port" yaml:"profile_port"`
43+
Command []string `yaml:"command"`
44+
MetricsPort int `env:"DAPR_METRICS_PORT" arg:"metrics-port" yaml:"metrics_port"`
45+
UnixDomainSocket string `arg:"unix-domain-socket" yaml:"unix_domain_socket"`
46+
InternalGRPCPort int `arg:"dapr-internal-grpc-port" yaml:"dapr_internal_grpc_port"`
47+
DaprPathCmdFlag string `yaml:"dapr_path_cmd_flag"`
48+
SharedRunConfig `yaml:",inline"`
49+
}
50+
51+
// SharedRunConfig represents the application configuration parameters, which can be shared across many apps.
52+
type SharedRunConfig struct {
53+
ConfigFile string `arg:"config" yaml:"config_file"`
54+
AppProtocol string `arg:"app-protocol" yaml:"app_protocol"`
55+
APIListenAddresses string `arg:"dapr-listen-addresses" yaml:"api_listen_addresses"`
56+
EnableProfiling bool `arg:"enable-profiling" yaml:"enable_profiling"`
57+
LogLevel string `arg:"log-level" yaml:"log_level"`
58+
MaxConcurrency int `arg:"app-max-concurrency" yaml:"_appmax_concurrency"`
59+
PlacementHostAddr string `arg:"placement-host-address" yaml:"placement_host_address"`
5160
ComponentsPath string `arg:"components-path"`
52-
ResourcesPath string `arg:"resources-path"`
53-
AppSSL bool `arg:"app-ssl"`
54-
MetricsPort int `env:"DAPR_METRICS_PORT" arg:"metrics-port"`
55-
MaxRequestBodySize int `arg:"dapr-http-max-request-size"`
56-
HTTPReadBufferSize int `arg:"dapr-http-read-buffer-size"`
57-
UnixDomainSocket string `arg:"unix-domain-socket"`
58-
InternalGRPCPort int `arg:"dapr-internal-grpc-port"`
59-
EnableAppHealth bool `arg:"enable-app-health-check"`
60-
AppHealthPath string `arg:"app-health-check-path"`
61-
AppHealthInterval int `arg:"app-health-probe-interval" ifneq:"0"`
62-
AppHealthTimeout int `arg:"app-health-probe-timeout" ifneq:"0"`
63-
AppHealthThreshold int `arg:"app-health-threshold" ifneq:"0"`
64-
EnableAPILogging bool `arg:"enable-api-logging"`
65-
DaprPathCmdFlag string
61+
ResourcesPath string `arg:"resources-path" yaml:"resources_path"`
62+
AppSSL bool `arg:"app-ssl" yaml:"app_ssl"`
63+
MaxRequestBodySize int `arg:"dapr-http-max-request-size" yaml:"dapr_http_max_request_size"`
64+
HTTPReadBufferSize int `arg:"dapr-http-read-buffer-size" yaml:"dapr_http_read_buffer_size"`
65+
EnableAppHealth bool `arg:"enable-app-health-check" yaml:"enable_app_health_check"`
66+
AppHealthPath string `arg:"app-health-check-path" yaml:"app_health_check_path"`
67+
AppHealthInterval int `arg:"app-health-probe-interval" ifneq:"0" yaml:"app_health_probe_interval"`
68+
AppHealthTimeout int `arg:"app-health-probe-timeout" ifneq:"0" yaml:"app_health_probe_timeout"`
69+
AppHealthThreshold int `arg:"app-health-threshold" ifneq:"0" yaml:"app_health_threshold"`
70+
EnableAPILogging bool `arg:"enable-api-logging" yaml:"enable_api_logging"`
6671
}
6772

6873
func (meta *DaprMeta) newAppID() string {
@@ -239,10 +244,33 @@ func (config *RunConfig) getArgs() []string {
239244
args := []string{}
240245

241246
schema := reflect.ValueOf(*config)
247+
args = getArgsFromSchema(schema, args)
248+
249+
if config.ConfigFile != "" {
250+
sentryAddress := mtlsEndpoint(config.ConfigFile)
251+
if sentryAddress != "" {
252+
// mTLS is enabled locally, set it up.
253+
args = append(args, "--enable-mtls", "--sentry-address", sentryAddress)
254+
}
255+
}
256+
257+
if print.IsJSONLogEnabled() {
258+
args = append(args, "--log-as-json")
259+
}
260+
return args
261+
}
262+
263+
// Recursive function to get all the args from the config struct.
264+
// This is needed because the config struct has embedded struct.
265+
func getArgsFromSchema(schema reflect.Value, args []string) []string {
242266
for i := 0; i < schema.NumField(); i++ {
243267
valueField := schema.Field(i).Interface()
244268
typeField := schema.Type().Field(i)
245269
key := typeField.Tag.Get("arg")
270+
if typeField.Type.Kind() == reflect.Struct {
271+
args = getArgsFromSchema(schema.Field(i), args)
272+
continue
273+
}
246274
if len(key) == 0 {
247275
continue
248276
}
@@ -262,18 +290,6 @@ func (config *RunConfig) getArgs() []string {
262290
}
263291
}
264292
}
265-
266-
if config.ConfigFile != "" {
267-
sentryAddress := mtlsEndpoint(config.ConfigFile)
268-
if sentryAddress != "" {
269-
// mTLS is enabled locally, set it up.
270-
args = append(args, "--enable-mtls", "--sentry-address", sentryAddress)
271-
}
272-
}
273-
274-
if print.IsJSONLogEnabled() {
275-
args = append(args, "--log-as-json")
276-
}
277293
return args
278294
}
279295

@@ -343,16 +359,16 @@ func mtlsEndpoint(configFile string) string {
343359
}
344360

345361
func getAppCommand(config *RunConfig) *exec.Cmd {
346-
argCount := len(config.Arguments)
362+
argCount := len(config.Command)
347363

348364
if argCount == 0 {
349365
return nil
350366
}
351-
command := config.Arguments[0]
367+
command := config.Command[0]
352368

353369
args := []string{}
354370
if argCount > 1 {
355-
args = config.Arguments[1:]
371+
args = config.Command[1:]
356372
}
357373

358374
cmd := exec.Command(command, args...)

pkg/standalone/run_test.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,25 +172,28 @@ func TestRun(t *testing.T) {
172172
componentsDir := GetDaprComponentsPath(myDaprPath)
173173
configFile := GetDaprConfigPath(myDaprPath)
174174

175-
basicConfig := &RunConfig{
176-
AppID: "MyID",
177-
AppPort: 3000,
178-
HTTPPort: 8000,
179-
GRPCPort: 50001,
175+
sharedRunConfig := &SharedRunConfig{
180176
LogLevel: "WARN",
181-
Arguments: []string{"MyCommand", "--my-arg"},
182177
EnableProfiling: false,
183-
ProfilePort: 9090,
184-
Protocol: "http",
178+
AppProtocol: "http",
185179
ComponentsPath: componentsDir,
186180
AppSSL: true,
187-
MetricsPort: 9001,
188181
MaxRequestBodySize: -1,
189-
InternalGRPCPort: 5050,
190182
HTTPReadBufferSize: -1,
191183
EnableAPILogging: true,
192184
APIListenAddresses: "127.0.0.1",
193185
}
186+
basicConfig := &RunConfig{
187+
AppID: "MyID",
188+
AppPort: 3000,
189+
HTTPPort: 8000,
190+
GRPCPort: 50001,
191+
Command: []string{"MyCommand", "--my-arg"},
192+
ProfilePort: 9090,
193+
MetricsPort: 9001,
194+
InternalGRPCPort: 5050,
195+
SharedRunConfig: *sharedRunConfig,
196+
}
194197

195198
t.Run("run happy http", func(t *testing.T) {
196199
output, err := Run(basicConfig)
@@ -203,7 +206,7 @@ func TestRun(t *testing.T) {
203206
})
204207

205208
t.Run("run without app command", func(t *testing.T) {
206-
basicConfig.Arguments = nil
209+
basicConfig.Command = nil
207210
basicConfig.LogLevel = "INFO"
208211
basicConfig.EnableAPILogging = true
209212
basicConfig.ConfigFile = configFile
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2022 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package runfileconfig
15+
16+
import "github.com/dapr/cli/pkg/standalone"
17+
18+
// RunFileConfig represents the complete configuration options for the run file.
19+
// It is meant to be used with - "dapr run --run-file <path-to-run-file>" command.
20+
type RunFileConfig struct {
21+
Common Common `yaml:"common"`
22+
Apps []Apps `yaml:"apps"`
23+
Version int `yaml:"version"`
24+
}
25+
26+
// Apps represents the configuration options for the apps in the run file.
27+
type Apps struct {
28+
standalone.RunConfig `yaml:",inline"`
29+
AppDirPath string `yaml:"app_dir_path"`
30+
Env []EnvItems `yaml:"env"`
31+
}
32+
33+
// Common represents the configuration options for the common section in the run file.
34+
type Common struct {
35+
Env []EnvItems `yaml:"env"`
36+
standalone.SharedRunConfig `yaml:",inline"`
37+
}
38+
39+
// EnvItems represents the env configuration options that are present in commmon and/or individual app's section.
40+
type EnvItems struct {
41+
Name string `yaml:"name"`
42+
Value string `yaml:"value"`
43+
}

0 commit comments

Comments
 (0)