Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[teleport-update] Isolated installation suffix #49364

Merged
merged 22 commits into from
Dec 3, 2024
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
Loading