fix: cli prompt termination exit code

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
This commit is contained in:
Alano Terblanche
2024-02-21 16:36:17 +01:00
parent 310daf2db9
commit 10bf91a02d
33 changed files with 739 additions and 63 deletions

View File

@ -3,7 +3,6 @@ package trust
import (
"context"
"fmt"
"os"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
@ -44,7 +43,11 @@ func revokeTrust(ctx context.Context, dockerCLI command.Cli, remote string, opti
return fmt.Errorf("cannot use a digest reference for IMAGE:TAG")
}
if imgRefAndAuth.Tag() == "" && !options.forceYes {
deleteRemote := command.PromptForConfirmation(os.Stdin, dockerCLI.Out(), fmt.Sprintf("Please confirm you would like to delete all signature data for %s?", remote))
deleteRemote, err := command.PromptForConfirmation(ctx, dockerCLI.In(), dockerCLI.Out(), fmt.Sprintf("Please confirm you would like to delete all signature data for %s?", remote))
if err != nil {
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
return errors.Wrap(err, "aborting action")
}
if !deleteRemote {
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
return nil

View File

@ -1,9 +1,11 @@
package trust
import (
"context"
"io"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
@ -12,6 +14,7 @@ import (
"github.com/theupdateframework/notary/trustpinning"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
func TestTrustRevokeCommandErrors(t *testing.T) {
@ -148,3 +151,18 @@ func TestGetSignableRolesForTargetAndRemoveError(t *testing.T) {
err = getSignableRolesForTargetAndRemove(target, notaryRepo)
assert.Error(t, err, "client is offline")
}
func TestRevokeTrustPromptTermination(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
cli := test.NewFakeCli(&fakeClient{})
cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"example/trust-demo"})
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
t.Helper()
assert.ErrorIs(t, err, command.ErrPromptTerminated)
})
assert.Equal(t, cli.ErrBuffer().String(), "")
golden.Assert(t, cli.OutBuffer().String(), "trust-revoke-prompt-termination.golden")
}

View File

@ -3,7 +3,6 @@ package trust
import (
"context"
"fmt"
"os"
"strings"
"github.com/docker/cli/cli"
@ -76,6 +75,22 @@ func isLastSignerForReleases(roleWithSig data.Role, allRoles []client.RoleWithSi
return counter < releasesRoleWithSigs.Threshold, nil
}
func maybePromptForSignerRemoval(ctx context.Context, dockerCLI command.Cli, repoName, signerName string, isLastSigner, forceYes bool) (bool, error) {
if isLastSigner && !forceYes {
message := fmt.Sprintf("The signer \"%s\" signed the last released version of %s. "+
"Removing this signer will make %s unpullable. "+
"Are you sure you want to continue?",
signerName, repoName, repoName,
)
removeSigner, err := command.PromptForConfirmation(ctx, dockerCLI.In(), dockerCLI.Out(), message)
if err != nil {
return false, err
}
return removeSigner, nil
}
return false, nil
}
// removeSingleSigner attempts to remove a single signer and returns whether signer removal happened.
// The signer not being removed doesn't necessarily raise an error e.g. user choosing "No" when prompted for confirmation.
func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, signerName string, forceYes bool) (bool, error) {
@ -110,28 +125,26 @@ func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, si
if err != nil {
return false, err
}
if ok, err := isLastSignerForReleases(role, allRoles); ok && !forceYes {
removeSigner := command.PromptForConfirmation(os.Stdin, dockerCLI.Out(), fmt.Sprintf("The signer \"%s\" signed the last released version of %s. "+
"Removing this signer will make %s unpullable. "+
"Are you sure you want to continue?",
signerName, repoName, repoName,
))
if !removeSigner {
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
return false, nil
}
} else if err != nil {
return false, err
}
if err = notaryRepo.RemoveDelegationKeys(releasesRoleTUFName, role.KeyIDs); err != nil {
return false, err
}
if err = notaryRepo.RemoveDelegationRole(signerDelegation); err != nil {
isLastSigner, err := isLastSignerForReleases(role, allRoles)
if err != nil {
return false, err
}
if err = notaryRepo.Publish(); err != nil {
ok, err := maybePromptForSignerRemoval(ctx, dockerCLI, repoName, signerName, isLastSigner, forceYes)
if err != nil || !ok {
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
return false, err
}
if err := notaryRepo.RemoveDelegationKeys(releasesRoleTUFName, role.KeyIDs); err != nil {
return false, err
}
if err := notaryRepo.RemoveDelegationRole(signerDelegation); err != nil {
return false, err
}
if err := notaryRepo.Publish(); err != nil {
return false, err
}

View File

@ -111,7 +111,8 @@ func TestIsLastSignerForReleases(t *testing.T) {
releaserole.Name = releasesRoleTUFName
releaserole.Threshold = 1
allrole := []client.RoleWithSignatures{releaserole}
lastsigner, _ := isLastSignerForReleases(role, allrole)
lastsigner, err := isLastSignerForReleases(role, allrole)
assert.Error(t, err, "all signed tags are currently revoked, use docker trust sign to fix")
assert.Check(t, is.Equal(false, lastsigner))
role.KeyIDs = []string{"deadbeef"}
@ -120,13 +121,15 @@ func TestIsLastSignerForReleases(t *testing.T) {
releaserole.Signatures = []data.Signature{sig}
releaserole.Threshold = 1
allrole = []client.RoleWithSignatures{releaserole}
lastsigner, _ = isLastSignerForReleases(role, allrole)
lastsigner, err = isLastSignerForReleases(role, allrole)
assert.NilError(t, err)
assert.Check(t, is.Equal(true, lastsigner))
sig.KeyID = "8badf00d"
releaserole.Signatures = []data.Signature{sig}
releaserole.Threshold = 1
allrole = []client.RoleWithSignatures{releaserole}
lastsigner, _ = isLastSignerForReleases(role, allrole)
lastsigner, err = isLastSignerForReleases(role, allrole)
assert.NilError(t, err)
assert.Check(t, is.Equal(false, lastsigner))
}

View File

@ -0,0 +1,2 @@
Please confirm you would like to delete all signature data for example/trust-demo? [y/N]
Aborting action.