Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "link2aws"
description = "Converts ARNs to direct links to the AWS console"
repository = "https://github.com/link2aws/link2aws-rs"
authors = ["Felix Kaiser"]
version = "0.1.0"
version = "0.1.1"
edition = "2024"
license = "ISC"
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Converts ARNs to direct links to the AWS console.
Amazon Resource Names (ARNs) are strings like `arn:aws:s3:::abc123`
that uniquely identify resources in Amazon Web Services (AWS).

* Copyright (c) 2020-2025, Felix Kaiser.
* Copyright (c) 2020-2026, Felix Kaiser.
* License: ISC (<https://spdx.org/licenses/ISC.html>).

## How to use
Expand Down
57 changes: 49 additions & 8 deletions src/parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {

// Amazon EC2
("acm", "certificate") => Some(format!(
"https://{domain}/acm/home?region={region}#/?id={resource}",
"https://{domain}/acm/home?region={region}#/certificates/{resource}",
domain = self.domain()?,
region = self.region(),
resource = self.resource_id(),
Expand Down Expand Up @@ -338,7 +338,12 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
// AWS CodeBuild
// ("codebuild", "build") => None,
// ("codebuild", "build-batch") => None,
// ("codebuild", "project") => None,
("codebuild", "project") => Some(format!(
"https://{region}.{domain}/codesuite/codebuild/projects/{resource}",
region = self.region(),
domain = self.domain()?,
resource = self.resource_id(),
)),
// ("codebuild", "report") => None,
// ("codebuild", "report-group") => None,

Expand Down Expand Up @@ -550,6 +555,12 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
// ("ec2", "egress-only-internet-gateway") => None,
// ("ec2", "elastic-gpu") => None,
// ("ec2", "elastic-ip") => None,
("ec2", "eip-allocation") => Some(format!(
"https://{region}.{domain}/ec2/home?region={region}#Addresses:v=3;search=:{resource}",
region = self.region(),
domain = self.domain()?,
resource = self.resource_id(),
)),
// ("ec2", "export-image-task") => None,
// ("ec2", "export-instance-task") => None,
// ("ec2", "fleet") => None,
Expand Down Expand Up @@ -653,7 +664,13 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
// ("ec2", "vpn-gateway") => None,

// Amazon Elastic Container Registry
// ("ecr", "repository") => None,
("ecr", "repository") => Some(format!(
"https://{region}.{domain}/ecr/repositories/private/{account}/{resource}",
region = self.region(),
domain = self.domain()?,
account = self.account(),
resource = self.resource_id(),
)),

// Amazon Elastic Container Registry Public
// ("ecr-public", "registry") => None,
Expand Down Expand Up @@ -739,7 +756,11 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
// ("elasticbeanstalk", "application") => None,
// ("elasticbeanstalk", "applicationversion") => None,
// ("elasticbeanstalk", "configurationtemplate") => None,
// ("elasticbeanstalk", "environment") => None,
("elasticbeanstalk", "environment") => Some(format!(
"https://{region}.{domain}/elasticbeanstalk/home?region={region}#/environments",
region = self.region(),
domain = self.domain()?,
)),
// ("elasticbeanstalk", "platform") => None,
// ("elasticbeanstalk", "solutionstack") => None,

Expand Down Expand Up @@ -782,7 +803,12 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
// ("emr-containers", "virtualCluster") => None,

// Amazon Elasticsearch Service
// ("es", "domain") => None,
("es", "domain") => Some(format!(
"https://{region}.{domain}/aos/home?region={region}#opensearch/domains/{resource}",
region = self.region(),
domain = self.domain()?,
resource = self.resource_id(),
)),

// Amazon EventBridge
// ("events", "archive") => None,
Expand Down Expand Up @@ -868,7 +894,12 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
// ("glue", "crawler") => None,
// ("glue", "database") => None,
// ("glue", "devendpoint") => None,
// ("glue", "job") => None,
("glue", "job") => Some(format!(
"https://{region}.{domain}/gluestudio/home?region={region}#/editor/job/{resource}/script",
region = self.region(),
domain = self.domain()?,
resource = self.resource_id(),
)),
// ("glue", "mlTransform") => None,
// ("glue", "registry") => None,
// ("glue", "schema") => None,
Expand Down Expand Up @@ -1064,7 +1095,12 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {

// Amazon Kinesis
// ("kinesis", "consumer") => None,
// ("kinesis", "stream") => None,
("kinesis", "stream") => Some(format!(
"https://{region}.{domain}/kinesis/home?region={region}#/streams/details/{resource}/details",
region = self.region(),
domain = self.domain()?,
resource = self.resource_id(),
)),

// Amazon Kinesis Analytics V2
// ("kinesisanalytics", "application") => None,
Expand Down Expand Up @@ -1349,7 +1385,12 @@ pub trait ArnParts<'a>: ArnPartsHelper<'a> {
)),
// ("rds", "cluster-endpoint") => None,
// ("rds", "cluster-pg") => None,
// ("rds", "cluster-snapshot") => None,
("rds", "cluster-snapshot") => Some(format!(
"https://{domain}/rds/home?region={region}#db-snapshot:id={resource}",
domain = self.domain()?,
region = self.region(),
resource = self.resource_id(),
)),
("rds", "db") => Some(format!(
"https://{domain}/rds/home?region={region}#database:id={resource}",
domain = self.domain()?,
Expand Down
30 changes: 15 additions & 15 deletions tests/cli_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Tests the command line interface of the `link2aws` binary.

use assert_cmd::{Command, assert::Assert};
use assert_cmd::{assert::Assert, cargo::cargo_bin_cmd};

const VALID_ARN_1: &str = "arn:aws:s3:::111";
const VALID_ARN_1_LINK: &str = "https://s3.console.aws.amazon.com/s3/buckets/111";
Expand All @@ -20,14 +20,14 @@ fn verify_help(assert: Assert) {

#[test]
fn dash_dash_help() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd.arg("--help").assert();
verify_help(assert);
}

#[test]
fn dash_h() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd.arg("-h").assert();
verify_help(assert);
}
Expand All @@ -40,37 +40,37 @@ fn verify_version(assert: Assert) {

#[test]
fn dash_dash_version() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd.arg("--version").assert();
verify_version(assert);
}

#[test]
fn dash_capital_v() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd.arg("-V").assert();
verify_version(assert);
}

#[test]
fn one_positional_arn_success() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd.arg(VALID_ARN_1).assert().success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert_eq!(stdout, format!("{VALID_ARN_1_LINK}\n"));
}

#[test]
fn two_positional_arns_success() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd.arg(VALID_ARN_1).arg(VALID_ARN_2).assert().success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert_eq!(stdout, format!("{VALID_ARN_1_LINK}\n{VALID_ARN_2_LINK}\n"));
}

#[test]
fn two_positional_arns_one_valid_and_one_invalid_dash_q() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg(VALID_ARN_1)
.arg(INVALID_ARN)
Expand All @@ -86,7 +86,7 @@ fn two_positional_arns_one_valid_and_one_invalid_dash_q() {

#[test]
fn two_positional_arns_one_valid_and_one_invalid_dash_dash_quiet() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg(VALID_ARN_1)
.arg(INVALID_ARN)
Expand All @@ -102,7 +102,7 @@ fn two_positional_arns_one_valid_and_one_invalid_dash_dash_quiet() {

#[test]
fn two_positional_arns_one_valid_and_one_invalid() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg(VALID_ARN_1)
.arg(INVALID_ARN)
Expand All @@ -120,7 +120,7 @@ fn two_positional_arns_one_valid_and_one_invalid() {

#[test]
fn one_stdin_arn_success() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg("--stdin")
.write_stdin(VALID_ARN_1)
Expand All @@ -132,7 +132,7 @@ fn one_stdin_arn_success() {

#[test]
fn two_stdin_arns_success() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg("--stdin")
.write_stdin(format!("{VALID_ARN_1}\n{VALID_ARN_2}"))
Expand All @@ -144,7 +144,7 @@ fn two_stdin_arns_success() {

#[test]
fn two_stdin_arns_with_trailing_newline_success() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg("--stdin")
.write_stdin(format!("{VALID_ARN_1}\n{VALID_ARN_2}\n"))
Expand All @@ -156,7 +156,7 @@ fn two_stdin_arns_with_trailing_newline_success() {

#[test]
fn two_stdin_arns_one_valid_and_one_invalid() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg("--stdin")
.write_stdin(format!("{VALID_ARN_1}\n{INVALID_ARN}\n"))
Expand All @@ -174,7 +174,7 @@ fn two_stdin_arns_one_valid_and_one_invalid() {

#[test]
fn two_stdin_arns_one_valid_and_one_invalid_dash_dash_quiet() {
let mut cmd = Command::cargo_bin("link2aws").unwrap();
let mut cmd = cargo_bin_cmd!("link2aws");
let assert = cmd
.arg("--stdin")
.arg("--quiet")
Expand Down
16 changes: 13 additions & 3 deletions tests/data/aws.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@

"arn:aws:access-analyzer:us-west-1:123456789012:analyzer/ConsoleAnalyzer-a1e73456-f62e-4803-abc0-df02b4241122": "https://us-west-1.console.aws.amazon.com/access-analyzer/home?region=us-west-1#/analyzer/ConsoleAnalyzer-a1e73456-f62e-4803-abc0-df02b4241122",

"arn:aws:acm:us-east-1:123456789012:certificate/1f6ee793-4064-4a10-9567-f03875640b35": "https://console.aws.amazon.com/acm/home?region=us-east-1#/?id=1f6ee793-4064-4a10-9567-f03875640b35",
"arn:aws:acm:us-east-1:123456789012:certificate/1f6ee793-4064-4a10-9567-f03875640b35": "https://console.aws.amazon.com/acm/home?region=us-east-1#/certificates/1f6ee793-4064-4a10-9567-f03875640b35",

"arn:aws:apigateway:us-west-1::/restapis/abc123defg": "https://us-west-1.console.aws.amazon.com/apigateway/main/apis/abc123defg/resources?api=abc123defg&region=us-west-1",

"arn:aws:ec2:us-west-1:136693071363:image/ami-0851c4af3ebd71c35": "https://us-west-1.console.aws.amazon.com/ec2/home?region=us-west-1#ImageDetails:imageId=ami-0851c4af3ebd71c35",
"arn:aws:ec2:us-east-1:123456789012:instance/1234": "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#InstanceDetails:instanceId=1234",
"arn:aws:ec2:us-east-1:123456789012:natgateway/nat-123456": "https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#NatGatewayDetails:natGatewayId=nat-123456",
"arn:aws:ec2:us-east-1:123456789012:launch-template/lt-123456": "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#LaunchTemplateDetails:launchTemplateId=lt-123456",
"arn:aws:ec2:us-east-1:123456789012:eip-allocation/eipalloc-1234567890abcdef0": "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#Addresses:v=3;search=:eipalloc-1234567890abcdef0",
"arn:aws:ec2:us-east-1:123456789012:subnet/subnet-12345612312312312" : "https://us-east-1.console.aws.amazon.com/vpc/home?region=us-east-1#SubnetDetails:subnetId=subnet-12345612312312312",
"arn:aws:ec2:us-east-1:123456789012:security-group/sg-12345123123123123": "https://us-east-1.console.aws.amazon.com/vpc/home?region=us-east-1#SecurityGroup:groupId=sg-12345123123123123",
"arn:aws:ec2:us-east-1:123456789012:snapshot/snap-123456": "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#SnapshotDetails:snapshotId=snap-123456",
"arn:aws:ec2:us-east-1:123456789012:volume/vol-123456" : "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#VolumeDetails:volumeId=vol-123456",
"arn:aws:ec2:us-east-1:123456789012:vpc/vpc-5432109876" : "https://us-east-1.console.aws.amazon.com/vpc/home?region=us-east-1#VpcDetails:VpcId=vpc-5432109876",
"arn:aws:ec2:us-east-1:123456789012:vpc-endpoint/vpce-123456": "https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#EndpointDetails:vpcEndpointId=vpce-123456",

"arn:aws:ecr:us-east-1:123456789012:repository/test-repository": "https://us-east-1.console.aws.amazon.com/ecr/repositories/private/123456789012/test-repository",

"arn:aws:ecs:us-east-1:123456789012:cluster/mycluster": "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/mycluster?region=us-east-1",
"arn:aws:ecs:us-east-1:123456789012:task/mycluster/581a40b2431e6c9c23834b0760666c36": "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/mycluster/tasks/581a40b2431e6c9c23834b0760666c36?region=us-east-1",
"arn:aws:ecs:us-east-1:123456789012:service/mycluster/myservice": "https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/mycluster/services/myservice?region=us-east-1",
Expand Down Expand Up @@ -48,7 +51,7 @@
"arn:aws:route53:::trafficpolicy/4869e13a-1e77-457f-a15e-d0bed0584175": "https://console.aws.amazon.com/route53/trafficflow/home#/policy/4869e13a-1e77-457f-a15e-d0bed0584175",
"arn:aws:route53:::trafficpolicyinstance/6461c3a8-3572-4d56-a65b-5dd1c4470cb7": "https://console.aws.amazon.com/route53/trafficflow/home#/modify-records/edit/6461c3a8-3572-4d56-a65b-5dd1c4470cb7",

"arn:aws:s3:::abc123": "https://s3.console.aws.amazon.com/s3/buckets/abc123",
"arn:aws:s3:::abcdefgh1234": "https://s3.console.aws.amazon.com/s3/buckets/abcdefgh1234",

"arn:aws:sqs:us-east-1:123456789012:b6a826b4-b44b-11ec-8d84-acde48001122": "https://us-east-1.console.aws.amazon.com/sqs/v2/home?region=us-east-1#/queues/https%3A%2F%2Fsqs.us-east-1.amazonaws.com%2F123456789012%2Fb6a826b4-b44b-11ec-8d84-acde48001122",

Expand All @@ -59,6 +62,8 @@
"arn:aws:amplify:sa-east-1:384862141196:apps/aaaaaaaaa/branches/master/jobs/00000001810": "https://sa-east-1.console.aws.amazon.com/amplify/home?region=sa-east-1#/aaaaaaaaa/master/1810",
"arn:aws:amplify:sa-east-1:384862141196:apps/test-null-path": null,

"arn:aws:codebuild:us-east-1:123456789012:project/test-project": "https://us-east-1.console.aws.amazon.com/codesuite/codebuild/projects/test-project",

"arn:aws:codepipeline:us-east-1:115131055398:my-pipeline": "https://us-east-1.console.aws.amazon.com/codesuite/codepipeline/pipelines/my-pipeline/view?region=us-east-1",

"arn:aws:cloudfront::123456789012:distribution/E2YDLZ26QPSF11": "https://console.aws.amazon.com/cloudfront/v4/home#/distributions/E2YDLZ26QPSF11",
Expand All @@ -75,20 +80,25 @@
"arn:aws:rds:us-east-1:123456789012:subgrp:test-db": "https://console.aws.amazon.com/rds/home?region=us-east-1#db-subnet-group:id=test-db",
"arn:aws:rds:us-east-1:123456789012:og:test-db": "https://console.aws.amazon.com/rds/home?region=us-east-1#option-group-details:option-group-name=test-db",
"arn:aws:rds:us-east-1:123456789012:cluster:test-cluster": "https://console.aws.amazon.com/rds/home?region=us-east-1#database:id=test-cluster;is-cluster=true",
"arn:aws:rds:us-east-1:123456789012:cluster-snapshot:test-cluster-snapshot": "https://console.aws.amazon.com/rds/home?region=us-east-1#db-snapshot:id=test-cluster-snapshot",

"arn:aws:kms:us-east-1:123456789012:key/4bd42b4b-af95-4698-b685-58d3296890fe": "https://console.aws.amazon.com/kms/home?region=us-east-1#/kms/keys/4bd42b4b-af95-4698-b685-58d3296890fe",

"arn:aws:eks:us-east-1:123456789012:cluster/test-cluster": "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/test-cluster",
"arn:aws:eks:us-east-1:123456789012:nodegroup/test-cluster/test-workers/a2c51527-da50-031f-1130-c5d5d77be06c": "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/test-cluster/nodegroups/test-workers",
"arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/test-app/test-env": "https://us-east-1.console.aws.amazon.com/elasticbeanstalk/home?region=us-east-1#/environments",
"arn:aws:es:us-east-1:123456789012:domain/test-domain": "https://us-east-1.console.aws.amazon.com/aos/home?region=us-east-1#opensearch/domains/test-domain",

"arn:aws:elasticloadbalancing:us-east-1:1234:loadbalancer/net/k8s-devops-ingressn-5a6b7c/8d9e0f": "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#LoadBalancer:loadBalancerArn=arn:aws:elasticloadbalancing:us-east-1:1234:loadbalancer/net/k8s-devops-ingressn-5a6b7c/8d9e0f",

"arn:aws:backup:us-east-1:123456789012:backup-vault:backups-primary": "https://console.aws.amazon.com/backup/home?region=us-east-1#/backupvaults/details/backups-primary",

"arn:aws:firehose:us-east-1:123456789012:deliverystream/test-stream": "https://console.aws.amazon.com/firehose/home?region=us-east-1#/details/test-stream/monitoring",
"arn:aws:glue:us-east-1:123456789012:job/test-job": "https://us-east-1.console.aws.amazon.com/gluestudio/home?region=us-east-1#/editor/job/test-job/script",
"arn:aws:kinesis:us-east-1:123456789012:stream/test-stream": "https://us-east-1.console.aws.amazon.com/kinesis/home?region=us-east-1#/streams/details/test-stream/details",

"arn:aws:codeconnections:us-west-2:384862141196:connection/f8234ecc-8990-4e63-b25a-ec36764b7701": "https://us-west-2.console.aws.amazon.com/codesuite/settings/384862141196/us-west-2/codeconnections/connections/f8234ecc-8990-4e63-b25a-ec36764b7701",
"arn:aws:codestar-connections:us-west-2:384862141196:connection/f8234ecc-8990-4e63-b25a-ec36764b7701": "https://us-west-2.console.aws.amazon.com/codesuite/settings/384862141196/us-west-2/codestar-connections/connections/f8234ecc-8990-4e63-b25a-ec36764b7701",

"arn:aws:secretsmanager:us-west-2:123456789012:secret:MySecret9A3F29-vdHtS43BP1i1-knwb3S": "https://us-west-2.console.aws.amazon.com/secretsmanager/secret?name=MySecret9A3F29-vdHtS43BP1i1"
}
}
Loading