Andrey Smirnov 951904554e
chore: bump dependencies (go 1.22.2)
Update Go to 1.22.2, update Go modules to resolve
[HTTP/2 issue](https://www.kb.cert.org/vuls/id/421644).

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2024-04-04 14:59:24 +04:00

1111 lines
32 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package client provides Talos API client.
package client
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"time"
cosiv1alpha1 "github.com/cosi-project/runtime/api/v1alpha1"
"github.com/cosi-project/runtime/pkg/state"
"github.com/cosi-project/runtime/pkg/state/protobuf/client"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/emptypb"
clusterapi "github.com/siderolabs/talos/pkg/machinery/api/cluster"
"github.com/siderolabs/talos/pkg/machinery/api/common"
inspectapi "github.com/siderolabs/talos/pkg/machinery/api/inspect"
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
storageapi "github.com/siderolabs/talos/pkg/machinery/api/storage"
timeapi "github.com/siderolabs/talos/pkg/machinery/api/time"
clientconfig "github.com/siderolabs/talos/pkg/machinery/client/config"
)
// Client implements the proto.MachineServiceClient interface. It serves as the
// concrete type with the required methods.
type Client struct {
options *Options
conn *grpcConnectionWrapper
MachineClient machineapi.MachineServiceClient
TimeClient timeapi.TimeServiceClient
ClusterClient clusterapi.ClusterServiceClient
StorageClient storageapi.StorageServiceClient
InspectClient inspectapi.InspectServiceClient
COSI state.State
Inspect *InspectClient
}
func (c *Client) resolveConfigContext() error {
var ok bool
if c.options.unixSocketPath != "" {
return nil
}
if c.options.configContext != nil {
return nil
}
if c.options.config == nil {
if err := WithDefaultConfig()(c.options); err != nil {
return fmt.Errorf("failed to load default config: %w", err)
}
}
if c.options.contextOverrideSet {
c.options.configContext, ok = c.options.config.Contexts[c.options.contextOverride]
if !ok {
return fmt.Errorf("context %q not found in config", c.options.contextOverride)
}
return nil
}
c.options.configContext, ok = c.options.config.Contexts[c.options.config.Context]
if !ok {
if c.options.config.Context == "" && len(c.options.config.Contexts) == 0 {
return errors.New("talos config file is empty")
}
return fmt.Errorf("default context %q not found in config", c.options.config.Context)
}
return nil
}
// GetConfigContext returns resolved config context.
func (c *Client) GetConfigContext() *clientconfig.Context {
if err := c.resolveConfigContext(); err != nil {
return nil
}
return c.options.configContext
}
// GetEndpoints returns the client's endpoints from the override set with WithEndpoints
// or from the configuration.
func (c *Client) GetEndpoints() []string {
if c.options.unixSocketPath != "" {
return []string{c.options.unixSocketPath}
}
if len(c.options.endpointsOverride) > 0 {
return c.options.endpointsOverride
}
if c.options.configContext != nil {
return c.options.configContext.Endpoints
}
if c.options.config != nil {
if err := c.resolveConfigContext(); err != nil {
return nil
}
return c.options.configContext.Endpoints
}
return nil
}
// GetClusterName returns the client's cluster name from the override set with WithClustername
// or from the configuration.
func (c *Client) GetClusterName() string {
if c.options.clusterNameOverride != "" {
return c.options.clusterNameOverride
}
if c.options.config != nil {
if err := c.resolveConfigContext(); err != nil {
return ""
}
if c.options.configContext != nil {
return c.options.configContext.Cluster
}
}
return ""
}
// New returns a new Client.
func New(ctx context.Context, opts ...OptionFunc) (c *Client, err error) {
c = new(Client)
c.options = new(Options)
for _, opt := range opts {
if err = opt(c.options); err != nil {
return nil, err
}
}
if len(c.GetEndpoints()) < 1 {
return nil, errors.New("failed to determine endpoints")
}
c.conn, err = c.getConn()
if err != nil {
return nil, fmt.Errorf("failed to create client connection: %w", err)
}
c.MachineClient = machineapi.NewMachineServiceClient(c.conn)
c.TimeClient = timeapi.NewTimeServiceClient(c.conn)
c.ClusterClient = clusterapi.NewClusterServiceClient(c.conn)
c.StorageClient = storageapi.NewStorageServiceClient(c.conn)
c.InspectClient = inspectapi.NewInspectServiceClient(c.conn)
c.Inspect = &InspectClient{c.InspectClient}
c.COSI = state.WrapCore(client.NewAdapter(cosiv1alpha1.NewStateClient(c.conn)))
return c, nil
}
// Close shuts down client protocol.
func (c *Client) Close() error {
return c.conn.Close()
}
// KubeconfigRaw returns K8s client config (kubeconfig).
//
// This method doesn't support multiplexing of the result:
// * either client.WithNodes is not used, or it contains a single node in the list.
func (c *Client) KubeconfigRaw(ctx context.Context) (io.ReadCloser, error) {
stream, err := c.MachineClient.Kubeconfig(ctx, &emptypb.Empty{})
if err != nil {
return nil, err
}
return ReadStream(stream)
}
func (c *Client) extractKubeconfig(r io.ReadCloser) ([]byte, error) {
defer r.Close() //nolint:errcheck
gzR, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
// returned .tar.gz should contain only single file (kubeconfig)
var kubeconfigBuf bytes.Buffer
tar := tar.NewReader(gzR)
for {
_, err = tar.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
_, err = io.Copy(&kubeconfigBuf, tar)
if err != nil {
return nil, err
}
}
if err = gzR.Close(); err != nil {
return nil, err
}
return kubeconfigBuf.Bytes(), nil
}
// Kubeconfig returns K8s client config (kubeconfig).
func (c *Client) Kubeconfig(ctx context.Context) ([]byte, error) {
r, err := c.KubeconfigRaw(ctx)
if err != nil {
return nil, err
}
return c.extractKubeconfig(r)
}
// ApplyConfiguration implements proto.MachineServiceClient interface.
func (c *Client) ApplyConfiguration(ctx context.Context, req *machineapi.ApplyConfigurationRequest, callOptions ...grpc.CallOption) (resp *machineapi.ApplyConfigurationResponse, err error) {
resp, err = c.MachineClient.ApplyConfiguration(ctx, req, callOptions...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ApplyConfigurationResponse) //nolint:errcheck
return
}
// GenerateConfiguration implements proto.MachineServiceClient interface.
func (c *Client) GenerateConfiguration(ctx context.Context, req *machineapi.GenerateConfigurationRequest, callOptions ...grpc.CallOption) (resp *machineapi.GenerateConfigurationResponse, err error) {
resp, err = c.MachineClient.GenerateConfiguration(ctx, req, callOptions...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.GenerateConfigurationResponse) //nolint:errcheck
return
}
// Disks returns the list of block devices.
func (c *Client) Disks(ctx context.Context, callOptions ...grpc.CallOption) (resp *storageapi.DisksResponse, err error) {
resp, err = c.StorageClient.Disks(ctx, &emptypb.Empty{}, callOptions...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*storageapi.DisksResponse) //nolint:errcheck
return
}
// Stats implements the proto.MachineServiceClient interface.
func (c *Client) Stats(ctx context.Context, namespace string, driver common.ContainerDriver, callOptions ...grpc.CallOption) (resp *machineapi.StatsResponse, err error) {
resp, err = c.MachineClient.Stats(
ctx, &machineapi.StatsRequest{
Namespace: namespace,
Driver: driver,
},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.StatsResponse) //nolint:errcheck
return
}
// Containers implements the proto.MachineServiceClient interface.
func (c *Client) Containers(ctx context.Context, namespace string, driver common.ContainerDriver, callOptions ...grpc.CallOption) (resp *machineapi.ContainersResponse, err error) {
resp, err = c.MachineClient.Containers(
ctx,
&machineapi.ContainersRequest{
Namespace: namespace,
Driver: driver,
},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ContainersResponse) //nolint:errcheck
return
}
// Restart implements the proto.MachineServiceClient interface.
func (c *Client) Restart(ctx context.Context, namespace string, driver common.ContainerDriver, id string, callOptions ...grpc.CallOption) (err error) {
resp, err := c.MachineClient.Restart(ctx, &machineapi.RestartRequest{
Id: id,
Namespace: namespace,
Driver: driver,
})
if err == nil {
_, err = FilterMessages(resp, err)
}
return
}
// Reset implements the proto.MachineServiceClient interface.
func (c *Client) Reset(ctx context.Context, graceful, reboot bool) (err error) {
resp, err := c.MachineClient.Reset(ctx, &machineapi.ResetRequest{Graceful: graceful, Reboot: reboot})
if err == nil {
_, err = FilterMessages(resp, err)
}
return
}
// ResetGeneric implements the proto.MachineServiceClient interface.
func (c *Client) ResetGeneric(ctx context.Context, req *machineapi.ResetRequest) error {
_, err := c.ResetGenericWithResponse(ctx, req)
return err
}
// ResetGenericWithResponse resets the machine and returns the response.
func (c *Client) ResetGenericWithResponse(ctx context.Context, req *machineapi.ResetRequest) (*machineapi.ResetResponse, error) {
resp, err := c.MachineClient.Reset(ctx, req)
if err == nil {
_, err = FilterMessages(resp, err)
}
return resp, err
}
// RebootMode provides various mode through which the reboot process can be done.
type RebootMode func(*machineapi.RebootRequest)
// WithPowerCycle option runs the Reboot fun in powercycle mode.
func WithPowerCycle(req *machineapi.RebootRequest) {
req.Mode = machineapi.RebootRequest_POWERCYCLE
}
// Reboot implements the proto.MachineServiceClient interface.
func (c *Client) Reboot(ctx context.Context, opts ...RebootMode) error {
_, err := c.RebootWithResponse(ctx, opts...)
return err
}
// RebootWithResponse reboots the machine and returns the response.
func (c *Client) RebootWithResponse(ctx context.Context, opts ...RebootMode) (*machineapi.RebootResponse, error) {
var req machineapi.RebootRequest
for _, opt := range opts {
opt(&req)
}
resp, err := c.MachineClient.Reboot(ctx, &req)
if err == nil {
_, err = FilterMessages(resp, err)
}
return resp, err
}
// Rollback implements the proto.MachineServiceClient interface.
func (c *Client) Rollback(ctx context.Context) (err error) {
resp, err := c.MachineClient.Rollback(ctx, &machineapi.RollbackRequest{})
if err == nil {
_, err = FilterMessages(resp, err)
}
return
}
// Bootstrap implements the proto.MachineServiceClient interface.
func (c *Client) Bootstrap(ctx context.Context, req *machineapi.BootstrapRequest) (err error) {
resp, err := c.MachineClient.Bootstrap(ctx, req)
if err == nil {
_, err = FilterMessages(resp, err)
}
return
}
// ShutdownOption provides shutdown API options.
type ShutdownOption func(*machineapi.ShutdownRequest)
// WithShutdownForce forces the shutdown even if the Kubernetes API is down.
func WithShutdownForce(force bool) ShutdownOption {
return func(req *machineapi.ShutdownRequest) {
req.Force = force
}
}
// Shutdown implements the proto.MachineServiceClient interface.
func (c *Client) Shutdown(ctx context.Context, opts ...ShutdownOption) error {
_, err := c.ShutdownWithResponse(ctx, opts...)
return err
}
// ShutdownWithResponse shuts down the machine and returns the response.
func (c *Client) ShutdownWithResponse(ctx context.Context, opts ...ShutdownOption) (*machineapi.ShutdownResponse, error) {
var req machineapi.ShutdownRequest
for _, opt := range opts {
opt(&req)
}
resp, err := c.MachineClient.Shutdown(ctx, &req)
if err == nil {
_, err = FilterMessages(resp, err)
}
return resp, err
}
// Dmesg implements the proto.MachineServiceClient interface.
func (c *Client) Dmesg(ctx context.Context, follow, tail bool) (machineapi.MachineService_DmesgClient, error) {
return c.MachineClient.Dmesg(ctx, &machineapi.DmesgRequest{
Follow: follow,
Tail: tail,
})
}
// Logs implements the proto.MachineServiceClient interface.
func (c *Client) Logs(ctx context.Context, namespace string, driver common.ContainerDriver, id string, follow bool, tailLines int32) (stream machineapi.MachineService_LogsClient, err error) {
stream, err = c.MachineClient.Logs(ctx, &machineapi.LogsRequest{
Namespace: namespace,
Driver: driver,
Id: id,
Follow: follow,
TailLines: tailLines,
})
return
}
// LogsContainers implements the proto.MachineServiceClient interface.
func (c *Client) LogsContainers(ctx context.Context, callOptions ...grpc.CallOption) (resp *machineapi.LogsContainersResponse, err error) {
resp, err = c.MachineClient.LogsContainers(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.LogsContainersResponse) //nolint:errcheck
return
}
// Version implements the proto.MachineServiceClient interface.
func (c *Client) Version(ctx context.Context, callOptions ...grpc.CallOption) (resp *machineapi.VersionResponse, err error) {
resp, err = c.MachineClient.Version(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.VersionResponse) //nolint:errcheck
return
}
// Processes implements the proto.MachineServiceClient interface.
func (c *Client) Processes(ctx context.Context, callOptions ...grpc.CallOption) (resp *machineapi.ProcessesResponse, err error) {
resp, err = c.MachineClient.Processes(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ProcessesResponse) //nolint:errcheck
return
}
// Memory implements the proto.MachineServiceClient interface.
func (c *Client) Memory(ctx context.Context, callOptions ...grpc.CallOption) (resp *machineapi.MemoryResponse, err error) {
resp, err = c.MachineClient.Memory(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.MemoryResponse) //nolint:errcheck
return
}
// Mounts implements the proto.MachineServiceClient interface.
func (c *Client) Mounts(ctx context.Context, callOptions ...grpc.CallOption) (resp *machineapi.MountsResponse, err error) {
resp, err = c.MachineClient.Mounts(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.MountsResponse) //nolint:errcheck
return
}
// LS implements the proto.MachineServiceClient interface.
func (c *Client) LS(ctx context.Context, req *machineapi.ListRequest) (stream machineapi.MachineService_ListClient, err error) {
return c.MachineClient.List(ctx, req)
}
// DiskUsage implements the proto.MachineServiceClient interface.
func (c *Client) DiskUsage(ctx context.Context, req *machineapi.DiskUsageRequest) (stream machineapi.MachineService_DiskUsageClient, err error) {
return c.MachineClient.DiskUsage(ctx, req)
}
// Copy implements the proto.MachineServiceClient interface.
//
// This method doesn't support multiplexing of the result:
// * either client.WithNodes is not used, or it contains a single node in the list.
func (c *Client) Copy(ctx context.Context, rootPath string) (io.ReadCloser, error) {
stream, err := c.MachineClient.Copy(ctx, &machineapi.CopyRequest{
RootPath: rootPath,
})
if err != nil {
return nil, err
}
return ReadStream(stream)
}
// UpgradeOptions provides upgrade API options.
type UpgradeOptions struct {
Request machineapi.UpgradeRequest
GRPCCallOptions []grpc.CallOption
}
// UpgradeOption provides upgrade API options.
type UpgradeOption func(*UpgradeOptions)
// WithUpgradeImage sets the image for the upgrade.
func WithUpgradeImage(image string) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Image = image
}
}
// WithUpgradeRebootMode sets the reboot mode for the upgrade.
func WithUpgradeRebootMode(mode machineapi.UpgradeRequest_RebootMode) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.RebootMode = mode
}
}
// WithUpgradePreserve sets the preserve flag for the upgrade.
func WithUpgradePreserve(preserve bool) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Preserve = preserve
}
}
// WithUpgradeStage sets the stage flag for the upgrade.
func WithUpgradeStage(stage bool) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Stage = stage
}
}
// WithUpgradeForce sets the force flag for the upgrade.
func WithUpgradeForce(force bool) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Force = force
}
}
// WithUpgradeGRPCCallOptions sets the gRPC call options for the upgrade.
func WithUpgradeGRPCCallOptions(opts ...grpc.CallOption) UpgradeOption {
return func(req *UpgradeOptions) {
req.GRPCCallOptions = opts
}
}
// Upgrade initiates a Talos upgrade and implements the proto.MachineServiceClient interface.
func (c *Client) Upgrade(ctx context.Context, image string, preserve, stage, force bool, callOptions ...grpc.CallOption) (*machineapi.UpgradeResponse, error) {
return c.UpgradeWithOptions(
ctx,
WithUpgradeImage(image),
WithUpgradeRebootMode(machineapi.UpgradeRequest_DEFAULT),
WithUpgradePreserve(preserve),
WithUpgradeStage(stage),
WithUpgradeForce(force),
WithUpgradeGRPCCallOptions(callOptions...),
)
}
// UpgradeWithOptions initiates a Talos upgrade with the given options.
func (c *Client) UpgradeWithOptions(ctx context.Context, opts ...UpgradeOption) (*machineapi.UpgradeResponse, error) {
var options UpgradeOptions
for _, opt := range opts {
opt(&options)
}
resp, err := c.MachineClient.Upgrade(ctx, &options.Request, options.GRPCCallOptions...)
var filtered any
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.UpgradeResponse) //nolint:errcheck
return resp, err
}
// ServiceList returns list of services with their state.
func (c *Client) ServiceList(ctx context.Context, callOptions ...grpc.CallOption) (resp *machineapi.ServiceListResponse, err error) {
resp, err = c.MachineClient.ServiceList(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ServiceListResponse) //nolint:errcheck
return
}
// ServiceInfo provides info about a service and node metadata.
type ServiceInfo struct {
Metadata *common.Metadata
Service *machineapi.ServiceInfo
}
// ServiceInfo returns info about a single service
//
// This is implemented via service list API, as we don't have many services
// If service with given id is not registered, function returns nil.
func (c *Client) ServiceInfo(ctx context.Context, id string, callOptions ...grpc.CallOption) (services []ServiceInfo, err error) {
var resp *machineapi.ServiceListResponse
resp, err = c.MachineClient.ServiceList(
ctx,
&emptypb.Empty{},
callOptions...,
)
if err != nil {
return services, err
}
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ServiceListResponse) //nolint:errcheck
// FilterMessages might remove responses if they actually contain errors,
// errors will be merged into `resp`.
if resp == nil {
return services, err
}
for _, resp := range resp.Messages {
for _, svc := range resp.Services {
if svc.Id == id {
services = append(services, ServiceInfo{
Metadata: resp.Metadata,
Service: svc,
})
}
}
}
return services, err
}
// ServiceStart starts a service.
func (c *Client) ServiceStart(ctx context.Context, id string, callOptions ...grpc.CallOption) (resp *machineapi.ServiceStartResponse, err error) {
resp, err = c.MachineClient.ServiceStart(
ctx,
&machineapi.ServiceStartRequest{Id: id},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ServiceStartResponse) //nolint:errcheck
return
}
// ServiceStop stops a service.
func (c *Client) ServiceStop(ctx context.Context, id string, callOptions ...grpc.CallOption) (resp *machineapi.ServiceStopResponse, err error) {
resp, err = c.MachineClient.ServiceStop(
ctx,
&machineapi.ServiceStopRequest{Id: id},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ServiceStopResponse) //nolint:errcheck
return
}
// ServiceRestart restarts a service.
func (c *Client) ServiceRestart(ctx context.Context, id string, callOptions ...grpc.CallOption) (resp *machineapi.ServiceRestartResponse, err error) {
resp, err = c.MachineClient.ServiceRestart(
ctx,
&machineapi.ServiceRestartRequest{Id: id},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.ServiceRestartResponse) //nolint:errcheck
return
}
// Time returns the time.
func (c *Client) Time(ctx context.Context, callOptions ...grpc.CallOption) (resp *timeapi.TimeResponse, err error) {
resp, err = c.TimeClient.Time(
ctx,
&emptypb.Empty{},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*timeapi.TimeResponse) //nolint:errcheck
return
}
// TimeCheck returns the time compared to the specified ntp server.
func (c *Client) TimeCheck(ctx context.Context, server string, callOptions ...grpc.CallOption) (resp *timeapi.TimeResponse, err error) {
resp, err = c.TimeClient.TimeCheck(
ctx,
&timeapi.TimeRequest{Server: server},
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*timeapi.TimeResponse) //nolint:errcheck
return
}
// Read reads a file.
//
// This method doesn't support multiplexing of the result:
// * either client.WithNodes is not used, or it contains a single node in the list.
func (c *Client) Read(ctx context.Context, path string) (io.ReadCloser, error) {
stream, err := c.MachineClient.Read(ctx, &machineapi.ReadRequest{Path: path})
if err != nil {
return nil, err
}
return ReadStream(stream)
}
// ClusterHealthCheck runs a Talos cluster health check.
func (c *Client) ClusterHealthCheck(ctx context.Context, waitTimeout time.Duration, clusterInfo *clusterapi.ClusterInfo) (clusterapi.ClusterService_HealthCheckClient, error) {
return c.ClusterClient.HealthCheck(ctx, &clusterapi.HealthCheckRequest{
WaitTimeout: durationpb.New(waitTimeout),
ClusterInfo: clusterInfo,
})
}
// EtcdRemoveMemberByID removes a node from etcd cluster by etcd member ID.
func (c *Client) EtcdRemoveMemberByID(ctx context.Context, req *machineapi.EtcdRemoveMemberByIDRequest, callOptions ...grpc.CallOption) error {
resp, err := c.MachineClient.EtcdRemoveMemberByID(ctx, req, callOptions...)
if err == nil {
_, err = FilterMessages(resp, err)
}
return err
}
// EtcdLeaveCluster makes node leave etcd cluster.
func (c *Client) EtcdLeaveCluster(ctx context.Context, req *machineapi.EtcdLeaveClusterRequest, callOptions ...grpc.CallOption) error {
resp, err := c.MachineClient.EtcdLeaveCluster(ctx, req, callOptions...)
if err == nil {
_, err = FilterMessages(resp, err)
}
return err
}
// EtcdForfeitLeadership makes node forfeit leadership in the etcd cluster.
func (c *Client) EtcdForfeitLeadership(ctx context.Context, req *machineapi.EtcdForfeitLeadershipRequest, callOptions ...grpc.CallOption) (*machineapi.EtcdForfeitLeadershipResponse, error) {
resp, err := c.MachineClient.EtcdForfeitLeadership(ctx, req, callOptions...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdForfeitLeadershipResponse) //nolint:errcheck
return resp, err
}
// EtcdMemberList lists etcd members of the cluster.
func (c *Client) EtcdMemberList(ctx context.Context, req *machineapi.EtcdMemberListRequest, callOptions ...grpc.CallOption) (*machineapi.EtcdMemberListResponse, error) {
resp, err := c.MachineClient.EtcdMemberList(ctx, req, callOptions...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdMemberListResponse) //nolint:errcheck
return resp, err
}
// EtcdSnapshot receives a snapshot of the etcd from the node.
//
// This method doesn't support multiplexing of the result:
// * either client.WithNodes is not used, or it contains a single node in the list.
func (c *Client) EtcdSnapshot(ctx context.Context, req *machineapi.EtcdSnapshotRequest, callOptions ...grpc.CallOption) (io.ReadCloser, error) {
stream, err := c.MachineClient.EtcdSnapshot(ctx, req, callOptions...)
if err != nil {
return nil, err
}
return ReadStream(stream)
}
// EtcdRecover uploads etcd snapshot created with EtcdSnapshot to the node.
func (c *Client) EtcdRecover(ctx context.Context, snapshot io.Reader, callOptions ...grpc.CallOption) (*machineapi.EtcdRecoverResponse, error) {
cli, err := c.MachineClient.EtcdRecover(ctx, callOptions...)
if err != nil {
return nil, err
}
buf := make([]byte, 4096)
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
var n int
n, err = snapshot.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("error reading snapshot: %w", err)
}
if err = cli.Send(&common.Data{
Bytes: buf[:n],
}); err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
}
resp, err := cli.CloseAndRecv()
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdRecoverResponse) //nolint:errcheck
return resp, err
}
// EtcdAlarmList lists etcd alarms for the current node.
//
// This method is available only on control plane nodes (which run etcd).
func (c *Client) EtcdAlarmList(ctx context.Context, opts ...grpc.CallOption) (*machineapi.EtcdAlarmListResponse, error) {
resp, err := c.MachineClient.EtcdAlarmList(ctx, &emptypb.Empty{}, opts...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdAlarmListResponse) //nolint:errcheck
return resp, err
}
// EtcdAlarmDisarm disarms etcd alarms for the current node.
//
// This method is available only on control plane nodes (which run etcd).
func (c *Client) EtcdAlarmDisarm(ctx context.Context, opts ...grpc.CallOption) (*machineapi.EtcdAlarmDisarmResponse, error) {
resp, err := c.MachineClient.EtcdAlarmDisarm(ctx, &emptypb.Empty{}, opts...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdAlarmDisarmResponse) //nolint:errcheck
return resp, err
}
// EtcdDefragment defragments etcd data directory for the current node.
//
// Defragmentation is a resource-heavy operation, so it should only run on a specific
// node.
//
// This method is available only on control plane nodes (which run etcd).
func (c *Client) EtcdDefragment(ctx context.Context, opts ...grpc.CallOption) (*machineapi.EtcdDefragmentResponse, error) {
resp, err := c.MachineClient.EtcdDefragment(ctx, &emptypb.Empty{}, opts...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdDefragmentResponse) //nolint:errcheck
return resp, err
}
// EtcdStatus returns etcd status for the current member.
//
// This method is available only on control plane nodes (which run etcd).
func (c *Client) EtcdStatus(ctx context.Context, opts ...grpc.CallOption) (*machineapi.EtcdStatusResponse, error) {
resp, err := c.MachineClient.EtcdStatus(ctx, &emptypb.Empty{}, opts...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.EtcdStatusResponse) //nolint:errcheck
return resp, err
}
// GenerateClientConfiguration implements proto.MachineServiceClient interface.
func (c *Client) GenerateClientConfiguration(ctx context.Context, req *machineapi.GenerateClientConfigurationRequest, callOptions ...grpc.CallOption) (resp *machineapi.GenerateClientConfigurationResponse, err error) { //nolint:lll
resp, err = c.MachineClient.GenerateClientConfiguration(ctx, req, callOptions...)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.GenerateClientConfigurationResponse) //nolint:errcheck
return
}
// PacketCapture implements the proto.MachineServiceClient interface.
//
// This method doesn't support multiplexing of the result:
// * either client.WithNodes is not used, or it contains a single node in the list.
func (c *Client) PacketCapture(ctx context.Context, req *machineapi.PacketCaptureRequest) (io.ReadCloser, error) {
stream, err := c.MachineClient.PacketCapture(ctx, req)
if err != nil {
return nil, err
}
return ReadStream(stream)
}
// MachineStream is a common interface for streams returned by streaming APIs.
type MachineStream interface {
Recv() (*common.Data, error)
grpc.ClientStream
}
// ReadStream converts grpc stream into io.Reader.
func ReadStream(stream MachineStream) (io.ReadCloser, error) {
pr, pw := io.Pipe()
go func() {
//nolint:errcheck
defer pw.Close()
for {
data, err := stream.Recv()
if err != nil {
if errors.Is(err, io.EOF) || StatusCode(err) == codes.Canceled || StatusCode(err) == codes.DeadlineExceeded {
return
}
pw.CloseWithError(err)
return
}
if data.Bytes != nil {
_, err = pw.Write(data.Bytes)
if err != nil {
return
}
}
if data.Metadata != nil && data.Metadata.Error != "" {
pw.CloseWithError(metaToErr(data.Metadata))
return
}
}
}()
return pr, stream.CloseSend()
}
func metaToErr(md *common.Metadata) error {
if md.Status == nil {
return errors.New(md.Error)
}
code := codes.Code(md.Status.Code)
if code == codes.Canceled || code == codes.DeadlineExceeded {
return nil
}
return status.FromProto(md.Status).Err()
}
// Netstat lists the network sockets on the current node.
func (c *Client) Netstat(ctx context.Context, req *machineapi.NetstatRequest, callOptions ...grpc.CallOption) (*machineapi.NetstatResponse, error) {
resp, err := c.MachineClient.Netstat(
ctx,
req,
callOptions...,
)
var filtered interface{}
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.NetstatResponse) //nolint:errcheck
return resp, err
}
// MetaWrite writes a key to META storage.
func (c *Client) MetaWrite(ctx context.Context, key uint8, value []byte, callOptions ...grpc.CallOption) error {
resp, err := c.MachineClient.MetaWrite(
ctx,
&machineapi.MetaWriteRequest{
Key: uint32(key),
Value: value,
},
callOptions...,
)
_, err = FilterMessages(resp, err)
return err
}
// MetaDelete deletes a key from META storage.
func (c *Client) MetaDelete(ctx context.Context, key uint8, callOptions ...grpc.CallOption) error {
resp, err := c.MachineClient.MetaDelete(
ctx,
&machineapi.MetaDeleteRequest{
Key: uint32(key),
},
callOptions...,
)
_, err = FilterMessages(resp, err)
return err
}
// ImageList lists images in the CRI.
func (c *Client) ImageList(ctx context.Context, namespace common.ContainerdNamespace, callOptions ...grpc.CallOption) (machineapi.MachineService_ImageListClient, error) {
return c.MachineClient.ImageList(ctx,
&machineapi.ImageListRequest{
Namespace: namespace,
},
callOptions...,
)
}
// ImagePull pre-pulls an image to the CRI.
func (c *Client) ImagePull(ctx context.Context, namespace common.ContainerdNamespace, imageRef string, callOptions ...grpc.CallOption) error {
resp, err := c.MachineClient.ImagePull(ctx,
&machineapi.ImagePullRequest{
Namespace: namespace,
Reference: imageRef,
},
callOptions...,
)
_, err = FilterMessages(resp, err)
return err
}