talos/cmd/osctl/pkg/client/client.go
Andrey Smirnov 0c0a0340b2
fix(osctl): allow '-target' flag for osctl restart (#732)
I couldn't find any use for the `timeout` flag nor the value passed in
the API, but it block much more useful and present in other commands
flag 'target'.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2019-06-14 21:37:57 +03:00

260 lines
7.1 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
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/gob"
"fmt"
"github.com/golang/protobuf/ptypes/empty"
"github.com/talos-systems/talos/cmd/osctl/pkg/client/config"
initproto "github.com/talos-systems/talos/internal/app/init/proto"
"github.com/talos-systems/talos/internal/app/osd/proto"
"github.com/talos-systems/talos/internal/pkg/proc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// Credentials represents the set of values required to initialize a vaild
// Client.
type Credentials struct {
Target string
ca []byte
crt []byte
key []byte
}
// Client implements the proto.OSDClient interface. It serves as the
// concrete type with the required methods.
type Client struct {
conn *grpc.ClientConn
client proto.OSDClient
initClient initproto.InitClient
}
// NewDefaultClientCredentials initializes ClientCredentials using default paths
// to the required CA, certificate, and key.
func NewDefaultClientCredentials(p string) (creds *Credentials, err error) {
c, err := config.Open(p)
if err != nil {
return
}
if c.Context == "" {
return nil, fmt.Errorf("'context' key is not set in the config")
}
context := c.Contexts[c.Context]
if context == nil {
return nil, fmt.Errorf("context %q is not defined in 'contexts' key in config", c.Context)
}
caBytes, err := base64.StdEncoding.DecodeString(context.CA)
if err != nil {
return
}
crtBytes, err := base64.StdEncoding.DecodeString(context.Crt)
if err != nil {
return
}
keyBytes, err := base64.StdEncoding.DecodeString(context.Key)
if err != nil {
return
}
creds = &Credentials{
Target: context.Target,
ca: caBytes,
crt: crtBytes,
key: keyBytes,
}
return creds, nil
}
// NewClient initializes a Client.
func NewClient(port int, clientcreds *Credentials) (c *Client, err error) {
grpcOpts := []grpc.DialOption{}
c = &Client{}
crt, err := tls.X509KeyPair(clientcreds.crt, clientcreds.key)
if err != nil {
return nil, fmt.Errorf("could not load client key pair: %s", err)
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(clientcreds.ca); !ok {
return nil, fmt.Errorf("failed to append client certs")
}
// TODO(andrewrynhard): Do not parse the address. Pass the IP and port in as separate
// parameters.
creds := credentials.NewTLS(&tls.Config{
ServerName: clientcreds.Target,
Certificates: []tls.Certificate{crt},
// Set the root certificate authorities to use the self-signed
// certificate.
RootCAs: certPool,
})
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(creds))
c.conn, err = grpc.Dial(fmt.Sprintf("%s:%d", clientcreds.Target, port), grpcOpts...)
if err != nil {
return
}
c.client = proto.NewOSDClient(c.conn)
c.initClient = initproto.NewInitClient(c.conn)
return c, nil
}
// Close shuts down client protocol
func (c *Client) Close() error {
return c.conn.Close()
}
// Kubeconfig implements the proto.OSDClient interface.
func (c *Client) Kubeconfig(ctx context.Context) ([]byte, error) {
r, err := c.client.Kubeconfig(ctx, &empty.Empty{})
if err != nil {
return nil, err
}
return r.Bytes, nil
}
// Stats implements the proto.OSDClient interface.
func (c *Client) Stats(ctx context.Context, namespace string) (reply *proto.StatsReply, err error) {
reply, err = c.client.Stats(ctx, &proto.StatsRequest{Namespace: namespace})
return
}
// Processes implements the proto.OSDClient interface.
func (c *Client) Processes(ctx context.Context, namespace string) (reply *proto.ProcessesReply, err error) {
reply, err = c.client.Processes(ctx, &proto.ProcessesRequest{Namespace: namespace})
return
}
// Restart implements the proto.OSDClient interface.
func (c *Client) Restart(ctx context.Context, namespace, id string) (err error) {
_, err = c.client.Restart(ctx, &proto.RestartRequest{
Id: id,
Namespace: namespace,
})
return
}
// Reset implements the proto.OSDClient interface.
func (c *Client) Reset(ctx context.Context) (err error) {
_, err = c.client.Reset(ctx, &empty.Empty{})
return
}
// Reboot implements the proto.OSDClient interface.
func (c *Client) Reboot(ctx context.Context) (err error) {
_, err = c.initClient.Reboot(ctx, &empty.Empty{})
return
}
// Shutdown implements the proto.OSDClient interface.
func (c *Client) Shutdown(ctx context.Context) (err error) {
_, err = c.initClient.Shutdown(ctx, &empty.Empty{})
return
}
// Dmesg implements the proto.OSDClient interface.
func (c *Client) Dmesg(ctx context.Context) ([]byte, error) {
data, err := c.client.Dmesg(ctx, &empty.Empty{})
if err != nil {
return nil, err
}
return data.Bytes, nil
}
// Logs implements the proto.OSDClient interface.
func (c *Client) Logs(ctx context.Context, namespace, id string) (stream proto.OSD_LogsClient, err error) {
stream, err = c.client.Logs(ctx, &proto.LogsRequest{
Namespace: namespace,
Id: id,
})
return
}
// Version implements the proto.OSDClient interface.
func (c *Client) Version(ctx context.Context) ([]byte, error) {
data, err := c.client.Version(ctx, &empty.Empty{})
if err != nil {
return nil, err
}
return data.Bytes, nil
}
// Routes implements the proto.OSDClient interface.
func (c *Client) Routes(ctx context.Context) (reply *proto.RoutesReply, err error) {
reply, err = c.client.Routes(ctx, &empty.Empty{})
return
}
// Top implements the proto.OSDClient interface.
func (c *Client) Top(ctx context.Context) (pl []proc.ProcessList, err error) {
var reply *proto.TopReply
reply, err = c.client.Top(ctx, &empty.Empty{})
if err != nil {
return
}
buf := bytes.NewBuffer(reply.ProcessList.Bytes)
dec := gob.NewDecoder(buf)
err = dec.Decode(&pl)
return
}
// DF implements the proto.OSDClient interface.
func (c *Client) DF(ctx context.Context) (*proto.DFReply, error) {
return c.client.DF(ctx, &empty.Empty{})
}
// LS implements the proto.OSDClient interface.
func (c *Client) LS(ctx context.Context, req proto.LSRequest) (stream proto.OSD_LSClient, err error) {
return c.client.LS(ctx, &req)
}
// Upgrade initiates a Talos upgrade ... and implements the proto.OSDClient
// interface
func (c *Client) Upgrade(ctx context.Context, asseturl string) (string, error) {
reply, err := c.initClient.Upgrade(ctx, &initproto.UpgradeRequest{Url: asseturl})
if err != nil {
return "", err
}
return reply.Ack, nil
}
// ServiceList returns list of services with their state
func (c *Client) ServiceList(ctx context.Context) (*initproto.ServiceListReply, error) {
return c.initClient.ServiceList(ctx, &empty.Empty{})
}
// 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) (*initproto.ServiceInfo, error) {
reply, err := c.initClient.ServiceList(ctx, &empty.Empty{})
if err != nil {
return nil, err
}
for _, svc := range reply.Services {
if svc.Id == id {
return svc, nil
}
}
return nil, nil
}