-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/microcloud: Add initiating and joining session handlers for the CLI
Signed-off-by: Julian Pelizäus <[email protected]>
- Loading branch information
1 parent
1501533
commit 0275390
Showing
1 changed file
with
160 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/canonical/microcloud/microcloud/api/types" | ||
cloudClient "github.com/canonical/microcloud/microcloud/client" | ||
"github.com/canonical/microcloud/microcloud/mdns" | ||
"github.com/canonical/microcloud/microcloud/service" | ||
) | ||
|
||
type SessionFunc func(gw *cloudClient.WebsocketGateway) error | ||
|
||
func (c *initConfig) runSession(ctx context.Context, s *service.Handler, role types.SessionRole, timeout time.Duration, f SessionFunc) error { | ||
cloud := s.Services[types.MicroCloud].(*service.CloudService) | ||
conn, err := cloud.StartSession(ctx, string(role), timeout) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer conn.Close() | ||
|
||
return f(cloudClient.NewWebsocketGateway(ctx, conn)) | ||
} | ||
|
||
func (c *initConfig) initiatingSession(gw *cloudClient.WebsocketGateway, sh *service.Handler, services []types.ServiceType, passphrase string, expectedSystems []string) error { | ||
session := types.Session{ | ||
Address: c.address, | ||
Interface: c.lookupIface.Name, | ||
Services: services, | ||
Passphrase: passphrase, | ||
} | ||
|
||
err := gw.Write(session) | ||
if err != nil { | ||
return fmt.Errorf("Failed to send session start: %w", err) | ||
} | ||
|
||
err = gw.ReceiveWithContext(gw.Context(), &session) | ||
if err != nil { | ||
return fmt.Errorf("Failed to read session reply: %w", err) | ||
} | ||
|
||
if !c.autoSetup { | ||
cloud := sh.Services[types.MicroCloud].(*service.CloudService) | ||
cert, err := cloud.Certificate() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fingerprint, err := c.shortFingerprint(cert.Fingerprint()) | ||
if err != nil { | ||
return fmt.Errorf("Failed to shorten fingerprint: %w", err) | ||
} | ||
|
||
fmt.Printf(" Using fingerprint %q for MicroCloud\n\n", fingerprint) | ||
fmt.Printf("Use the following command on systems that you want to join the cluster:\n\n microcloud join\n\n") | ||
fmt.Printf("When requested enter the passphrase:\n\n %s\n\n", session.Passphrase) | ||
fmt.Println("Waiting for systems to join ...") | ||
} | ||
|
||
confirmedIntents, err := c.askJoinIntents(gw, expectedSystems) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = gw.Write(types.Session{ | ||
ConfirmedIntents: confirmedIntents, | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("Failed to send join intents: %w", err) | ||
} | ||
|
||
err = gw.ReceiveWithContext(gw.Context(), &session) | ||
if err != nil { | ||
return fmt.Errorf("Failed to read confirmation errors: %w", err) | ||
} | ||
|
||
if !session.Accepted { | ||
return fmt.Errorf("Join confirmations didn't get accepted on all systems") | ||
} | ||
|
||
for _, joinIntent := range confirmedIntents { | ||
certBlock, _ := pem.Decode([]byte(joinIntent.Certificate)) | ||
if certBlock == nil { | ||
return fmt.Errorf("Invalid certificate file") | ||
} | ||
|
||
remoteCert, err := x509.ParseCertificate(certBlock.Bytes) | ||
if err != nil { | ||
return fmt.Errorf("Failed to parse certificate: %w", err) | ||
} | ||
|
||
// Register init system | ||
c.systems[joinIntent.Name] = InitSystem{ | ||
ServerInfo: mdns.ServerInfo{ | ||
Version: joinIntent.Version, | ||
Name: joinIntent.Name, | ||
Address: joinIntent.Address, | ||
Services: joinIntent.Services, | ||
// Store the peers certificate to allow mTLS server validation | ||
// for requests after the trust establishment. | ||
Certificate: remoteCert, | ||
}, | ||
} | ||
} | ||
|
||
if !c.autoSetup { | ||
for _, info := range c.systems { | ||
fmt.Printf(" Selected %q at %q\n", info.ServerInfo.Name, info.ServerInfo.Address) | ||
} | ||
|
||
// Add a space between the CLI and the response. | ||
fmt.Println("") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *initConfig) joiningSession(gw *cloudClient.WebsocketGateway, sh *service.Handler, services []types.ServiceType, target string, passphrase string) error { | ||
session := types.Session{ | ||
Passphrase: passphrase, | ||
Address: sh.Address, | ||
TargetAddress: target, | ||
Interface: c.lookupIface.Name, | ||
Services: services, | ||
LookupTimeout: c.lookupTimeout, | ||
} | ||
|
||
err := gw.Write(session) | ||
if err != nil { | ||
return fmt.Errorf("Failed to send session start: %w", err) | ||
} | ||
|
||
if !c.autoSetup && target == "" { | ||
fmt.Println("Searching an eligible system ...") | ||
} | ||
|
||
// The server confirms the target regardless whether or not one was provided. | ||
err = gw.ReceiveWithContext(gw.Context(), &session) | ||
if err != nil { | ||
return fmt.Errorf("Failed to find an eligible system: %w", err) | ||
} | ||
|
||
if !c.autoSetup { | ||
fingerprint, err := c.shortFingerprint(session.TargetFingerprint) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Printf("\n Found system %q at %q using fingerprint %q\n\n", session.TargetName, session.TargetAddress, fingerprint) | ||
fmt.Printf("Select %q on %q to let it join the cluster\n", sh.Name, session.TargetName) | ||
} | ||
|
||
return c.askJoinConfirmation(gw) | ||
} |