Merge pull request #2375 from souleb/issue-2365
Add contextual error code for flux diff kustomization
This commit is contained in:
@@ -23,7 +23,7 @@ import (
|
|||||||
var diffCmd = &cobra.Command{
|
var diffCmd = &cobra.Command{
|
||||||
Use: "diff",
|
Use: "diff",
|
||||||
Short: "Diff a flux resource",
|
Short: "Diff a flux resource",
|
||||||
Long: "The diff command is used to do a server-side dry-run on flux resources, then output the diff.",
|
Long: "The diff command is used to do a server-side dry-run on flux resources, then prints the diff.",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ var diffKsCmd = &cobra.Command{
|
|||||||
Use: "kustomization",
|
Use: "kustomization",
|
||||||
Aliases: []string{"ks"},
|
Aliases: []string{"ks"},
|
||||||
Short: "Diff Kustomization",
|
Short: "Diff Kustomization",
|
||||||
Long: `The diff command does a build, then it performs a server-side dry-run and output the diff.`,
|
Long: `The diff command does a build, then it performs a server-side dry-run and prints the diff.
|
||||||
Example: `# Preview changes local changes as they were applied on the cluster
|
Exit status: 0 No differences were found. 1 Differences were found. >1 diff failed with an error.`,
|
||||||
|
Example: `# Preview local changes as they were applied on the cluster
|
||||||
flux diff kustomization my-app --path ./path/to/local/manifests`,
|
flux diff kustomization my-app --path ./path/to/local/manifests`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: diffKsCmdRun,
|
RunE: diffKsCmdRun,
|
||||||
@@ -56,16 +57,16 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if diffKsArgs.path == "" {
|
if diffKsArgs.path == "" {
|
||||||
return fmt.Errorf("invalid resource path %q", diffKsArgs.path)
|
return &RequestError{StatusCode: 2, Err: fmt.Errorf("invalid resource path %q", diffKsArgs.path)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs, err := os.Stat(diffKsArgs.path); err != nil || !fs.IsDir() {
|
if fs, err := os.Stat(diffKsArgs.path); err != nil || !fs.IsDir() {
|
||||||
return fmt.Errorf("invalid resource path %q", diffKsArgs.path)
|
return &RequestError{StatusCode: 2, Err: fmt.Errorf("invalid resource path %q", diffKsArgs.path)}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder, err := build.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout))
|
builder, err := build.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &RequestError{StatusCode: 2, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a signal channel
|
// create a signal channel
|
||||||
@@ -74,13 +75,18 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
output, err := builder.Diff()
|
output, hasChanged, err := builder.Diff()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- &RequestError{StatusCode: 2, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Print(output)
|
cmd.Print(output)
|
||||||
errChan <- nil
|
|
||||||
|
if hasChanged {
|
||||||
|
errChan <- &RequestError{StatusCode: 1, Err: fmt.Errorf("identified at least one change, exiting with non-zero exit code")}
|
||||||
|
} else {
|
||||||
|
errChan <- nil
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -105,6 +105,16 @@ type rootFlags struct {
|
|||||||
defaults install.Options
|
defaults install.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestError is a custom error type that wraps an error returned by the flux api.
|
||||||
|
type RequestError struct {
|
||||||
|
StatusCode int
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestError) Error() string {
|
||||||
|
return r.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
var rootArgs = NewRootFlags()
|
var rootArgs = NewRootFlags()
|
||||||
var kubeconfigArgs = genericclioptions.NewConfigFlags(false)
|
var kubeconfigArgs = genericclioptions.NewConfigFlags(false)
|
||||||
|
|
||||||
@@ -144,6 +154,11 @@ func main() {
|
|||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
logger.Failuref("%v", err)
|
logger.Failuref("%v", err)
|
||||||
|
|
||||||
|
if err, ok := err.(*RequestError); ok {
|
||||||
|
os.Exit(err.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,6 +325,12 @@ type cmdTestCase struct {
|
|||||||
|
|
||||||
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
|
||||||
actual, testErr := executeCommand(cmd.args)
|
actual, testErr := executeCommand(cmd.args)
|
||||||
|
|
||||||
|
// If the cmd error is a change, discard it
|
||||||
|
if isChangeError(testErr) {
|
||||||
|
testErr = nil
|
||||||
|
}
|
||||||
|
|
||||||
if assertErr := cmd.assert(actual, testErr); assertErr != nil {
|
if assertErr := cmd.assert(actual, testErr); assertErr != nil {
|
||||||
t.Error(assertErr)
|
t.Error(assertErr)
|
||||||
}
|
}
|
||||||
@@ -366,3 +372,12 @@ func resetCmdArgs() {
|
|||||||
getArgs = GetFlags{}
|
getArgs = GetFlags{}
|
||||||
secretGitArgs = NewSecretGitFlags()
|
secretGitArgs = NewSecretGitFlags()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isChangeError(err error) bool {
|
||||||
|
if reqErr, ok := err.(*RequestError); ok {
|
||||||
|
if strings.Contains(err.Error(), "identified at least one change, exiting with non-zero exit code") && reqErr.StatusCode == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,28 +51,29 @@ func (b *Builder) Manager() (*ssa.ResourceManager, error) {
|
|||||||
return ssa.NewResourceManager(b.client, statusPoller, owner), nil
|
return ssa.NewResourceManager(b.client, statusPoller, owner), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) Diff() (string, error) {
|
func (b *Builder) Diff() (string, bool, error) {
|
||||||
output := strings.Builder{}
|
output := strings.Builder{}
|
||||||
|
createdOrDrifted := false
|
||||||
res, err := b.Build()
|
res, err := b.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
// convert the build result into Kubernetes unstructured objects
|
// convert the build result into Kubernetes unstructured objects
|
||||||
objects, err := ssa.ReadObjects(bytes.NewReader(res))
|
objects, err := ssa.ReadObjects(bytes.NewReader(res))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceManager, err := b.Manager()
|
resourceManager, err := b.Manager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := ssa.SetNativeKindsDefaults(objects); err != nil {
|
if err := ssa.SetNativeKindsDefaults(objects); err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an inventory of objects to be reconciled
|
// create an inventory of objects to be reconciled
|
||||||
@@ -101,20 +102,23 @@ func (b *Builder) Diff() (string, error) {
|
|||||||
|
|
||||||
if change.Action == string(ssa.CreatedAction) {
|
if change.Action == string(ssa.CreatedAction) {
|
||||||
output.WriteString(writeString(fmt.Sprintf("► %s created\n", change.Subject), bunt.Green))
|
output.WriteString(writeString(fmt.Sprintf("► %s created\n", change.Subject), bunt.Green))
|
||||||
|
createdOrDrifted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if change.Action == string(ssa.ConfiguredAction) {
|
if change.Action == string(ssa.ConfiguredAction) {
|
||||||
output.WriteString(writeString(fmt.Sprintf("► %s drifted\n", change.Subject), bunt.WhiteSmoke))
|
output.WriteString(writeString(fmt.Sprintf("► %s drifted\n", change.Subject), bunt.WhiteSmoke))
|
||||||
liveFile, mergedFile, tmpDir, err := writeYamls(liveObject, mergedObject)
|
liveFile, mergedFile, tmpDir, err := writeYamls(liveObject, mergedObject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
defer cleanupDir(tmpDir)
|
defer cleanupDir(tmpDir)
|
||||||
|
|
||||||
err = diff(liveFile, mergedFile, &output)
|
err = diff(liveFile, mergedFile, &output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createdOrDrifted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
addObjectsToInventory(newInventory, change)
|
addObjectsToInventory(newInventory, change)
|
||||||
@@ -125,7 +129,7 @@ func (b *Builder) Diff() (string, error) {
|
|||||||
if oldStatus.Inventory != nil {
|
if oldStatus.Inventory != nil {
|
||||||
diffObjects, err := diffInventory(oldStatus.Inventory, newInventory)
|
diffObjects, err := diffInventory(oldStatus.Inventory, newInventory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", createdOrDrifted, err
|
||||||
}
|
}
|
||||||
for _, object := range diffObjects {
|
for _, object := range diffObjects {
|
||||||
output.WriteString(writeString(fmt.Sprintf("► %s deleted\n", ssa.FmtUnstructured(object)), bunt.OrangeRed))
|
output.WriteString(writeString(fmt.Sprintf("► %s deleted\n", ssa.FmtUnstructured(object)), bunt.OrangeRed))
|
||||||
@@ -133,7 +137,7 @@ func (b *Builder) Diff() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.String(), nil
|
return output.String(), createdOrDrifted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, string, string, error) {
|
func writeYamls(liveObject, mergedObject *unstructured.Unstructured) (string, string, string, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user