Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Webserver #17

Merged
merged 13 commits into from
Jun 28, 2024
Merged
6 changes: 6 additions & 0 deletions .env.example-d → .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ LOG_STDOUT=true
LOG_LEVEL=debug
SHELL=/usr/bin/bash
SHELL_ARGS=-c

WEBSERVER_LISTEN_ADDRESS=0.0.0.0
WEBSERVER_USERNAME=admin
WEBSERVER_PASSWORD=TEST

TZ=Asia/Tehran
10 changes: 9 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
}
},
"cSpell.words": [
"alecthomas",
"bodyclose",
"cfgcompiler",
"dupl",
"errcheck",
"exportloopref",
Expand All @@ -37,23 +39,29 @@
"goconst",
"gocritic",
"gocyclo",
"godotenv",
"gofumpt",
"goimports",
"gonic",
"goprintffuncname",
"gosec",
"gosimple",
"govet",
"ineffassign",
"joho",
"mocklogger",
"nakedret",
"noctx",
"nolint",
"rowserrcheck",
"sqlclosecheck",
"staticcheck",
"stylecheck",
"Tracef",
"typecheck",
"unconvert",
"unparam"
"unparam",
"webserver"
],
"json.schemaDownload.enable": true,
"yaml.schemas": {
Expand Down
1 change: 0 additions & 1 deletion abstraction/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ package abstraction
// Event is an object that can be executed using a execute method and stopped using cancel method
type Event interface {
BuildTickChannel() <-chan any
Cancel()
}
79 changes: 57 additions & 22 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,32 @@ func panicOnErr(err error, message string) {
}

func initConfig() {
setupEnv()

if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
}

viper.AutomaticEnv()

panicOnErr(
viper.ReadInConfig(),
"Cannot read the config file: %s",
)
panicOnErr(
viper.Unmarshal(CFG),
"Cannot unmarshal the config file: %s",
)
panicOnErr(
CFG.Validate(logrus.WithField("section", "config.validation")),
"Failed to initialize config file: %s",
)
}

func setupEnv() {
viper.SetDefault("log_timestamp_format", "2006-01-02T15:04:05Z07:00")
warnOnErr(
viper.BindEnv(
Expand Down Expand Up @@ -93,6 +119,37 @@ func initConfig() {
"Cannot bind log_stdout env variable: %s",
)

warnOnErr(
viper.BindEnv(
"webserver_port",
"listen_port",
),
"Cannot bind webserver_port env variable: %s",
)
warnOnErr(
viper.BindEnv(
"webserver_address",
"webserver_listen_address",
"listen_address",
),
"Cannot bind webserver_address env variable: %s",
)
warnOnErr(
viper.BindEnv(
"webserver_password",
"password",
),
"Cannot bind webserver_password env variable: %s",
)

warnOnErr(
viper.BindEnv(
"webserver_username",
"username",
),
"Cannot bind webserver_username env variable: %s",
)

viper.SetDefault("log_level", "info")
warnOnErr(
viper.BindEnv(
Expand Down Expand Up @@ -121,26 +178,4 @@ func initConfig() {
),
"Cannot bind shell_args env variable: %s",
)

if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
}

viper.AutomaticEnv()

panicOnErr(
viper.ReadInConfig(),
"Cannot read the config file: %s",
)
panicOnErr(
viper.Unmarshal(CFG),
"Cannot unmarshal the config file: %s",
)
panicOnErr(
CFG.Validate(logrus.WithField("section", "config.validation")),
"Failed to initialize config file: %s",
)
}
16 changes: 8 additions & 8 deletions config.local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
jobs:
- name: Test Job
tasks:
- command: php -v
- command: echo 1
retry-delay: 5s
connections:
- image: docker-mysql-database-phpmyadmin
volumes:
- "/home/motalleb/Downloads:/var/local/test"
env:
SHELL: /bin/bash
# connections:
# - image: docker-mysql-database-phpmyadmin
# volumes:
# - "/home/motalleb/Downloads:/var/local/test"
# env:
# SHELL: /bin/bash
events:
- on-init: true
- interval: 10m10s
- web-event: test
3 changes: 3 additions & 0 deletions config/compiler/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ func CompileEvent(sh *config.JobEvent, cr *cron.Cron, logger *logrus.Entry) abst
logger,
)
return &events
case sh.WebEvent != "":
events := event.NewEventListener(sh.WebEvent)
return &events
FMotalleb marked this conversation as resolved.
Show resolved Hide resolved
case sh.Interval != 0:
events := event.NewInterval(
sh.Interval,
Expand Down
1 change: 0 additions & 1 deletion config/compiler/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ func TestCompileEvent_IntervalZero(t *testing.T) {
cr := cron.New()
logger, _ := mocklogger.HijackOutput(logrus.New())
log := logrus.NewEntry(logger)

event := cfgcompiler.CompileEvent(sh, cr, log)
assert.Equal(t, event, nil)
}
Expand Down
17 changes: 14 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@ import (

// Config represents the configuration for the crontab application.
type Config struct {
// Log configs
LogTimestampFormat string `mapstructure:"log_timestamp_format" json:"log_timestamp_format"`
LogFormat enums.LoggerFormatType `mapstructure:"log_format" json:"log_format"`
LogFile string `mapstructure:"log_file" json:"log_file,omitempty"`
LogStdout bool `mapstructure:"log_stdout" json:"log_stdout"`
LogLevel enums.LogLevel `mapstructure:"log_level" json:"log_level"`
Shell string `mapstructure:"shell" json:"shell"`
ShellArgs []string `mapstructure:"shell_args" json:"shell_args"`
Jobs []JobConfig `mapstructure:"jobs" json:"jobs"`

// Command executor configs
Shell string `mapstructure:"shell" json:"shell"`
ShellArgs []string `mapstructure:"shell_args" json:"shell_args"`

// Web-server config
WebServerAddress string `mapstructure:"webserver_address" json:"webserver_listen_address"`
WebServerPort uint `mapstructure:"webserver_port" json:"webserver_port"`
WebserverUsername string `mapstructure:"webserver_username" json:"webserver_username"`
WebServerPassword string `mapstructure:"webserver_password" json:"webserver_password"`

Jobs []JobConfig `mapstructure:"jobs" json:"jobs"`
}

// JobConfig represents the configuration for a specific job.
Expand All @@ -35,6 +45,7 @@ type JobEvent struct {
Cron string `mapstructure:"cron" json:"cron,omitempty"`
Interval time.Duration `mapstructure:"interval" json:"interval,omitempty"`
OnInit bool `mapstructure:"on-init" json:"on_init,omitempty"`
WebEvent string `mapstructure:"web-event" json:"web_event,omitempty"`
}

// JobHooks represents the hooks configuration for a job.
Expand Down
18 changes: 17 additions & 1 deletion config/events_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,27 @@ import (
mocklogger "github.com/FMotalleb/crontab-go/logger/mock_logger"
)

func TestJobEvent_Validate_WebEvent(t *testing.T) {
event := config.JobEvent{
Interval: 0,
Cron: "",
OnInit: false,
WebEvent: "test-event",
}
logger, _ := mocklogger.HijackOutput(logrus.New())
log := logrus.NewEntry(logger)

err := event.Validate(log)

assert.NoError(t, err)
}
Comment on lines +14 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of Test Case for WebEvent Validation:

The test case for WebEvent validation checks for no errors when a valid event name is provided. However, it lacks negative test cases which are crucial for thorough testing.

  • Completeness: Include negative test scenarios to ensure robust validation. Test cases should cover scenarios where WebEvent is set to invalid values to ensure the validation logic correctly identifies and handles errors.
func TestJobEvent_Validate_WebEvent_Invalid(t *testing.T) {
    event := config.JobEvent{
        WebEvent: "invalid-event",
    }
    logger, _ := mocklogger.HijackOutput(logrus.New())
    log := logrus.NewEntry(logger)

    err := event.Validate(log)

    assert.Error(t, err, "expected error for invalid web event")
}

This additional test case ensures that the validation logic correctly handles invalid WebEvent values.


func TestJobEvent_Validate_PositiveInterval(t *testing.T) {
event := config.JobEvent{
Interval: 10,
Cron: "",
OnInit: false,
WebEvent: "",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Modification to include WebEvent in test validation was not fully verified.

The test cases in config/events_validator_test.go do not comprehensively cover the WebEvent attribute. Ensure that both positive and negative scenarios for WebEvent are included in the test cases.

  • Add test cases that validate the behavior when WebEvent is set to valid and invalid values.
Analysis chain

Modification to include WebEvent in test validation.

The test now checks the WebEvent attribute as part of the JobEvent validation. Ensure that the test covers all relevant scenarios for WebEvent, including both positive and negative cases.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the test coverage for `WebEvent`.
# Test: Search for test cases involving `WebEvent`.
rg --type go $'WebEvent' ./tests/

Length of output: 81



Script:

#!/bin/bash
# Locate all Go test files and search for `WebEvent` within those files.
fd --type f --extension go | xargs rg 'WebEvent'

Length of output: 664



Script:

#!/bin/bash
# Extract test functions involving `WebEvent` from `config/events_validator_test.go`
ast-grep --lang go --pattern 'func Test$_($$_) { $$$ }' config/events_validator_test.go | rg 'WebEvent'

Length of output: 103



Script:

#!/bin/bash
# Display the contents of `config/events_validator_test.go` for manual inspection.
cat config/events_validator_test.go

Length of output: 2026

}
logger, _ := mocklogger.HijackOutput(logrus.New())
log := logrus.NewEntry(logger)
Expand Down Expand Up @@ -80,7 +96,7 @@ func TestJobEvent_Validate_MultipleActiveSchedules(t *testing.T) {

err := event.Validate(log)

expectedErr := "a single event must have one of (on_init: true,interval,cron) field, received:(on_init: true,cron: `0 0 * * *`, interval: `60ns`)"
expectedErr := "a single event must have one of "

assert.Error(t, err)
assert.Contains(t, err.Error(), expectedErr)
Expand Down
29 changes: 29 additions & 0 deletions config/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"fmt"

"github.com/sirupsen/logrus"
)

var (
shouldLog = true
internalLogLevel = -1
)

func (cfg *Config) debugLog(text string, opts ...any) {
if internalLogLevel == -1 {
lvl, err := cfg.LogLevel.ToLogrusLevel()
if err != nil {
internalLogLevel = 5
} else {
internalLogLevel = int(lvl)
if internalLogLevel < int(logrus.DebugLevel) {
shouldLog = false
}
}
}
if shouldLog {
fmt.Printf("[LOADER-LOG] %s\n", fmt.Sprintf(text, opts...))
}
}
Loading
Loading