diff --git a/cmd/agent/container/setup.go b/cmd/agent/container/setup.go index 62e1d3413..7aa3490cd 100644 --- a/cmd/agent/container/setup.go +++ b/cmd/agent/container/setup.go @@ -180,7 +180,7 @@ func (cmd *SetupContainerCmd) Run(ctx context.Context) error { } // setup container - err = setup.SetupContainer(ctx, setupInfo, workspaceInfo.CLIOptions.WorkspaceEnv, cmd.ChownWorkspace, logger) + err = setup.SetupContainer(ctx, setupInfo, workspaceInfo.CLIOptions.WorkspaceEnv, cmd.ChownWorkspace, tunnelClient, logger) if err != nil { return err } diff --git a/cmd/agent/container_tunnel.go b/cmd/agent/container_tunnel.go index d47eca53f..817c552de 100644 --- a/cmd/agent/container_tunnel.go +++ b/cmd/agent/container_tunnel.go @@ -65,7 +65,7 @@ func (cmd *ContainerTunnelCmd) Run(ctx context.Context, log log.Logger) error { } // create runner - runner, err := workspace.CreateRunner(workspaceInfo, log) + runner, err := workspace.CreateRunner(workspaceInfo, nil, log) if err != nil { return err } diff --git a/cmd/agent/workspace/build.go b/cmd/agent/workspace/build.go index 78eaeb605..85254cc65 100644 --- a/cmd/agent/workspace/build.go +++ b/cmd/agent/workspace/build.go @@ -65,7 +65,7 @@ func (cmd *BuildCmd) Run(ctx context.Context) error { }() } - runner, err := CreateRunner(workspaceInfo, logger) + runner, err := CreateRunner(workspaceInfo, nil, logger) if err != nil { return err } diff --git a/cmd/agent/workspace/delete.go b/cmd/agent/workspace/delete.go index ba70378aa..166790de1 100644 --- a/cmd/agent/workspace/delete.go +++ b/cmd/agent/workspace/delete.go @@ -77,7 +77,7 @@ func (cmd *DeleteCmd) Run(ctx context.Context) error { func removeContainer(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { log.Debugf("Removing DevPod container from server...") - runner, err := CreateRunner(workspaceInfo, log) + runner, err := CreateRunner(workspaceInfo, nil, log) if err != nil { return err } diff --git a/cmd/agent/workspace/logs.go b/cmd/agent/workspace/logs.go index c714898c7..98625f96d 100644 --- a/cmd/agent/workspace/logs.go +++ b/cmd/agent/workspace/logs.go @@ -49,7 +49,7 @@ func (cmd *LogsCmd) Run(ctx context.Context) error { logger := log.Default.ErrorStreamOnly() // create new runner - runner, err := devcontainer.NewRunner(agent.ContainerDevPodHelperLocation, agent.DefaultAgentDownloadURL(), workspaceInfo, logger) + runner, err := devcontainer.NewRunner(agent.ContainerDevPodHelperLocation, agent.DefaultAgentDownloadURL(), workspaceInfo, nil, logger) if err != nil { return fmt.Errorf("create runner: %w", err) } diff --git a/cmd/agent/workspace/status.go b/cmd/agent/workspace/status.go index 7bb511f66..8ef1502c4 100644 --- a/cmd/agent/workspace/status.go +++ b/cmd/agent/workspace/status.go @@ -47,7 +47,7 @@ func (cmd *StatusCmd) Run(ctx context.Context, log log.Logger) error { } // create runner - runner, err := CreateRunner(workspaceInfo, log) + runner, err := CreateRunner(workspaceInfo, nil, log) if err != nil { return err } diff --git a/cmd/agent/workspace/stop.go b/cmd/agent/workspace/stop.go index 5a6501a93..9664eff61 100644 --- a/cmd/agent/workspace/stop.go +++ b/cmd/agent/workspace/stop.go @@ -57,7 +57,7 @@ func (cmd *StopCmd) Run(ctx context.Context) error { func stopContainer(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { log.Debugf("Stopping DevPod container...") - runner, err := CreateRunner(workspaceInfo, log) + runner, err := CreateRunner(workspaceInfo, nil, log) if err != nil { return err } diff --git a/cmd/agent/workspace/up.go b/cmd/agent/workspace/up.go index 536962ce8..98baf05b7 100644 --- a/cmd/agent/workspace/up.go +++ b/cmd/agent/workspace/up.go @@ -101,7 +101,7 @@ func (cmd *UpCmd) Run(ctx context.Context) error { func (cmd *UpCmd) up(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, tunnelClient tunnel.TunnelClient, logger log.Logger) error { // create devcontainer - result, err := cmd.devPodUp(ctx, workspaceInfo, logger) + result, err := cmd.devPodUp(ctx, workspaceInfo, tunnelClient, logger) if err != nil { return err } @@ -119,8 +119,8 @@ func (cmd *UpCmd) up(ctx context.Context, workspaceInfo *provider2.AgentWorkspac return nil } -func (cmd *UpCmd) devPodUp(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) (*config2.Result, error) { - runner, err := CreateRunner(workspaceInfo, log) +func (cmd *UpCmd) devPodUp(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, tunnelClient tunnel.TunnelClient, log log.Logger) (*config2.Result, error) { + runner, err := CreateRunner(workspaceInfo, tunnelClient, log) if err != nil { return nil, err } @@ -137,8 +137,8 @@ func (cmd *UpCmd) devPodUp(ctx context.Context, workspaceInfo *provider2.AgentWo return result, nil } -func CreateRunner(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) (devcontainer.Runner, error) { - return devcontainer.NewRunner(agent.ContainerDevPodHelperLocation, agent.DefaultAgentDownloadURL(), workspaceInfo, log) +func CreateRunner(workspaceInfo *provider2.AgentWorkspaceInfo, tunnelClient tunnel.TunnelClient, log log.Logger) (devcontainer.Runner, error) { + return devcontainer.NewRunner(agent.ContainerDevPodHelperLocation, agent.DefaultAgentDownloadURL(), workspaceInfo, tunnelClient, log) } func InitContentFolder(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) (bool, error) { diff --git a/cmd/ssh.go b/cmd/ssh.go index e68b896a6..5b4d14487 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -689,7 +689,7 @@ func (cmd *SSHCmd) jumpLocalProxyContainer(ctx context.Context, devPodConfig *co return err } - runner, err := workspace.CreateRunner(workspaceInfo, log) + runner, err := workspace.CreateRunner(workspaceInfo, nil, log) if err != nil { return err } diff --git a/pkg/agent/tunnel/tunnel.pb.go b/pkg/agent/tunnel/tunnel.pb.go index 8ead91ed4..dabffee2c 100644 --- a/pkg/agent/tunnel/tunnel.pb.go +++ b/pkg/agent/tunnel/tunnel.pb.go @@ -2,7 +2,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.35.1 +// protoc-gen-go v1.36.4 // protoc v5.27.3 // source: tunnel.proto @@ -13,6 +13,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -78,11 +79,10 @@ func (LogLevel) EnumDescriptor() ([]byte, []int) { } type StreamMountRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` unknownFields protoimpl.UnknownFields - - Mount string `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + sizeCache protoimpl.SizeCache } func (x *StreamMountRequest) Reset() { @@ -123,11 +123,10 @@ func (x *StreamMountRequest) GetMount() string { } type StopForwardPortRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty"` unknownFields protoimpl.UnknownFields - - Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty"` + sizeCache protoimpl.SizeCache } func (x *StopForwardPortRequest) Reset() { @@ -168,9 +167,9 @@ func (x *StopForwardPortRequest) GetPort() string { } type StopForwardPortResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *StopForwardPortResponse) Reset() { @@ -204,11 +203,10 @@ func (*StopForwardPortResponse) Descriptor() ([]byte, []int) { } type ForwardPortRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty"` unknownFields protoimpl.UnknownFields - - Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ForwardPortRequest) Reset() { @@ -249,9 +247,9 @@ func (x *ForwardPortRequest) GetPort() string { } type ForwardPortResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ForwardPortResponse) Reset() { @@ -285,11 +283,10 @@ func (*ForwardPortResponse) Descriptor() ([]byte, []int) { } type Message struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` unknownFields protoimpl.UnknownFields - - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Message) Reset() { @@ -330,11 +327,10 @@ func (x *Message) GetMessage() string { } type Chunk struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Content []byte `protobuf:"bytes,1,opt,name=Content,proto3" json:"Content,omitempty"` unknownFields protoimpl.UnknownFields - - Content []byte `protobuf:"bytes,1,opt,name=Content,proto3" json:"Content,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Chunk) Reset() { @@ -375,12 +371,11 @@ func (x *Chunk) GetContent() []byte { } type LogMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + LogLevel LogLevel `protobuf:"varint,1,opt,name=logLevel,proto3,enum=tunnel.LogLevel" json:"logLevel,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` unknownFields protoimpl.UnknownFields - - LogLevel LogLevel `protobuf:"varint,1,opt,name=logLevel,proto3,enum=tunnel.LogLevel" json:"logLevel,omitempty"` - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LogMessage) Reset() { @@ -428,9 +423,9 @@ func (x *LogMessage) GetMessage() string { } type Empty struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Empty) Reset() { @@ -465,7 +460,7 @@ func (*Empty) Descriptor() ([]byte, []int) { var File_tunnel_proto protoreflect.FileDescriptor -var file_tunnel_proto_rawDesc = []byte{ +var file_tunnel_proto_rawDesc = string([]byte{ 0x0a, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x22, 0x2a, 0x0a, 0x12, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, @@ -493,7 +488,7 @@ var file_tunnel_proto_rawDesc = []byte{ 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0x8d, 0x06, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0xbf, 0x06, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x03, 0x4c, 0x6f, 0x67, @@ -522,40 +517,43 @@ var file_tunnel_proto_rawDesc = []byte{ 0x47, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x0f, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, - 0x48, 0x0a, 0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1a, - 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, - 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x74, 0x75, 0x6e, - 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x0f, 0x53, 0x74, 0x6f, - 0x70, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x2e, 0x74, - 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, - 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x32, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x47, 0x69, 0x74, 0x43, 0x6c, 0x6f, 0x6e, - 0x65, 0x12, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, - 0x00, 0x30, 0x01, 0x12, 0x33, 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x43, - 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3c, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, - 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x6f, 0x66, 0x74, 0x2d, 0x73, 0x68, 0x2f, 0x64, 0x65, 0x76, - 0x70, 0x6f, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x75, - 0x6e, 0x6e, 0x65, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} + 0x30, 0x0a, 0x0a, 0x4b, 0x75, 0x62, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0f, 0x2e, + 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, + 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, + 0x12, 0x1a, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x74, + 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x0f, 0x53, + 0x74, 0x6f, 0x70, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1e, + 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x32, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x47, 0x69, 0x74, 0x43, 0x6c, + 0x6f, 0x6e, 0x65, 0x12, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x33, 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3c, 0x0a, 0x0b, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x74, 0x75, 0x6e, 0x6e, + 0x65, 0x6c, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x43, + 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x00, 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x6f, 0x66, 0x74, 0x2d, 0x73, 0x68, 0x2f, 0x64, + 0x65, 0x76, 0x70, 0x6f, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, + 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) var ( file_tunnel_proto_rawDescOnce sync.Once - file_tunnel_proto_rawDescData = file_tunnel_proto_rawDesc + file_tunnel_proto_rawDescData []byte ) func file_tunnel_proto_rawDescGZIP() []byte { file_tunnel_proto_rawDescOnce.Do(func() { - file_tunnel_proto_rawDescData = protoimpl.X.CompressGZIP(file_tunnel_proto_rawDescData) + file_tunnel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_tunnel_proto_rawDesc), len(file_tunnel_proto_rawDesc))) }) return file_tunnel_proto_rawDescData } @@ -585,27 +583,29 @@ var file_tunnel_proto_depIdxs = []int32{ 9, // 7: tunnel.Tunnel.GitUser:input_type -> tunnel.Empty 6, // 8: tunnel.Tunnel.LoftConfig:input_type -> tunnel.Message 6, // 9: tunnel.Tunnel.GPGPublicKeys:input_type -> tunnel.Message - 4, // 10: tunnel.Tunnel.ForwardPort:input_type -> tunnel.ForwardPortRequest - 2, // 11: tunnel.Tunnel.StopForwardPort:input_type -> tunnel.StopForwardPortRequest - 9, // 12: tunnel.Tunnel.StreamGitClone:input_type -> tunnel.Empty - 9, // 13: tunnel.Tunnel.StreamWorkspace:input_type -> tunnel.Empty - 1, // 14: tunnel.Tunnel.StreamMount:input_type -> tunnel.StreamMountRequest - 9, // 15: tunnel.Tunnel.Ping:output_type -> tunnel.Empty - 9, // 16: tunnel.Tunnel.Log:output_type -> tunnel.Empty - 9, // 17: tunnel.Tunnel.SendResult:output_type -> tunnel.Empty - 6, // 18: tunnel.Tunnel.DockerCredentials:output_type -> tunnel.Message - 6, // 19: tunnel.Tunnel.GitCredentials:output_type -> tunnel.Message - 6, // 20: tunnel.Tunnel.GitSSHSignature:output_type -> tunnel.Message - 6, // 21: tunnel.Tunnel.GitUser:output_type -> tunnel.Message - 6, // 22: tunnel.Tunnel.LoftConfig:output_type -> tunnel.Message - 6, // 23: tunnel.Tunnel.GPGPublicKeys:output_type -> tunnel.Message - 5, // 24: tunnel.Tunnel.ForwardPort:output_type -> tunnel.ForwardPortResponse - 3, // 25: tunnel.Tunnel.StopForwardPort:output_type -> tunnel.StopForwardPortResponse - 7, // 26: tunnel.Tunnel.StreamGitClone:output_type -> tunnel.Chunk - 7, // 27: tunnel.Tunnel.StreamWorkspace:output_type -> tunnel.Chunk - 7, // 28: tunnel.Tunnel.StreamMount:output_type -> tunnel.Chunk - 15, // [15:29] is the sub-list for method output_type - 1, // [1:15] is the sub-list for method input_type + 6, // 10: tunnel.Tunnel.KubeConfig:input_type -> tunnel.Message + 4, // 11: tunnel.Tunnel.ForwardPort:input_type -> tunnel.ForwardPortRequest + 2, // 12: tunnel.Tunnel.StopForwardPort:input_type -> tunnel.StopForwardPortRequest + 9, // 13: tunnel.Tunnel.StreamGitClone:input_type -> tunnel.Empty + 9, // 14: tunnel.Tunnel.StreamWorkspace:input_type -> tunnel.Empty + 1, // 15: tunnel.Tunnel.StreamMount:input_type -> tunnel.StreamMountRequest + 9, // 16: tunnel.Tunnel.Ping:output_type -> tunnel.Empty + 9, // 17: tunnel.Tunnel.Log:output_type -> tunnel.Empty + 9, // 18: tunnel.Tunnel.SendResult:output_type -> tunnel.Empty + 6, // 19: tunnel.Tunnel.DockerCredentials:output_type -> tunnel.Message + 6, // 20: tunnel.Tunnel.GitCredentials:output_type -> tunnel.Message + 6, // 21: tunnel.Tunnel.GitSSHSignature:output_type -> tunnel.Message + 6, // 22: tunnel.Tunnel.GitUser:output_type -> tunnel.Message + 6, // 23: tunnel.Tunnel.LoftConfig:output_type -> tunnel.Message + 6, // 24: tunnel.Tunnel.GPGPublicKeys:output_type -> tunnel.Message + 6, // 25: tunnel.Tunnel.KubeConfig:output_type -> tunnel.Message + 5, // 26: tunnel.Tunnel.ForwardPort:output_type -> tunnel.ForwardPortResponse + 3, // 27: tunnel.Tunnel.StopForwardPort:output_type -> tunnel.StopForwardPortResponse + 7, // 28: tunnel.Tunnel.StreamGitClone:output_type -> tunnel.Chunk + 7, // 29: tunnel.Tunnel.StreamWorkspace:output_type -> tunnel.Chunk + 7, // 30: tunnel.Tunnel.StreamMount:output_type -> tunnel.Chunk + 16, // [16:31] is the sub-list for method output_type + 1, // [1:16] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name @@ -620,7 +620,7 @@ func file_tunnel_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_tunnel_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_tunnel_proto_rawDesc), len(file_tunnel_proto_rawDesc)), NumEnums: 1, NumMessages: 9, NumExtensions: 0, @@ -632,7 +632,6 @@ func file_tunnel_proto_init() { MessageInfos: file_tunnel_proto_msgTypes, }.Build() File_tunnel_proto = out.File - file_tunnel_proto_rawDesc = nil file_tunnel_proto_goTypes = nil file_tunnel_proto_depIdxs = nil } diff --git a/pkg/agent/tunnel/tunnel.proto b/pkg/agent/tunnel/tunnel.proto index 4320e7b99..6b11a5a0f 100644 --- a/pkg/agent/tunnel/tunnel.proto +++ b/pkg/agent/tunnel/tunnel.proto @@ -16,6 +16,7 @@ service Tunnel { rpc GitUser(Empty) returns (Message) {} rpc LoftConfig(Message) returns (Message) {} rpc GPGPublicKeys(Message) returns (Message) {} + rpc KubeConfig(Message) returns (Message) {} rpc ForwardPort(ForwardPortRequest) returns (ForwardPortResponse) {} rpc StopForwardPort(StopForwardPortRequest) returns (StopForwardPortResponse) {} diff --git a/pkg/agent/tunnel/tunnel_grpc.pb.go b/pkg/agent/tunnel/tunnel_grpc.pb.go index bad5ae407..901b9d353 100644 --- a/pkg/agent/tunnel/tunnel_grpc.pb.go +++ b/pkg/agent/tunnel/tunnel_grpc.pb.go @@ -30,6 +30,7 @@ const ( Tunnel_GitUser_FullMethodName = "/tunnel.Tunnel/GitUser" Tunnel_LoftConfig_FullMethodName = "/tunnel.Tunnel/LoftConfig" Tunnel_GPGPublicKeys_FullMethodName = "/tunnel.Tunnel/GPGPublicKeys" + Tunnel_KubeConfig_FullMethodName = "/tunnel.Tunnel/KubeConfig" Tunnel_ForwardPort_FullMethodName = "/tunnel.Tunnel/ForwardPort" Tunnel_StopForwardPort_FullMethodName = "/tunnel.Tunnel/StopForwardPort" Tunnel_StreamGitClone_FullMethodName = "/tunnel.Tunnel/StreamGitClone" @@ -50,6 +51,7 @@ type TunnelClient interface { GitUser(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Message, error) LoftConfig(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) GPGPublicKeys(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) + KubeConfig(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) ForwardPort(ctx context.Context, in *ForwardPortRequest, opts ...grpc.CallOption) (*ForwardPortResponse, error) StopForwardPort(ctx context.Context, in *StopForwardPortRequest, opts ...grpc.CallOption) (*StopForwardPortResponse, error) StreamGitClone(ctx context.Context, in *Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Chunk], error) @@ -155,6 +157,16 @@ func (c *tunnelClient) GPGPublicKeys(ctx context.Context, in *Message, opts ...g return out, nil } +func (c *tunnelClient) KubeConfig(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Message) + err := c.cc.Invoke(ctx, Tunnel_KubeConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *tunnelClient) ForwardPort(ctx context.Context, in *ForwardPortRequest, opts ...grpc.CallOption) (*ForwardPortResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ForwardPortResponse) @@ -245,6 +257,7 @@ type TunnelServer interface { GitUser(context.Context, *Empty) (*Message, error) LoftConfig(context.Context, *Message) (*Message, error) GPGPublicKeys(context.Context, *Message) (*Message, error) + KubeConfig(context.Context, *Message) (*Message, error) ForwardPort(context.Context, *ForwardPortRequest) (*ForwardPortResponse, error) StopForwardPort(context.Context, *StopForwardPortRequest) (*StopForwardPortResponse, error) StreamGitClone(*Empty, grpc.ServerStreamingServer[Chunk]) error @@ -287,6 +300,9 @@ func (UnimplementedTunnelServer) LoftConfig(context.Context, *Message) (*Message func (UnimplementedTunnelServer) GPGPublicKeys(context.Context, *Message) (*Message, error) { return nil, status.Errorf(codes.Unimplemented, "method GPGPublicKeys not implemented") } +func (UnimplementedTunnelServer) KubeConfig(context.Context, *Message) (*Message, error) { + return nil, status.Errorf(codes.Unimplemented, "method KubeConfig not implemented") +} func (UnimplementedTunnelServer) ForwardPort(context.Context, *ForwardPortRequest) (*ForwardPortResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ForwardPort not implemented") } @@ -485,6 +501,24 @@ func _Tunnel_GPGPublicKeys_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Tunnel_KubeConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Message) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TunnelServer).KubeConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Tunnel_KubeConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TunnelServer).KubeConfig(ctx, req.(*Message)) + } + return interceptor(ctx, in, info, handler) +} + func _Tunnel_ForwardPort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ForwardPortRequest) if err := dec(in); err != nil { @@ -597,6 +631,10 @@ var Tunnel_ServiceDesc = grpc.ServiceDesc{ MethodName: "GPGPublicKeys", Handler: _Tunnel_GPGPublicKeys_Handler, }, + { + MethodName: "KubeConfig", + Handler: _Tunnel_KubeConfig_Handler, + }, { MethodName: "ForwardPort", Handler: _Tunnel_ForwardPort_Handler, diff --git a/pkg/agent/tunnelserver/options.go b/pkg/agent/tunnelserver/options.go index 18764ed49..9b04cf0de 100644 --- a/pkg/agent/tunnelserver/options.go +++ b/pkg/agent/tunnelserver/options.go @@ -1,6 +1,7 @@ package tunnelserver import ( + "github.com/loft-sh/devpod/pkg/agent/tunnel" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/netstat" provider2 "github.com/loft-sh/devpod/pkg/provider" @@ -36,6 +37,13 @@ func WithAllowDockerCredentials(allowDockerCredentials bool) Option { } } +func WithAllowKubeConfig(allow bool) Option { + return func(s *tunnelServer) *tunnelServer { + s.allowKubeConfig = allow + return s + } +} + func WithMounts(mounts []*config.Mount) Option { return func(s *tunnelServer) *tunnelServer { s.mounts = mounts @@ -52,3 +60,10 @@ func WithGitCredentialsOverride(username string, token string) Option { return s } } + +func WithTunnelClient(tunnelClient tunnel.TunnelClient) Option { + return func(s *tunnelServer) *tunnelServer { + s.tunnelClient = tunnelClient + return s + } +} diff --git a/pkg/agent/tunnelserver/tunnelserver.go b/pkg/agent/tunnelserver/tunnelserver.go index bafc2485d..c835fb074 100644 --- a/pkg/agent/tunnelserver/tunnelserver.go +++ b/pkg/agent/tunnelserver/tunnelserver.go @@ -21,6 +21,7 @@ import ( "github.com/loft-sh/devpod/pkg/gpg" "github.com/loft-sh/devpod/pkg/loftconfig" "github.com/loft-sh/devpod/pkg/netstat" + "github.com/loft-sh/devpod/pkg/platform" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/stdio" "github.com/loft-sh/log" @@ -53,11 +54,13 @@ func RunUpServer(ctx context.Context, reader io.Reader, writer io.WriteCloser, a return tunnelServ.RunWithResult(ctx, reader, writer) } -func RunSetupServer(ctx context.Context, reader io.Reader, writer io.WriteCloser, allowGitCredentials, allowDockerCredentials bool, mounts []*config.Mount, log log.Logger, options ...Option) (*config.Result, error) { +func RunSetupServer(ctx context.Context, reader io.Reader, writer io.WriteCloser, allowGitCredentials, allowDockerCredentials bool, mounts []*config.Mount, tunnelClient tunnel.TunnelClient, log log.Logger, options ...Option) (*config.Result, error) { opts := append(options, []Option{ WithMounts(mounts), WithAllowGitCredentials(allowGitCredentials), WithAllowDockerCredentials(allowDockerCredentials), + WithAllowKubeConfig(true), + WithTunnelClient(tunnelClient), }...) tunnelServ := New(log, opts...) @@ -84,10 +87,12 @@ type tunnelServer struct { forwarder netstat.Forwarder allowGitCredentials bool allowDockerCredentials bool + allowKubeConfig bool result *config.Result workspace *provider2.Workspace log log.Logger gitCredentialsOverride gitCredentialsOverride + tunnelClient tunnel.TunnelClient } type gitCredentialsOverride struct { @@ -292,6 +297,40 @@ func (t *tunnelServer) LoftConfig(ctx context.Context, message *tunnel.Message) return &tunnel.Message{Message: string(out)}, nil } +func (t *tunnelServer) KubeConfig(ctx context.Context, message *tunnel.Message) (*tunnel.Message, error) { + if !t.allowKubeConfig || t.tunnelClient == nil { + return nil, fmt.Errorf("kube config forbidden") + } + + // fetch loft config from host machine + req, err := json.Marshal(loftconfig.LoftConfigRequest{}) + if err != nil { + return nil, err + } + rawLoftConfigRes, err := t.tunnelClient.LoftConfig(ctx, &tunnel.Message{Message: string(req)}) + if err != nil { + return nil, fmt.Errorf("fetch loft config: %w", err) + } + loftConfigRes := &loftconfig.LoftConfigResponse{} + err = json.Unmarshal([]byte(rawLoftConfigRes.Message), loftConfigRes) + if err != nil { + return nil, fmt.Errorf("get loft config: %w", err) + } + + // get info from runner + spaceInstanceName := os.Getenv(platform.SpaceInstanceNameEnv) + virtualClusterInstanceName := os.Getenv(platform.VirtualClusterInstanceNameEnv) + namespace := os.Getenv(platform.InstanceNamespaceEnv) + + // create kubeconfig based on info + kubeConfig, err := platform.NewInstanceKubeConfig(ctx, loftConfigRes.LoftConfig, spaceInstanceName, virtualClusterInstanceName, namespace) + if err != nil { + return nil, fmt.Errorf("create kube config: %w", err) + } + + return &tunnel.Message{Message: string(kubeConfig)}, nil +} + func (t *tunnelServer) GPGPublicKeys(ctx context.Context, message *tunnel.Message) (*tunnel.Message, error) { rawPubKeys, err := gpg.GetHostPubKey() if err != nil { diff --git a/pkg/devcontainer/run.go b/pkg/devcontainer/run.go index 66e24cb2a..e78503b58 100644 --- a/pkg/devcontainer/run.go +++ b/pkg/devcontainer/run.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/loft-sh/devpod/pkg/agent/tunnel" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/driver" "github.com/loft-sh/devpod/pkg/driver/drivercreate" @@ -48,6 +49,7 @@ type Runner interface { func NewRunner( agentPath, agentDownloadURL string, workspaceConfig *provider2.AgentWorkspaceInfo, + tunnelClient tunnel.TunnelClient, log log.Logger, ) (Runner, error) { driver, err := drivercreate.NewDriver(workspaceConfig, log) @@ -64,12 +66,14 @@ func NewRunner( LocalWorkspaceFolder: workspaceConfig.ContentFolder, ID: GetRunnerIDFromWorkspace(workspaceConfig.Workspace), WorkspaceConfig: workspaceConfig, + TunnelClient: tunnelClient, Log: log, }, nil } type runner struct { - Driver driver.Driver + Driver driver.Driver + TunnelClient tunnel.TunnelClient WorkspaceConfig *provider2.AgentWorkspaceInfo AgentPath string diff --git a/pkg/devcontainer/setup.go b/pkg/devcontainer/setup.go index c0c713af2..95a8fd576 100644 --- a/pkg/devcontainer/setup.go +++ b/pkg/devcontainer/setup.go @@ -141,6 +141,7 @@ func (r *runner) setupContainer( r.WorkspaceConfig.Agent.InjectGitCredentials != "false", r.WorkspaceConfig.Agent.InjectDockerCredentials != "false", config.GetMounts(result), + r.TunnelClient, r.Log, ) }, diff --git a/pkg/devcontainer/setup/setup.go b/pkg/devcontainer/setup/setup.go index c40d56ae7..c24fe4047 100644 --- a/pkg/devcontainer/setup/setup.go +++ b/pkg/devcontainer/setup/setup.go @@ -9,19 +9,22 @@ import ( "sort" "strings" + "github.com/loft-sh/devpod/pkg/agent/tunnel" "github.com/loft-sh/devpod/pkg/command" copy2 "github.com/loft-sh/devpod/pkg/copy" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/envfile" "github.com/loft-sh/log" "github.com/pkg/errors" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) const ( ResultLocation = "/var/run/devpod/result.json" ) -func SetupContainer(ctx context.Context, setupInfo *config.Result, extraWorkspaceEnv []string, chownProjects bool, log log.Logger) error { +func SetupContainer(ctx context.Context, setupInfo *config.Result, extraWorkspaceEnv []string, chownProjects bool, tunnelClient tunnel.TunnelClient, log log.Logger) error { // write result to ResultLocation WriteResult(setupInfo, log) @@ -60,6 +63,11 @@ func SetupContainer(ctx context.Context, setupInfo *config.Result, extraWorkspac return errors.Wrap(err, "chown ssh agent sock file") } + err = SetupKubeConfig(ctx, setupInfo, tunnelClient, log) + if err != nil { + log.Errorf("Error setting up KubeConfig: %v", err) + } + // run commands log.Debugf("Run lifecycle hooks commands...") err = RunLifecycleHooks(ctx, setupInfo, log) @@ -234,6 +242,79 @@ func ChownAgentSock(setupInfo *config.Result) error { return nil } +func SetupKubeConfig(ctx context.Context, setupInfo *config.Result, tunnelClient tunnel.TunnelClient, log log.Logger) error { + exists, err := markerFileExists("setupKubeConfig", "") + if err != nil { + return err + } else if exists || tunnelClient == nil { + return nil + } + + // get kubernetes config from setup server + kubeConfigRes, err := tunnelClient.KubeConfig(ctx, &tunnel.Message{}) + if err != nil { + return err + } else if kubeConfigRes.Message == "" { + return nil + } + + log.Info("Setup KubeConfig") + user := config.GetRemoteUser(setupInfo) + homeDir, err := command.GetHome(user) + if err != nil { + return err + } + + kubeDir := filepath.Join(homeDir, ".kube") + err = os.Mkdir(kubeDir, 0755) + if err != nil && !errors.Is(err, os.ErrExist) { + return err + } + + configPath := filepath.Join(kubeDir, "config") + existingConfig, err := clientcmd.LoadFromFile(configPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + if existingConfig == nil { + existingConfig = clientcmdapi.NewConfig() + } + + kubeConfig, err := clientcmd.Load([]byte(kubeConfigRes.Message)) + if err != nil { + return err + } + // merge with existing kubeConfig + for name, cluster := range kubeConfig.Clusters { + existingConfig.Clusters[name] = cluster + } + for name, authInfo := range kubeConfig.AuthInfos { + existingConfig.AuthInfos[name] = authInfo + } + for name, context := range kubeConfig.Contexts { + existingConfig.Contexts[name] = context + } + + // Set the current context to the new one. + // This might not always be the correct choice but given that someone + // explicitly required this workspace to be in a virtual cluster/space + // it's fair to assume they also want to point the current context to it + existingConfig.CurrentContext = kubeConfig.CurrentContext + + err = clientcmd.WriteToFile(*existingConfig, configPath) + if err != nil { + return err + } + + // ensure `remoteUser` owns kubeConfig + err = copy2.ChownR(kubeDir, user) + if err != nil { + return err + } + + return nil +} + func markerFileExists(markerName string, markerContent string) (bool, error) { markerName = filepath.Join("/var/devpod", markerName+".marker") t, err := os.ReadFile(markerName) diff --git a/pkg/platform/annotations/annotations.go b/pkg/platform/annotations/annotations.go new file mode 100644 index 000000000..6f0140c80 --- /dev/null +++ b/pkg/platform/annotations/annotations.go @@ -0,0 +1,5 @@ +package annotations + +// LoftDirectClusterEndpoint is a cluster annotation that tells clients to use this endpoint instead of +// the default loft server address to connect to this cluster. +const LoftDirectClusterEndpoint = "loft.sh/direct-cluster-endpoint" diff --git a/pkg/platform/env.go b/pkg/platform/env.go index 5aca78399..4015a14e1 100644 --- a/pkg/platform/env.go +++ b/pkg/platform/env.go @@ -18,4 +18,8 @@ const ( TimeoutEnv = "LOFT_TIMEOUT" ProviderBinaryEnv = "PRO_PROVIDER" + + SpaceInstanceNameEnv = "LOFT_SPACE_INSTANCE_NAME" + VirtualClusterInstanceNameEnv = "LOFT_VIRTUAL_CLUSTER_INSTANCE_NAME" + InstanceNamespaceEnv = "LOFT_INSTANCE_NAMESPACE" ) diff --git a/pkg/platform/kubeconfig.go b/pkg/platform/kubeconfig.go new file mode 100644 index 000000000..2721234ee --- /dev/null +++ b/pkg/platform/kubeconfig.go @@ -0,0 +1,253 @@ +package platform + +import ( + "context" + "fmt" + "strings" + "time" + + managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" + storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" + "github.com/loft-sh/devpod/pkg/platform/annotations" + "github.com/loft-sh/devpod/pkg/platform/client" + "github.com/loft-sh/devpod/pkg/platform/project" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +var configTTL time.Duration = time.Hour * 24 * 365 + +func NewInstanceKubeConfig(ctx context.Context, loftConfig *client.Config, spaceInstanceName, virtualClusterInstanceName, namespace string) ([]byte, error) { + if spaceInstanceName == "" && virtualClusterInstanceName == "" { + // nothing to do here + return nil, nil + } + if spaceInstanceName != "" && virtualClusterInstanceName != "" { + return nil, fmt.Errorf("cannot use virtual cluster and space instance together") + } + if namespace == "" { + return nil, fmt.Errorf("namespace missing") + } + + baseClient := client.NewClientFromConfig(loftConfig) + err := baseClient.RefreshSelf(ctx) + if err != nil { + return nil, fmt.Errorf("refresh self: %w", err) + } + + var kubeConfig *clientcmdapi.Config + if spaceInstanceName != "" { + kubeConfig, err = kubeConfigForSpaceInstance(ctx, baseClient, spaceInstanceName, namespace) + if err != nil { + return nil, err + } + } else if virtualClusterInstanceName != "" { + kubeConfig, err = kubeConfigForVirtualClusterInstance(ctx, baseClient, virtualClusterInstanceName, namespace) + if err != nil { + return nil, err + } + } + + return clientcmd.Write(*kubeConfig) +} + +func kubeConfigForSpaceInstance(ctx context.Context, baseClient client.Client, spaceInstanceName string, namespace string) (*clientcmdapi.Config, error) { + projectName := project.ProjectFromNamespace(namespace) + managementClient, err := baseClient.Management() + if err != nil { + return nil, err + } + + spaceInstance, err := managementClient.Loft().ManagementV1().SpaceInstances(namespace).Get(ctx, spaceInstanceName, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("get space instance: %w", err) + } + + // find cluster by clusterRef + hostCluster, err := findHostCluster(ctx, baseClient, projectName, spaceInstance.Spec.ClusterRef) + if err != nil { + return nil, fmt.Errorf("find host cluster: %w", err) + } + + scope := &storagev1.AccessKeyScope{ + Spaces: []storagev1.AccessKeyScopeSpace{{ + Project: projectName, + Space: spaceInstance.Name, + }}, + } + ttl := int64(configTTL.Seconds()) + + // direct cluster access? + if hostCluster.GetAnnotations()[annotations.LoftDirectClusterEndpoint] != "" { + tok := &managementv1.DirectClusterEndpointToken{ + Spec: managementv1.DirectClusterEndpointTokenSpec{ + Scope: scope, + TTL: ttl, + }, + } + directClusterEndpointToken, err := managementClient.Loft().ManagementV1().DirectClusterEndpointTokens().Create(ctx, tok, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("create direct cluster endpoint token: %w", err) + } + + directClusterEndpoint := hostCluster.GetAnnotations()[annotations.LoftDirectClusterEndpoint] + host := fmt.Sprintf("https://%s/kubernetes/project/%s/space/%s", directClusterEndpoint, projectName, spaceInstance.Name) + + return newKubeConfig(host, directClusterEndpointToken.Status.Token, spaceInstance.Spec.ClusterRef.Namespace, true), nil + } + + // access through management cluster + access key + key := &managementv1.OwnedAccessKey{ + Spec: managementv1.OwnedAccessKeySpec{ + AccessKeySpec: storagev1.AccessKeySpec{ + User: baseClient.Self().Status.User.Name, + Scope: scope, + TTL: ttl, + DisplayName: fmt.Sprintf("Kube Config for Space %s/%s", spaceInstance.Namespace, spaceInstance.Name), + }, + }, + } + ownedAccessKey, err := managementClient.Loft().ManagementV1().OwnedAccessKeys().Create(ctx, key, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("create access key: %w", err) + } + hostName := strings.TrimPrefix(strings.TrimPrefix(baseClient.Config().Host, "https://"), "https://") + host := fmt.Sprintf("https://%s/kubernetes/project/%s/space/%s", hostName, projectName, spaceInstance.Name) + + return newKubeConfig(host, ownedAccessKey.Spec.Key, spaceInstance.Spec.ClusterRef.Namespace, true), nil +} + +func kubeConfigForVirtualClusterInstance(ctx context.Context, baseClient client.Client, virtualClusterInstanceName string, namespace string) (*clientcmdapi.Config, error) { + projectName := project.ProjectFromNamespace(namespace) + managementClient, err := baseClient.Management() + if err != nil { + return nil, err + } + + virtualClusterInstance, err := managementClient.Loft().ManagementV1().VirtualClusterInstances(namespace).Get(ctx, virtualClusterInstanceName, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("get virtual cluster instance: %w", err) + } + + scope := &storagev1.AccessKeyScope{ + VirtualClusters: []storagev1.AccessKeyScopeVirtualCluster{{ + Project: projectName, + VirtualCluster: virtualClusterInstance.Name, + }}, + } + ttl := int64(configTTL.Seconds()) + + // direct virtual cluster ingress access? + virtualCluster := virtualClusterInstance.Status.VirtualCluster + if virtualCluster != nil && virtualCluster.AccessPoint.Ingress.Enabled { + certTTL := int32(ttl) + config := &managementv1.VirtualClusterInstanceKubeConfig{ + Spec: managementv1.VirtualClusterInstanceKubeConfigSpec{ + CertificateTTL: &certTTL, + }, + } + directVirtualClusterKubeConfig, err := managementClient.Loft().ManagementV1().VirtualClusterInstances(namespace). + GetKubeConfig(ctx, virtualClusterInstance.Name, config, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("create direct cluster endpoint token: %w", err) + } + + kubeConfig, err := clientcmd.Load([]byte(directVirtualClusterKubeConfig.Status.KubeConfig)) + if err != nil { + return nil, err + } + + return kubeConfig, nil + } + + // find cluster by clusterRef + hostCluster, err := findHostCluster(ctx, baseClient, projectName, virtualClusterInstance.Spec.ClusterRef.ClusterRef) + if err != nil { + return nil, fmt.Errorf("find host cluster: %w", err) + } + + // direct cluster access? + if hostCluster.GetAnnotations()[annotations.LoftDirectClusterEndpoint] != "" { + tok := &managementv1.DirectClusterEndpointToken{ + Spec: managementv1.DirectClusterEndpointTokenSpec{ + Scope: scope, + TTL: ttl, + }, + } + directClusterEndpointToken, err := managementClient.Loft().ManagementV1().DirectClusterEndpointTokens().Create(ctx, tok, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("create direct cluster endpoint token: %w", err) + } + + directClusterEndpoint := hostCluster.GetAnnotations()[annotations.LoftDirectClusterEndpoint] + host := fmt.Sprintf("https://%s/kubernetes/project/%s/virtualcluster/%s", directClusterEndpoint, projectName, virtualClusterInstance.Name) + + return newKubeConfig(host, directClusterEndpointToken.Status.Token, virtualClusterInstance.Spec.ClusterRef.Namespace, true), nil + } + + // access through management cluster + access key + key := &managementv1.OwnedAccessKey{ + Spec: managementv1.OwnedAccessKeySpec{ + AccessKeySpec: storagev1.AccessKeySpec{ + User: baseClient.Self().Status.User.Name, + Scope: scope, + TTL: ttl, + DisplayName: fmt.Sprintf("Kube Config for Virtual Cluster %s/%s", virtualClusterInstance.Namespace, virtualClusterInstance.Name), + }, + }, + } + ownedAccessKey, err := managementClient.Loft().ManagementV1().OwnedAccessKeys().Create(ctx, key, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("create access key: %w", err) + } + hostName := strings.TrimPrefix(strings.TrimPrefix(baseClient.Config().Host, "https://"), "https://") + host := fmt.Sprintf("https://%s/kubernetes/project/%s/virtualcluster/%s", hostName, projectName, virtualClusterInstance.Name) + + return newKubeConfig(host, ownedAccessKey.Spec.Key, virtualClusterInstance.Spec.ClusterRef.Namespace, true), nil +} + +func findHostCluster(ctx context.Context, baseClient client.Client, projectName string, clusterRef storagev1.ClusterRef) (managementv1.Cluster, error) { + managementClient, err := baseClient.Management() + if err != nil { + return managementv1.Cluster{}, err + } + projectClusters, err := managementClient.Loft().ManagementV1().Projects().ListClusters(ctx, projectName, metav1.GetOptions{}) + if err != nil { + return managementv1.Cluster{}, fmt.Errorf("get project clusters: %w", err) + } + + for _, cluster := range projectClusters.Clusters { + if clusterRef.Cluster == cluster.GetName() { + return cluster, nil + } + } + + return managementv1.Cluster{}, nil +} + +func newKubeConfig(host, token, namespace string, insecure bool) *clientcmdapi.Config { + contextName := "loft" + kubeConfig := clientcmdapi.NewConfig() + kubeConfig.Contexts = map[string]*clientcmdapi.Context{ + contextName: { + Cluster: contextName, + AuthInfo: contextName, + Namespace: namespace, + }, + } + kubeConfig.Clusters = map[string]*clientcmdapi.Cluster{ + contextName: { + Server: host, + InsecureSkipTLSVerify: insecure, + }, + } + kubeConfig.AuthInfos = map[string]*clientcmdapi.AuthInfo{ + contextName: { + Token: token, + }, + } + kubeConfig.CurrentContext = contextName + + return kubeConfig +}