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

Fix false positives in fastly and fastly-compute deployment types #1148

Merged
merged 15 commits into from
Nov 9, 2023

Conversation

marsavar
Copy link
Contributor

@marsavar marsavar commented May 16, 2023

What does this change?

Fixes an issue with fastly and fastly-compute deployment types that incorrectly deploy successfully when no API token is present in the keyring.

This is a problem because you might deploy your VCL or Compute@Edge application and think everything has gone smoothly, when in reality nothing has happened because the execute function wasn't triggered.

In its current implementation, foreach is called on the optional FastlyApiClientProvider.get(keyring) function, meaning that execute will only run if it is a Some value. No implementation is defined when it's None.

How to test

Deployed to CODE Riff-Raff.

Scenario 1: Fastly API key not present in the parameter store (should fail)

image

Scenario 2: Invalid or expired Fastly API key present in the parameter store (should fail)

image

Scenario 3: Valid Fastly API key present in the parameter store (should succeed)

image

@marsavar marsavar changed the title Do not mark Fastly tasks as successfully completed if Riff-Raff can't… Fix false positives with fastly and fastly-compute deployment types May 16, 2023
@marsavar marsavar changed the title Fix false positives with fastly and fastly-compute deployment types Fix false positives in fastly and fastly-compute deployment types May 16, 2023
@marsavar marsavar marked this pull request as ready for review May 16, 2023 16:21
@marsavar marsavar requested review from a team as code owners May 16, 2023 16:21
)
FastlyApiClientProvider.get(keyRing) match {
case None =>
resources.reporter.fail("Failed to fetch Fastly API credentials")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth expanding this slightly to give a clearer sense of where credentials should exist.

Copy link
Contributor

@nicl nicl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Small code style advice (non-blocking though): I'd probably favour pulling the case Some(client) => ... body out into a function to improve readability - as easier to see the actual patterns being matched. And have the function return a Try[..] rather than a large try/catch - again as it makes it easier to scan quickly the actual flow. But this is somewhat subjective.

@akash1810 akash1810 closed this May 25, 2023
@twrichards twrichards self-requested a review August 18, 2023 15:32
@marsavar marsavar reopened this Aug 18, 2023
@marsavar
Copy link
Contributor Author

marsavar commented Aug 18, 2023

This was closed by mistake - reopened it and refactored it with Nic's suggestion

@github-actions
Copy link
Contributor

This PR is stale because it has been open 30 days with no activity. Unless a comment is added or the “stale” label removed, this will be closed in 3 days

@github-actions github-actions bot added the Stale label Sep 18, 2023
@marsavar
Copy link
Contributor Author

This PR is stale because it has been open 30 days with no activity. Unless a comment is added or the “stale” label removed, this will be closed in 3 days

🥹

@github-actions github-actions bot removed the Stale label Sep 19, 2023
Copy link
Member

@davidfurey davidfurey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a few suggestions, but this already looks like a good change that would be great to get merged.

): Unit = {
FastlyApiClientProvider.get(keyRing) match {
case None =>
resources.reporter.fail("Failed to fetch Fastly API credentials")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
resources.reporter.fail("Failed to fetch Fastly API credentials")
resources.reporter.fail("Failed to fetch Fastly API credentials from keyring")

Comment on lines 88 to 100
FastlyApiClientProvider.get(keyRing) match {
case None =>
resources.reporter.fail("Failed to fetch Fastly API credentials")
case Some(client) =>
_execute(client, resources, stopFlag) match {
case Success(nextVersionNumber) =>
resources.reporter
.info(s"Fastly version $nextVersionNumber is now active")
case Failure(err) =>
resources.reporter.fail(
s"$err. Your API key may be invalid or it may have expired"
)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be slightly easier to read:

Suggested change
FastlyApiClientProvider.get(keyRing) match {
case None =>
resources.reporter.fail("Failed to fetch Fastly API credentials")
case Some(client) =>
_execute(client, resources, stopFlag) match {
case Success(nextVersionNumber) =>
resources.reporter
.info(s"Fastly version $nextVersionNumber is now active")
case Failure(err) =>
resources.reporter.fail(
s"$err. Your API key may be invalid or it may have expired"
)
}
override def execute(
resources: DeploymentResources,
stopFlag: => Boolean
): Unit = {
FastlyApiClientProvider
.get(keyRing)
.map(_execute(_, resources, stopFlag)) match {
case Some(Success(nextVersionNumber)) =>
resources.reporter
.info(s"Fastly version $nextVersionNumber is now active")
case Some(Failure(err)) =>
resources.reporter.fail(
s"$err. Your API key may be invalid or it may have expired"
)
case None =>
resources.reporter.fail("Failed to fetch Fastly API credentials")
}
}

@@ -30,43 +31,73 @@ case class UpdateFastlyConfig(s3Package: S3Path)(implicit
implicit val ec: ExecutionContextExecutorService =
ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(10))

override def execute(
private def _execute(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more meaningful name for this method? Perhaps executeWithCredentials?

magenta-lib/src/main/scala/magenta/tasks/FastlyTasks.scala Outdated Show resolved Hide resolved
.info(
s"Fastly Compute@Edge service ${client.serviceId} - version $nextVersionNumber is now active"
FastlyApiClientProvider.get(keyRing) match {
case None =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar flattening of pattern match could be done as for FastlyTask.scala

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - yes, I'm not a big fan of nested pattern matching either. Have refactored accordingly :)

Copy link
Member

@davidfurey davidfurey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Might be nice to have a 👍 from a current member of devX?

)
case Some((_, Failure(err))) =>
resources.reporter.fail(
s"$err. Your API key may be invalid or it may have expired"
Copy link
Contributor

@jacobwinch jacobwinch Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor:

IIUC, this case is going to catch a number of potential errors (network problems, Fastly API downtime/errors) as well as the problems that we've already mentioned in the error message.

If we find reliability problems with this deployment type in the future, it might be worth extracting the specific error (perhaps by adding some verbose logging or a different return type to activateVersion), but I don't think that needs to block this PR (which is already a big improvement!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this some more, I think there is another potential false positive here. IIUC calling activateVersion can result in a successful Future (i.e. we got a Response from the Fastly API), where the response/status code indicates that the request was unsuccessful from Fastly's perspective (i.e. we got a 500 back and did not actually activate the new version). In this case we will still match on: case Some((client, Success(nextVersionNumber))).

Happy to pair on fixing that via a separate PR if helpful.

Copy link
Contributor

@jacobwinch jacobwinch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work; thanks for improving this and documenting the testing so clearly 👍

@marsavar marsavar enabled auto-merge (squash) October 19, 2023 11:24
@marsavar marsavar merged commit 669ccc5 into main Nov 9, 2023
1 check passed
@marsavar marsavar deleted the ms-fastly-fail branch November 9, 2023 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants