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

Release v0.5.6 #31

Merged
merged 16 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/dev-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.23
-
name: Run linting
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
only-new-issues: true # Only show new issues for pull requests.
format:
Expand All @@ -34,7 +34,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.23
-
name: Run formatting
run: gofmt -s -w duplocloud cmd/duplo-aws-credential-process
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dev-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.23
-
name: Run tests
run: make test
2 changes: 1 addition & 1 deletion .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.23
-
name: Run tests
run: make test
Expand Down
23 changes: 23 additions & 0 deletions .pr_agent.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See: https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app
# See: https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml

[pr_reviewer]
enable_review_labels_effort = true

[pr_description]
add_original_user_description = true
keep_original_user_title = true

[github_app]
handle_pr_actions = ['opened', 'reopened', 'ready_for_review', 'review_requested']
pr_commands = [
"/describe",
"/review",
"/update_changelog --pr_update_changelog.push_changelog_changes=true"
]
handle_push_trigger = true
push_commands = [
"/describe",
"/review -i --pr_reviewer.remove_previous_review_comment=true",
"/update_changelog"
]
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## 2024-02-14

### Added
- Introduced a new section in the README for Homebrew installation, enhancing the accessibility of the tool for macOS users.

## 2024-01-24

### Fixed
- Improved error message format when a tenant is missing or not allowed.
- Prevented appending a nil error object to fatal error messages.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION=0.5.5
VERSION=0.5.6

default: all

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ Command-line tools for JIT Duplo, AWS and Kubernetes access

## Installation

### From release zip files

See the *Releases* section of this repository.

- Download a release artifact that matches your system's architecture.
- Unzip the artifact.
- Install the binaries somewhere in your `PATH`, such as the `/usr/local/bin` directory.

### With Homebrew

run `brew install duplocloud/tap/duplo-jit` from your terminal

## Usage

### duplo-jit aws
Expand Down Expand Up @@ -47,6 +53,8 @@ Usage of duplo-jit:
Duplo API base URL
-interactive
Allow getting Duplo credentials via an interactive browser session
-port
Allow choosing a port for the interactive browser session. Default is random
-no-cache
Disable caching (not recommended)
-tenant string
Expand All @@ -67,6 +75,8 @@ Usage of duplo-jit:
Duplo API base URL
-interactive
Allow getting Duplo credentials via an interactive browser session
-port
Allow choosing a port for the interactive browser session. Default is random
-no-cache
Disable caching (not recommended)
-token string
Expand Down
11 changes: 6 additions & 5 deletions cmd/duplo-aws-credential-process/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/duplocloud/duplo-jit/internal"
)

func mustDuploClient(host, token string, interactive, admin bool) *duplocloud.Client {
func mustDuploClient(host, token string, interactive, admin bool, port int) *duplocloud.Client {
otp := ""

// Possibly get a token from an interactive process.
Expand All @@ -21,7 +21,7 @@ func mustDuploClient(host, token string, interactive, admin bool) *duplocloud.Cl
log.Fatalf("%s: --token not specified and --interactive mode is disabled", os.Args[0])
}

tokenResult := internal.MustTokenInteractive(host, admin, "duplo-aws-credential-process")
tokenResult := internal.MustTokenInteractive(host, admin, "duplo-aws-credential-process", port)
token = tokenResult.Token
otp = tokenResult.OTP
}
Expand Down Expand Up @@ -50,6 +50,7 @@ func main() {
noCache := flag.Bool("no-cache", false, "Disable caching (not recommended)")
interactive := flag.Bool("interactive", false, "Allow getting Duplo credentials via an interactive browser session")
showVersion := flag.Bool("version", false, "Output version information and exit")
port := flag.Int("port", 0, "Port to use for the local web server")
flag.Parse()

// Output version information
Expand Down Expand Up @@ -92,7 +93,7 @@ func main() {

// Otherwise, get the credentials from Duplo.
if creds == nil {
client := mustDuploClient(*host, *token, *interactive, true)
client := mustDuploClient(*host, *token, *interactive, true, *port)
result, err := client.AdminGetJitAwsCredentials()
internal.DieIf(err, "failed to get credentials")
creds = internal.ConvertAwsCreds(result)
Expand All @@ -108,7 +109,7 @@ func main() {

// Otherwise, get the credentials from Duplo.
if creds == nil {
client := mustDuploClient(*host, *token, *interactive, true)
client := mustDuploClient(*host, *token, *interactive, true, *port)
result, err := client.AdminAwsGetJitAccess("duplo-ops")
internal.DieIf(err, "failed to get credentials")
creds = internal.ConvertAwsCreds(result)
Expand All @@ -129,7 +130,7 @@ func main() {

// Otherwise, get the credentials from Duplo.
if creds == nil {
client := mustDuploClient(*host, *token, *interactive, false)
client := mustDuploClient(*host, *token, *interactive, false, *port)

// If it doesn't look like a UUID, get the tenant ID from the name.
if len(*tenantID) < 32 {
Expand Down
23 changes: 12 additions & 11 deletions cmd/duplo-jit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func main() {
debug := flag.Bool("debug", false, "Turn on verbose (debugging) output")
noCache := flag.Bool("no-cache", false, "Disable caching (not recommended)")
interactive := flag.Bool("interactive", false, "Allow getting Duplo credentials via an interactive browser session")
port := flag.Int("port", 0, "Port to use for the local web server")
showVersion := flag.Bool("version", false, "Output version information and exit")
admin = new(bool)
duploOps = new(bool)
Expand Down Expand Up @@ -105,7 +106,7 @@ func main() {

// Otherwise, get the credentials from Duplo.
if creds == nil {
client, _ := internal.MustDuploClient(*host, *token, *interactive, true)
client, _ := internal.MustDuploClient(*host, *token, *interactive, true, *port)
result, err := client.AdminGetJitAwsCredentials()
internal.DieIf(err, "failed to get credentials")
creds = internal.ConvertAwsCreds(result)
Expand All @@ -121,7 +122,7 @@ func main() {

// Otherwise, get the credentials from Duplo.
if creds == nil {
client, _ := internal.MustDuploClient(*host, *token, *interactive, true)
client, _ := internal.MustDuploClient(*host, *token, *interactive, true, *port)
result, err := client.AdminAwsGetJitAccess("duplo-ops")
internal.DieIf(err, "failed to get credentials")
creds = internal.ConvertAwsCreds(result)
Expand All @@ -136,8 +137,8 @@ func main() {

// Identify the tenant name to use for the cache key.
var tenantName string
client, _ := internal.MustDuploClient(*host, *token, *interactive, false)
*tenantID, tenantName = GetTenantIdAndName(*tenantID, client)
client, _ := internal.MustDuploClient(*host, *token, *interactive, false, *port)
*tenantID, tenantName = getTenantIDAndName(*tenantID, client)

// Build the cache key.
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "tenant", tenantName}, ",")
Expand All @@ -158,7 +159,7 @@ func main() {
internal.OutputAwsCreds(creds, cacheKey)

case "duplo":
_, creds := internal.MustDuploClient(*host, *token, *interactive, true)
_, creds := internal.MustDuploClient(*host, *token, *interactive, true, *port)
internal.OutputDuploCreds(creds)

case "k8s":
Expand All @@ -174,7 +175,7 @@ func main() {

// Otherwise, get the credentials from Duplo.
if creds == nil {
client, _ := internal.MustDuploClient(*host, *token, *interactive, true)
client, _ := internal.MustDuploClient(*host, *token, *interactive, true, *port)
result, err := client.AdminGetK8sJitAccess(*planID)
internal.DieIf(err, "failed to get credentials")
creds = internal.ConvertK8sCreds(result)
Expand All @@ -189,8 +190,8 @@ func main() {

// Identify the tenant name to use for the cache key.
var tenantName string
client, _ := internal.MustDuploClient(*host, *token, *interactive, false)
*tenantID, tenantName = GetTenantIdAndName(*tenantID, client)
client, _ := internal.MustDuploClient(*host, *token, *interactive, false, *port)
*tenantID, tenantName = getTenantIDAndName(*tenantID, client)

// Build the cache key.
cacheKey = strings.Join([]string{strings.TrimPrefix(*host, "https://"), "tenant", tenantName}, ",")
Expand All @@ -214,7 +215,7 @@ func main() {
}
}

func GetTenantIdAndName(tenantIDorName string, client *duplocloud.Client) (string, string) {
func getTenantIDAndName(tenantIDorName string, client *duplocloud.Client) (string, string) {
var tenantID string
var tenantName string

Expand All @@ -224,7 +225,7 @@ func GetTenantIdAndName(tenantIDorName string, client *duplocloud.Client) (strin
tenantName = tenantIDorName
tenant, err := client.GetTenantByNameForUser(tenantName)
if tenant == nil || err != nil {
internal.Fatal(fmt.Sprintf("%s: tenant missing or not allowed", tenantName), err)
internal.Fatal(fmt.Sprintf("tenant '%s' missing or not allowed", tenantName), err)
} else {
tenantID = tenant.TenantID
}
Expand All @@ -234,7 +235,7 @@ func GetTenantIdAndName(tenantIDorName string, client *duplocloud.Client) (strin
tenantID = tenantIDorName
tenant, err := client.GetTenantForUser(tenantIDorName)
if tenant == nil || err != nil {
internal.Fatal(fmt.Sprintf("%s: tenant missing or not allowed", tenantID), err)
internal.Fatal(fmt.Sprintf("tenant '%s' missing or not allowed", tenantID), err)
} else {
tenantName = tenant.AccountName
}
Expand Down
11 changes: 6 additions & 5 deletions duplocloud/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ func (c *Client) AdminGetK8sJitAccess(plan string) (*DuploPlanK8ClusterConfig, C
return &creds, nil
}

// AdminGetJITAwsCredentials retrieves just-in-time admin AWS credentials via the Duplo API.
// AdminGetJitAwsCredentials retrieves just-in-time admin AWS credentials via the Duplo API.
func (c *Client) AdminGetJitAwsCredentials() (*AwsJitCredentials, ClientError) {
return c.AdminAwsGetJitAccess("admin")
}

// TenantGetJITAwsCredentials retrieves just-in-time AWS credentials for a tenant via the Duplo API.
// TenantGetJitAwsCredentials retrieves just-in-time AWS credentials for a tenant via the Duplo API.
func (c *Client) TenantGetJitAwsCredentials(tenantID string) (*AwsJitCredentials, ClientError) {
creds := AwsJitCredentials{}
err := c.getAPI(
Expand Down Expand Up @@ -143,11 +143,12 @@ func (c *Client) ListTenantsForUser() (*[]UserTenant, ClientError) {
return &list, nil
}

func (c *Client) GetTenantFeatures(tenantId string) (*DuploTenantFeatures, ClientError) {
// GetTenantFeatures retrieves a tenant's current configuration via the Duplo API.
func (c *Client) GetTenantFeatures(tenantID string) (*DuploTenantFeatures, ClientError) {
features := DuploTenantFeatures{}
err := c.getAPI(
fmt.Sprintf("GetTenantFeatures(%s)", tenantId),
fmt.Sprintf("v3/features/tenant/%s", tenantId),
fmt.Sprintf("GetTenantFeatures(%s)", tenantID),
fmt.Sprintf("v3/features/tenant/%s", tenantID),
&features,
)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions duplocloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"time"
)

// Represents a connection to the Duplo API
// Client represents a connection to the Duplo API
type Client struct {
HTTPClient *http.Client
HostURL string
Expand Down Expand Up @@ -66,7 +66,7 @@ func (e clientError) Response() map[string]interface{} {
return e.response
}

// Represents an error from an API call.
// ClientError represents an error from an API call.
type ClientError interface {
Error() string
Status() int
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/duplocloud/duplo-jit

go 1.21
go 1.23

require (
github.com/aws/aws-sdk-go-v2 v1.23.5
Expand Down
8 changes: 4 additions & 4 deletions internal/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func CacheGetAwsConfigOutput(cacheKey string) (creds *AwsConfigOutput) {

// Check credentials for expiry.
if creds != nil {
five_minutes_from_now := time.Now().UTC().Add(5 * time.Minute)
fiveMinutesFromNow := time.Now().UTC().Add(5 * time.Minute)
expiration, err := time.Parse(time.RFC3339, creds.Expiration)

// Invalid expiration?
Expand All @@ -107,7 +107,7 @@ func CacheGetAwsConfigOutput(cacheKey string) (creds *AwsConfigOutput) {
creds = nil

// Expires in five minutes or less?
} else if five_minutes_from_now.After(expiration) {
} else if fiveMinutesFromNow.After(expiration) {
creds = nil
}
}
Expand Down Expand Up @@ -189,9 +189,9 @@ func CacheGetK8sConfigOutput(cacheKey string, tenantName string) (creds *clienta
// Check credentials for expiry.
if creds != nil {
// Expires in five minutes or less?
five_minutes_from_now := time.Now().UTC().Add(5 * time.Minute)
fiveMinutesFromNow := time.Now().UTC().Add(5 * time.Minute)
expiration := creds.Status.ExpirationTimestamp.Time
if five_minutes_from_now.After(expiration) {
if fiveMinutesFromNow.After(expiration) {
creds = nil
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/duplo.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func duploClientAndOtpFlag(host, token, otp string, admin bool) (*duplocloud.Cli
}

// MustDuploClient retrieves a duplo client (and credentials) or panics.
func MustDuploClient(host, token string, interactive, admin bool) (client *duplocloud.Client, creds *DuploCredsOutput) {
func MustDuploClient(host, token string, interactive, admin bool, port int) (client *duplocloud.Client, creds *DuploCredsOutput) {
needsOtp := false
cacheKey := strings.TrimPrefix(host, "https://")

Expand Down Expand Up @@ -91,7 +91,7 @@ func MustDuploClient(host, token string, interactive, admin bool) (client *duplo
}

// Get the token, or fail.
tokenResult := MustTokenInteractive(host, admin, "duplo-jit")
tokenResult := MustTokenInteractive(host, admin, "duplo-jit", port)
if tokenResult.Token == "" {
log.Fatalf("%s: authentication failure: failed to get token interactively", os.Args[0])
}
Expand Down
8 changes: 4 additions & 4 deletions internal/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ func handlerTokenViaPost(baseUrl string, res http.ResponseWriter, req *http.Requ
return
}

func TokenViaPost(baseUrl string, admin bool, cmd string, timeout time.Duration) TokenResult {
func TokenViaPost(baseUrl string, admin bool, cmd string, port int, timeout time.Duration) TokenResult {

// Create the listener on a random port.
listener, err := net.Listen("tcp", "127.0.0.1:0")
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return TokenResult{err: err}
}
Expand Down Expand Up @@ -127,8 +127,8 @@ func TokenViaPost(baseUrl string, admin bool, cmd string, timeout time.Duration)
}
}

func MustTokenInteractive(host string, admin bool, cmd string) (tokenResult TokenResult) {
tokenResult = TokenViaPost(host, admin, cmd, 180*time.Second)
func MustTokenInteractive(host string, admin bool, cmd string, port int) (tokenResult TokenResult) {
tokenResult = TokenViaPost(host, admin, cmd, port, 180*time.Second)
DieIf(tokenResult.err, "failed to get token from interactive browser session")
return
}
Loading
Loading