Skip to content

Commit

Permalink
add file remove promotion step
Browse files Browse the repository at this point in the history
Signed-off-by: nitishfy <[email protected]>
  • Loading branch information
nitishfy committed Dec 7, 2024
1 parent 404422a commit 9866605
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 0 deletions.
79 changes: 79 additions & 0 deletions internal/directives/file_deleter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package directives

import (
"context"
"fmt"

Check failure on line 5 in internal/directives/file_deleter.go

View workflow job for this annotation

GitHub Actions / lint-go

File is not `goimports`-ed with -local github.com/org/project (goimports)
kargoapi "github.com/akuity/kargo/api/v1alpha1"

Check failure on line 6 in internal/directives/file_deleter.go

View workflow job for this annotation

GitHub Actions / lint-go

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/akuity) -s dot -s blank --custom-order (gci)
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/xeipuuv/gojsonschema"
"os"

Check failure on line 9 in internal/directives/file_deleter.go

View workflow job for this annotation

GitHub Actions / lint-go

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/akuity) -s dot -s blank --custom-order (gci)
)

func init() {
builtins.RegisterPromotionStepRunner(newFileDeleter(), nil)
}

type fileDeleter struct {
schemaLoader gojsonschema.JSONLoader
}

func newFileDeleter() PromotionStepRunner {
r := &fileDeleter{}
r.schemaLoader = getConfigSchemaLoader(r.Name())
return r
}

func (f *fileDeleter) Name() string {
return "delete"
}

func (f *fileDeleter) RunPromotionStep(
ctx context.Context,
stepCtx *PromotionStepContext,
) (PromotionStepResult, error) {
// Validate the configuration against the JSON Schema.
if err := validate(f.schemaLoader, gojsonschema.NewGoLoader(stepCtx.Config), f.Name()); err != nil {
return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}, err
}

Check warning on line 37 in internal/directives/file_deleter.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/file_deleter.go#L33-L37

Added lines #L33 - L37 were not covered by tests

// Convert the configuration into a typed object.
cfg, err := ConfigToStruct[DeleteConfig](stepCtx.Config)
if err != nil {
return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored},
fmt.Errorf("could not convert config into %s config: %w", f.Name(), err)
}

Check warning on line 44 in internal/directives/file_deleter.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/file_deleter.go#L40-L44

Added lines #L40 - L44 were not covered by tests

return f.runPromotionStep(ctx, stepCtx, cfg)

Check warning on line 46 in internal/directives/file_deleter.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/file_deleter.go#L46

Added line #L46 was not covered by tests
}

func (f *fileDeleter) runPromotionStep(
_ context.Context,
stepCtx *PromotionStepContext,
cfg DeleteConfig,
) (PromotionStepResult, error) {
pathToDelete, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.Path)
if err != nil {
return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored},
fmt.Errorf("could not secure join path %q: %w", cfg.Path, err)
}

Check warning on line 58 in internal/directives/file_deleter.go

View check run for this annotation

Codecov / codecov/patch

internal/directives/file_deleter.go#L56-L58

Added lines #L56 - L58 were not covered by tests

if err = removePath(pathToDelete); err != nil {
return PromotionStepResult{Status: kargoapi.PromotionPhaseErrored},
fmt.Errorf("failed to delete %q: %w", cfg.Path, sanitizePathError(err, stepCtx.WorkDir))
}

return PromotionStepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
}

func removePath(path string) error {
fi, err := os.Lstat(path)
if err != nil {
return err
}

if fi.IsDir() {
return os.RemoveAll(path)
}

return os.Remove(path)
}
88 changes: 88 additions & 0 deletions internal/directives/file_deleter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package directives

import (
"context"
kargoapi "github.com/akuity/kargo/api/v1alpha1"

Check failure on line 5 in internal/directives/file_deleter_test.go

View workflow job for this annotation

GitHub Actions / lint-go

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/akuity) -s dot -s blank --custom-order (gci)
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"path/filepath"
"testing"
)

func Test_fileDeleter_runPromotionStep(t *testing.T) {
tests := []struct {
name string
setupFiles func(*testing.T) string
cfg DeleteConfig
assertions func(*testing.T, string, PromotionStepResult, error)
}{
{
name: "succeeds deleting file",
setupFiles: func(t *testing.T) string {
tmpDir := t.TempDir()

path := filepath.Join(tmpDir, "input.txt")
require.NoError(t, os.WriteFile(path, []byte("test content"), 0o600))

return tmpDir
},
cfg: DeleteConfig{
Path: "input.txt",
},
assertions: func(t *testing.T, workDir string, result PromotionStepResult, err error) {

Check warning on line 33 in internal/directives/file_deleter_test.go

View workflow job for this annotation

GitHub Actions / lint-go

unused-parameter: parameter 'workDir' seems to be unused, consider removing or renaming it as _ (revive)
assert.NoError(t, err)
assert.Equal(t, PromotionStepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)

_, statError := os.Stat("input.txt")
assert.ErrorIs(t, statError, os.ErrNotExist)
},
},
{
name: "succeeds deleting directory",
setupFiles: func(t *testing.T) string {
tmpDir := t.TempDir()
dirPath := filepath.Join(tmpDir, "dirToDelete")
require.NoError(t, os.Mkdir(dirPath, 0o700))
return tmpDir
},
cfg: DeleteConfig{
Path: "dirToDelete",
},
assertions: func(t *testing.T, workDir string, result PromotionStepResult, err error) {
assert.NoError(t, err)
assert.Equal(t, PromotionStepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)

_, statErr := os.Stat(filepath.Join(workDir, "dirToDelete"))
assert.ErrorIs(t, statErr, os.ErrNotExist)
},
},
{
name: "fails for non-existent path",
setupFiles: func(t *testing.T) string {
return t.TempDir()
},
cfg: DeleteConfig{
Path: "nonExistentFile.txt",
},
assertions: func(t *testing.T, _ string, result PromotionStepResult, err error) {
assert.Error(t, err)
assert.Equal(t, PromotionStepResult{Status: kargoapi.PromotionPhaseErrored}, result)
},
},
}

runner := &fileDeleter{}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
workDir := tt.setupFiles(t)
result, err := runner.runPromotionStep(
context.Background(),
&PromotionStepContext{WorkDir: workDir},
tt.cfg,
)
tt.assertions(t, workDir, result, err)
})
}
}
14 changes: 14 additions & 0 deletions internal/directives/schemas/delete-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "DeleteConfig",
"type": "object",
"additionalProperties": false,
"required": ["Path"],
"properties": {
"Path": {
"type": "string",
"description": "Path is the path to the file or directory to delete.",
"minLength": 1
}
}
}
5 changes: 5 additions & 0 deletions internal/directives/zz_config_types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions ui/src/gen/directives/delete-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "DeleteConfig",
"type": "object",
"additionalProperties": false,
"properties": {
"Path": {
"type": "string",
"description": "Path is the path to the file or directory to delete.",
"minLength": 1
}
}
}

0 comments on commit 9866605

Please sign in to comment.