Files

57 lines
1.7 KiB
Go

// Package planner builds offline previews. It does not inspect or change a host.
package planner
import (
"errors"
"net"
"regexp"
"strings"
)
const ProtocolVersion = 1
var (
idPattern = regexp.MustCompile(`^[a-z][a-z0-9-]{0,47}$`)
digestPattern = regexp.MustCompile(`^sha256:[a-f0-9]{64}$`)
labelPattern = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$`)
)
// Intent contains no secrets. ObservedStateDigest is caller-supplied in offline
// mode; a future executor must obtain it independently from the target host.
type Intent struct {
ProtocolVersion int `json:"protocolVersion"`
HostID string `json:"hostId"`
InstanceID string `json:"instanceId"`
AppID string `json:"appId"`
PackageDigest string `json:"packageDigest"`
ImageDigest string `json:"imageDigest"`
Domain string `json:"domain"`
ObservedStateDigest string `json:"observedStateDigest"`
}
func (i Intent) Validate() error {
if i.ProtocolVersion != ProtocolVersion {
return errors.New("unsupported protocol version")
}
for _, id := range []string{i.HostID, i.InstanceID, i.AppID} {
if !idPattern.MatchString(id) {
return errors.New("invalid resource identifier")
}
}
for _, digest := range []string{i.PackageDigest, i.ImageDigest, i.ObservedStateDigest} {
if !digestPattern.MatchString(digest) {
return errors.New("expected a SHA-256 digest")
}
}
labels := strings.Split(i.Domain, ".")
if len(i.Domain) > 253 || len(labels) < 2 || net.ParseIP(i.Domain) != nil {
return errors.New("expected an ASCII DNS hostname")
}
for _, label := range labels {
if !labelPattern.MatchString(label) {
return errors.New("invalid DNS hostname label")
}
}
return nil
}