diff --git a/docs/index.md b/docs/index.md index 00aeb96..79d157d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -87,6 +87,7 @@ variable, use `fix_code`: - item - item ``` +- Quote jobs.*.containers.volumes entries in GitHub Actions workflow yamls to not confuse GitHubs Yaml parser. # Configuration diff --git a/src/yamlfix/adapters.py b/src/yamlfix/adapters.py index ac2d0d4..9955ff7 100644 --- a/src/yamlfix/adapters.py +++ b/src/yamlfix/adapters.py @@ -6,9 +6,11 @@ from io import StringIO from typing import Any, Callable, List, Match, Optional, Tuple +from ruyaml.comments import CommentedMap, CommentedSeq from ruyaml.main import YAML from ruyaml.nodes import MappingNode, Node, ScalarNode, SequenceNode from ruyaml.representer import RoundTripRepresenter +from ruyaml.scalarstring import DoubleQuotedScalarString, SingleQuotedScalarString from ruyaml.tokens import CommentToken from yamlfix.model import YamlfixConfig, YamlNodeStyle @@ -379,12 +381,27 @@ def _ruamel_yaml_fixer(self, source_code: str) -> str: # Return the output to a string string_stream = StringIO() for source_dict in source_dicts: + self._quote_gha_container_volumes(source_dict) self.yaml.dump(source_dict, string_stream) source_code = string_stream.getvalue() string_stream.close() return source_code.strip() + def _quote_gha_container_volumes( + self, source_dict: CommentedMap | CommentedSeq + ) -> None: + """Quote jobs.*.container.volumes entries.""" + if not isinstance(source_dict, CommentedMap): + return + scalar_type = SingleQuotedScalarString + if self.config.quote_representation == '"': + scalar_type = DoubleQuotedScalarString + for job in source_dict.get("jobs", {}).values(): + if volumes := job.get("container", {}).get("volumes", []): + for i, _ in enumerate(volumes): + volumes[i] = scalar_type(volumes[i]) + @staticmethod def _fix_top_level_lists(source_code: str) -> str: """Deindent the source with a top level list. diff --git a/tests/unit/test_services.py b/tests/unit/test_services.py index ca23f1b..cd1cfd6 100644 --- a/tests/unit/test_services.py +++ b/tests/unit/test_services.py @@ -1034,3 +1034,78 @@ def test_fix_code_fix_whitelines( result = fix_code(source_code=source, config=config) assert result == desired_source + + @pytest.mark.parametrize( + ("source", "config", "desired_source"), + [ + ( + dedent( + """\ + --- + jobs: + test: + container: + volumes: + - /data:/data + - a:b # commented + - 'c:d' + - >- + multi: + line + """ + ), + YamlfixConfig(sequence_style=YamlNodeStyle.FLOW_STYLE), + dedent( + """\ + --- + jobs: + test: + container: + volumes: + - '/data:/data' + - 'a:b' # commented + - 'c:d' + - 'multi: line' + """ + ), + ), + ( + dedent( + """\ + --- + jobs: + test2: + container: + volumes: + - /data:/data + - a:b + - 'c:d' + - >- + multi: + line + """ + ), + YamlfixConfig(sequence_style=YamlNodeStyle.FLOW_STYLE), + dedent( + """\ + --- + jobs: + test2: + container: + volumes: ['/data:/data', 'a:b', 'c:d', 'multi: line'] + """ + ), + ), + ], + ) + def test_strings_with_colons_are_quoted( + self, source: str, config: Optional[YamlfixConfig], desired_source: str + ) -> None: + """ + Given: A GitHub Action workflow yaml containing jobs.*.containers.volumes + When: fix_code is run + Then: The volumes entries are quoted + """ + result = fix_code(source, config=config) + + assert result == desired_source