-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add recipe to use
secrets: inherit
if possible (#103)
* Add recipe to use `secrets: inherit` if possible This addresses #89 * Address review feedback - switch to iso visitor - extract variable for children match for better readability - use mapping entry returned by super call when updating mapping entry - use `Pattern` for more type safe regex - add additional test case for different workflow file name * Minor project conventions * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update PreferSecretsInheritWorkflowTest.java * Run recipe through defaults to prevent repetition Keep link clickable * Remove excess indentation from text blocks * Place each Preconditions.check argument on a new line * Swap order import to silence code suggestions --------- Co-authored-by: Tim te Beek <timtebeek@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tim te Beek <tim@moderne.io>
- Loading branch information
1 parent
a086963
commit 99be3f5
Showing
2 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
90 changes: 90 additions & 0 deletions
90
src/main/java/org/openrewrite/github/PreferSecretsInheritWorkflow.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* Copyright 2024 the original author or authors. | ||
* <p> | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.openrewrite.github; | ||
|
||
import org.openrewrite.*; | ||
import org.openrewrite.yaml.JsonPathMatcher; | ||
import org.openrewrite.yaml.YamlIsoVisitor; | ||
import org.openrewrite.yaml.tree.Yaml; | ||
import org.openrewrite.yaml.tree.Yaml.Scalar; | ||
|
||
import java.util.regex.Pattern; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Value; | ||
|
||
import static org.openrewrite.marker.Markers.EMPTY; | ||
import static org.openrewrite.yaml.tree.Yaml.Scalar.Style.PLAIN; | ||
|
||
@Value | ||
@EqualsAndHashCode(callSuper = false) | ||
public class PreferSecretsInheritWorkflow extends Recipe { | ||
@Override | ||
public String getDisplayName() { | ||
return "Use `secrets: inherit` if possible"; | ||
} | ||
|
||
@Override | ||
public String getDescription() { | ||
return "Pass all secrets to a reusable workflow using `secrets: inherit`. See " + | ||
"[Simplify using secrets with reusable workflows]" + | ||
"(https://github.blog/changelog/2022-05-03-github-actions-simplify-using-secrets-with-reusable-workflows/)" + | ||
" for details."; | ||
} | ||
|
||
@Override | ||
public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
final JsonPathMatcher secrets = new JsonPathMatcher("$.jobs..secrets"); | ||
|
||
return Preconditions.check( | ||
new FindSourceFiles(".github/workflows/*.yml").getVisitor(), | ||
new YamlIsoVisitor<ExecutionContext>() { | ||
private static final String USE_INHERIT = "USE_INHERIT"; | ||
|
||
@Override | ||
public Yaml.Mapping visitMapping(final Yaml.Mapping mapping, final ExecutionContext ctx) { | ||
Cursor parentEntry = getCursor().getParent(); | ||
if (parentEntry != null && secrets.matches(parentEntry)) { | ||
boolean allUntransformed = mapping.getEntries().stream().allMatch(this::isUntransformedSecret); | ||
if (allUntransformed) { | ||
getCursor().putMessageOnFirstEnclosing(Yaml.Mapping.Entry.class, USE_INHERIT, true); | ||
} | ||
} | ||
|
||
return super.visitMapping(mapping, ctx); | ||
} | ||
|
||
@Override | ||
public Yaml.Mapping.Entry visitMappingEntry(final Yaml.Mapping.Entry entry, final ExecutionContext ctx) { | ||
Yaml.Mapping.Entry e = super.visitMappingEntry(entry, ctx); | ||
|
||
if (getCursor().getMessage(USE_INHERIT, false)) { | ||
Scalar inheritValue = new Scalar(Tree.randomId(), " ", EMPTY, PLAIN, null, "inherit"); | ||
return e.withValue(inheritValue); | ||
} | ||
|
||
return e; | ||
} | ||
|
||
private boolean isUntransformedSecret(final Yaml.Mapping.Entry entry) { | ||
String key = entry.getKey().getValue(); | ||
Pattern secretPattern = Pattern.compile("\\$\\{\\{\\s*secrets." + key + "\\s*}}"); | ||
|
||
Yaml.Block value = entry.getValue(); | ||
return (value instanceof Scalar && secretPattern.matcher(((Scalar) value).getValue()).matches()); | ||
} | ||
}); | ||
} | ||
} |
134 changes: 134 additions & 0 deletions
134
src/test/java/org/openrewrite/github/PreferSecretsInheritWorkflowTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
* Copyright 2024 the original author or authors. | ||
* <p> | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* <p> | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.openrewrite.github; | ||
|
||
import org.openrewrite.DocumentExample; | ||
import org.openrewrite.test.RecipeSpec; | ||
import org.openrewrite.test.RewriteTest; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.openrewrite.yaml.Assertions.yaml; | ||
|
||
class PreferSecretsInheritWorkflowTest implements RewriteTest { | ||
|
||
@Override | ||
public void defaults(RecipeSpec spec) { | ||
spec.recipe(new PreferSecretsInheritWorkflow()); | ||
} | ||
|
||
@DocumentExample | ||
@Test | ||
void replaceIdenticalSecretsWithInherit() { | ||
rewriteRun( | ||
//language=yaml | ||
yaml( | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: | ||
envPAT: ${{ secrets.envPAT }} | ||
""", | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: inherit | ||
""", | ||
spec -> spec.path(".github/workflows/ci.yml") | ||
) | ||
); | ||
} | ||
|
||
@Test | ||
void leaveModifiedSecrets() { | ||
rewriteRun( | ||
//language=yaml | ||
yaml( | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: | ||
envPAT: ${{ secrets.envPAT }} | ||
some_secret: ${{ secrets.SOME_SECRET }} | ||
""", | ||
spec -> spec.path(".github/workflows/ci.yml") | ||
) | ||
); | ||
} | ||
|
||
@Test | ||
void transformFromOtherWorkflowFile() { | ||
rewriteRun( | ||
//language=yaml | ||
yaml( | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: | ||
envPAT: ${{ secrets.envPAT }} | ||
""", | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: inherit | ||
""", | ||
spec -> spec.path(".github/workflows/some-workflow.yml") | ||
) | ||
); | ||
} | ||
|
||
@Test | ||
void handleAdditionalSpacesInSecretsReference() { | ||
rewriteRun( | ||
//language=yaml | ||
yaml( | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: | ||
envPAT: ${{ secrets.envPAT}} | ||
""", | ||
""" | ||
jobs: | ||
call-workflow-passing-data: | ||
uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main | ||
with: | ||
config-path: .github/labeler.yml | ||
secrets: inherit | ||
""", | ||
spec -> spec.path(".github/workflows/ci.yml") | ||
) | ||
); | ||
} | ||
} |