forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cmd] Implement rootless deprecation messages
I have added a system for showing fatal and non-fatal deprecation warnings. It's configurable by command and environment. If we merge this PR, running a rootless image with any OPA command other than `opa run` will result in a fatal error and exit code 1. It's possible for users to continue to use the image by unsetting: OPA_DOCKER_IMAGE_TAG=rootless. `opa run` will show the message, but it's not fatal for this command. This is intended to avoid production disruption. Signed-off-by: Charlie Egan <[email protected]>
- Loading branch information
1 parent
cda3bfb
commit d584a15
Showing
5 changed files
with
322 additions
and
6 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
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,54 @@ | ||
package deprecation | ||
|
||
// TODO: these warnings can be removed when the rootless images are no longer published. | ||
|
||
const rootlessWarningMessage = `OPA appears to be running in a deprecated -rootless image. | ||
Since v0.50.0, the default OPA images have been configured to use a non-root | ||
user. | ||
This image will soon cease to be updated. The following images should now be | ||
used instead: | ||
* openpolicyagent/opa:latest and NOT (openpolicyagent/opa:latest-rootless) | ||
* openpolicyagent/opa:edge and NOT (openpolicyagent/opa:edge-rootless) | ||
* openpolicyagent/opa:X.Y.Z and NOT (openpolicyagent/opa:X.Y.Z-rootless) | ||
You can choose to acknowledge and ignore this message by unsetting: | ||
OPA_DOCKER_IMAGE_TAG=rootless | ||
` | ||
|
||
// warningRootless is a fatal warning is triggered when the user is running OPA | ||
// in a deprecated rootless image. | ||
var warningRootless = warning{ | ||
MatchEnv: func(env []string) bool { | ||
for _, e := range env { | ||
if e == "OPA_DOCKER_IMAGE_TAG=rootless" { | ||
return true | ||
} | ||
} | ||
return false | ||
}, | ||
MatchCommand: func(name string) bool { | ||
return name != "run" | ||
}, | ||
Fatal: true, | ||
Message: rootlessWarningMessage, | ||
} | ||
|
||
// warningRootlessRun is a non-fatal version of the warning reserved for opa run. | ||
// The warning for run is non-fatal to avoid production disruption | ||
var warningRootlessRun = warning{ | ||
MatchEnv: func(env []string) bool { | ||
for _, e := range env { | ||
if e == "OPA_DOCKER_IMAGE_TAG=rootless" { | ||
return true | ||
} | ||
} | ||
return false | ||
}, | ||
MatchCommand: func(name string) bool { | ||
return name == "run" | ||
}, | ||
Fatal: false, | ||
Message: rootlessWarningMessage, | ||
} |
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,92 @@ | ||
package deprecation | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"strings" | ||
) | ||
|
||
const width, border = 80, 3 | ||
const titleChar, dividerChar = "#", "-" | ||
|
||
// warning is a struct which can be used to define deprecation warnings based on | ||
// the environment and command being run. | ||
type warning struct { | ||
MatchEnv func([]string) bool | ||
MatchCommand func(string) bool | ||
Fatal bool | ||
Message string | ||
} | ||
|
||
// CheckWarnings runs messageForWarnings with a default set of real warnings. | ||
func CheckWarnings(env []string, command string) (string, bool) { | ||
warnings := []warning{ | ||
warningRootless, | ||
warningRootlessRun, | ||
} | ||
|
||
return messageForWarnings(warnings, env, command) | ||
} | ||
|
||
// messageForWarnings returns an obnoxious banner with the contents of all firing warnings. | ||
// If no warnings fire, it returns an empty string. | ||
// If any warnings are fatal, it returns true for the second return value. | ||
func messageForWarnings(warnings []warning, env []string, command string) (string, bool) { | ||
var messages []string | ||
var fatal bool | ||
|
||
for _, w := range warnings { | ||
if w.MatchEnv(env) && w.MatchCommand(command) { | ||
messages = append(messages, w.Message) | ||
if w.Fatal { | ||
fatal = true | ||
} | ||
} | ||
} | ||
|
||
buf := bytes.NewBuffer(nil) | ||
|
||
if len(messages) == 0 { | ||
return "", false | ||
} | ||
|
||
title := "Deprecation Warnings" | ||
if fatal { | ||
title = "Fatal Deprecation Warnings" | ||
} | ||
|
||
printFormattedTitle(buf, title) | ||
|
||
for i, msg := range messages { | ||
fmt.Fprintln(buf, strings.TrimSpace(msg)) | ||
if i < len(messages)-1 { | ||
printFormattedDivider(buf) | ||
} | ||
} | ||
|
||
printFormattedTitle(buf, "end "+title) | ||
|
||
return buf.String(), fatal | ||
} | ||
|
||
func printFormattedTitle(out io.Writer, title string) { | ||
padding := (width - len(title) - border*2) / 2 | ||
|
||
fmt.Fprintln(out, strings.Repeat(titleChar, width)) | ||
fmt.Fprintln(out, | ||
strings.Join( | ||
[]string{ | ||
strings.Repeat(titleChar, border), | ||
strings.Repeat(" ", padding), strings.ToUpper(title), strings.Repeat(" ", padding), | ||
strings.Repeat(titleChar, border), | ||
}, | ||
"", | ||
), | ||
) | ||
fmt.Fprintln(out, strings.Repeat(titleChar, width)) | ||
} | ||
|
||
func printFormattedDivider(out io.Writer) { | ||
fmt.Fprintln(out, strings.Repeat(dividerChar, width)) | ||
} |
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,163 @@ | ||
package deprecation | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestMessageForWarnings(t *testing.T) { | ||
testCases := map[string]struct { | ||
Env []string | ||
Command string | ||
Warnings []warning | ||
ExpectedMessage string | ||
ExpectedFatal bool | ||
}{ | ||
"warning that does not fire": { | ||
Env: []string{"OPA_FOOBAR=1"}, | ||
Command: "foobar", | ||
Warnings: []warning{ | ||
{ | ||
MatchEnv: func(env []string) bool { | ||
return false | ||
}, | ||
MatchCommand: func(command string) bool { | ||
return false | ||
}, | ||
Fatal: true, | ||
Message: "fatal warning", | ||
}, | ||
}, | ||
ExpectedFatal: false, | ||
ExpectedMessage: "", | ||
}, | ||
"warning that fires": { | ||
Env: []string{"OPA_FOOBAR=1"}, | ||
Command: "foobar", | ||
Warnings: []warning{ | ||
{ | ||
MatchEnv: func(env []string) bool { | ||
for _, e := range env { | ||
if e == "OPA_FOOBAR=1" { | ||
return true | ||
} | ||
} | ||
return false | ||
}, | ||
MatchCommand: func(command string) bool { | ||
return command == "foobar" | ||
}, | ||
Fatal: true, | ||
Message: "fatal warning for foobar", | ||
}, | ||
}, | ||
ExpectedMessage: `################################################################################ | ||
### FATAL DEPRECATION WARNINGS ### | ||
################################################################################ | ||
fatal warning for foobar | ||
################################################################################ | ||
### END FATAL DEPRECATION WARNINGS ### | ||
################################################################################ | ||
`, | ||
ExpectedFatal: true, | ||
}, | ||
"two warnings that fire, one fatally": { | ||
Env: []string{"OPA_FOOBAR=1"}, | ||
Command: "foobar", | ||
Warnings: []warning{ | ||
{ | ||
MatchEnv: func(env []string) bool { | ||
for _, e := range env { | ||
if e == "OPA_FOOBAR=1" { | ||
return true | ||
} | ||
} | ||
return false | ||
}, | ||
MatchCommand: func(command string) bool { | ||
return command == "foobar" | ||
}, | ||
Fatal: true, | ||
Message: "fatal warning for foobar", | ||
}, | ||
{ | ||
MatchEnv: func(env []string) bool { | ||
return true | ||
}, | ||
MatchCommand: func(command string) bool { | ||
return command == "foobar" | ||
}, | ||
Fatal: false, | ||
Message: "non fatal warning for foobar", | ||
}, | ||
}, | ||
ExpectedMessage: `################################################################################ | ||
### FATAL DEPRECATION WARNINGS ### | ||
################################################################################ | ||
fatal warning for foobar | ||
-------------------------------------------------------------------------------- | ||
non fatal warning for foobar | ||
################################################################################ | ||
### END FATAL DEPRECATION WARNINGS ### | ||
################################################################################ | ||
`, | ||
ExpectedFatal: true, | ||
}, | ||
"two warnings that fire, neither fatally": { | ||
Env: []string{"OPA_FOOBAR=1"}, | ||
Command: "foobar", | ||
Warnings: []warning{ | ||
{ | ||
MatchEnv: func(env []string) bool { | ||
for _, e := range env { | ||
if e == "OPA_FOOBAR=1" { | ||
return true | ||
} | ||
} | ||
return false | ||
}, | ||
MatchCommand: func(command string) bool { | ||
return command == "foobar" | ||
}, | ||
Fatal: false, | ||
Message: "warning for foobar", | ||
}, | ||
{ | ||
MatchEnv: func(env []string) bool { | ||
return true | ||
}, | ||
MatchCommand: func(command string) bool { | ||
return command == "foobar" | ||
}, | ||
Fatal: false, | ||
Message: "another warning for foobar", | ||
}, | ||
}, | ||
ExpectedMessage: `################################################################################ | ||
### DEPRECATION WARNINGS ### | ||
################################################################################ | ||
warning for foobar | ||
-------------------------------------------------------------------------------- | ||
another warning for foobar | ||
################################################################################ | ||
### END DEPRECATION WARNINGS ### | ||
################################################################################ | ||
`, | ||
ExpectedFatal: false, | ||
}, | ||
} | ||
|
||
for name, tc := range testCases { | ||
t.Run(name, func(t *testing.T) { | ||
message, fatal := messageForWarnings(tc.Warnings, tc.Env, tc.Command) | ||
|
||
if fatal != tc.ExpectedFatal { | ||
t.Errorf("Expected fatal to be %v but got %v", tc.ExpectedFatal, fatal) | ||
} | ||
|
||
if message != tc.ExpectedMessage { | ||
t.Errorf("Expected message\n%s\nbut got\n%s", tc.ExpectedMessage, message) | ||
} | ||
|
||
}) | ||
} | ||
} |
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