diff --git a/cmd/agent/deploy.go b/cmd/agent/deploy.go index c4b48fa..469e570 100644 --- a/cmd/agent/deploy.go +++ b/cmd/agent/deploy.go @@ -25,6 +25,7 @@ func init() { deployCmd.Flags().String("agent.config", "", "path to agent configuration file") deployCmd.Flags().String("run.format", "progress", "output format") deployCmd.Flags().Bool("run.dry-run", false, "perform a dry run without deploying the agent") + deployCmd.Flags().String("alloy.version", "latest", "version of Alloy to install") _ = deployCmd.RegisterFlagCompletionFunc("run.format", completion.CompleteRunFormat) } @@ -45,6 +46,7 @@ func runDeployCmd(cmd *cobra.Command, args []string) { a, err := agent.New(config, targetUrl, formatType, dryRun) errors.CheckErr(err, formatType) - err = a.Deploy() + alloyVersion, _ := cmd.Flags().GetString("alloy.version") + err = a.Deploy(alloyVersion) errors.CheckErr(err, formatType) } diff --git a/cmd/agent/update.go b/cmd/agent/update.go index 1edcbdc..803083f 100644 --- a/cmd/agent/update.go +++ b/cmd/agent/update.go @@ -24,9 +24,10 @@ var updateCmd = &cobra.Command{ func init() { updateCmd.Flags().String("agent.config", "", "path to agent configuration file") updateCmd.Flags().String("run.format", "progress", "output format") - updateCmd.Flags().Bool("run.dry-run", false, "perform a dry run without updateing the agent") + updateCmd.Flags().Bool("run.dry-run", false, "perform a dry run without updating the agent") updateCmd.Flags().Bool("skip.config", false, "skip configuration file update") updateCmd.Flags().Bool("skip.binaries", false, "skip binaries update") + updateCmd.Flags().String("alloy.version", "latest", "version of Alloy to install") _ = updateCmd.RegisterFlagCompletionFunc("run.format", completion.CompleteRunFormat) } @@ -39,6 +40,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) { config, _ := cmd.Flags().GetString("agent.config") skipConfig, _ := cmd.Flags().GetBool("skip.config") skipBinaries, _ := cmd.Flags().GetBool("skip.binaries") + alloyVersion, _ := cmd.Flags().GetString("alloy.version") if skipConfig && skipBinaries { errors.CheckErr(fmt.Errorf("at least one of --skip.config or --skip.binaries must be false"), formatType) @@ -54,6 +56,6 @@ func runUpdateCmd(cmd *cobra.Command, args []string) { a, err := agent.New(config, targetUrl, formatType, dryRun) errors.CheckErr(err, formatType) - err = a.Update(skipConfig, skipBinaries) + err = a.Update(skipConfig, skipBinaries, alloyVersion) errors.CheckErr(err, formatType) } diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 328376a..fc735e2 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -47,7 +47,7 @@ func (a *Agent) Teardown() error { return nil } -func (a *Agent) Deploy() error { +func (a *Agent) Deploy(version string) error { defer func() { if a.format == target.FormatProgress { println() @@ -63,7 +63,7 @@ func (a *Agent) Deploy() error { return err } - if err := a.deployAgent(machine); err != nil { + if err := a.deployAgent(machine, version); err != nil { return err } @@ -86,7 +86,7 @@ func (a *Agent) Config(service, resourceID string) ([]byte, error) { return a.configAgent(service, resourceID) } -func (a *Agent) Update(skipConfig bool, skipBinaries bool) error { +func (a *Agent) Update(skipConfig bool, skipBinaries bool, version string) error { defer func() { if a.format == target.FormatProgress { println() @@ -102,7 +102,7 @@ func (a *Agent) Update(skipConfig bool, skipBinaries bool) error { return err } - return a.updateAgent(machine, skipConfig, skipBinaries) + return a.updateAgent(machine, skipConfig, skipBinaries, version) } func (a *Agent) Describe(service, resourceID string) (*DescribeData, error) { diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 0991c7a..068f59a 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -24,7 +24,7 @@ func Test_Deploy(t *testing.T) { assert.NoError(t, err, "create agent") record := capture(func() { - err = a.Deploy() + err = a.Deploy("latest") }) assert.NoError(t, err) @@ -41,7 +41,7 @@ func Test_Deploy(t *testing.T) { assert.NoError(t, err, "create agent") record = capture(func() { - err = a.Deploy() + err = a.Deploy("latest") }) assert.NoError(t, err) @@ -94,7 +94,7 @@ func Test_Update(t *testing.T) { assert.NoError(t, err, "create agent") record := capture(func() { - err = a.Update(false, false) + err = a.Update(false, false, "latest") }) assert.NoError(t, err, "update agent") @@ -111,7 +111,7 @@ func Test_Update(t *testing.T) { assert.NoError(t, err, "create agent") record = capture(func() { - err = a.Update(false, false) + err = a.Update(false, false, "latest") }) assert.NoError(t, err, "update agent") diff --git a/internal/agent/deploy.go b/internal/agent/deploy.go index 9989d10..86f5ac8 100644 --- a/internal/agent/deploy.go +++ b/internal/agent/deploy.go @@ -68,8 +68,13 @@ func (a *Agent) __deployCopySystemdServiceUnit() error { return nil } -func (a *Agent) __deployDownloadRelease(release string, tmpdir string) (string, error) { - url := fmt.Sprintf("https://github.com/grafana/alloy/releases/latest/download/%s.zip", release) +func (a *Agent) __deployDownloadRelease(release string, version string, tmpdir string) (string, error) { + var url string + if version != "latest" { + url = fmt.Sprintf("https://github.com/grafana/alloy/releases/download/%s/%s.zip", version, release) + } else { + url = fmt.Sprintf("https://github.com/grafana/alloy/releases/latest/download/%s.zip", release) + } tmpfile := fmt.Sprintf("%s/%s-%s.zip", tmpdir, release, time.Now().Format("19800212015200")) a.__helperPrintProgress(fmt.Sprintf("Running 'GET %s'", url)) @@ -201,7 +206,7 @@ func (a *Agent) __helperPrintProgress(message string) { target.PrintProgress(fmt.Sprintf("%s as %s@localhost", message, username), a.format) } -func (a *Agent) deployAgent(machine *MachineInfo) error { +func (a *Agent) deployAgent(machine *MachineInfo, alloyVersion string) error { if err := a.__deployMakeDirHierarchy(); err != nil { return err } @@ -223,7 +228,7 @@ func (a *Agent) deployAgent(machine *MachineInfo) error { }() release := fmt.Sprintf("alloy-%s-%s", machine.Kernel, machine.Arch) - zip, err := a.__deployDownloadRelease(release, tmpdir) + zip, err := a.__deployDownloadRelease(release, alloyVersion, tmpdir) if err != nil { return err } diff --git a/internal/agent/update.go b/internal/agent/update.go index 67d4a92..5f97605 100644 --- a/internal/agent/update.go +++ b/internal/agent/update.go @@ -15,39 +15,54 @@ import ( "github.com/tschaefer/finchctl/internal/target" ) -func (a *Agent) __updateServiceBinaryIsNeeded() (bool, error) { - if a.dryRun { - target.PrintProgress("Skipping update check due to dry-run mode", a.format) - return false, nil - } - +func (a *Agent) __updateServiceBinaryGetLatestTag() (string, error) { url := "https://api.github.com/repos/grafana/alloy/releases/latest" a.__helperPrintProgress(fmt.Sprintf("Running 'GET %s'", url)) resp, err := http.Get(url) if err != nil { - return false, &UpdateAgentError{Message: err.Error(), Reason: ""} + return "", &UpdateAgentError{Message: err.Error(), Reason: ""} } defer func() { _ = resp.Body.Close() }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return false, &UpdateAgentError{Message: "failed to fetch latest release info", Reason: fmt.Sprintf("status code: %d", resp.StatusCode)} + return "", &UpdateAgentError{Message: "failed to fetch latest release info", Reason: fmt.Sprintf("status code: %d", resp.StatusCode)} } out, err := io.ReadAll(resp.Body) if err != nil { - return false, &UpdateAgentError{Message: err.Error(), Reason: ""} + return "", &UpdateAgentError{Message: err.Error(), Reason: ""} } a.__helperPrintProgress("Running 'JSON unmarshal \"tag_name\"'") var data any if err := json.Unmarshal(out, &data); err != nil { - return false, &UpdateAgentError{Message: err.Error(), Reason: string(out)} + return "", &UpdateAgentError{Message: err.Error(), Reason: string(out)} + } + + return data.(map[string]any)["tag_name"].(string), nil +} + +func (a *Agent) __updateServiceBinaryIsNeeded(version string) (bool, error) { + if a.dryRun { + target.PrintProgress( + fmt.Sprintf("Skipping Alloy update check for version '%s' due to dry-run mode", version), + a.format, + ) + return false, nil + } + + latestVersion := version + if version == "latest" { + var err error + latestVersion, err = a.__updateServiceBinaryGetLatestTag() + if err != nil { + return false, err + } } - latestVersion := data.(map[string]any)["tag_name"].(string) - out, err = a.target.Run("alloy --version | grep -o -E 'v[0-9\\.]+'") + out, err := a.target.Run("alloy --version | grep -o -E 'v[0-9\\.]+'") if err != nil { return false, &UpdateAgentError{Message: err.Error(), Reason: string(out)} } @@ -56,8 +71,8 @@ func (a *Agent) __updateServiceBinaryIsNeeded() (bool, error) { return latestVersion != currentVersion, nil } -func (a *Agent) __updateServiceBinary(machine *MachineInfo) error { - ok, err := a.__updateServiceBinaryIsNeeded() +func (a *Agent) __updateServiceBinary(machine *MachineInfo, version string) error { + ok, err := a.__updateServiceBinaryIsNeeded(version) if err != nil { return err } @@ -75,7 +90,7 @@ func (a *Agent) __updateServiceBinary(machine *MachineInfo) error { }() release := fmt.Sprintf("alloy-%s-%s", machine.Kernel, machine.Arch) - zip, err := a.__deployDownloadRelease(release, tmpdir) + zip, err := a.__deployDownloadRelease(release, version, tmpdir) if err != nil { return convertError(err, &UpdateAgentError{}) } @@ -92,7 +107,7 @@ func (a *Agent) __updateServiceBinary(machine *MachineInfo) error { return nil } -func (a *Agent) updateAgent(machine *MachineInfo, skipConfig bool, skipBinaries bool) error { +func (a *Agent) updateAgent(machine *MachineInfo, skipConfig bool, skipBinaries bool, version string) error { if !skipConfig { if err := a.__deployCopyConfigFile(); err != nil { return convertError(err, &UpdateAgentError{}) @@ -100,7 +115,7 @@ func (a *Agent) updateAgent(machine *MachineInfo, skipConfig bool, skipBinaries } if !skipBinaries { - if err := a.__updateServiceBinary(machine); err != nil { + if err := a.__updateServiceBinary(machine, version); err != nil { return convertError(err, &UpdateAgentError{}) } }