-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactored bash command * added max exec time * fix bash script support * split bash and ansbile * implementing ansible support * added full ansible support * updated readme
- Loading branch information
1 parent
f21da61
commit 211b556
Showing
28 changed files
with
711 additions
and
955 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
.PHONY: lint | ||
lint: | ||
golangci-lint run -E whitespace -E wsl -E wastedassign -E unconvert -E tparallel -E thelper -E stylecheck -E prealloc \ | ||
golangci-lint run --fix -E whitespace -E wsl -E wastedassign -E unconvert -E tparallel -E thelper -E stylecheck -E prealloc \ | ||
-E predeclared -E nlreturn -E misspell -E makezero -E lll -E importas -E gosec -E gofmt -E goconst \ | ||
-E forcetypeassert -E dogsled -E dupl -E errname -E errorlint -E nolintlint --timeout 2m |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,72 @@ | ||
# AWS Commander | ||
|
||
A tool used for running bash scripts on AWS EC2 instances, leveraging AWS Systems Manager > Run Command feature. | ||
User can load a bash script or define a single command, that will execute on all instances with defined instance ID. | ||
A tool used for easier automation of the AWS EC2 instances, leveraging AWS Systems Manager - Run Command feature. | ||
Supported scripts: | ||
* One-liner bash command | ||
* Bash script loaded from a local filesystem | ||
* Ansible playbook loaded from a local filesystem | ||
|
||
The command/script/playbook will run across all EC2 instances simultaneously. | ||
EC2 instances, for now, can be selected only by their IDs. Support for selection by tags will be | ||
added in future versions. | ||
|
||
|
||
## Prerequisites | ||
|
||
* The **AmazonSSMManagedInstanceCore** must be placed on all instances that need to be managed via this tool. | ||
* AWS API credentials defined in *aws credentials* file or as environment variables | ||
* The **AmazonSSMManagedInstanceCore** IAM role, attached on all EC2 instances. | ||
* Authenticated AWS CLI session | ||
|
||
## Usage | ||
|
||
### AWS credentials | ||
AWS credentials can be pulled from environment variables or from aws credentials file. | ||
To define a which profile from credentials file should be used, set `aws-profile` flag. By default, it is set to `default`. | ||
Environment variables with credentials that can be set: | ||
* `AWS_ACCESS_KEY_ID` - the aws access key id | ||
* `AWS_SECRET_ACCESS_KEY` - the access key secret | ||
* `AWS_SESSION_TOKEN` - the session token (optional) | ||
|
||
AWS access must be authenticated via `aws cli`. | ||
|
||
### General Parameters | ||
* `aws-profile` - AWS profile as defined in *aws credentials* file. Default: `default` | ||
* `aws-zone` - AWS zone in which EC2 instances reside. Default: `eu-central-1` | ||
* `instances` - instance IDs, separated by comma (,). This is a mandatory flag. | ||
* `log-level` - the level of logging output (info, debug, error). Default: `info` | ||
* `output` - a file name to write the output result of a command/script. Default: `console output` | ||
* `mode` - switch between modes - Bash script or Ansible playbook. Default: `bash` | ||
* `log-level` - the level of logging output (`info`, `debug`, `error`). Default: `info` | ||
* `mode` - commands running mode (`bash`, `ansible`) Default: `bash` | ||
* `profile` - AWS profile as defined in *aws credentials* file. | ||
* `region` - AWS region in which EC2 instances reside. | ||
* `ids` - instance IDs, separated by comma (`,`). This is a mandatory flag. | ||
* `max-wait` - maximum wait time in seconds to run the command Default: `30` | ||
* `max-exec` - maximum wait time in seconds to get command result Default: `300` | ||
|
||
### Running Bash scripts | ||
* `cmd` - one-liner bash command that will be executed on EC2 instances. | ||
* `script` - the location of bash script file that will run on EC2 instances. | ||
* `mode` - for running bash scripts `mode` can be omitted as the default value is `bash` | ||
|
||
If both `cmd` and `script` flags are defined, `script` will take precedence, and `cmd` will be disregarded. | ||
* `mode` - for running Bash script or oneliner `mode` can be omitted or set to `bash` | ||
|
||
#### Example | ||
|
||
```bash | ||
# AWS authentication | ||
aws sso login --profile test-account | ||
|
||
# oneliner | ||
aws-commander -instances i-0bf9c273c67f684a0,i-011c9b3e3607a63b5,i-0e53e37f7b34517f5,i-0f02ca10faf8f349e -cmd "cd /tmp && ls -lah" -aws-profile test-account | ||
|
||
# or bash script | ||
aws-commander -instances i-0bf9c273c67f684a0,i-011c9b3e3607a63b5,i-0e53e37f7b34517f5,i-0f02ca10faf8f349e -script ./script.sh -aws-profile test-account | ||
``` | ||
|
||
### Running Ansible Playbook | ||
* `playbook` - the location of Ansible playbook that will be executed on EC2 instances. | ||
* `ansible-url` - the URL locaction of the Ansible playbook | ||
* `extra-vars` - comma delimited, key value pairs of Ansible variables | ||
* `dryrun` - when set to true, Ansible playbook will run and the output will be shown, but | ||
no data will be changed. | ||
no data will be changed. Default: `false` | ||
* `mode` - for running Ansible playbook `mode` must be set to `ansible` | ||
|
||
#### Ansible prerequisites | ||
Every EC2 instance, that should run Ansible playbook, must have Ansible already installed. | ||
If Ansible is not installed, the deployment will fail. | ||
You can use `bash` mode to simply install Ansible from your OS package manager before running the playbook. | ||
|
||
#### Example | ||
```bash | ||
## if Ansible is not installed on host - install Ansible | ||
aws-commander -instances i-0bf9c273c67f684a0,i-011c9b3e3607a63b5,i-0e53e37f7b34517f5,i-0f02ca10faf8f349e -cmd "sudo apt install -y ansible" -aws-profile test-account -aws-zone us-west-2 | ||
## run playbook | ||
aws-commander -instances i-0bf9c273c67f684a0,i-011c9b3e3607a63b5,i-0e53e37f7b34517f5,i-0f02ca10faf8f349e -mode ansible -playbook scripts/nodes-restart.yaml -aws-profile test-account -aws-zone us-west-2 | ||
# AWS authentication | ||
aws sso login | ||
|
||
# run local playbook | ||
aws-commander -instances i-0bf9c273c67f684a0,i-011c9b3e3607a63b5,i-0e53e37f7b34517f5,i-0f02ca10faf8f349e -mode ansible -playbook scripts/init.yaml -extra-vars foo=bar,faz=baz | ||
|
||
# or from url | ||
aws-commander -instances i-0bf9c273c67f684a0,i-011c9b3e3607a63b5,i-0e53e37f7b34517f5,i-0f02ca10faf8f349e -mode ansible -ansible-url https://example.com/init.yaml -extra-vars foo=bar,faz=baz | ||
``` | ||
|
||
#### Missing features | ||
Currently, running the Ansible playbook from a remote location via URL / S3 is not supported. | ||
It will be supported in the future release. | ||
* Select EC2 instances using instance tags |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package app | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/Trapesys/aws-commander/aws" | ||
"github.com/Trapesys/aws-commander/conf" | ||
"github.com/Trapesys/aws-commander/logger" | ||
"go.uber.org/fx" | ||
) | ||
|
||
func Run() { | ||
fx.New( | ||
fx.Provide( | ||
conf.New, | ||
logger.New, | ||
aws.New, | ||
), | ||
fx.Invoke(mainApp), | ||
fx.NopLogger, | ||
).Run() | ||
} | ||
|
||
func mainApp(log logger.Logger, awss aws.Aws) { | ||
if err := awss.Run(); err != nil { | ||
log.Error("Run command error", "err", err) | ||
os.Exit(1) | ||
} | ||
|
||
os.Exit(0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package aws | ||
|
||
import ( | ||
"github.com/Trapesys/aws-commander/aws/ssm" | ||
"github.com/Trapesys/aws-commander/conf" | ||
"github.com/Trapesys/aws-commander/logger" | ||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type mode string | ||
|
||
const ( | ||
bash mode = "bash" | ||
ansible mode = "ansible" | ||
) | ||
|
||
type modeHandler func() error | ||
|
||
type modesFactory map[mode]modeHandler | ||
|
||
var ( | ||
ErrModeNotSupported = errors.New("selected mode not supported") | ||
) | ||
|
||
type SSM interface { | ||
RunBash() error | ||
RunAnsible() error | ||
} | ||
|
||
type Aws struct { | ||
conf conf.Config | ||
ssm SSM | ||
modes modesFactory | ||
} | ||
|
||
func New(conf conf.Config, log logger.Logger) Aws { | ||
sess, err := provideSesson(conf) | ||
if err != nil { | ||
log.Fatalln("Could not create AWS session", "err", err.Error()) | ||
} | ||
|
||
localssm := ssm.New(log, conf, sess) | ||
|
||
return Aws{ | ||
conf: conf, | ||
ssm: localssm, | ||
modes: modesFactory{ | ||
bash: localssm.RunBash, | ||
ansible: localssm.RunAnsible, | ||
}, | ||
} | ||
} | ||
|
||
func (a *Aws) Run() error { | ||
modeHn, ok := a.modes[mode(a.conf.Mode)] | ||
if !ok { | ||
return ErrModeNotSupported | ||
} | ||
|
||
return modeHn() | ||
} | ||
|
||
func provideSesson(conf conf.Config) (*session.Session, error) { | ||
sessOpt := session.Options{} | ||
sessConf := aws.Config{} | ||
|
||
if conf.AWSRegion != "" { | ||
sessConf.Region = &conf.AWSRegion | ||
} | ||
|
||
if conf.AWSProfile != "" { | ||
sessOpt.Profile = conf.AWSProfile | ||
} | ||
|
||
sessOpt.Config = sessConf | ||
sessOpt.SharedConfigState = session.SharedConfigEnable | ||
|
||
return session.NewSessionWithOptions(sessOpt) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package ssm | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
assm "github.com/aws/aws-sdk-go/service/ssm" | ||
"github.com/davecgh/go-spew/spew" | ||
) | ||
|
||
func (s ssm) RunAnsible() error { | ||
s.log.Info("Running ssm ansible command") | ||
|
||
command, err := s.cl.SendCommand(&assm.SendCommandInput{ | ||
DocumentName: aws.String("AWS-RunAnsiblePlaybook"), | ||
DocumentVersion: aws.String("$LATEST"), | ||
InstanceIds: s.provideInstanceIDs(), | ||
Parameters: s.provideAnsibleCommands(), | ||
TimeoutSeconds: &s.conf.CommandExecMaxWait, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
s.log.Info("Ansible playbook deployed successfully") | ||
s.log.Info("Waiting for results...") | ||
|
||
s.waitForCmdExecAndDisplayCmdOutput(command) | ||
|
||
return nil | ||
} | ||
|
||
func (s ssm) provideAnsibleCommands() map[string][]*string { | ||
var ( | ||
trueStr = "True" | ||
falseStr = "False" | ||
resp = map[string][]*string{} | ||
check = map[bool]*string{ | ||
true: &trueStr, | ||
false: &falseStr, | ||
} | ||
) | ||
|
||
resp["check"] = []*string{check[s.conf.AnsibleDryRun]} | ||
|
||
if s.conf.AnsiblePlaybook != "" { | ||
playbookStr, err := os.ReadFile(s.conf.AnsiblePlaybook) | ||
if err != nil { | ||
s.log.Fatalln("Could not read ansible playbook", "err", err.Error()) | ||
} | ||
|
||
playbook := string(playbookStr) | ||
|
||
resp["playbook"] = []*string{&playbook} | ||
} | ||
|
||
if s.conf.AnsibleURL != "" { | ||
resp["playbookurl"] = []*string{&s.conf.AnsibleURL} | ||
} | ||
|
||
if s.conf.AnsibleExtraVars != "" { | ||
resp["extravars"] = []*string{s.processExtraVars()} | ||
} | ||
|
||
s.log.Debug("Ansible params", "prams", spew.Sdump(resp)) | ||
|
||
return resp | ||
} | ||
|
||
func (s ssm) processExtraVars() *string { | ||
var ( | ||
trimmedVars = make([]string, 0) | ||
processedVars string | ||
) | ||
|
||
vars := strings.Split(strings.TrimSpace(s.conf.AnsibleExtraVars), ",") | ||
for _, v := range vars { | ||
trimmedVars = append(trimmedVars, strings.TrimSpace(v)) | ||
} | ||
|
||
for _, tv := range trimmedVars { | ||
processedVars += tv + " " | ||
} | ||
|
||
processedVars = processedVars[:len(processedVars)-1] // trim last space char | ||
|
||
s.log.Debug("Processed extra vars", "vars", processedVars) | ||
|
||
return &processedVars | ||
} |
Oops, something went wrong.