mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
detect if the machine is already initialised
This commit is contained in:
+50
-21
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/huh"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -132,24 +133,6 @@ func (cli *CLI) InitCluster(
|
||||
func (cli *CLI) initRemoteMachine(
|
||||
ctx context.Context, remoteMachine RemoteMachine, clusterName, machineName string, netPrefix netip.Prefix,
|
||||
) error {
|
||||
// Create a command executor and a machine API client over the SSH connection to the remote machine.
|
||||
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"SSH login to remote machine %s: %w",
|
||||
config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), err,
|
||||
)
|
||||
}
|
||||
exec := sshexec.NewRemote(sshClient)
|
||||
|
||||
c, err := client.New(ctx, connector.NewSSHConnectorFromClient(sshClient))
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to remote machine: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = c.Close()
|
||||
}()
|
||||
|
||||
if clusterName == "" {
|
||||
clusterName = defaultClusterName
|
||||
}
|
||||
@@ -161,10 +144,57 @@ func (cli *CLI) initRemoteMachine(
|
||||
return fmt.Errorf("generate cluster user: %w", err)
|
||||
}
|
||||
|
||||
// Create a command executor and a machine API client over the SSH connection to the remote machine.
|
||||
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"SSH login to remote machine %s: %w",
|
||||
config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), err,
|
||||
)
|
||||
}
|
||||
exec := sshexec.NewRemote(sshClient)
|
||||
|
||||
// Install and run the Uncloud daemon and dependencies on the remote machine.
|
||||
if err = provisionMachine(ctx, exec); err != nil {
|
||||
return fmt.Errorf("provision machine: %w", err)
|
||||
}
|
||||
// TODO: Check if the machine is already provisioned using machineClient and ask the user to reset it first.
|
||||
|
||||
machineClient, err := client.New(ctx, connector.NewSSHConnectorFromClient(sshClient))
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to remote machine: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = machineClient.Close()
|
||||
}()
|
||||
|
||||
minfo, err := machineClient.Inspect(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect machine: %w", err)
|
||||
}
|
||||
if minfo.Id != "" {
|
||||
var confirm bool
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title(
|
||||
"The remote machine is already initialised as a cluster member. Do you want to reset it first?",
|
||||
).
|
||||
Affirmative("Yes!").
|
||||
Negative("No").
|
||||
Value(&confirm),
|
||||
),
|
||||
)
|
||||
if err = form.Run(); err != nil {
|
||||
return fmt.Errorf("prompt user to confirm: %w", err)
|
||||
}
|
||||
|
||||
if !confirm {
|
||||
return fmt.Errorf("remote machine is already initialised as a cluster member")
|
||||
}
|
||||
// TODO: implement resetting the remote machine.
|
||||
return fmt.Errorf("resetting the remote machine is not implemented yet")
|
||||
}
|
||||
|
||||
req := &pb.InitClusterRequest{
|
||||
MachineName: machineName,
|
||||
@@ -176,7 +206,7 @@ func (cli *CLI) initRemoteMachine(
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := c.InitCluster(ctx, req)
|
||||
resp, err := machineClient.InitCluster(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init cluster: %w", err)
|
||||
}
|
||||
@@ -221,8 +251,7 @@ func (cli *CLI) AddMachine(ctx context.Context, remoteMachine RemoteMachine, clu
|
||||
}
|
||||
machineExec := sshexec.NewRemote(sshClient)
|
||||
|
||||
conn := connector.NewSSHConnectorFromClient(sshClient)
|
||||
machineClient, err := client.New(ctx, conn)
|
||||
machineClient, err := client.New(ctx, connector.NewSSHConnectorFromClient(sshClient))
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to remote machine API: %w", err)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ type RemoteMachine struct {
|
||||
|
||||
// provisionMachine provisions the remote machine by downloading the Uncloud install script from GitHub and running it.
|
||||
func provisionMachine(ctx context.Context, exec sshexec.Executor) error {
|
||||
// TODO: Check if the machine is already provisioned and ask the user to reset it first.
|
||||
|
||||
user, err := exec.Run(ctx, "whoami")
|
||||
if err != nil {
|
||||
return fmt.Errorf("run whoami: %w", err)
|
||||
|
||||
@@ -267,7 +267,7 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
|
||||
0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68,
|
||||
0x69, 0x6e, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xc0, 0x01, 0x0a, 0x07,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xf5, 0x01, 0x0a, 0x07,
|
||||
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x43,
|
||||
0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x69,
|
||||
0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
@@ -279,11 +279,15 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
|
||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37,
|
||||
0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76,
|
||||
0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33,
|
||||
0x0a, 0x07, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49,
|
||||
0x6e, 0x66, 0x6f, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63,
|
||||
0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61,
|
||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -318,11 +322,13 @@ var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
|
||||
0, // 5: api.Machine.InitCluster:input_type -> api.InitClusterRequest
|
||||
2, // 6: api.Machine.JoinCluster:input_type -> api.JoinClusterRequest
|
||||
7, // 7: api.Machine.Token:input_type -> google.protobuf.Empty
|
||||
1, // 8: api.Machine.InitCluster:output_type -> api.InitClusterResponse
|
||||
7, // 9: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
|
||||
3, // 10: api.Machine.Token:output_type -> api.TokenResponse
|
||||
8, // [8:11] is the sub-list for method output_type
|
||||
5, // [5:8] is the sub-list for method input_type
|
||||
7, // 8: api.Machine.Inspect:input_type -> google.protobuf.Empty
|
||||
1, // 9: api.Machine.InitCluster:output_type -> api.InitClusterResponse
|
||||
7, // 10: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
|
||||
3, // 11: api.Machine.Token:output_type -> api.TokenResponse
|
||||
6, // 12: api.Machine.Inspect:output_type -> api.MachineInfo
|
||||
9, // [9:13] is the sub-list for method output_type
|
||||
5, // [5:9] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
5, // [5:5] is the sub-list for extension extendee
|
||||
0, // [0:5] is the sub-list for field type_name
|
||||
|
||||
@@ -12,6 +12,7 @@ service Machine {
|
||||
rpc InitCluster(InitClusterRequest) returns (InitClusterResponse);
|
||||
rpc JoinCluster(JoinClusterRequest) returns (google.protobuf.Empty);
|
||||
rpc Token(google.protobuf.Empty) returns (TokenResponse);
|
||||
rpc Inspect(google.protobuf.Empty) returns (MachineInfo);
|
||||
}
|
||||
|
||||
message InitClusterRequest {
|
||||
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
Machine_InitCluster_FullMethodName = "/api.Machine/InitCluster"
|
||||
Machine_JoinCluster_FullMethodName = "/api.Machine/JoinCluster"
|
||||
Machine_Token_FullMethodName = "/api.Machine/Token"
|
||||
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
|
||||
)
|
||||
|
||||
// MachineClient is the client API for Machine service.
|
||||
@@ -32,6 +33,7 @@ type MachineClient interface {
|
||||
InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error)
|
||||
JoinCluster(ctx context.Context, in *JoinClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error)
|
||||
Inspect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MachineInfo, error)
|
||||
}
|
||||
|
||||
type machineClient struct {
|
||||
@@ -72,6 +74,16 @@ func (c *machineClient) Token(ctx context.Context, in *emptypb.Empty, opts ...gr
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *machineClient) Inspect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MachineInfo, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(MachineInfo)
|
||||
err := c.cc.Invoke(ctx, Machine_Inspect_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MachineServer is the server API for Machine service.
|
||||
// All implementations must embed UnimplementedMachineServer
|
||||
// for forward compatibility.
|
||||
@@ -79,6 +91,7 @@ type MachineServer interface {
|
||||
InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error)
|
||||
JoinCluster(context.Context, *JoinClusterRequest) (*emptypb.Empty, error)
|
||||
Token(context.Context, *emptypb.Empty) (*TokenResponse, error)
|
||||
Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error)
|
||||
mustEmbedUnimplementedMachineServer()
|
||||
}
|
||||
|
||||
@@ -98,6 +111,9 @@ func (UnimplementedMachineServer) JoinCluster(context.Context, *JoinClusterReque
|
||||
func (UnimplementedMachineServer) Token(context.Context, *emptypb.Empty) (*TokenResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Token not implemented")
|
||||
}
|
||||
func (UnimplementedMachineServer) Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Inspect not implemented")
|
||||
}
|
||||
func (UnimplementedMachineServer) mustEmbedUnimplementedMachineServer() {}
|
||||
func (UnimplementedMachineServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -173,6 +189,24 @@ func _Machine_Token_Handler(srv interface{}, ctx context.Context, dec func(inter
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Machine_Inspect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MachineServer).Inspect(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Machine_Inspect_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MachineServer).Inspect(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Machine_ServiceDesc is the grpc.ServiceDesc for Machine service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -192,6 +226,10 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Token",
|
||||
Handler: _Machine_Token_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Inspect",
|
||||
Handler: _Machine_Inspect_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "internal/machine/api/pb/machine.proto",
|
||||
|
||||
@@ -443,3 +443,15 @@ func (m *Machine) Token(_ context.Context, _ *emptypb.Empty) (*pb.TokenResponse,
|
||||
|
||||
return &pb.TokenResponse{Token: tokenStr}, nil
|
||||
}
|
||||
|
||||
func (m *Machine) Inspect(_ context.Context, _ *emptypb.Empty) (*pb.MachineInfo, error) {
|
||||
return &pb.MachineInfo{
|
||||
Id: m.state.ID,
|
||||
Name: m.state.Name,
|
||||
Network: &pb.NetworkConfig{
|
||||
Subnet: pb.NewIPPrefix(m.state.Network.Subnet),
|
||||
ManagementIp: pb.NewIP(m.state.Network.ManagementIP),
|
||||
PublicKey: m.state.Network.PublicKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user