Skip to content

Commit

Permalink
[teleport-update] Isolated installation suffix (#49364)
Browse files Browse the repository at this point in the history
* namespacing

* words

* cli

* fix

* err

* use structured logs consistently

* comments

* bugs

* test

* switch to new paths

* test

* adjust

* reserved

* cleanup

* cleanup

* docs

* fix uninstall

* test

* simplify init

* cleanup

* namespace -> install-suffix

* log
  • Loading branch information
sclevine authored Dec 3, 2024
1 parent 088cc60 commit a079deb
Show file tree
Hide file tree
Showing 11 changed files with 613 additions and 276 deletions.
97 changes: 48 additions & 49 deletions lib/autoupdate/agent/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,8 @@ const (
const (
// serviceDir contains the relative path to the Teleport SystemD service dir.
serviceDir = "lib/systemd/system"
// serviceName contains the name of the Teleport SystemD service file.
// serviceName contains the upstream name of the Teleport SystemD service file.
serviceName = "teleport.service"
// updateServiceName contains the name of the Teleport Update Systemd service
updateServiceName = "teleport-update.service"
// updateTimerName contains the name of the Teleport Update Systemd timer
updateTimerName = "teleport-update.timer"
)

// LocalInstaller manages the creation and removal of installations
Expand All @@ -74,12 +70,12 @@ type LocalInstaller struct {
InstallDir string
// LinkBinDir contains symlinks to the linked installation's binaries.
LinkBinDir string
// LinkServiceDir contains a copy of the linked installation's systemd service.
LinkServiceDir string
// CopyServiceFile contains a copy of the linked installation's systemd service.
CopyServiceFile string
// SystemBinDir contains binaries for the system (packaged) install of Teleport.
SystemBinDir string
// SystemServiceDir contains the systemd service file for the system (packaged) install of Teleport.
SystemServiceDir string
// SystemServiceFile contains the systemd service file for the system (packaged) install of Teleport.
SystemServiceFile string
// HTTP is an HTTP client for downloading Teleport.
HTTP *http.Client
// Log contains a logger.
Expand All @@ -88,6 +84,8 @@ type LocalInstaller struct {
ReservedFreeTmpDisk uint64
// ReservedFreeInstallDisk is the amount of disk that must remain free in the install directory.
ReservedFreeInstallDisk uint64
// TransformService transforms the systemd service during copying.
TransformService func([]byte) []byte
}

// Remove a Teleport version directory from InstallDir.
Expand Down Expand Up @@ -356,7 +354,7 @@ func (li *LocalInstaller) extract(ctx context.Context, dstDir string, src io.Rea
}
zr, err := gzip.NewReader(src)
if err != nil {
return trace.Errorf("requires gzip-compressed body: %v", err)
return trace.Errorf("requires gzip-compressed body: %w", err)
}
li.Log.InfoContext(ctx, "Extracting Teleport tarball.", "path", dstDir, "size", max)

Expand Down Expand Up @@ -417,7 +415,7 @@ func (li *LocalInstaller) List(ctx context.Context) (versions []string, err erro
return versions, nil
}

// Link the specified version into the system LinkBinDir and LinkServiceDir.
// Link the specified version into the system LinkBinDir and CopyServiceFile.
// The revert function restores the previous linking.
// See Installer interface for additional specs.
func (li *LocalInstaller) Link(ctx context.Context, version string) (revert func(context.Context) bool, err error) {
Expand All @@ -428,19 +426,19 @@ func (li *LocalInstaller) Link(ctx context.Context, version string) (revert func
}
revert, err = li.forceLinks(ctx,
filepath.Join(versionDir, "bin"),
filepath.Join(versionDir, serviceDir),
filepath.Join(versionDir, serviceDir, serviceName),
)
if err != nil {
return revert, trace.Wrap(err)
}
return revert, nil
}

// LinkSystem links the system (package) version into LinkBinDir and LinkServiceDir.
// LinkSystem links the system (package) version into LinkBinDir and CopyServiceFile.
// The revert function restores the previous linking.
// See Installer interface for additional specs.
func (li *LocalInstaller) LinkSystem(ctx context.Context) (revert func(context.Context) bool, err error) {
revert, err = li.forceLinks(ctx, li.SystemBinDir, li.SystemServiceDir)
revert, err = li.forceLinks(ctx, li.SystemBinDir, li.SystemServiceFile)
return revert, trace.Wrap(err)
}

Expand All @@ -454,18 +452,18 @@ func (li *LocalInstaller) TryLink(ctx context.Context, version string) error {
}
return trace.Wrap(li.tryLinks(ctx,
filepath.Join(versionDir, "bin"),
filepath.Join(versionDir, serviceDir),
filepath.Join(versionDir, serviceDir, serviceName),
))
}

// TryLinkSystem links the system installation, but only in the case that
// no installation of Teleport is already linked or partially linked.
// See Installer interface for additional specs.
func (li *LocalInstaller) TryLinkSystem(ctx context.Context) error {
return trace.Wrap(li.tryLinks(ctx, li.SystemBinDir, li.SystemServiceDir))
return trace.Wrap(li.tryLinks(ctx, li.SystemBinDir, li.SystemServiceFile))
}

// Unlink unlinks a version from LinkBinDir and LinkServiceDir.
// Unlink unlinks a version from LinkBinDir and CopyServiceFile.
// See Installer interface for additional specs.
func (li *LocalInstaller) Unlink(ctx context.Context, version string) error {
versionDir, err := li.versionDir(version)
Expand All @@ -474,14 +472,14 @@ func (li *LocalInstaller) Unlink(ctx context.Context, version string) error {
}
return trace.Wrap(li.removeLinks(ctx,
filepath.Join(versionDir, "bin"),
filepath.Join(versionDir, serviceDir),
filepath.Join(versionDir, serviceDir, serviceName),
))
}

// UnlinkSystem unlinks the system (package) version from LinkBinDir and LinkServiceDir.
// UnlinkSystem unlinks the system (package) version from LinkBinDir and CopyServiceFile.
// See Installer interface for additional specs.
func (li *LocalInstaller) UnlinkSystem(ctx context.Context) error {
return trace.Wrap(li.removeLinks(ctx, li.SystemBinDir, li.SystemServiceDir))
return trace.Wrap(li.removeLinks(ctx, li.SystemBinDir, li.SystemServiceFile))
}

// symlink from oldname to newname
Expand All @@ -501,7 +499,7 @@ type smallFile struct {
// forceLinks will revert any overridden links or files if it hits an error.
// If successful, forceLinks may also be reverted after it returns by calling revert.
// The revert function returns true if reverting succeeds.
func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcDir string) (revert func(context.Context) bool, err error) {
func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcPath string) (revert func(context.Context) bool, err error) {
// setup revert function
var (
revertLinks []symlink
Expand Down Expand Up @@ -544,7 +542,7 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcDir string)
if err != nil {
return revert, trace.Wrap(err)
}
err = os.MkdirAll(li.LinkServiceDir, systemDirMode)
err = os.MkdirAll(filepath.Dir(li.CopyServiceFile), systemDirMode)
if err != nil {
return revert, trace.Wrap(err)
}
Expand Down Expand Up @@ -580,18 +578,27 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcDir string)

// create systemd service file

src := filepath.Join(svcDir, serviceName)
dst := filepath.Join(li.LinkServiceDir, serviceName)
orig, err := forceCopy(dst, src, maxServiceFileSize)
orig, err := li.forceCopyService(li.CopyServiceFile, svcPath, maxServiceFileSize)
if err != nil && !errors.Is(err, os.ErrExist) {
return revert, trace.Errorf("failed to write file %s: %w", serviceName, err)
return revert, trace.Errorf("failed to copy service: %w", err)
}
if orig != nil {
revertFiles = append(revertFiles, *orig)
}
return revert, nil
}

// forceCopyService uses forceCopy to copy a systemd service file from src to dst.
// The contents of both src and dst must be smaller than n.
// See forceCopy for more details.
func (li *LocalInstaller) forceCopyService(dst, src string, n int64) (orig *smallFile, err error) {
srcData, err := readFileN(src, n)
if err != nil {
return nil, trace.Wrap(err)
}
return forceCopy(dst, li.TransformService(srcData), n)
}

// forceLink attempts to create a symlink, atomically replacing an existing link if already present.
// If a non-symlink file or directory exists in newname already, forceLink errors.
// If the link is already present with the desired oldname, forceLink returns os.ErrExist.
Expand Down Expand Up @@ -633,16 +640,12 @@ func isExecutable(path string) (bool, error) {
fi.Mode()&0111 == 0111, nil
}

// forceCopy atomically copies a file from src to dst, replacing an existing file at dst if needed.
// Both src and dst must be smaller than n.
// forceCopy atomically copies a file from srcData to dst, replacing an existing file at dst if needed.
// The contents of dst must be smaller than n.
// forceCopy returns the original file path, mode, and contents as orig.
// If an irregular file, too large file, or directory exists in path already, forceCopy errors.
// If an irregular file, too large file, or directory exists in dst already, forceCopy errors.
// If the file is already present with the desired contents, forceCopy returns os.ErrExist.
func forceCopy(dst, src string, n int64) (orig *smallFile, err error) {
srcData, err := readFileN(src, n)
if err != nil {
return nil, trace.Wrap(err)
}
func forceCopy(dst string, srcData []byte, n int64) (orig *smallFile, err error) {
fi, err := os.Lstat(dst)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -681,7 +684,7 @@ func readFileN(name string, n int64) ([]byte, error) {
return data, trace.Wrap(err)
}

func (li *LocalInstaller) removeLinks(ctx context.Context, binDir, svcDir string) error {
func (li *LocalInstaller) removeLinks(ctx context.Context, binDir, svcPath string) error {
removeService := false
entries, err := os.ReadDir(binDir)
if err != nil {
Expand Down Expand Up @@ -720,26 +723,24 @@ func (li *LocalInstaller) removeLinks(ctx context.Context, binDir, svcDir string
li.Log.DebugContext(ctx, "Teleport binary not unlinked. Skipping removal of teleport.service.")
return nil
}
src := filepath.Join(svcDir, serviceName)
srcBytes, err := readFileN(src, maxServiceFileSize)
srcBytes, err := readFileN(svcPath, maxServiceFileSize)
if err != nil {
return trace.Wrap(err)
}
dst := filepath.Join(li.LinkServiceDir, serviceName)
dstBytes, err := readFileN(dst, maxServiceFileSize)
dstBytes, err := readFileN(li.CopyServiceFile, maxServiceFileSize)
if errors.Is(err, os.ErrNotExist) {
li.Log.DebugContext(ctx, "Service not present.", "path", dst)
li.Log.DebugContext(ctx, "Service not present.", "path", li.CopyServiceFile)
return nil
}
if err != nil {
return trace.Wrap(err)
}
if !bytes.Equal(srcBytes, dstBytes) {
if !bytes.Equal(li.TransformService(srcBytes), dstBytes) {
li.Log.WarnContext(ctx, "Removed teleport binary link, but skipping removal of custom teleport.service: the service file does not match the reference file for this version. The file might have been manually edited.")
return nil
}
if err := os.Remove(dst); err != nil {
return trace.Errorf("error removing copy of %s: %w", filepath.Base(dst), err)
if err := os.Remove(li.CopyServiceFile); err != nil {
return trace.Errorf("error removing copy of %s: %w", filepath.Base(li.CopyServiceFile), err)
}
return nil
}
Expand All @@ -748,13 +749,13 @@ func (li *LocalInstaller) removeLinks(ctx context.Context, binDir, svcDir string
// Existing links that point to files outside binDir or svcDir, as well as existing non-link files, will error.
// tryLinks will not attempt to create any links if linking could result in an error.
// However, concurrent changes to links may result in an error with partially-complete linking.
func (li *LocalInstaller) tryLinks(ctx context.Context, binDir, svcDir string) error {
func (li *LocalInstaller) tryLinks(ctx context.Context, binDir, svcPath string) error {
// ensure target directories exist before trying to create links
err := os.MkdirAll(li.LinkBinDir, systemDirMode)
if err != nil {
return trace.Wrap(err)
}
err = os.MkdirAll(li.LinkServiceDir, systemDirMode)
err = os.MkdirAll(filepath.Dir(li.CopyServiceFile), systemDirMode)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -795,11 +796,9 @@ func (li *LocalInstaller) tryLinks(ctx context.Context, binDir, svcDir string) e
}

// if any binaries are linked from binDir, always link the service from svcDir
src := filepath.Join(svcDir, serviceName)
dst := filepath.Join(li.LinkServiceDir, serviceName)
_, err = forceCopy(dst, src, maxServiceFileSize)
_, err = li.forceCopyService(li.CopyServiceFile, svcPath, maxServiceFileSize)
if err != nil && !errors.Is(err, os.ErrExist) {
return trace.Errorf("error writing %s: %w", serviceName, err)
return trace.Errorf("failed to copy service: %w", err)
}

return nil
Expand Down
Loading

0 comments on commit a079deb

Please sign in to comment.