Skip to content

Commit 50f6817

Browse files
RoboticPrismfacebook-github-bot
authored andcommitted
Add change directory step (#515)
Summary: Pull Request resolved: #515 Add a cd step that allowers users to change the working directory for all subsequent steps. Reviewed By: inesusvet Differential Revision: D63702296 fbshipit-source-id: c120144fd577c764fbb2583d4266b9058bbc5aa8
1 parent 1b645b8 commit 50f6817

4 files changed

Lines changed: 262 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
api_version: 2.0
3+
uuid: 236f8a86-8034-43aa-a6e5-979337c375a4
4+
name: change_directory_example
5+
description: |
6+
This TTP shows you how to use the change_directory action type
7+
to change the working directory for all future actions. If you specify cleanup
8+
as default, the directory will be reverted during the cleanup phase. This may
9+
be useful if any of your cleanups make assumptions about their working directory.
10+
args:
11+
- name: cd_destination
12+
description: this argument is where we will try to cd to
13+
default: /tmp
14+
steps:
15+
- name: "Initial directory"
16+
inline: |
17+
echo "Current working directory is: \"$(pwd)\""
18+
- name: "cd"
19+
cd: {{.Args.cd_destination}}
20+
cleanup: default
21+
- name: "New directory"
22+
inline: |
23+
echo "Current working directory is: \"$(pwd)\""

pkg/blocks/changedirectory.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
Copyright © 2024-present, Meta Platforms, Inc. and affiliates
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
The above copyright notice and this permission notice shall be included in
10+
all copies or substantial portions of the Software.
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
17+
THE SOFTWARE.
18+
*/
19+
20+
package blocks
21+
22+
import (
23+
"errors"
24+
"fmt"
25+
26+
"github.com/facebookincubator/ttpforge/pkg/logging"
27+
"github.com/spf13/afero"
28+
)
29+
30+
// ChangeDirectoryStep is a step that changes the current working directory
31+
type ChangeDirectoryStep struct {
32+
actionDefaults `yaml:",inline"`
33+
Cd string `yaml:"cd"`
34+
PreviousDir string
35+
PreviousCDStep *ChangeDirectoryStep
36+
FileSystem afero.Fs `yaml:"-,omitempty"`
37+
}
38+
39+
// NewChangeDirectoryStep creates a new ChangeDirectoryStep instance with an initialized Act struct.
40+
func NewChangeDirectoryStep() *ChangeDirectoryStep {
41+
return &ChangeDirectoryStep{}
42+
}
43+
44+
// IsNil checks if a ChangeDirectoryStep is considered empty or unitializied
45+
func (step *ChangeDirectoryStep) IsNil() bool {
46+
return step.Cd == ""
47+
}
48+
49+
// Validate validates the ChangeDirectoryStep, checking for the necessary attributes and dependencies.
50+
//
51+
// **Returns:**
52+
//
53+
// error: error if validation fails, nil otherwise
54+
func (step *ChangeDirectoryStep) Validate(_ TTPExecutionContext) error {
55+
// If this has a parent cd step, hold off on validation until execute
56+
if step.PreviousCDStep != nil {
57+
return nil
58+
}
59+
60+
// Check if cd is provided
61+
if step.Cd == "" {
62+
err := errors.New("cd must be provided")
63+
return err
64+
}
65+
66+
// Check if cd is a valid directory
67+
fsys := step.FileSystem
68+
if fsys == nil {
69+
fsys = afero.NewOsFs()
70+
}
71+
72+
exists, err := afero.DirExists(fsys, step.Cd)
73+
if err != nil {
74+
return err
75+
}
76+
77+
if !exists {
78+
return fmt.Errorf("directory \"%s\" does not exist", step.Cd)
79+
}
80+
81+
return nil
82+
}
83+
84+
// Execute runs the ChangeDirectoryStep, changing the current working directory and returns an error if any occur.
85+
//
86+
// **Returns:**
87+
//
88+
// ActResult: the result of the action
89+
// error: error if execution fails, nil otherwise
90+
func (step *ChangeDirectoryStep) Execute(ctx TTPExecutionContext) (*ActResult, error) {
91+
// If this has a parent, then it's a cleanup step, so we need to grab the previous dir from it
92+
if step.PreviousCDStep != nil {
93+
if step.PreviousCDStep.PreviousDir == "" {
94+
return nil, fmt.Errorf("no previous directory found in parent cd step")
95+
}
96+
step.Cd = step.PreviousCDStep.PreviousDir
97+
}
98+
99+
logging.L().Infof("Changing directory to %s", step.Cd)
100+
101+
if step.Cd == "" {
102+
return nil, fmt.Errorf("empty cd value in Execute(...)")
103+
}
104+
105+
// Set workdir to the current cd value and store the previous workdir
106+
step.PreviousDir = ctx.Vars.WorkDir
107+
ctx.Vars.WorkDir = step.Cd
108+
109+
return &ActResult{}, nil
110+
}
111+
112+
// GetDefaultCleanupAction sets the directory back to the previous directory
113+
func (step *ChangeDirectoryStep) GetDefaultCleanupAction() Action {
114+
return &ChangeDirectoryStep{
115+
PreviousCDStep: step,
116+
}
117+
}

pkg/blocks/changedirectory_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright © 2023-present, Meta Platforms, Inc. and affiliates
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
The above copyright notice and this permission notice shall be included in
10+
all copies or substantial portions of the Software.
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
17+
THE SOFTWARE.
18+
*/
19+
20+
package blocks
21+
22+
import (
23+
"testing"
24+
25+
"github.com/facebookincubator/ttpforge/pkg/testutils"
26+
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
28+
)
29+
30+
func TestChangeDirectoryExecute(t *testing.T) {
31+
32+
testCases := []struct {
33+
name string
34+
description string
35+
step *ChangeDirectoryStep
36+
fsysContents map[string][]byte
37+
expectedError bool
38+
startingDir string
39+
}{
40+
{
41+
name: "Change directory to valid directory",
42+
description: "Change directory and expect successful change of workdir",
43+
step: &ChangeDirectoryStep{
44+
Cd: "/tmp",
45+
},
46+
fsysContents: map[string][]byte{
47+
"/home/testuser/test": []byte("test"),
48+
"/tmp/test": []byte("test"),
49+
},
50+
expectedError: false,
51+
startingDir: "/home/testuser/",
52+
},
53+
{
54+
name: "Change directory to invalid directory",
55+
description: "Try to change directory to invalid directory and expect error",
56+
step: &ChangeDirectoryStep{
57+
Cd: "/doesntexist",
58+
},
59+
fsysContents: map[string][]byte{
60+
"/home/testuser/test": []byte("test"),
61+
"/tmp/test": []byte("test"),
62+
},
63+
expectedError: true,
64+
startingDir: "/home/testuser/",
65+
},
66+
{
67+
name: "Change directory with no given directory",
68+
description: "Try to change directory to no directory and expect error",
69+
step: &ChangeDirectoryStep{
70+
Cd: "",
71+
},
72+
fsysContents: map[string][]byte{
73+
"/home/testuser/test": []byte("test"),
74+
"/tmp/test": []byte("test"),
75+
},
76+
expectedError: true,
77+
startingDir: "/home/testuser/",
78+
},
79+
}
80+
81+
for _, tc := range testCases {
82+
t.Run(tc.name, func(t *testing.T) {
83+
// Prep filesystem
84+
fsys, err := testutils.MakeAferoTestFs(tc.fsysContents)
85+
require.NoError(t, err)
86+
tc.step.FileSystem = fsys
87+
88+
// validate and check error
89+
execCtx := NewTTPExecutionContext()
90+
execCtx.Vars.WorkDir = tc.startingDir
91+
err = tc.step.Validate(execCtx)
92+
93+
if tc.expectedError && err != nil {
94+
require.Error(t, err)
95+
return
96+
}
97+
require.NoError(t, err)
98+
99+
// execute and check error
100+
_, err = tc.step.Execute(execCtx)
101+
102+
if tc.expectedError && err != nil {
103+
require.Error(t, err)
104+
return
105+
}
106+
require.NoError(t, err)
107+
108+
// check current working directory
109+
assert.Equal(t, tc.step.Cd, execCtx.Vars.WorkDir)
110+
111+
// cleanup and check error
112+
err = tc.step.GetDefaultCleanupAction().Validate(execCtx)
113+
require.NoError(t, err)
114+
_, err = tc.step.GetDefaultCleanupAction().Execute(execCtx)
115+
require.NoError(t, err)
116+
117+
// expect working directory to be rolled back to starting directory
118+
assert.Equal(t, tc.startingDir, execCtx.Vars.WorkDir)
119+
})
120+
}
121+
}

pkg/blocks/step.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ func (s *Step) ParseAction(node *yaml.Node) (Action, error) {
250250

251251
actionCandidates := []Action{
252252
NewBasicStep(),
253+
NewChangeDirectoryStep(),
253254
NewFileStep(),
254255
NewSubTTPStep(),
255256
NewEditStep(),

0 commit comments

Comments
 (0)