diff --git a/README.md b/README.md index 071f55541..ceb33b170 100644 --- a/README.md +++ b/README.md @@ -307,5 +307,4 @@ See `circle.yml` and `_ci/build-and-push-release-asset.sh` for details. * Add a `show-lock` command. * Add a command to automatically set up best-practices remote state storage in a versioned, encrypted, S3 bucket. * Add a command to list the different versions of state available in a versioned S3 bucket and to diff any two state - files. -* Use IAM username instead of the local OS username in lock metadata. \ No newline at end of file + files. \ No newline at end of file diff --git a/cli/cli_app.go b/cli/cli_app.go index 4df9d4578..0fe0dfed3 100644 --- a/cli/cli_app.go +++ b/cli/cli_app.go @@ -70,6 +70,12 @@ func runApp(cliContext *cli.Context) error { return err } + // If someone calls us with no args at all, show the help text and exit + if !cliContext.Args().Present() { + cli.ShowAppHelp(cliContext) + return nil + } + if err := downloadModules(cliContext); err != nil { return err } diff --git a/dynamodb/dynamo_lock.go b/dynamodb/dynamo_lock.go index c6f1fe161..4fc4bd21d 100644 --- a/dynamodb/dynamo_lock.go +++ b/dynamodb/dynamo_lock.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/gruntwork-io/terragrunt/util" "github.com/gruntwork-io/terragrunt/errors" + "github.com/aws/aws-sdk-go/aws" ) // A lock that uses AWS's DynamoDB to acquire and release locks @@ -82,6 +83,16 @@ func (dynamoLock DynamoDbLock) String() string { // Create an authenticated client for DynamoDB func createDynamoDbClient(awsRegion string) (*dynamodb.DynamoDB, error) { + config, err := createAwsConfig(awsRegion) + if err != nil { + return nil, err + } + + return dynamodb.New(session.New(), config), nil +} + +// Returns an AWS config object for the given region, ensuring that the config has credentials +func createAwsConfig(awsRegion string) (*aws.Config, error) { config := defaults.Get().Config.WithRegion(awsRegion) _, err := config.Credentials.Get() @@ -89,7 +100,7 @@ func createDynamoDbClient(awsRegion string) (*dynamodb.DynamoDB, error) { return nil, errors.WithStackTraceAndPrefix(err, "Error finding AWS credentials (did you set the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables?)") } - return dynamodb.New(session.New(), config), nil + return config, nil } var StateFileIdMissing = fmt.Errorf("The dynamodb.stateFileId field cannot be empty") diff --git a/dynamodb/dynamo_lock_item.go b/dynamodb/dynamo_lock_item.go index 326e1baca..5485715bb 100644 --- a/dynamodb/dynamo_lock_item.go +++ b/dynamodb/dynamo_lock_item.go @@ -8,6 +8,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "fmt" "github.com/gruntwork-io/terragrunt/errors" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/aws/session" ) // Create a DynamoDB key for the given item id @@ -87,8 +89,13 @@ func getAttribute(item map[string]*dynamodb.AttributeValue, attribute string) (s // Create a DynamoDB item for the given item id. This item represents a lock and will include metadata about the // current user, who is trying to acquire the lock. -func createItem(itemId string) (map[string]*dynamodb.AttributeValue, error) { - lockMetadata, err := locks.CreateLockMetadata(itemId) +func createItemAttributes(itemId string, client *dynamodb.DynamoDB) (map[string]*dynamodb.AttributeValue, error) { + iamUsername, err := getIamUsername(client) + if err != nil { + return nil, err + } + + lockMetadata, err := locks.CreateLockMetadata(itemId, iamUsername) if err != nil { return nil, err } @@ -101,6 +108,17 @@ func createItem(itemId string) (map[string]*dynamodb.AttributeValue, error) { }, nil } +// Return the IAM username of the currently logged in user +func getIamUsername(client *dynamodb.DynamoDB) (string, error) { + iamClient := iam.New(session.New(), &client.Config) + output, err := iamClient.GetUser(&iam.GetUserInput{}) + if err != nil { + return "", errors.WithStackTrace(err) + } + + return *output.User.UserName, nil +} + type AttributeMissing struct { AttributeName string } diff --git a/dynamodb/dynamo_lock_table.go b/dynamodb/dynamo_lock_table.go index 51d2b6ae0..80147c5e5 100644 --- a/dynamodb/dynamo_lock_table.go +++ b/dynamodb/dynamo_lock_table.go @@ -114,7 +114,7 @@ func removeItemFromLockTable(itemId string, tableName string, client *dynamodb.D // Write the given item to the DynamoDB lock table. If the given item already exists, return an error. func writeItemToLockTable(itemId string, tableName string, client *dynamodb.DynamoDB) error { - item, err := createItem(itemId) + item, err := createItemAttributes(itemId, client) if err != nil { return err } diff --git a/locks/lock_metadata.go b/locks/lock_metadata.go index be11d7260..47b423e41 100644 --- a/locks/lock_metadata.go +++ b/locks/lock_metadata.go @@ -3,7 +3,6 @@ package locks import ( "time" "net" - "os/user" "github.com/gruntwork-io/terragrunt/errors" "fmt" ) @@ -19,13 +18,8 @@ type LockMetadata struct { DateCreated time.Time } -// Create the LockMetadata for the current user -func CreateLockMetadata(stateFileId string) (*LockMetadata, error) { - user, err := user.Current() - if err != nil { - return nil, errors.WithStackTrace(err) - } - +// Create the LockMetadata for the given state file and user +func CreateLockMetadata(stateFileId string, username string) (*LockMetadata, error) { ipAddress, err := getIpAddress() if err != nil { return nil, errors.WithStackTrace(err) @@ -35,7 +29,7 @@ func CreateLockMetadata(stateFileId string) (*LockMetadata, error) { return &LockMetadata{ StateFileId: stateFileId, - Username: user.Username, + Username: username, IpAddress: ipAddress, DateCreated: dateCreated, }, nil diff --git a/locks/lock_metdata_test.go b/locks/lock_metdata_test.go index 8c2076c9d..f3170a1f4 100644 --- a/locks/lock_metdata_test.go +++ b/locks/lock_metdata_test.go @@ -18,13 +18,14 @@ func TestCreateLockMetadata(t *testing.T) { t.Parallel() expectedStateFileId := "expected-state-file-id" - lockMetadata, err := CreateLockMetadata(expectedStateFileId) + expectedUsername := "jim" + lockMetadata, err := CreateLockMetadata(expectedStateFileId, expectedUsername) assert.Nil(t, err) assert.Equal(t, expectedStateFileId, lockMetadata.StateFileId) assert.False(t, lockMetadata.DateCreated.IsZero()) assertIsValidIp(t, lockMetadata.IpAddress) - assert.NotEmpty(t, lockMetadata.Username) + assert.Equal(t, expectedUsername, lockMetadata.Username) } func assertIsValidIp(t *testing.T, ip string) {