diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..921fca22 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,118 @@ +linters-settings: + exhaustive: + check: + - switch + - map + default-signifies-exhaustive: true + lll: + line-length: 120 + gci: + sections: + - standard # Captures all standard packages if they do not match another section. + - default # Contains all imports that could not be matched to another section type. + - prefix(github.com/controlplaneio/simulator/) # Groups all imports with the specified Prefix. + +issues: + exclude-rules: + # disable funlen for test funcs + - source: "^func Test" + linters: + - funlen + +linters: + # please, do not use `enable-all`: it's deprecated and will be removed soon. + # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint + disable-all: true + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - decorder + # - depguard # just for limiting packages + - dogsled + - dupl + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - execinquery + - exhaustive +# - exhaustruct + - exportloopref + - forbidigo + - forcetypeassert + # - funlen # complexity + # disable gci while it doesn't play nice with gofumpt and goimports + # https://github.com/golangci/golangci-lint/issues/1490 + # - gci + # - ginkgolinter # not using ginkgo + - gocheckcompilerdirectives +# - gochecknoglobals +# - gochecknoinits + - gocognit # complexity + - goconst + - gocritic + # - gocyclo # using gocognit + # - godot # comment style + - gofmt +# - gofumpt + - goheader + - goimports + - gomnd # detect loose numbers + - gomoddirectives # not sure + - gomodguard # same as depguard? + - goprintffuncname + - gosec + - gosmopolitan + - gosimple + - govet + - grouper + - importas # configure this + - interfacebloat # maybe configure + - ineffassign +# - ireturn + - lll + - loggercheck + # - maintidx # using gocognit + - makezero + - mirror + - misspell + - musttag + - nakedret + # - nestif # using gocognit + - nilerr + - nilnil + # - nlreturn # probably not + - noctx + - nolintlint + - nonamedreturns # maybe + - nosprintfhostport + - paralleltest + - prealloc + - predeclared + - promlinter + - reassign + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tagalign + - tagliatelle + - tenv + - testpackage + - thelper + - tparallel # same with parallel test + - unconvert + - unparam + - usestdlibvars + - unused +# - varnamelen + - wastedassign + - whitespace + - wrapcheck + # - wsl diff --git a/Makefile b/Makefile index 00d68049..860ddba6 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ SIMULATOR_IMAGE ?= controlplane/simulator -simulator-dev-image: +lint: + golangci-lint run -c .golangci.yml + +simulator-dev-image: lint docker build -t $(SIMULATOR_IMAGE):dev -f dev.Dockerfile . simulator-image: simulator-dev-image docker build -t $(SIMULATOR_IMAGE) . -simulator-cli: +simulator-cli: lint go build -v -o bin/simulator internal/cmd/main.go diff --git a/controlplane/aws/aws.go b/controlplane/aws/aws.go index 28c86f4e..06bbed6a 100644 --- a/controlplane/aws/aws.go +++ b/controlplane/aws/aws.go @@ -23,7 +23,7 @@ func CreateBucket(ctx context.Context, name string) error { cfg, err := config.LoadDefaultConfig(ctx) if err != nil { slog.Error("failed to create bucket", "name", name, "error", err) - return err + return errors.Join(errors.New("failed to create bucket"), err) } var bucketAlreadyOwnedByYou *types.BucketAlreadyOwnedByYou @@ -35,12 +35,9 @@ func CreateBucket(ctx context.Context, name string) error { LocationConstraint: types.BucketLocationConstraintEuWest2, }, }) - if err != nil { - if !errors.As(err, &bucketAlreadyOwnedByYou) { - } else { - slog.Error("failed to create bucket", "name", name, "error", err) - return err - } + if err != nil && !errors.As(err, &bucketAlreadyOwnedByYou) { + slog.Error("failed to create bucket", "name", name, "error", err) + return errors.Join(errors.New("failed to create bucket"), err) } return nil diff --git a/controlplane/cli/bucket.go b/controlplane/cli/bucket.go index 26742ba1..7bf89040 100644 --- a/controlplane/cli/bucket.go +++ b/controlplane/cli/bucket.go @@ -16,12 +16,13 @@ var bucketCmd = &cobra.Command{ var createBucketCmd = &cobra.Command{ Use: "create", - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() cp := controlplane.New() - return cp.CreateBucket(ctx, bucket) + err := cp.CreateBucket(ctx, bucket) + cobra.CheckErr(err) }, } diff --git a/controlplane/cli/image.go b/controlplane/cli/image.go index 902517d6..c544dd45 100644 --- a/controlplane/cli/image.go +++ b/controlplane/cli/image.go @@ -20,12 +20,13 @@ var imageCmd = &cobra.Command{ var buildCmd = &cobra.Command{ Use: "build", - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() cp := controlplane.New() - return cp.BuildImage(ctx, template) + err := cp.BuildImage(ctx, template) + cobra.CheckErr(err) }, } diff --git a/controlplane/cli/infra.go b/controlplane/cli/infra.go index fcd4e510..b94fda6e 100644 --- a/controlplane/cli/infra.go +++ b/controlplane/cli/infra.go @@ -16,23 +16,25 @@ var infraCmd = &cobra.Command{ var createCmd = &cobra.Command{ Use: "create", - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() cp := controlplane.New() - return cp.CreateInfrastructure(ctx, bucket, key, name) + err := cp.CreateInfrastructure(ctx, bucket, key, name) + cobra.CheckErr(err) }, } var destroyCmd = &cobra.Command{ Use: "destroy", - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() cp := controlplane.New() - return cp.DestroyInfrastructure(ctx, bucket, key, name) + err := cp.DestroyInfrastructure(ctx, bucket, key, name) + cobra.CheckErr(err) }, } diff --git a/controlplane/cli/scenario.go b/controlplane/cli/scenario.go index b31b611e..b72a2da6 100644 --- a/controlplane/cli/scenario.go +++ b/controlplane/cli/scenario.go @@ -16,23 +16,25 @@ var scenarioCmd = &cobra.Command{ var installCmd = &cobra.Command{ Use: "install", - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() cp := controlplane.New() - return cp.InstallScenario(ctx, name) + err := cp.InstallScenario(ctx, name) + cobra.CheckErr(err) }, } var uninstallCmd = &cobra.Command{ Use: "uninstall", - RunE: func(cmd *cobra.Command, args []string) error { + Run: func(cmd *cobra.Command, args []string) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() cp := controlplane.New() - return cp.UninstallScenario(ctx, name) + err := cp.UninstallScenario(ctx, name) + cobra.CheckErr(err) }, } @@ -42,5 +44,4 @@ func init() { scenarioCmd.AddCommand(installCmd) scenarioCmd.AddCommand(uninstallCmd) simulatorCmd.AddCommand(scenarioCmd) - } diff --git a/controlplane/commands/ansible.go b/controlplane/commands/ansible.go index b1b788a5..ac4f1bc3 100644 --- a/controlplane/commands/ansible.go +++ b/controlplane/commands/ansible.go @@ -17,7 +17,7 @@ func AnsiblePlaybookCommand(workingDir, playbookDir, playbook string, extraVars if len(extraVars) > 0 { args = append(args, "--extra-vars", - fmt.Sprintf("%s", strings.Join(extraVars, " ")), + strings.Join(extraVars, " "), ) } diff --git a/controlplane/commands/runner.go b/controlplane/commands/runner.go index b936649a..e260e123 100644 --- a/controlplane/commands/runner.go +++ b/controlplane/commands/runner.go @@ -2,6 +2,7 @@ package commands import ( "context" + "errors" "fmt" "io" "log/slog" @@ -34,6 +35,7 @@ func (c command) Run(ctx context.Context, output ...io.Writer) error { writer = output[0] } + //nolint:gosec cmd := exec.CommandContext(ctx, string(c.Executable), c.Arguments...) cmd.Dir = c.WorkingDir cmd.Env = c.Environment @@ -43,18 +45,19 @@ func (c command) Run(ctx context.Context, output ...io.Writer) error { // TODO: Ensure ctrl-c stops the command err := cmd.Run() if err != nil { - slog.Error("failed to run", "command", c) + return errors.Join(errors.New("failed to run command"), err) } - return err + return nil } func (c command) LogValue() slog.Value { cmd := fmt.Sprintf("%s %s", c.Executable, strings.Join(c.Arguments, " ")) - var env []string + env := make([]string, 0) // Only log env keys, not values for _, value := range c.Environment { + //nolint:gocritic env = append(env, value[:strings.Index(value, "=")]) } diff --git a/controlplane/simulator.go b/controlplane/simulator.go index 42ccae08..1b2f46fe 100644 --- a/controlplane/simulator.go +++ b/controlplane/simulator.go @@ -49,47 +49,56 @@ type Simulator interface { UninstallScenario(ctx context.Context, name string) error } -type simulator struct { -} +type simulator struct{} func (s simulator) CreateBucket(ctx context.Context, name string) error { + //nolint:wrapcheck return aws.CreateBucket(ctx, name) } func (s simulator) BuildImage(ctx context.Context, name string) error { err := commands.PackerInitCommand(PackerTemplateDir, name).Run(ctx) if err != nil { - return err + return err //nolint:wrapcheck } + //nolint:wrapcheck return commands.PackerBuildCommand(PackerTemplateDir, name).Run(ctx) } func (s simulator) CreateInfrastructure(ctx context.Context, bucket, key, name string) error { err := commands.TerraformInitCommand(TerraformWorkspaceDir, backendConfig(bucket, key)).Run(ctx) if err != nil { + //nolint:wrapcheck return err } - return commands.TerraformCommand(TerraformWorkspaceDir, commands.TerraformApply, terraformVars(name, bucket)).Run(ctx) - + //nolint:wrapcheck + return commands.TerraformCommand(TerraformWorkspaceDir, commands.TerraformApply, terraformVars(name, bucket)). + Run(ctx) } func (s simulator) DestroyInfrastructure(ctx context.Context, bucket, key, name string) error { err := commands.TerraformInitCommand(TerraformWorkspaceDir, backendConfig(bucket, key)).Run(ctx) if err != nil { + //nolint:wrapcheck return err } - return commands.TerraformCommand(TerraformWorkspaceDir, commands.TerraformDestroy, terraformVars(name, bucket)).Run(ctx) + //nolint:wrapcheck + return commands.TerraformCommand(TerraformWorkspaceDir, commands.TerraformDestroy, terraformVars(name, bucket)). + Run(ctx) } func (s simulator) InstallScenario(ctx context.Context, name string) error { + //nolint:wrapcheck return commands.AnsiblePlaybookCommand(AdminSSHBundleDir, AnsiblePlaybookDir, name).Run(ctx) } func (s simulator) UninstallScenario(ctx context.Context, name string) error { - return commands.AnsiblePlaybookCommand(AdminSSHBundleDir, AnsiblePlaybookDir, name, "state=absent").Run(ctx) + //nolint:wrapcheck + return commands.AnsiblePlaybookCommand(AdminSSHBundleDir, AnsiblePlaybookDir, name, "state=absent"). + Run(ctx) } func backendConfig(bucket, key string) []string { diff --git a/dev.Dockerfile b/dev.Dockerfile index f1a1fda1..ba4632c7 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -10,7 +10,7 @@ WORKDIR /app COPY . . -RUN /usr/bin/golangci-lint run -v +RUN /usr/bin/golangci-lint run -v -c .golangci.yml FROM ${GOLANG_IMAGE} as BUILDER diff --git a/internal/cli/scenario.go b/internal/cli/scenario.go index 59a8f79a..c6c89a74 100644 --- a/internal/cli/scenario.go +++ b/internal/cli/scenario.go @@ -113,7 +113,8 @@ func tabulateScenarios(scenarios []scenarios.Scenario) { s.Name, s.Description, s.Category, - s.Difficulty}) + s.Difficulty, + }) table.SetRowLine(true) } table.Render() diff --git a/internal/config/config.go b/internal/config/config.go index e9e34b96..c2a33d6d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,8 +10,10 @@ import ( ) const ( - Dir = "SIMULATOR_DIR" - FileName = "config.yaml" + Dir = "SIMULATOR_DIR" + FileName = "config.yaml" + ownerReadWrite = 0600 + ownerReadWriteExecute = 0700 ) //go:embed config.yaml @@ -33,36 +35,36 @@ type Config struct { } func (c *Config) Read() error { - file := file() + file := simulatorConfigFile() if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) { dir := filepath.Dir(file) if _, err := os.Stat(dir); err != nil { - err = os.MkdirAll(dir, 0755) + err = os.MkdirAll(dir, ownerReadWriteExecute) if err != nil { - return err + return errors.Join(errors.New("failed to create directory"), err) } } config, err := defaultConfig.ReadFile(FileName) if err != nil { - return err + return errors.Join(errors.New("failed to read config"), err) } - err = os.WriteFile(file, config, 0644) + err = os.WriteFile(file, config, ownerReadWrite) if err != nil { - return err + return errors.Join(errors.New("failed to write config"), err) } } config, err := os.ReadFile(file) if err != nil { - return err + return errors.Join(errors.New("failed to read config"), err) } err = yaml.Unmarshal(config, &c) if err != nil { - return err + return errors.Join(errors.New("failed to unmarshall config"), err) } return nil @@ -71,13 +73,17 @@ func (c *Config) Read() error { func (c *Config) Write() error { config, err := yaml.Marshal(&c) if err != nil { - return err + return errors.Join(errors.New("failed to unmarshall config"), err) } - return os.WriteFile(file(), config, 0644) + if err := os.WriteFile(simulatorConfigFile(), config, ownerReadWrite); err != nil { + return errors.Join(errors.New("failed to write config"), err) + } + + return nil } -func file() string { +func simulatorConfigFile() string { dir, ok := os.LookupEnv(Dir) if !ok { home, err := os.UserHomeDir() diff --git a/internal/container/runner.go b/internal/container/runner.go index 829fb08f..194fc682 100644 --- a/internal/container/runner.go +++ b/internal/container/runner.go @@ -22,12 +22,8 @@ import ( "github.com/controlplaneio/simulator/v2/internal/config" ) -var ( - NoHome = errors.New("unable to determine your home directory") - NoClient = errors.New("unable to create docker client") - CreateFailed = errors.New("unable to create simulator container") - StartFailed = errors.New("unable to start simulator container") - AttachFailed = errors.New("unable to attach to simulator container") +const ( + ownerReadWriteExecute = 0700 ) type Simulator interface { @@ -47,7 +43,7 @@ type simulator struct { func (r simulator) Run(ctx context.Context, command []string) error { home, err := os.UserHomeDir() if err != nil { - return NoHome + return errors.Join(errors.New("failed to determine home directory"), err) } // TODO: work with env var for directory @@ -57,12 +53,12 @@ func (r simulator) Run(ctx context.Context, command []string) error { err = mkdirsIfNotExisting(localAdminSSHBundleDir, localPlayerSSHBundleDir) if err != nil { - return err + return errors.Join(errors.New("failed to create bundle directory"), err) } cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { - return NoClient + return errors.Join(errors.New("failed to create docker client"), err) } mounts := []mount.Mount{ @@ -130,10 +126,10 @@ func (r simulator) Run(ctx context.Context, command []string) error { "", ) if err != nil { - return CreateFailed + return errors.Join(errors.New("failed to create container"), err) } defer func() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) //nolint:gomnd defer cancel() err = cli.ContainerStop(ctx, cont.ID, container.StopOptions{}) @@ -153,12 +149,12 @@ func (r simulator) Run(ctx context.Context, command []string) error { Stderr: true, }) if err != nil { - return AttachFailed + return errors.Join(errors.New("failed to attach to container"), err) } err = cli.ContainerStart(ctx, cont.ID, types.ContainerStartOptions{}) if err != nil { - return errors.Join(StartFailed, err) + return errors.Join(errors.New("failed to start container"), err) } var wg sync.WaitGroup @@ -176,9 +172,9 @@ func (r simulator) Run(ctx context.Context, command []string) error { func mkdirsIfNotExisting(dirs ...string) error { for _, dir := range dirs { if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { - err := os.MkdirAll(dir, 0750) + err := os.MkdirAll(dir, ownerReadWriteExecute) if err != nil { - return err + return errors.Join(errors.New("failed to create directory"), err) } } } diff --git a/scenarios/scenarios.go b/scenarios/scenarios.go index 54e4621b..2176f3ab 100644 --- a/scenarios/scenarios.go +++ b/scenarios/scenarios.go @@ -3,12 +3,9 @@ package scenarios import ( "embed" "errors" - "fmt" "log/slog" "gopkg.in/yaml.v2" - - "github.com/controlplaneio/simulator/v2/controlplane" ) //go:embed scenarios.yaml @@ -20,13 +17,13 @@ func List() ([]Scenario, error) { b, err := config.ReadFile("scenarios.yaml") if err != nil { slog.Error("failed to load scenarios file") - return nil, err + return nil, errors.Join(errors.New("failed to list scenarios"), err) } err = yaml.Unmarshal(b, &scenarios) if err != nil { slog.Error("failed to unmarshall scenarios") - return nil, err + return nil, errors.Join(errors.New("failed to list scenarios"), err) } return scenarios, nil @@ -37,7 +34,7 @@ func Find(id string) (Scenario, error) { scenarios, err := List() if err != nil { - return s, err + return s, errors.Join(errors.New("failed to find scenario"), err) } for _, scenario := range scenarios { @@ -56,11 +53,3 @@ type Scenario struct { Category string `yaml:"category"` Difficulty string `yaml:"difficulty"` } - -func (s Scenario) Challenge() ([]byte, error) { - return config.ReadFile(fmt.Sprintf("roles/%s/files/challenge.txt", s.ID)) -} - -func (s Scenario) Playbook() string { - return fmt.Sprintf("%s/%s", controlplane.AnsiblePlaybookDir, s.ID) -}