kedge/internal/quartermaster/client.go
Tyler King 6058e62348 Initial commit: Kedge network automation platform
Go-based network automation with YANG models, gRPC, Ansible,
Terraform, and Kubernetes integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:09:30 -05:00

102 lines
2.9 KiB
Go

package quartermaster
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/guildhouse-co/kedge/internal/config"
)
// Client is a gRPC client for the Quartermaster governance and notary services.
type Client struct {
cfg config.QuartermasterConfig
conn *grpc.ClientConn
log *zap.SugaredLogger
// TODO: Add typed gRPC stub clients when proto generation is wired up.
// governance quartermasterv1.GovernanceServiceClient
// notary quartermasterv1.QuartermasterNotaryClient
}
// NewClient creates a new Quartermaster gRPC client.
func NewClient(cfg config.QuartermasterConfig) (*Client, error) {
log := zap.L().Sugar().Named("quartermaster")
// TODO: Use mTLS with SPIFFE SVID for production.
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
conn, err := grpc.NewClient(cfg.Endpoint, opts...)
if err != nil {
return nil, fmt.Errorf("failed to connect to quartermaster at %s: %w", cfg.Endpoint, err)
}
log.Infof("connected to quartermaster at %s", cfg.Endpoint)
return &Client{
cfg: cfg,
conn: conn,
log: log,
}, nil
}
// Close closes the gRPC connection.
func (c *Client) Close() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// SubmitSessionTransit submits a SessionTransitArtifact to Quartermaster for notarization.
// This records overlay session crossings between clusters.
func (c *Client) SubmitSessionTransit(ctx context.Context, artifact *SessionTransitArtifact) error {
artifact.Timestamp = time.Now().UTC()
// Serialize to canonical JSON (RFC 8785) for hashing.
canonical, err := artifact.CanonicalBytes()
if err != nil {
return fmt.Errorf("failed to serialize session transit artifact: %w", err)
}
c.log.Infow("submitting session transit",
"session_id", artifact.SessionID,
"source", artifact.SourceCluster,
"dest", artifact.DestCluster,
"mode", artifact.GrantedMode,
"bytes", len(canonical),
)
// TODO: Call governance.CreateIntent + notary.CreateAnchor via gRPC stubs.
// For Phase 1, the anchor buffer flush is async — operations are not blocked.
_ = canonical
return nil
}
// SubmitNetworkMutation submits a NetworkMutationArtifact to Quartermaster for notarization.
// This records underlay infrastructure config changes.
func (c *Client) SubmitNetworkMutation(ctx context.Context, artifact *NetworkMutationArtifact) error {
artifact.Timestamp = time.Now().UTC()
canonical, err := artifact.CanonicalBytes()
if err != nil {
return fmt.Errorf("failed to serialize network mutation artifact: %w", err)
}
c.log.Infow("submitting network mutation",
"mutation_id", artifact.MutationID,
"device", artifact.Device,
"operation", artifact.Operation,
"bytes", len(canonical),
)
// TODO: Call governance.CreateIntent + notary.CreateAnchor via gRPC stubs.
_ = canonical
return nil
}