diff --git a/Cargo.toml b/Cargo.toml index 67e99cc..1c490af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 80d2a6a..31ae6ec 100644 --- a/README.md +++ b/README.md @@ -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 (). ## How to use diff --git a/src/parts.rs b/src/parts.rs index 6850eff..42c24af 100644 --- a/src/parts.rs +++ b/src/parts.rs @@ -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(), @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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()?, diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs index 23d44a3..ffa7fca 100644 --- a/tests/cli_tests.rs +++ b/tests/cli_tests.rs @@ -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"; @@ -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); } @@ -40,21 +40,21 @@ 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")); @@ -62,7 +62,7 @@ fn one_positional_arn_success() { #[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")); @@ -70,7 +70,7 @@ fn two_positional_arns_success() { #[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) @@ -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) @@ -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) @@ -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) @@ -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}")) @@ -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")) @@ -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")) @@ -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") diff --git a/tests/data/aws.json b/tests/data/aws.json index bafd44d..e0b07a0 100644 --- a/tests/data/aws.json +++ b/tests/data/aws.json @@ -4,7 +4,7 @@ "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®ion=us-west-1", @@ -12,6 +12,7 @@ "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", @@ -19,6 +20,8 @@ "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", @@ -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", @@ -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", @@ -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" -} \ No newline at end of file +}