Skip to main content

Overview

The Talos API uses several common types shared across all services. These types are defined in the common package and provide consistent data structures for metadata, errors, and data transfer.
package common;

Metadata

Every response message includes metadata identifying the responding node and any errors.
message Metadata {
  string hostname = 1;
  string error = 2;
  google.rpc.Status status = 3;
}
hostname
string
Hostname of the node that generated this response. Injected by the proxy when targeting multiple nodes.
error
string
Human-readable error message if the request failed. Empty string indicates success.
status
google.rpc.Status
gRPC status code and details if the request failed. Contains:
  • code: gRPC status code (int32)
  • message: Error message
  • details: Additional error details

Example

{
  "metadata": {
    "hostname": "worker-1",
    "error": "",
    "status": null
  }
}
With error:
{
  "metadata": {
    "hostname": "worker-2",
    "error": "service kubelet is not running",
    "status": {
      "code": 5,
      "message": "not found"
    }
  }
}

Error Types

Error

Represents a Talos-specific error with additional context.
message Error {
  Code code = 1;
  string message = 2;
  repeated google.protobuf.Any details = 3;
}
code
Code
Talos error code (see below)
message
string
Human-readable error message
details
Any[]
Additional error details (type-specific)

Error Codes

enum Code {
  FATAL = 0;
  LOCKED = 1;
  CANCELED = 2;
}
FATAL
0
Fatal error, operation cannot be retried
LOCKED
1
Resource is locked, retry later
CANCELED
2
Operation was canceled

Data Transfer

Data

Used for streaming binary data (files, logs, etc.).
message Data {
  Metadata metadata = 1;
  bytes bytes = 2;
}
metadata
Metadata
Standard metadata
bytes
bytes
Chunk of binary data
Usage example:
// Read file from node
stream, err := client.Read(ctx, &machine.ReadRequest{
    Path: "/etc/os-release",
})
if err != nil {
    return err
}

var content []byte
for {
    data, err := stream.Recv()
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
    content = append(content, data.Bytes...)
}

fmt.Println(string(content))

DataResponse

Wraps multiple Data messages in a unary response.
message DataResponse {
  repeated Data messages = 1;
}

Empty

Empty response with metadata.
message Empty {
  Metadata metadata = 1;
}

EmptyResponse

Wraps multiple Empty messages.
message EmptyResponse {
  repeated Empty messages = 1;
}

Container Types

ContainerDriver

Specifies which container runtime to use.
enum ContainerDriver {
  CONTAINERD = 0;
  CRI = 1;
}
CONTAINERD
0
Use containerd directly (system containers)
CRI
1
Use CRI interface (Kubernetes containers)
Usage:
// List Kubernetes containers
client.Containers(ctx, &machine.ContainersRequest{
    Namespace: "k8s.io",
    Driver:    common.ContainerDriver_CRI,
})

// List system containers
client.Containers(ctx, &machine.ContainersRequest{
    Namespace: "system",
    Driver:    common.ContainerDriver_CONTAINERD,
})

ContainerdNamespace

Containerd namespace for container operations.
enum ContainerdNamespace {
  NS_UNKNOWN = 0;
  NS_SYSTEM = 1;
  NS_CRI = 2;
}
NS_UNKNOWN
0
Unknown/unspecified namespace
NS_SYSTEM
1
System namespace (Talos services)
NS_CRI
2
CRI namespace (Kubernetes pods)

ContainerdInstance

Specifies a containerd instance.
message ContainerdInstance {
  ContainerDriver driver = 1;
  ContainerdNamespace namespace = 2;
}

Network Types

NetIP

Represents an IP address.
message NetIP {
  bytes ip = 1;
}
ip
bytes
IP address in network byte order (4 bytes for IPv4, 16 bytes for IPv6)
Example:
// Create NetIP from string
ip := net.ParseIP("10.0.0.1")
netIP := &common.NetIP{
    Ip: ip,
}

// Convert back to string
ipAddr := net.IP(netIP.Ip).String() // "10.0.0.1"

NetIPPort

Represents an IP address and port.
message NetIPPort {
  bytes ip = 1;
  int32 port = 2;
}
ip
bytes
IP address in network byte order
port
int32
Port number (1-65535)
Example:
endpoint := &common.NetIPPort{
    Ip:   net.ParseIP("10.0.0.1"),
    Port: 6443,
}
// Represents: 10.0.0.1:6443

NetIPPrefix

Represents an IP address with CIDR prefix.
message NetIPPrefix {
  bytes ip = 1;
  int32 prefix_length = 2;
}
ip
bytes
Network address in network byte order
prefix_length
int32
CIDR prefix length (0-32 for IPv4, 0-128 for IPv6)
Example:
// Represents 10.0.0.0/24
cidr := &common.NetIPPrefix{
    Ip:           net.ParseIP("10.0.0.0"),
    PrefixLength: 24,
}

// Represents 2001:db8::/32
cidrV6 := &common.NetIPPrefix{
    Ip:           net.ParseIP("2001:db8::"),
    PrefixLength: 32,
}

Cryptographic Types

PEMEncodedCertificateAndKey

PEM-encoded certificate and private key pair.
message PEMEncodedCertificateAndKey {
  bytes crt = 1;
  bytes key = 2;
}
crt
bytes
PEM-encoded X.509 certificate
key
bytes
PEM-encoded private key (RSA, ECDSA, or Ed25519)
Example:
certPair := &common.PEMEncodedCertificateAndKey{
    Crt: []byte(`-----BEGIN CERTIFICATE-----
MIIC...
-----END CERTIFICATE-----`),
    Key: []byte(`-----BEGIN PRIVATE KEY-----
MIIE...
-----END PRIVATE KEY-----`),
}

PEMEncodedCertificate

PEM-encoded certificate only.
message PEMEncodedCertificate {
  bytes crt = 1;
}
crt
bytes
PEM-encoded X.509 certificate

PEMEncodedKey

PEM-encoded private key only.
message PEMEncodedKey {
  bytes key = 1;
}
key
bytes
PEM-encoded private key

URL Type

URL

Represents a URL.
message URL {
  string full_path = 1;
}
full_path
string
Complete URL including scheme, host, path, and query parameters
Example:
url := &common.URL{
    FullPath: "https://github.com/siderolabs/talos/releases/download/v1.7.0/talos-amd64.tar.gz",
}

Deprecation Annotations

The common package defines custom options for deprecation tracking:
extend google.protobuf.MessageOptions {
  string remove_deprecated_message = 93117;
}

extend google.protobuf.FieldOptions {
  string remove_deprecated_field = 93117;
}

extend google.protobuf.EnumOptions {
  string remove_deprecated_enum = 93117;
}

extend google.protobuf.EnumValueOptions {
  string remove_deprecated_enum_value = 93117;
}

extend google.protobuf.MethodOptions {
  string remove_deprecated_method = 93117;
}

extend google.protobuf.ServiceOptions {
  string remove_deprecated_service = 93117;
}
These annotations indicate which Talos version will remove deprecated API elements. Example:
rpc ImageList(ImageListRequest) returns (stream ImageListResponse) {
  option (common.remove_deprecated_method) = "v1.18";
  option deprecated = true;
}
This indicates ImageList will be removed in Talos v1.18.

Response Patterns

Single-Node Responses

When targeting a single node, responses contain one message:
resp, err := client.Version(ctx)
if err != nil {
    return err
}

if len(resp.Messages) != 1 {
    return fmt.Errorf("unexpected response")
}

msg := resp.Messages[0]
if msg.Metadata.Error != "" {
    return fmt.Errorf("node error: %s", msg.Metadata.Error)
}

fmt.Printf("Version: %s\n", msg.Version.Tag)

Multi-Node Responses

When targeting multiple nodes, responses contain multiple messages:
resp, err := client.Version(ctx)
if err != nil {
    return err
}

for _, msg := range resp.Messages {
    if msg.Metadata.Error != "" {
        fmt.Printf("%s: ERROR: %s\n",
            msg.Metadata.Hostname,
            msg.Metadata.Error,
        )
        continue
    }
    
    fmt.Printf("%s: Talos %s\n",
        msg.Metadata.Hostname,
        msg.Version.Tag,
    )
}

Streaming Responses

Streaming responses send multiple messages over time:
stream, err := client.Logs(ctx, &machine.LogsRequest{
    Namespace: "system",
    Id:        "kubelet",
    Follow:    true,
})
if err != nil {
    return err
}

for {
    data, err := stream.Recv()
    if err == io.EOF {
        break
    }
    if err != nil {
        return err
    }
    
    if data.Metadata.Error != "" {
        return fmt.Errorf("%s: %s",
            data.Metadata.Hostname,
            data.Metadata.Error,
        )
    }
    
    fmt.Print(string(data.Bytes))
}

Best Practices

Always Check Metadata

Never assume a response succeeded. Always check metadata:
// Bad
resp, _ := client.Version(ctx)
version := resp.Messages[0].Version.Tag

// Good
resp, err := client.Version(ctx)
if err != nil {
    return err
}

for _, msg := range resp.Messages {
    if msg.Metadata.Error != "" {
        log.Printf("%s failed: %s",
            msg.Metadata.Hostname,
            msg.Metadata.Error,
        )
        continue
    }
    // Use msg.Version...
}

Handle Partial Failures

In multi-node scenarios, some nodes may succeed while others fail:
successful := 0
failed := 0

for _, msg := range resp.Messages {
    if msg.Metadata.Error != "" {
        failed++
        continue
    }
    successful++
}

if failed > 0 {
    log.Printf("Partial failure: %d succeeded, %d failed", successful, failed)
}

Use Type-Safe Helpers

Create helper functions for common conversions:
func NetIPFromString(s string) (*common.NetIP, error) {
    ip := net.ParseIP(s)
    if ip == nil {
        return nil, fmt.Errorf("invalid IP: %s", s)
    }
    return &common.NetIP{Ip: ip}, nil
}

func NetIPToString(n *common.NetIP) string {
    return net.IP(n.Ip).String()
}

func NetIPPrefixFromString(s string) (*common.NetIPPrefix, error) {
    ip, ipNet, err := net.ParseCIDR(s)
    if err != nil {
        return nil, err
    }
    prefixLen, _ := ipNet.Mask.Size()
    return &common.NetIPPrefix{
        Ip:           ip,
        PrefixLength: int32(prefixLen),
    }, nil
}

See Also

Build docs developers (and LLMs) love