link2aws / link2aws.github.io

Get AWS console link from ARN
https://link2aws.github.io/
36 stars 20 forks source link

Linking to sub-resources and subpages #68

Open dleavitt opened 2 months ago

dleavitt commented 2 months ago

The way this library is set up, a particular resource ARN basically always maps to a single console URL. In practice though the AWS console often has multiple pages associated with a given ARN.

One case is when a resource has a number of subpages or tabs. For some resources you can just append the subpath to the end of the URL that link2aws gives you back, but for others (e.g. ECS Services, CodePipelines) the URL has a query string so you've got to splice the subpath in.

A more compelling case to me is sub-resources that have console URLs but don't have their own resource type. Some of these (e.g. Cloudwatch Log Streams, S3 Objects) have ARN representations, others (e.g. CodePipeline executions) don't appear to. It would be useful to be able to generate console links for these guys.

There are a bunch of ways this functionality could be implemented, but in general it seems pretty far outside the current scope of this project. Was wondering if you've thought about this and whether this is a feature you'd be interested in vs. a non-goal.

Thanks!

fxkr commented 2 months ago

Thanks for the suggestion!

I think we could add support for multiple URLs. The lambda's could just return a list of URLs going forward. We could add a new function/field to return that list, and the existing field could just be assigned the first item from the list so that we remain backwards compatible. Ideally we can make a reasonable guess on which URL is most likely the right one. For the CLI users, we could have a command line switch to enable the new behavior. For the web users, we can just show all of the URLs (except when the auto-redirect feature is used, of course).

(If we can unambiguously identify the sub-resources based on the full ARN, then we can still fit that into the existing model even if the resource type is shared between multiple... types of resources. We already support some resource types that do more logic than just a one-line string interpolation. But as in your CloudWatch example that doesn't always seem to be the case.)

If it's backwards compatible (for libray, cli and web based users), the code is clean and tested and it doesn't have any unexpected big drawbacks, I'd take above as a contribution, but I probably won't implement it myself.

If a resource doesn't have an ARN representation at all, what would the input to link2aws be? Taking the CodePipeline Execution example, based on the docs, there seems to be an Execution ID, which doesn't seem to contain all the info we would need to generate a console URL, and it would be a pretty big guess that this even is a CodePipeline Execution. I think we shouldn't do that. If we return a result instead of an error, we should be fairly confident that we know what kind of ID the input is.

dleavitt commented 2 months ago

Thanks for the detailed response, makes a lot of sense.

If we can unambiguously identify the sub-resources based on the full ARN

Yep, for some of these we could just add conditionals to the existing handlers.

The lambda's could just return a list of URLs going forward. If a resource doesn't have an ARN representation at all, what would the input to link2aws be?

A low-effort and low-assumption way might be something like:

const arn = new ARN(codepipelineArn)

// same as before, web UI just continues to use this
arn.consoleLink // => .../pipelines/id/view?region=us-west-2
// base console URL for resource
arn.baseConsoleURL // => .../pipelines/id
// user-supplied suffix appended to base URL, maybe do some parsing and add the `?region` etc to the end
arn.getConsoleLink(`/executions/${executionId}`) // => .../pipelines/id/executions/{executionId}

And then on a per-resource basis could add helpers for known subpages/subresources:

arn.consoleLinks.getExecutionLink(executionId) // => .../pipelines/id/executions/{executionId}
arn.consoleLinks.getHistoryLink() // => .../pipelines/id/executions

(Typescript ergonomics for these wouldn't be great though.)

Implementation-wise, instead of each resource type returning a single function, it could instead return an object. The object would always have a property named "default" or something, corresponding what get consoleLink returns, and a property named "base" or "root" or something, which would be the base that all the other ones are built off of. Could do some parsing of the URLs so that the paths, query strings, and hashes from the base URL and subpaths get combined correctly.

I'd take above as a contribution, but I probably won't implement it myself

For sure, LMK if the above sounds sane and if so I may take a pass at it.

fxkr commented 2 months ago

Thanks for your reply!

Ah, I was thinking more like a getConsoleLinks() function that returns a plain array of URL's. But I see now that this won't work in cases where additional data (like the execution ID) is needed.

Suffix parameter: The parameter, or, more broadly, sharing responsibility for / knowledge about the structure of the URL between the library and the user, seems like a compatibility trap. If AWS changes and we need to return a different URL some day, and the user keeps appending their outdated suffix to that, they'll end up with a garbage URL. I want to always be able to update the library to stay on top of changes in AWS. That can never be a breaking API change. And if the user is willing to ignore that, they can just append it themselves; no need to pass it to the library if all the library can do is append it. (Or they can just copy/paste the appropriate URL pattern from the library into their own code. That way at least there'll never be a mismatch between their code and a future library version.)

Base URL: That everything for a resource will be under just one base URL is a major assumption. I think it would be better for the library to only take individual pieces of information (ARN's, maybe ID's like the execution ID) and return full URLs.

Typescript: Thanks for bringing that up. I never got around to adding annotations but I do want to do it some day, and I agree it's worth designing for that (and also for the modern day conveniences like IntelliSense).

Helper object: Given that the ARN class instances already contain state, why not just add the methods to the main object (they can throw an exception if the type isn't right)? This would solve the typescript problem.

.isCodePipline()
.getCodePipelineExecutionUrl(executionId)

API size and backwards compat: I think one reason I am not sure about this feature is API size. Right now it's more or less "1 string in, 1 string out". It's simple to use, simple to test in a declarative way, and simple to explain without a lot of documentation. If we add per-service methods (whether directly or hidden in a helper object), that'll blow up. We'll need a lot more documentation, which could be a turnoff for library users. We won't be able to cover those functions with JSON based tests. And if AWS discontinues a feature, we'll have a bunch of obsolete functions that we can't delete without a breaking change. So I think there is some cost to this feature, and it should provide enough benefit to pay for that cost. I would prefer an API that isn't specific to any services.

Use case: Would you actually be using this feature yourself in practice? Could you describe a practical application? It seems it would only be useful to a very narrow set of users: only library users (not web/cli), that know in advance what their resource types are, need links for specific (also known in advance) subpages, are willing to hardcode a call to a subpage specific function but aren't willing to hardcode the URL itself, instead preferring to rely on a library. Compare that with the much broader use case that the library covers so far: "I have an ARN and I want to look at it in the console".

I'm still interested in your opinion, I'm not saying no - I do get the desire to somehow get the links to the subpages. But right now, the way it's proposed, I don't think it's worth the additional complexity in the library. And I don't really have any better ideas yet. On the one hand, I would prefer an API that isn't specific to particular services. On the other hand, there are additional ID's (like the execution ID) that are needed and are specific to services. So there is a mismatch that can't really be resolved in any truly satisfying way.

dleavitt commented 2 months ago

Very thoughtful response, thanks! I tend to agree - the simplicity and ease of contribution seem important to preserve.