Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ const (
// ComponentProxySecureGRPC represents a secure gRPC server running on Proxy (used for Kube).
ComponentProxySecureGRPC = "proxy:secure-grpc"

// ComponentUpdater represents the agent updater.
// ComponentUpdater represents the teleport-update binary.
ComponentUpdater = "updater"

// VerboseLogsEnvVar forces all logs to be verbose (down to DEBUG level)
Expand Down
150 changes: 136 additions & 14 deletions lib/autoupdate/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
package agent

import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"strings"
Expand Down Expand Up @@ -67,12 +69,87 @@ type UpdateSpec struct {

// UpdateStatus describes the status field in update.yaml.
type UpdateStatus struct {
// ActiveVersion is the currently active Teleport version.
ActiveVersion string `yaml:"active_version"`
// BackupVersion is the last working version of Teleport.
BackupVersion string `yaml:"backup_version"`
// SkipVersion is the last reverted version of Teleport.
SkipVersion string `yaml:"skip_version,omitempty"`
// Active is the currently active revision of Teleport.
Active Revision `yaml:"active"`
// Backup is the last working revision of Teleport.
Backup *Revision `yaml:"backup,omitempty"`
// Skip is the skipped revision of Teleport.
Comment thread
sclevine marked this conversation as resolved.
// Skipped revisions are not applied because they
// are known to crash.
Skip *Revision `yaml:"skip,omitempty"`
}

// Revision is a version and edition of Teleport.
type Revision struct {
// Version is the version of Teleport.
Version string `yaml:"version" json:"version"`
// Flags describe the edition of Teleport.
Flags InstallFlags `yaml:"flags,flow,omitempty" json:"flags,omitempty"`
}

// NewRevision create a Revision.
// If version is not set, no flags are returned.
// This ensures that all Revisions without versions are zero-valued.
func NewRevision(version string, flags InstallFlags) Revision {
if version != "" {
return Revision{
Version: version,
Flags: flags,
}
}
return Revision{}
}

// NewRevisionFromDir translates a directory path containing Teleport into a Revision.
func NewRevisionFromDir(dir string) (Revision, error) {
parts := strings.Split(dir, "_")
var out Revision
if len(parts) == 0 {
return out, trace.Errorf("dir name empty")
}
out.Version = parts[0]
if out.Version == "" {
return out, trace.Errorf("version missing in dir %s", dir)
}
switch flags := parts[1:]; len(flags) {
case 2:
if flags[1] != FlagFIPS.DirFlag() {
break
}
out.Flags |= FlagFIPS
fallthrough
case 1:
if flags[0] != FlagEnterprise.DirFlag() {
break
}
out.Flags |= FlagEnterprise
fallthrough
case 0:
return out, nil
}
return out, trace.Errorf("invalid flag in %s", dir)
}

// Dir returns the directory path name of a Revision.
func (r Revision) Dir() string {
// Do not change the order of these statements.
// Otherwise, installed versions will no longer match update.yaml.
var suffix string
if r.Flags&(FlagEnterprise|FlagFIPS) != 0 {
suffix += "_" + FlagEnterprise.DirFlag()
}
if r.Flags&FlagFIPS != 0 {
suffix += "_" + FlagFIPS.DirFlag()
}
return r.Version + suffix
}

// String returns a human-readable description of a Teleport revision.
func (r Revision) String() string {
if flags := r.Flags.Strings(); len(flags) > 0 {
return fmt.Sprintf("%s+%s", r.Version, strings.Join(flags, "+"))
}
return r.Version
}

// readConfig reads UpdateConfig from a file.
Expand All @@ -93,10 +170,10 @@ func readConfig(path string) (*UpdateConfig, error) {
return nil, trace.Wrap(err, "failed to parse")
}
if k := cfg.Kind; k != updateConfigKind {
return nil, trace.Errorf("invalid kind %q", k)
return nil, trace.Errorf("invalid kind %s", k)
}
if v := cfg.Version; v != updateConfigVersion {
return nil, trace.Errorf("invalid version %q", v)
return nil, trace.Errorf("invalid version %s", v)
}
return &cfg, nil
}
Expand Down Expand Up @@ -155,10 +232,8 @@ type Status struct {

// FindResp summarizes the auto-update status response from cluster.
type FindResp struct {
// Version of Teleport to install
TargetVersion string `yaml:"target_version"`
// Flags describing the edition of Teleport
Flags InstallFlags `yaml:"flags"`
// Target revision of Teleport to install
Target Revision `yaml:"target"`
// InWindow is true when the install should happen now.
InWindow bool `yaml:"in_window"`
// Jitter duration before an automated install
Expand All @@ -175,10 +250,23 @@ const (
FlagFIPS
)

func (i InstallFlags) MarshalYAML() (any, error) {
return i.Strings(), nil
// NewInstallFlagsFromStrings returns InstallFlags given a slice of human-readable strings.
func NewInstallFlagsFromStrings(s []string) InstallFlags {
var out InstallFlags
for _, f := range s {
for _, flag := range []InstallFlags{
FlagEnterprise,
FlagFIPS,
} {
if f == flag.String() {
out |= flag
}
}
}
return out
}

// Strings converts InstallFlags to a slice of human-readable strings.
func (i InstallFlags) Strings() []string {
var out []string
for _, flag := range []InstallFlags{
Expand All @@ -192,6 +280,7 @@ func (i InstallFlags) Strings() []string {
return out
}

// String returns the string representation of a single InstallFlag flag, or "Unknown".
func (i InstallFlags) String() string {
switch i {
case 0:
Expand All @@ -203,3 +292,36 @@ func (i InstallFlags) String() string {
}
return "Unknown"
}

// DirFlag returns the directory path representation of a single InstallFlag flag, or "unknown".
func (i InstallFlags) DirFlag() string {
switch i {
case 0:
return ""
case FlagEnterprise:
return "ent"
case FlagFIPS:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these bit flags? Any risk of us converting to "unknown" if both FlagEnterprise and FlagFIPS is set? Maybe this helps?

Suggested change
case FlagFIPS:
case FlagFIPS, FlagEnterprise|FlagFIPS:

Copy link
Copy Markdown
Member Author

@sclevine sclevine Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are bit flags. This method is just intended for single-bit contexts, similar to String(). The calling code ensures that FlagFIPS implies FlagEnterprise (and ensures that the order is always consistent).

return "fips"
}
return "unknown"
}

func (i InstallFlags) MarshalYAML() (any, error) {
return i.Strings(), nil
}

func (i InstallFlags) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Strings())
}

func (i *InstallFlags) UnmarshalYAML(n *yaml.Node) error {
var s []string
if err := n.Decode(&s); err != nil {
return trace.Wrap(err)
}
if i == nil {
return trace.BadParameter("nil install flags while parsing YAML")
}
*i = NewInstallFlagsFromStrings(s)
return nil
}
Loading