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

Feature/4890 detect fail early upgrade #5864

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
57e54a0
feature(4890): added shouldUpgrade function in the upgrade cli file
kaanyalti Oct 24, 2024
94ad0c3
feature(4890): added shouldUpgrade check into the upgrade command
kaanyalti Oct 24, 2024
3a4b97c
feature(4890): ran gofmt
kaanyalti Oct 24, 2024
accbd22
feature(4890): added a "force" flag, marked it as hidden
kaanyalti Oct 24, 2024
0339dff
feature(4890): removed dpkg, rpm and container logic
kaanyalti Oct 25, 2024
36b3a23
feature(4890): ran gofmt
kaanyalti Oct 25, 2024
e9f4fb5
feature(4890): updated the function signature of the upgrade command,…
kaanyalti Oct 26, 2024
b0b9b82
feature(4890): update comments
kaanyalti Oct 26, 2024
05195ae
feature(4890): added changelog fragment
kaanyalti Oct 26, 2024
21b09af
feature(4890): added fatal log in case there is an error while markin…
kaanyalti Oct 26, 2024
7853878
feature(4890): added error checks in tests
kaanyalti Oct 26, 2024
89f3e0d
feature(4890): updated the summary in the changelog fragment
kaanyalti Oct 28, 2024
0e9b791
feature(4890): removed the shorthand flag for the force flag
kaanyalti Oct 28, 2024
621b465
feature(4890): updated synchronization in the tests
kaanyalti Oct 28, 2024
7acd42e
Update internal/pkg/agent/cmd/upgrade_test.go
kaanyalti Oct 28, 2024
7c66dd1
feature(4890): using streams err output instead of defaulting to stderr
kaanyalti Oct 28, 2024
fde8ef3
feature(4890): use EXPECT instead of On
kaanyalti Oct 28, 2024
dabd25e
feature(4890): moved unconfirmed upgrade error to a package var
kaanyalti Oct 28, 2024
54d4da3
feature(4890): removed confirmation from upgrade check for when force…
kaanyalti Oct 29, 2024
073222c
Update internal/pkg/agent/cmd/upgrade.go
kaanyalti Oct 31, 2024
f2f5691
Update internal/pkg/agent/cmd/upgrade.go
kaanyalti Oct 31, 2024
5c6d62c
feature(4890): fix errors
kaanyalti Oct 31, 2024
1c1f99a
Update internal/pkg/agent/cmd/upgrade.go
kaanyalti Nov 1, 2024
3df1347
feature(4890): update test
kaanyalti Nov 5, 2024
6d26eb2
fearure(4890): replace ageninfo with state call
kaanyalti Nov 6, 2024
6bf8867
feature(4890): updated proto, client and server implementation
kaanyalti Nov 6, 2024
7d99902
feature(4890): fix struct tag
kaanyalti Nov 6, 2024
fe3e91e
feature(4890): added skip-verify checks
kaanyalti Nov 6, 2024
c9320c8
feature(4890): ran addLicenseHeaders
kaanyalti Nov 6, 2024
4601ea3
feature(4890): ran mage clean
kaanyalti Nov 6, 2024
d5d84bc
feature(4890): fix typo
kaanyalti Nov 6, 2024
186fcb1
feature(4890): added timeout to connection
kaanyalti Nov 6, 2024
eb73c7f
feature(4890): changed condition check order
kaanyalti Nov 6, 2024
2eb6b0d
feature(4890): fix unit tests
kaanyalti Nov 7, 2024
8ade18e
feature(4890): refactored tests, using mock client
kaanyalti Nov 7, 2024
6d21a40
Update internal/pkg/agent/cmd/upgrade.go
kaanyalti Nov 7, 2024
a4b27f7
feature(4890): use lower case "f" in error messages to be more consis…
kaanyalti Nov 7, 2024
3de964e
feature(4890): remove duplicate line
kaanyalti Nov 7, 2024
52f8884
feature(4890): ran mage controlProto with correct protoc version
kaanyalti Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
# - bug-fix: fixes a problem in a previous version
# - enhancement: extends functionality but does not break or fix existing behavior
# - feature: new functionality
# - known-issue: problems that we are aware of in a given version
# - security: impacts on the security of a product or a user’s deployment.
# - upgrade: important information for someone upgrading from a prior version
# - other: does not fit into any of the other categories
kind: enhancement

# Change summary; a 80ish characters long description of the change.
summary: Detect and fail-early cli upgrades if agent is fleet-managed

# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
description: This change brings restrictions on the upgrade cli command. If an agent is fleet-managed and is running in unprivileged mode, users won't be able to upgrade the agent using the cli. If an agent is fleet-managed and is running privileged, users will only be able to upgrade the agent using the cli if they provide --force flag.

# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
component: "elastic-agent"

# PR URL; optional; the PR number that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
pr: https://github.com/elastic/elastic-agent/pull/5864
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
#issue: https://github.com/owner/repo/1234
2 changes: 2 additions & 0 deletions control_v2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ message StateAgentInfo {
int32 pid = 6;
// True when running as unprivileged.
bool unprivileged = 7;
// True when agent is managed by fleet
bool isManaged = 8;
}

// StateResponse is the current state of Elastic Agent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,7 @@ func (c *Coordinator) generateComponentModel() (err error) {
configInjector = c.monitorMgr.MonitoringConfig
}

var existingCompState = make(map[string]uint64, len(c.state.Components))
existingCompState := make(map[string]uint64, len(c.state.Components))
for _, comp := range c.state.Components {
existingCompState[comp.Component.ID] = comp.State.Pid
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ func (c *Coordinator) refreshState() {
// Coordinator state and sets stateNeedsRefresh.
// Must be called on the main Coordinator goroutine.
func (c *Coordinator) applyComponentState(state runtime.ComponentComponentState) {

// check for any component updates to the known PID, so we can update the component monitoring
found := false
for i, other := range c.state.Components {
Expand Down Expand Up @@ -168,7 +167,6 @@ func (c *Coordinator) applyComponentState(state runtime.ComponentComponentState)
}

c.stateNeedsRefresh = true

}

// generateReportableState aggregates the internal state of the Coordinator
Expand Down
8 changes: 5 additions & 3 deletions internal/pkg/agent/application/info/agent_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import (
)

// defaultAgentConfigFile is a name of file used to store agent information
const agentInfoKey = "agent"
const defaultLogLevel = "info"
const maxRetriesloadAgentInfo = 5
const (
agentInfoKey = "agent"
defaultLogLevel = "info"
maxRetriesloadAgentInfo = 5
)

type persistentAgentInfo struct {
ID string `json:"id" yaml:"id" config:"id"`
Expand Down
120 changes: 112 additions & 8 deletions internal/pkg/agent/cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"os"
"strings"
"time"

"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
Expand All @@ -31,6 +32,14 @@ const (
flagPGPBytes = "pgp"
flagPGPBytesPath = "pgp-path"
flagPGPBytesURI = "pgp-uri"
flagForce = "force"
)

var (
unsupportedUpgradeError error = errors.New("this agent is fleet managed and must be upgraded using Fleet")
nonRootExecutionError = errors.New("upgrade command needs to be executed as root for fleet managed agents")
skipVerifyNotAllowedError = errors.New(fmt.Sprintf("\"%s\" flag is not allowed when upgrading a fleet managed agent using the cli", flagSkipVerify))
skipVerifyNotRootError = errors.New(fmt.Sprintf("user needs to be root to use \"%s\" flag when upgrading standalone agents", flagSkipVerify))
)

func newUpgradeCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command {
Expand All @@ -40,6 +49,7 @@ func newUpgradeCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Comman
Long: "This command upgrades the currently installed Elastic Agent to the specified version.",
Args: cobra.ExactArgs(1),
Run: func(c *cobra.Command, args []string) {
c.SetContext(context.Background())
if err := upgradeCmd(streams, c, args); err != nil {
fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
os.Exit(1)
Expand All @@ -53,24 +63,119 @@ func newUpgradeCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Comman
cmd.Flags().String(flagPGPBytes, "", "PGP to use for package verification")
cmd.Flags().String(flagPGPBytesURI, "", "Path to a web location containing PGP to use for package verification")
cmd.Flags().String(flagPGPBytesPath, "", "Path to a file containing PGP to use for package verification")
cmd.Flags().BoolP(flagForce, "", false, "Advanced option to force an upgrade on a fleet managed agent")
err := cmd.Flags().MarkHidden(flagForce)
andrzej-stencel marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
fmt.Fprintf(streams.Err, "error while setting upgrade force flag attributes: %s", err.Error())
os.Exit(1)
}

return cmd
}

type upgradeInput struct {
streams *cli.IOStreams
cmd *cobra.Command
args []string
c client.Client
agentInfo client.AgentStateInfo
isRoot bool
}

func upgradeCmd(streams *cli.IOStreams, cmd *cobra.Command, args []string) error {
c := client.New()
return upgradeCmdWithClient(streams, cmd, args, c)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

err := c.Connect(ctx)
if err != nil {
return errors.New(err, "failed communicating to running daemon", errors.TypeNetwork, errors.M("socket", control.Address()))
}
defer c.Disconnect()
state, err := c.State(cmd.Context())
if err != nil {
return fmt.Errorf("error while trying to get agent state: %w", err)
}

isRoot, err := utils.HasRoot()
if err != nil {
return fmt.Errorf("error while retrieving user permission: %w", err)
}

input := &upgradeInput{
streams,
cmd,
args,
c,
state.Info,
isRoot,
}
return upgradeCmdWithClient(input)
}

type upgradeCond struct {
isManaged bool
force bool
isRoot bool
skipVerify bool
}

func upgradeCmdWithClient(streams *cli.IOStreams, cmd *cobra.Command, args []string, c client.Client) error {
version := args[0]
func checkUpgradable(cond upgradeCond) error {
checkManaged := func() error {
if !cond.force {
return unsupportedUpgradeError
}

if cond.skipVerify {
return skipVerifyNotAllowedError
}

if !cond.isRoot {
return nonRootExecutionError
}

return nil
}

checkStandalone := func() error {
if cond.skipVerify && !cond.isRoot {
return skipVerifyNotRootError
}
return nil
}

if cond.isManaged {
return checkManaged()
}

return checkStandalone()
}

func upgradeCmdWithClient(input *upgradeInput) error {
cmd := input.cmd
c := input.c
version := input.args[0]
sourceURI, _ := cmd.Flags().GetString(flagSourceURI)

err := c.Connect(context.Background())
force, err := cmd.Flags().GetBool(flagForce)
if err != nil {
return errors.New(err, "Failed communicating to running daemon", errors.TypeNetwork, errors.M("socket", control.Address()))
return fmt.Errorf("failed to retrieve command flag information while trying to upgrade the agent: %w", err)
}

skipVerification, err := cmd.Flags().GetBool(flagSkipVerify)
if err != nil {
return fmt.Errorf("failed to retrieve %s flag information while upgrading the agent: %w", flagSkipVerify, err)
}

err = checkUpgradable(upgradeCond{
isManaged: input.agentInfo.IsManaged,
force: force,
isRoot: input.isRoot,
skipVerify: skipVerification,
})
if err != nil {
return fmt.Errorf("aborting upgrade: %w", err)
}
defer c.Disconnect()

isBeingUpgraded, err := upgrade.IsInProgress(c, utils.GetWatcherPIDs)
if err != nil {
Expand All @@ -80,7 +185,6 @@ func upgradeCmdWithClient(streams *cli.IOStreams, cmd *cobra.Command, args []str
return errors.New("an upgrade is already in progress; please try again later.")
}

skipVerification, _ := cmd.Flags().GetBool(flagSkipVerify)
var pgpChecks []string
if !skipVerification {
// get local PGP
Expand Down Expand Up @@ -122,6 +226,6 @@ func upgradeCmdWithClient(streams *cli.IOStreams, cmd *cobra.Command, args []str
return errors.New(err, "Failed trigger upgrade of daemon")
}
}
fmt.Fprintf(streams.Out, "Upgrade triggered to version %s, Elastic Agent is currently restarting\n", version)
fmt.Fprintf(input.streams.Out, "Upgrade triggered to version %s, Elastic Agent is currently restarting\n", version)
return nil
}
Loading