-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Platform Compatibility Checks to Requirements (#438)
Summary: Pull Request resolved: #438 Users can now specify a list of compatible platforms for a TTP and TTPForge will "fail fast" and report an incompatibility error if a user attempts to run the TTP on an incompatible platform - for example: ``` requirements: platforms: - os: darwin - os: linux ``` Reviewed By: nicolagiacchetta, cedowens Differential Revision: D51459753 fbshipit-source-id: 70c61a5ecc21144f55dd2ddc5e91dfd2da76aa94
- Loading branch information
1 parent
ddea151
commit 752ae9c
Showing
11 changed files
with
680 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
//go:build darwin | ||
// +build darwin | ||
|
||
/* | ||
Copyright © 2023-present, Meta Platforms, Inc. and affiliates | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
*/ | ||
|
||
package cmd | ||
|
||
import ( | ||
"bytes" | ||
"path/filepath" | ||
|
||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestRunDarwin(t *testing.T) { | ||
const testResourcesDir = "test-resources" | ||
const testRepoName = "test-repo" | ||
testConfigFilePath := filepath.Join(testResourcesDir, "test-config.yaml") | ||
|
||
testCases := []struct { | ||
name string | ||
description string | ||
args []string | ||
expectedStdout string | ||
wantError bool | ||
}{ | ||
{ | ||
name: "Check that requirements feature works on Darwin", | ||
description: "Should pass since this file will only be built if we are on darwin", | ||
args: []string{ | ||
"-c", | ||
testConfigFilePath, | ||
testRepoName + "//requirements/darwin_only.yaml", | ||
}, | ||
expectedStdout: "just a placeholder - we are testing `requirements:`\n", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
var stdoutBuf, stderrBuf bytes.Buffer | ||
rc := BuildRootCommand(&TestConfig{ | ||
Stdout: &stdoutBuf, | ||
Stderr: &stderrBuf, | ||
}) | ||
rc.SetArgs(append([]string{"run"}, tc.args...)) | ||
err := rc.Execute() | ||
if tc.wantError { | ||
require.Error(t, err) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
assert.Equal(t, tc.expectedStdout, stdoutBuf.String()) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
//go:build linux | ||
// +build linux | ||
|
||
/* | ||
Copyright © 2023-present, Meta Platforms, Inc. and affiliates | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
*/ | ||
|
||
package cmd | ||
|
||
import ( | ||
"bytes" | ||
"path/filepath" | ||
|
||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestRunLinux(t *testing.T) { | ||
const testResourcesDir = "test-resources" | ||
const testRepoName = "test-repo" | ||
testConfigFilePath := filepath.Join(testResourcesDir, "test-config.yaml") | ||
|
||
testCases := []struct { | ||
name string | ||
description string | ||
args []string | ||
expectedStdout string | ||
wantError bool | ||
}{ | ||
{ | ||
name: "Check that requirements feature works on Linux", | ||
description: "Should pass since this file will only be built if we are on linux", | ||
args: []string{ | ||
"-c", | ||
testConfigFilePath, | ||
testRepoName + "//requirements/linux_only.yaml", | ||
}, | ||
expectedStdout: "just a placeholder - we are testing `requirements:`\n", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
var stdoutBuf, stderrBuf bytes.Buffer | ||
rc := BuildRootCommand(&TestConfig{ | ||
Stdout: &stdoutBuf, | ||
Stderr: &stderrBuf, | ||
}) | ||
rc.SetArgs(append([]string{"run"}, tc.args...)) | ||
err := rc.Execute() | ||
if tc.wantError { | ||
require.Error(t, err) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
assert.Equal(t, tc.expectedStdout, stdoutBuf.String()) | ||
}) | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
cmd/test-resources/repos/test-repo/ttps/requirements/darwin_only.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
name: TTP That Should Only Run on macOS | ||
description: | | ||
This is a test care for the `requirements:` feature that | ||
enforces running only on macOS | ||
requirements: | ||
platforms: | ||
- os: darwin | ||
steps: | ||
- name: placeholder | ||
print_str: just a placeholder - we are testing `requirements:` |
11 changes: 11 additions & 0 deletions
11
cmd/test-resources/repos/test-repo/ttps/requirements/linux_only.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
name: TTP That Should Only Run on Linux | ||
description: | | ||
This is a test care for the `requirements:` feature that | ||
enforces running only on linux | ||
requirements: | ||
platforms: | ||
- os: linux | ||
steps: | ||
- name: placeholder | ||
print_str: just a placeholder - we are testing `requirements:` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* | ||
Copyright © 2023-present, Meta Platforms, Inc. and affiliates | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
*/ | ||
|
||
package blocks | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"runtime" | ||
|
||
"github.com/facebookincubator/ttpforge/pkg/checks" | ||
"github.com/facebookincubator/ttpforge/pkg/logging" | ||
"github.com/facebookincubator/ttpforge/pkg/platforms" | ||
) | ||
|
||
// RequirementsConfig specifies the prerequisites that must be | ||
// satisfied before executing a particular TTP. | ||
// | ||
// **Attributes:** | ||
// | ||
// ExpectSuperuser: Whether the TTP assumes superuser privileges | ||
type RequirementsConfig struct { | ||
ExpectSuperuser bool `yaml:"superuser,omitempty"` | ||
Platforms []platforms.Spec `yaml:"platforms,omitempty"` | ||
} | ||
|
||
// Validate checks that the requirements section | ||
// is well-formed - it does not actually | ||
// check that the requirements are met. | ||
func (rc *RequirementsConfig) Validate() error { | ||
// nil is valid - it just means don't enforce any | ||
// requirements | ||
if rc == nil { | ||
return nil | ||
} | ||
for _, platform := range rc.Platforms { | ||
if err := platform.Validate(); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Verify checks that the requirements specified | ||
// in the requirements section are actually satisfied by the environment in | ||
// which the TTP is currently running. | ||
func (rc *RequirementsConfig) Verify(ctx checks.VerificationContext) error { | ||
// simplifies things a bit for callers | ||
if rc == nil { | ||
return nil | ||
} | ||
|
||
// check platform compatibility: | ||
// if there are no platforms specified, then we assume | ||
// that the TTP is compatible with all platforms | ||
// (even though it probably isn't, but there | ||
// are a lot of existing TTPs from before this feature | ||
// existed that don't explicitly declare supported platforms) | ||
if len(rc.Platforms) > 0 { | ||
var ttpIsCompatibleWithCurrentPlatform bool | ||
for _, platform := range rc.Platforms { | ||
if platform.IsCompatibleWith(ctx.Platform) { | ||
ttpIsCompatibleWithCurrentPlatform = true | ||
break | ||
} | ||
} | ||
if !ttpIsCompatibleWithCurrentPlatform { | ||
logging.L().Errorf("The current platform %q is not compatible with this TTP", ctx.Platform.String()) | ||
logging.L().Errorf("Supported platforms are:") | ||
for _, p := range rc.Platforms { | ||
logging.L().Errorf("\t%v", p.String()) | ||
} | ||
return fmt.Errorf("the current platform is not compatible with this TTP") | ||
} | ||
} | ||
|
||
// check superuser requirement | ||
if rc.ExpectSuperuser { | ||
if runtime.GOOS == "windows" { | ||
logging.L().Warnf("not enforcing superuser requirement because it is not supported on windows yet") | ||
} else { | ||
if os.Geteuid() != 0 { | ||
err := errors.New("must be root (UID 0) to run this TTP") | ||
return err | ||
} | ||
logging.L().Debug("[+] Running as root") | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
Copyright © 2023-present, Meta Platforms, Inc. and affiliates | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
*/ | ||
|
||
package blocks | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
func TestRequirements(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
content string | ||
expectValidateError bool | ||
expectExecuteError bool | ||
expectedRequirements *RequirementsConfig | ||
}{ | ||
{ | ||
name: "Omitted requirements section", | ||
content: ` | ||
name: TestTTP | ||
description: Test description | ||
steps: | ||
- name: hello | ||
print_str: hello world`, | ||
expectedRequirements: nil, | ||
}, | ||
{ | ||
name: "Valid requirements section", | ||
content: ` | ||
name: TestTTP | ||
description: Test description | ||
requirements: | ||
superuser: false | ||
steps: | ||
- name: hello | ||
inline: echo "hello world"`, | ||
expectedRequirements: &RequirementsConfig{ | ||
ExpectSuperuser: false, | ||
}, | ||
}, | ||
{ | ||
name: "Invalid requirements section - cannot become root in tests", | ||
content: ` | ||
name: TestTTP | ||
description: Test description | ||
requirements: | ||
superuser: true | ||
steps: | ||
- name: hello | ||
print_str: hello world`, | ||
expectExecuteError: true, | ||
expectedRequirements: &RequirementsConfig{ | ||
ExpectSuperuser: true, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
var ttp TTP | ||
err := yaml.Unmarshal([]byte(tc.content), &ttp) | ||
require.NoError(t, err) | ||
var ctx TTPExecutionContext | ||
err = ttp.Validate(ctx) | ||
if tc.expectValidateError { | ||
require.Error(t, err) | ||
return | ||
} | ||
require.NoError(t, err) | ||
|
||
_, err = ttp.Execute(&ctx) | ||
if tc.expectExecuteError { | ||
assert.Error(t, err) | ||
return | ||
} | ||
assert.NoError(t, err) | ||
}) | ||
} | ||
} |
Oops, something went wrong.