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] Add support for systemd process management #49102

Closed
wants to merge 14 commits into from
Closed
122 changes: 122 additions & 0 deletions lib/autoupdate/agent/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package agent

import (
"context"
"errors"
"log/slog"
"os"
"path/filepath"
"text/template"

"github.com/google/renameio/v2"
"github.com/gravitational/trace"
)

const (
updateServiceTemplate = `# teleport-update
[Unit]
Description=Teleport auto-update service

[Service]
Type=oneshot
ExecStart={{.LinkDir}}/bin/teleport-update update
`
updateTimerTemplate = `# teleport-update
[Unit]
Description=Teleport auto-update timer unit

[Timer]
OnActiveSec=1m
OnUnitActiveSec=5m
RandomizedDelaySec=1m

[Install]
WantedBy=teleport.service
`
)

func Setup(ctx context.Context, log *slog.Logger, linkDir, dataDir string) error {
err := writeConfigFiles(linkDir, dataDir)
if err != nil {
return trace.Errorf("failed to write teleport-update systemd config files: %w", err)
}
svc := &SystemdService{
ServiceName: "teleport-update.timer",
Log: log,
}
err = svc.Sync(ctx)
if errors.Is(err, ErrNotSupported) {
log.WarnContext(ctx, "Not enabling systemd service because systemd is not running.")
return nil
}
if err != nil {
return trace.Errorf("failed to sync systemd config: %w", err)
}
if err := svc.Enable(ctx, true); err != nil {
return trace.Errorf("failed to enable teleport-update systemd timer: %w", err)
}
return nil
}

func writeConfigFiles(linkDir, dataDir string) error {
// TODO(sclevine): revert on failure

servicePath := filepath.Join(linkDir, serviceDir, updateServiceName)
err := writeTemplate(servicePath, updateServiceTemplate, linkDir, dataDir)
if err != nil {
return trace.Wrap(err)
}
timerPath := filepath.Join(linkDir, serviceDir, updateTimerName)
err = writeTemplate(timerPath, updateTimerTemplate, linkDir, dataDir)
if err != nil {
return trace.Wrap(err)
}

return nil
}

func writeTemplate(path, t, linkDir, dataDir string) error {
if err := os.MkdirAll(filepath.Dir(path), systemDirMode); err != nil {
return trace.Wrap(err)
}
opts := []renameio.Option{
renameio.WithPermissions(configFileMode),
renameio.WithExistingPermissions(),
}
f, err := renameio.NewPendingFile(path, opts...)
if err != nil {
return trace.Wrap(err)
}
defer f.Cleanup()

tmpl, err := template.New(filepath.Base(path)).Parse(t)
if err != nil {
return trace.Wrap(err)
}
err = tmpl.Execute(f, struct {
LinkDir string
DataDir string
}{linkDir, dataDir})
if err != nil {
return trace.Wrap(err)
}
return trace.Wrap(f.CloseAtomicallyReplace())
}
65 changes: 65 additions & 0 deletions lib/autoupdate/agent/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package agent

import (
"bytes"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

libdefaults "github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils/golden"
)

func TestWriteConfigFiles(t *testing.T) {
t.Parallel()
linkDir := t.TempDir()
dataDir := t.TempDir()
err := writeConfigFiles(linkDir, dataDir)
require.NoError(t, err)

for _, p := range []string{
filepath.Join(linkDir, serviceDir, updateServiceName),
filepath.Join(linkDir, serviceDir, updateTimerName),
} {
t.Run(filepath.Base(p), func(t *testing.T) {
data, err := os.ReadFile(p)
require.NoError(t, err)
data = replaceValues(data, map[string]string{
DefaultLinkDir: linkDir,
libdefaults.DataDir: dataDir,
})
if golden.ShouldSet() {
golden.Set(t, data)
}
require.Equal(t, string(golden.Get(t)), string(data))
})
}
}

func replaceValues(data []byte, m map[string]string) []byte {
for k, v := range m {
data = bytes.ReplaceAll(data, []byte(v),
[]byte(k))
}
return data
}
20 changes: 8 additions & 12 deletions lib/autoupdate/agent/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ const (
systemDirMode = 0755
)

var (
const (
// serviceDir contains the relative path to the Teleport SystemD service dir.
serviceDir = filepath.Join("lib", "systemd", "system")
serviceDir = "lib/systemd/system"
// serviceName contains the 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 Down Expand Up @@ -539,7 +543,7 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcDir string)
dst := filepath.Join(li.LinkServiceDir, serviceName)
orig, err := forceCopy(dst, src, maxServiceFileSize)
if err != nil && !errors.Is(err, os.ErrExist) {
return revert, trace.Errorf("failed to create file for %s: %w", serviceName, err)
return revert, trace.Errorf("failed to write file %s: %w", serviceName, err)
}
if orig != nil {
revertFiles = append(revertFiles, *orig)
Expand Down Expand Up @@ -782,13 +786,5 @@ func (li *LocalInstaller) isLinked(versionDir string) (bool, error) {
return true, nil
}
}
linkData, err := readFileN(filepath.Join(li.LinkServiceDir, serviceName), maxServiceFileSize)
if err != nil {
return false, nil
}
versionData, err := readFileN(filepath.Join(versionDir, serviceDir, serviceName), maxServiceFileSize)
if err != nil {
return false, nil
}
return bytes.Equal(linkData, versionData), nil
return false, nil
}
Loading
Loading