github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/swarm/progress/root_rotation.go (about) 1 package progress 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "os" 8 "os/signal" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/swarm" 13 "github.com/docker/docker/client" 14 "github.com/docker/docker/pkg/progress" 15 "github.com/docker/docker/pkg/streamformatter" 16 "github.com/opencontainers/go-digest" 17 ) 18 19 const ( 20 certsRotatedStr = " rotated TLS certificates" 21 rootsRotatedStr = " rotated CA certificates" 22 // rootsAction has a single space because rootsRotatedStr is one character shorter than certsRotatedStr. 23 // This makes sure the progress bar are aligned. 24 certsAction = "" 25 rootsAction = " " 26 ) 27 28 // RootRotationProgress outputs progress information for convergence of a root rotation. 29 func RootRotationProgress(ctx context.Context, dclient client.APIClient, progressWriter io.WriteCloser) error { 30 defer progressWriter.Close() 31 32 progressOut := streamformatter.NewJSONProgressOutput(progressWriter, false) 33 34 sigint := make(chan os.Signal, 1) 35 signal.Notify(sigint, os.Interrupt) 36 defer signal.Stop(sigint) 37 38 // draw 2 progress bars, 1 for nodes with the correct cert, 1 for nodes with the correct trust root 39 progress.Update(progressOut, "desired root digest", "") 40 progress.Update(progressOut, certsRotatedStr, certsAction) 41 progress.Update(progressOut, rootsRotatedStr, rootsAction) 42 43 var done bool 44 45 for { 46 info, err := dclient.SwarmInspect(ctx) 47 if err != nil { 48 return err 49 } 50 51 if done { 52 return nil 53 } 54 55 nodes, err := dclient.NodeList(ctx, types.NodeListOptions{}) 56 if err != nil { 57 return err 58 } 59 60 done = updateProgress(progressOut, info.ClusterInfo.TLSInfo, nodes, info.ClusterInfo.RootRotationInProgress) 61 62 select { 63 case <-time.After(200 * time.Millisecond): 64 case <-sigint: 65 if !done { 66 progress.Message(progressOut, "", "Operation continuing in background.") 67 progress.Message(progressOut, "", "Use `swarmctl cluster inspect default` to check progress.") 68 } 69 return nil 70 } 71 } 72 } 73 74 func updateProgress(progressOut progress.Output, desiredTLSInfo swarm.TLSInfo, nodes []swarm.Node, rootRotationInProgress bool) bool { 75 // write the current desired root cert's digest, because the desired root certs might be too long 76 progressOut.WriteProgress(progress.Progress{ 77 ID: "desired root digest", 78 Action: digest.FromBytes([]byte(desiredTLSInfo.TrustRoot)).String(), 79 }) 80 81 // If we had reached a converged state, check if we are still converged. 82 var certsRight, trustRootsRight int64 83 for _, n := range nodes { 84 if bytes.Equal(n.Description.TLSInfo.CertIssuerPublicKey, desiredTLSInfo.CertIssuerPublicKey) && 85 bytes.Equal(n.Description.TLSInfo.CertIssuerSubject, desiredTLSInfo.CertIssuerSubject) { 86 certsRight++ 87 } 88 89 if n.Description.TLSInfo.TrustRoot == desiredTLSInfo.TrustRoot { 90 trustRootsRight++ 91 } 92 } 93 94 total := int64(len(nodes)) 95 progressOut.WriteProgress(progress.Progress{ 96 ID: certsRotatedStr, 97 Action: certsAction, 98 Current: certsRight, 99 Total: total, 100 Units: "nodes", 101 }) 102 103 rootsProgress := progress.Progress{ 104 ID: rootsRotatedStr, 105 Action: rootsAction, 106 Current: trustRootsRight, 107 Total: total, 108 Units: "nodes", 109 } 110 111 if certsRight == total && !rootRotationInProgress { 112 progressOut.WriteProgress(rootsProgress) 113 return certsRight == total && trustRootsRight == total 114 } 115 116 // we still have certs that need renewing, so display that there are zero roots rotated yet 117 rootsProgress.Current = 0 118 progressOut.WriteProgress(rootsProgress) 119 return false 120 }