// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. // Package main represents the entry point of the ssm agent updater. package main import ( "flag" "fmt" "os" "runtime/debug" "time" "github.com/aws/amazon-ssm-agent/agent/appconfig" "github.com/aws/amazon-ssm-agent/agent/context" "github.com/aws/amazon-ssm-agent/agent/contracts" loginterface "github.com/aws/amazon-ssm-agent/agent/log" "github.com/aws/amazon-ssm-agent/agent/log/logger" "github.com/aws/amazon-ssm-agent/agent/log/ssmlog" "github.com/aws/amazon-ssm-agent/agent/update/processor" "github.com/aws/amazon-ssm-agent/agent/updateutil" "github.com/aws/amazon-ssm-agent/agent/updateutil/updateconstants" "github.com/aws/amazon-ssm-agent/agent/updateutil/updateinfo" "github.com/aws/amazon-ssm-agent/agent/updateutil/updatemanifest" "github.com/aws/amazon-ssm-agent/agent/version" "github.com/aws/amazon-ssm-agent/common/identity" identity2 "github.com/aws/amazon-ssm-agent/common/identity/identity" "github.com/nightlyone/lockfile" ) const ( defaultLogFileName = "AmazonSSMAgent-update.txt" defaultWaitTimeForAgentToFinish = 3 errorExitCode = 1 nonErrorExitCode = 0 ) var ( updater processor.T log loginterface.T agentContext context.T ) var ( update *bool sourceVersion *string sourceLocation *string sourceHash *string targetVersion *string targetLocation *string targetHash *string packageName *string messageID *string stdout *string stderr *string outputKeyPrefix *string outputBucket *string manifestURL *string selfUpdate *bool disableDowngrade *bool upstreamServiceName *string ) var ( newAgentIdentity = identity2.NewAgentIdentity isIdentityRuntimeConfigSupported = updateutil.IsIdentityRuntimeConfigSupported ) func init() { log = ssmlog.GetUpdaterLogger(logger.DefaultLogDir, defaultLogFileName) // Load update detail from command line update = flag.Bool(updateconstants.UpdateCmd, false, "current Agent Version") sourceVersion = flag.String(updateconstants.SourceVersionCmd, "", "current Agent Version") sourceLocation = flag.String(updateconstants.SourceLocationCmd, "", "current Agent installer source") targetVersion = flag.String(updateconstants.TargetVersionCmd, "", "target Agent Version") packageName = flag.String(updateconstants.PackageNameCmd, "", "target Agent Version") messageID = flag.String(updateconstants.MessageIDCmd, "", "target Agent Version") stdout = flag.String(updateconstants.StdoutFileName, "", "standard output file path") stderr = flag.String(updateconstants.StderrFileName, "", "standard error file path") outputKeyPrefix = flag.String(updateconstants.OutputKeyPrefixCmd, "", "output key prefix") outputBucket = flag.String(updateconstants.OutputBucketNameCmd, "", "output bucket name") manifestURL = flag.String(updateconstants.ManifestFileUrlCmd, "", "Manifest file url") selfUpdate = flag.Bool(updateconstants.SelfUpdateCmd, false, "SelfUpdate command") disableDowngrade = flag.Bool(updateconstants.DisableDowngradeCmd, false, "defines if updater is allowed to downgrade") upstreamServiceName = flag.String(updateconstants.UpstreamServiceName, string(contracts.MessageDeliveryService), "defines the upstream messaging service") // Legacy flags no longer used, need to be defined or we get this error: flag provided but not defined flag.String(updateconstants.TargetLocationCmd, "", "target Agent installer source") flag.String(updateconstants.TargetHashCmd, "", "target Agent installer hash") flag.String(updateconstants.SourceHashCmd, "", "current Agent installer hash") } func main() { log.Infof("SSM Agent Updater - %s", version.String()) os.Exit(updateAgent()) } func updateAgent() int { defer log.Close() defer log.Flush() flag.Parse() // Initialize agent config for agent identity appConfig, err := appconfig.Config(true) if err != nil { log.Warnf("Failed to load agent config: %v", err) } // Create identity selector agentIdentity, err := resolveAgentIdentity(appConfig) if err != nil { log.Errorf("Failed to assume agent identity: %v", err) return errorExitCode } agentContext = context.Default(log, appConfig, agentIdentity) updateUtilRef := updateutil.NewUpdaterUtilWithLoadedDocContent(agentContext, *messageID) updateSSMUserShellProperties(log) // Create update info updateInfo, err := updateinfo.New(agentContext) if err != nil { log.Errorf("Failed to initialize update info object: %v", err) return errorExitCode } // Sleep 3 seconds to allow agent to finishing up it's work time.Sleep(defaultWaitTimeForAgentToFinish * time.Second) updater = processor.NewUpdater(agentContext, updateInfo, updateUtilRef) // If the updater already owns the lockfile, no harm done // If there is no lockfile, the updater will own it // If the updater is unable to lock the file, we retry and then fail lock, _ := lockfile.New(appconfig.UpdaterPidLockfile) err = lock.TryLockExpireWithRetry(updateconstants.UpdateLockFileMinutes) if err != nil { if err == lockfile.ErrBusy { log.Warnf("Failed to lock update lockfile, another update is in progress: %s", err) return nonErrorExitCode } else { log.Warnf("Proceeding update process with new lock. Failed to lock update lockfile: %s", err) } } defer lock.Unlock() // Return if update is not present in the command if !*update { log.Error("incorrect usage (use -update).") flag.Usage() return nonErrorExitCode } // Basic Validation if len(*manifestURL) == 0 && len(*sourceLocation) == 0 { log.Error("must pass either manifest url or source location") flag.Usage() } if len(*sourceVersion) == 0 { log.Error("no current version") flag.Usage() } if !*selfUpdate && len(*targetVersion) == 0 { log.Error("no target version") flag.Usage() } // Create new UpdateDetail updateDetail := &processor.UpdateDetail{ State: processor.NotStarted, Result: contracts.ResultStatusInProgress, SourceVersion: *sourceVersion, SourceLocation: *sourceLocation, TargetVersion: *targetVersion, StdoutFileName: *stdout, StderrFileName: *stderr, OutputS3KeyPrefix: *outputKeyPrefix, OutputS3BucketName: *outputBucket, PackageName: *packageName, MessageID: *messageID, StartDateTime: time.Now().UTC(), RequiresUninstall: false, ManifestURL: *manifestURL, Manifest: updatemanifest.New(agentContext, updateInfo), SelfUpdate: *selfUpdate, AllowDowngrade: !*disableDowngrade, UpstreamServiceName: *upstreamServiceName, } updateDetail.UpdateRoot, err = updateutil.ResolveUpdateRoot(updateDetail.SourceVersion) if err != nil { log.Errorf("Failed to resolve update root: %v", err) return nonErrorExitCode } log.Infof("Update root is: %v", updateDetail.UpdateRoot) // Initialize update detail with plugin info err = updater.InitializeUpdate(log, updateDetail) if err != nil { log.Errorf(err.Error()) return nonErrorExitCode } // Recover updater if panic occurs and fail the updater defer recoverUpdaterFromPanic(updateDetail) // Start or resume update if err = updater.StartOrResumeUpdate(log, updateDetail); err != nil { // We do not send any error above this to ICS/MGS except panic message // Rolled back, but service cannot start, Update failed. updater.Failed(updateDetail, log, updateconstants.ErrorUnexpected, err.Error(), false) } else { log.Infof(updateDetail.StandardOut) } return nonErrorExitCode } func resolveAgentIdentity(appConfig appconfig.SsmagentConfig) (identity.IAgentIdentity, error) { var selector identity2.IAgentIdentitySelector var agentIdentity identity.IAgentIdentity var err error // To support downgrades and rollbacks, we want to make sure that the source version supports runtime config if isIdentityRuntimeConfigSupported(*sourceVersion) { selector = identity2.NewRuntimeConfigIdentitySelector(log) agentIdentity, err = newAgentIdentity(log, &appConfig, selector) // If success, return the identity if err == nil { log.Debugf("Using identity from runtime config") return agentIdentity, nil } } // If not able to resolve agent identity with runtime config or source version // does not support runtimeconfig, fallback to default identity selector selector = identity2.NewDefaultAgentIdentitySelector(log) agentIdentity, err = newAgentIdentity(log, &appConfig, selector) if err != nil { return nil, err } return agentIdentity, nil } // recoverUpdaterFromPanic recovers updater if panic occurs and fails the updater func recoverUpdaterFromPanic(updateDetail *processor.UpdateDetail) { // recover in case the updater panics if err := recover(); err != nil { agentContext.Log().Errorf("recovered from panic for updater %v!", err) agentContext.Log().Errorf("Stacktrace:\n%s", debug.Stack()) updater.Failed(updateDetail, agentContext.Log(), updateconstants.ErrorUnexpectedThroughPanic, fmt.Sprintf("%v", err), false) } }