Skip to content

Commit

Permalink
Client auto updates integration for {tctl,tsh} (#47815)
Browse files Browse the repository at this point in the history
* Client auto updates integration for tctl/tsh

* Add version validation
Fix recursive version check for darwin platform
Fix cleanup for multi-package support

* Fix identifying tools removal from home directory

* Replace ToolsMode with ToolsAutoUpdate

* Reuse insecure flag for tests

* Fix CheckRemote with login

* Fix windows administrative access requirement
Update must be able to be canceled, re-execute with latest version or last updated
Show progress bar before request is made

* Fix update cancellation for login action
Address review comments

* Add signal handler with stack context cancellation

* Use copy instead of hard link for windows
Fix progress bar if we can't receive size of package

* Replace with list in order to support manual cancel

* Download archive package to temp directory

* Decrease timeout for client tools proxy call
  • Loading branch information
vapopov committed Nov 7, 2024
1 parent 7c761f0 commit e8dd714
Show file tree
Hide file tree
Showing 21 changed files with 601 additions and 179 deletions.
4 changes: 2 additions & 2 deletions api/client/webclient/webclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ type ProxySettings struct {
type AutoUpdateSettings struct {
// ToolsVersion defines the version of {tsh, tctl} for client auto update.
ToolsVersion string `json:"tools_version"`
// ToolsMode defines mode client auto update feature `enabled|disabled`.
ToolsMode string `json:"tools_mode"`
// ToolsAutoUpdate indicates if the requesting tools client should be updated.
ToolsAutoUpdate bool `json:"tools_auto_update"`
}

// KubeProxySettings is kubernetes proxy settings
Expand Down
17 changes: 9 additions & 8 deletions integration/autoupdate/tools/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import (

"github.com/gravitational/trace"

"github.com/gravitational/teleport/integration/helpers"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/integration/helpers/archive"
)

const (
Expand Down Expand Up @@ -133,25 +134,25 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers
for _, app := range []string{"tsh", "tctl"} {
output := filepath.Join(versionPath, app)
switch runtime.GOOS {
case "windows":
case constants.WindowsOS:
output = filepath.Join(versionPath, app+".exe")
case "darwin":
case constants.DarwinOS:
output = filepath.Join(versionPath, app+".app", "Contents", "MacOS", app)
}
if err := buildBinary(output, toolsDir, version, baseURL); err != nil {
return trace.Wrap(err)
}
}
switch runtime.GOOS {
case "darwin":
case constants.DarwinOS:
archivePath := filepath.Join(path, fmt.Sprintf("teleport-%s.pkg", version))
return trace.Wrap(helpers.CompressDirToPkgFile(ctx, versionPath, archivePath, "com.example.pkgtest"))
case "windows":
return trace.Wrap(archive.CompressDirToPkgFile(ctx, versionPath, archivePath, "com.example.pkgtest"))
case constants.WindowsOS:
archivePath := filepath.Join(path, fmt.Sprintf("teleport-v%s-windows-amd64-bin.zip", version))
return trace.Wrap(helpers.CompressDirToZipFile(ctx, versionPath, archivePath))
return trace.Wrap(archive.CompressDirToZipFile(ctx, versionPath, archivePath))
default:
archivePath := filepath.Join(path, fmt.Sprintf("teleport-v%s-linux-%s-bin.tar.gz", version, runtime.GOARCH))
return trace.Wrap(helpers.CompressDirToTarGzFile(ctx, versionPath, archivePath))
return trace.Wrap(archive.CompressDirToTarGzFile(ctx, versionPath, archivePath))
}
}

Expand Down
21 changes: 6 additions & 15 deletions integration/autoupdate/tools/updater/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ import (
"log"
"os"
"os/signal"
"runtime"
"syscall"
"time"

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/lib/autoupdate/tools"
)

Expand All @@ -40,17 +38,20 @@ var (
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)

updater := tools.NewUpdater(
clientTools(),
tools.DefaultClientTools(),
toolsDir,
version,
tools.WithBaseURL(baseURL),
)
toolsVersion, reExec := updater.CheckLocal()
toolsVersion, reExec, err := updater.CheckLocal()
if err != nil {
log.Fatal(err)
}
if reExec {
// Download and update the version of client tools required by the cluster.
// This is required if the user passed in the TELEPORT_TOOLS_VERSION explicitly.
Expand All @@ -76,13 +77,3 @@ func main() {
fmt.Printf("Teleport v%v git\n", version)
}
}

// clientTools list of the client tools needs to be updated.
func clientTools() []string {
switch runtime.GOOS {
case constants.WindowsOS:
return []string{"tsh.exe", "tctl.exe"}
default:
return []string{"tsh", "tctl"}
}
}
17 changes: 3 additions & 14 deletions integration/autoupdate/tools/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ import (
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/lib/autoupdate/tools"
)

Expand All @@ -51,7 +49,7 @@ func TestUpdate(t *testing.T) {

// Fetch compiled test binary with updater logic and install to $TELEPORT_HOME.
updater := tools.NewUpdater(
clientTools(),
tools.DefaultClientTools(),
toolsDir,
testVersions[0],
tools.WithBaseURL(baseURL),
Expand Down Expand Up @@ -93,7 +91,7 @@ func TestParallelUpdate(t *testing.T) {

// Initial fetch the updater binary un-archive and replace.
updater := tools.NewUpdater(
clientTools(),
tools.DefaultClientTools(),
toolsDir,
testVersions[0],
tools.WithBaseURL(baseURL),
Expand Down Expand Up @@ -167,7 +165,7 @@ func TestUpdateInterruptSignal(t *testing.T) {

// Initial fetch the updater binary un-archive and replace.
updater := tools.NewUpdater(
clientTools(),
tools.DefaultClientTools(),
toolsDir,
testVersions[0],
tools.WithBaseURL(baseURL),
Expand Down Expand Up @@ -220,12 +218,3 @@ func TestUpdateInterruptSignal(t *testing.T) {
}
assert.Contains(t, output.String(), "Update progress:")
}

func clientTools() []string {
switch runtime.GOOS {
case constants.WindowsOS:
return []string{"tsh.exe", "tctl.exe"}
default:
return []string{"tsh", "tctl"}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package helpers
package archive

import (
"archive/tar"
Expand Down
1 change: 1 addition & 0 deletions integrations/terraform/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ require (
github.com/google/go-tspi v0.3.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/safetext v0.0.0-20240104143208-7a7d9b3d812f // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
Expand Down
1 change: 0 additions & 1 deletion integrations/terraform/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,6 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
Expand Down
51 changes: 43 additions & 8 deletions lib/autoupdate/tools/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,59 @@ package tools

import (
"fmt"
"io"
"strings"

"github.com/gravitational/trace"
)

type progressWriter struct {
n int64
limit int64
n int64
limit int64
size int
progress int
}

func (w *progressWriter) Write(p []byte) (int, error) {
w.n = w.n + int64(len(p))
// newProgressWriter creates progress writer instance and prints empty
// progress bar right after initialisation.
func newProgressWriter(size int) (*progressWriter, func()) {
pw := &progressWriter{size: size}
pw.Print(0)
return pw, func() {
fmt.Print("\n")
}
}

n := int((w.n*100)/w.limit) / 10
bricks := strings.Repeat("▒", n) + strings.Repeat(" ", 10-n)
// Print prints the update progress bar with `n` bricks.
func (w *progressWriter) Print(n int) {
bricks := strings.Repeat("▒", n) + strings.Repeat(" ", w.size-n)
fmt.Print("\rUpdate progress: [" + bricks + "] (Ctrl-C to cancel update)")
}

if w.n == w.limit {
fmt.Print("\n")
func (w *progressWriter) Write(p []byte) (int, error) {
if w.limit == 0 || w.size == 0 {
return len(p), nil
}

w.n += int64(len(p))
bricks := int((w.n*100)/w.limit) / w.size
if w.progress != bricks {
w.Print(bricks)
w.progress = bricks
}

return len(p), nil
}

// CopyLimit sets the limit of writing bytes to the progress writer and initiate copying process.
func (w *progressWriter) CopyLimit(dst io.Writer, src io.Reader, limit int64) (written int64, err error) {
if limit < 0 {
n, err := io.Copy(dst, io.TeeReader(src, w))
w.Print(w.size)
return n, trace.Wrap(err)
}

w.limit = limit
n, err := io.CopyN(dst, io.TeeReader(src, w), limit)
return n, trace.Wrap(err)
}
Loading

0 comments on commit e8dd714

Please sign in to comment.