DockerCLI/cli/command/swarm/progress/root_rotation.go

121 lines
3.4 KiB
Go

package progress
import (
"bytes"
"context"
"io"
"os"
"os/signal"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/opencontainers/go-digest"
)
const (
certsRotatedStr = " rotated TLS certificates"
rootsRotatedStr = " rotated CA certificates"
// rootsAction has a single space because rootsRotatedStr is one character shorter than certsRotatedStr.
// This makes sure the progress bar are aligned.
certsAction = ""
rootsAction = " "
)
// RootRotationProgress outputs progress information for convergence of a root rotation.
func RootRotationProgress(ctx context.Context, dclient client.APIClient, progressWriter io.WriteCloser) error {
defer progressWriter.Close()
progressOut := streamformatter.NewJSONProgressOutput(progressWriter, false)
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt)
defer signal.Stop(sigint)
// draw 2 progress bars, 1 for nodes with the correct cert, 1 for nodes with the correct trust root
progress.Update(progressOut, "desired root digest", "")
progress.Update(progressOut, certsRotatedStr, certsAction)
progress.Update(progressOut, rootsRotatedStr, rootsAction)
var done bool
for {
info, err := dclient.SwarmInspect(ctx)
if err != nil {
return err
}
if done {
return nil
}
nodes, err := dclient.NodeList(ctx, types.NodeListOptions{})
if err != nil {
return err
}
done = updateProgress(progressOut, info.ClusterInfo.TLSInfo, nodes, info.ClusterInfo.RootRotationInProgress)
select {
case <-time.After(200 * time.Millisecond):
case <-sigint:
if !done {
progress.Message(progressOut, "", "Operation continuing in background.")
progress.Message(progressOut, "", "Use `swarmctl cluster inspect default` to check progress.")
}
return nil
}
}
}
func updateProgress(progressOut progress.Output, desiredTLSInfo swarm.TLSInfo, nodes []swarm.Node, rootRotationInProgress bool) bool {
// write the current desired root cert's digest, because the desired root certs might be too long
progressOut.WriteProgress(progress.Progress{
ID: "desired root digest",
Action: digest.FromBytes([]byte(desiredTLSInfo.TrustRoot)).String(),
})
// If we had reached a converged state, check if we are still converged.
var certsRight, trustRootsRight int64
for _, n := range nodes {
if bytes.Equal(n.Description.TLSInfo.CertIssuerPublicKey, desiredTLSInfo.CertIssuerPublicKey) &&
bytes.Equal(n.Description.TLSInfo.CertIssuerSubject, desiredTLSInfo.CertIssuerSubject) {
certsRight++
}
if n.Description.TLSInfo.TrustRoot == desiredTLSInfo.TrustRoot {
trustRootsRight++
}
}
total := int64(len(nodes))
progressOut.WriteProgress(progress.Progress{
ID: certsRotatedStr,
Action: certsAction,
Current: certsRight,
Total: total,
Units: "nodes",
})
rootsProgress := progress.Progress{
ID: rootsRotatedStr,
Action: rootsAction,
Current: trustRootsRight,
Total: total,
Units: "nodes",
}
if certsRight == total && !rootRotationInProgress {
progressOut.WriteProgress(rootsProgress)
return certsRight == total && trustRootsRight == total
}
// we still have certs that need renewing, so display that there are zero roots rotated yet
rootsProgress.Current = 0
progressOut.WriteProgress(rootsProgress)
return false
}