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

Handle version constraints #32

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

br3ndonland
Copy link

Description

Thanks for all the recent updates to the migration kit! I find it much easier to use than the previous implementation.

I encountered an issue with version constraints. Terraform versions in Terraform Cloud workspaces may have version constraints, and the exporter currently raises an exception on version constraints.

For example, exporting a Terraform Cloud workspace set to Terraform version ~>1.5.0 raises ValueError: ~>1.5.0 is not valid SemVer string.

Traceback (expand).
spacelift-migration-kit on  main [?] is 📦 v2.0.0-dev via 🐍 v3.12.2 (spacemk-py3.12) on ☁️  (us-east-2)
❯ spacemk export
smk-latest: Pulling from jmfontaine/tfc-agent
Digest: sha256:f92a300764ad2d54a56aba3283cdbfc903901640339d03fcf9df5885753e9ba7
Status: Image is up to date for jmfontaine/tfc-agent:smk-latest
docker.io/jmfontaine/tfc-agent:smk-latest
ERROR    The command failed
         ╭──────────────────────── Traceback (most recent call last) ─────────────────────────╮
         │ /Users/brendon/dev/spacelift-migration-kit/spacemk/cli.py:76 in app                │
         │                                                                                    │
         │   73                                                                               │
         │   74 def app():                                                                    │
         │   75 │   try:                                                                      │
         │ ❱ 76 │   │   spacemk()                                                             │
         │   77 │   except Exception:                                                         │
         │   78 │   │   logging.exception("The command failed")                               │
         │   79 │   │   sys.exit(1)                                                           │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/core.py:1157 in __call__                                                         │
         │                                                                                    │
         │   1154 │                                                                           │
         │   1155 │   def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:             │
         │   1156 │   │   """Alias for :meth:`main`."""                                       │
         │ ❱ 1157 │   │   return self.main(*args, **kwargs)                                   │
         │   1158                                                                             │
         │   1159                                                                             │
         │   1160 class Command(BaseCommand):                                                 │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/core.py:1078 in main                                                             │
         │                                                                                    │
         │   1075 │   │   try:                                                                │
         │   1076 │   │   │   try:                                                            │
         │   1077 │   │   │   │   with self.make_context(prog_name, args, **extra) as ctx:    │
         │ ❱ 1078 │   │   │   │   │   rv = self.invoke(ctx)                                   │
         │   1079 │   │   │   │   │   if not standalone_mode:                                 │
         │   1080 │   │   │   │   │   │   return rv                                           │
         │   1081 │   │   │   │   │   # it's not safe to `ctx.exit(rv)` here!                 │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/core.py:1688 in invoke                                                           │
         │                                                                                    │
         │   1685 │   │   │   │   super().invoke(ctx)                                         │
         │   1686 │   │   │   │   sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)      │
         │   1687 │   │   │   │   with sub_ctx:                                               │
         │ ❱ 1688 │   │   │   │   │   return _process_result(sub_ctx.command.invoke(sub_ctx)) │
         │   1689 │   │                                                                       │
         │   1690 │   │   # In chain mode we create the contexts step by step, but after the  │
         │   1691 │   │   # base command has been invoked.  Because at that point we do not   │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/core.py:1434 in invoke                                                           │
         │                                                                                    │
         │   1431 │   │   │   echo(style(message, fg="red"), err=True)                        │
         │   1432 │   │                                                                       │
         │   1433 │   │   if self.callback is not None:                                       │
         │ ❱ 1434 │   │   │   return ctx.invoke(self.callback, **ctx.params)                  │
         │   1435 │                                                                           │
         │   1436 │   def shell_complete(self, ctx: Context, incomplete: str) -> t.List["Comp │
         │   1437 │   │   """Return a list of completions for the incomplete value. Looks     │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/core.py:783 in invoke                                                            │
         │                                                                                    │
         │    780 │   │                                                                       │
         │    781 │   │   with augment_usage_errors(__self):                                  │
         │    782 │   │   │   with ctx:                                                       │
         │ ❱  783 │   │   │   │   return __callback(*args, **kwargs)                          │
         │    784 │                                                                           │
         │    785 │   def forward(                                                            │
         │    786 │   │   __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any  # noqa: B9 │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/decorators.py:118 in new_func                                                    │
         │                                                                                    │
         │   115 │   │   def new_func(*args: "P.args", **kwargs: "P.kwargs") -> R:            │
         │   116 │   │   │   ctx = get_current_context()                                      │
         │   117 │   │   │   obj = ctx.meta[key]                                              │
         │ ❱ 118 │   │   │   return ctx.invoke(f, obj, *args, **kwargs)                       │
         │   119 │   │                                                                        │
         │   120 │   │   return update_wrapper(new_func, f)                                   │
         │   121                                                                              │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/clic │
         │ k/core.py:783 in invoke                                                            │
         │                                                                                    │
         │    780 │   │                                                                       │
         │    781 │   │   with augment_usage_errors(__self):                                  │
         │    782 │   │   │   with ctx:                                                       │
         │ ❱  783 │   │   │   │   return __callback(*args, **kwargs)                          │
         │    784 │                                                                           │
         │    785 │   def forward(                                                            │
         │    786 │   │   __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any  # noqa: B9 │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/spacemk/commands/export.py:17 in export │
         │                                                                                    │
         │   14 @pass_meta_key("config")                                                      │
         │   15 def export(config):                                                           │
         │   16 │   exporter = load_exporter(config=config.get("exporter", {}))               │
         │ ❱ 17 │   exporter.export()                                                         │
         │   18                                                                               │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/spacemk/exporters/base.py:179 in export │
         │                                                                                    │
         │   176 │   │   data = self._extract_data()                                          │
         │   177 │   │   data = self._filter_data(data)                                       │
         │   178 │   │   data = self._enrich_data(data)                                       │
         │ ❱ 179 │   │   data = self._map_data(data)                                          │
         │   180 │   │   save_normalized_data(data)                                           │
         │   181 │   │                                                                        │
         │   182 │   │   logging.info("Stop exporting data")                                  │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/spacemk/exporters/terraform.py:1326 in  │
         │ _map_data                                                                          │
         │                                                                                    │
         │   1323 │   │   │   │   #     src_data                                              │
         │   1324 │   │   │   │   # ),  # Must be after contexts due to dependency            │
         │   1325 │   │   │   │   "modules": self._map_modules_data(src_data),                │
         │ ❱ 1326 │   │   │   │   "stacks": self._map_stacks_data(src_data),                  │
         │   1327 │   │   │   │   "stack_variables": self._map_stack_variables_data(src_data) │
         │        after stacks due to dependency                                              │
         │   1328 │   │   │   }                                                               │
         │   1329 │   │   )                                                                   │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/spacemk/exporters/terraform.py:1275 in  │
         │ _map_stacks_data                                                                   │
         │                                                                                    │
         │   1272 │   │   │   │   # KLUDGE: Stick to the latest MPL-licensed Terraform versio │
         │   1273 │   │   │   │   terraform_version = "1.5.7"                                 │
         │   1274 │   │   │                                                                   │
         │ ❱ 1275 │   │   │   if semver.match(workspace.get("attributes.terraform-version"),  │
         │   1276 │   │   │   │   terraform_workflow_tool = "CUSTOM"                          │
         │   1277 │   │   │   else:                                                           │
         │   1278 │   │   │   │   terraform_workflow_tool = "TERRAFORM_FOSS"                  │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/semv │
         │ er/_deprecated.py:80 in wrapper                                                    │
         │                                                                                    │
         │    77 │   │   # https://docs.python.org/3/library/inspect.html#the-interpreter-sta │
         │    78 │   │   # better remove the interpreter stack:                               │
         │    79 │   │   del frame                                                            │
         │ ❱  80 │   │   return func(*args, **kwargs)  # type: ignore                         │
         │    81 │                                                                            │
         │    82 │   return wrapper                                                           │
         │    83                                                                              │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/semv │
         │ er/_deprecated.py:196 in match                                                     │
         │                                                                                    │
         │   193 │   >>> semver.match("1.0.0", ">1.0.0")                                      │
         │   194 │   False                                                                    │
         │   195 │   """                                                                      │
         │ ❱ 196 │   ver = Version.parse(version)                                             │
         │   197 │   return ver.match(match_expr)                                             │
         │   198                                                                              │
         │   199                                                                              │
         │                                                                                    │
         │ /Users/brendon/dev/spacelift-migration-kit/.venv/lib/python3.12/site-packages/semv │
         │ er/version.py:646 in parse                                                         │
         │                                                                                    │
         │   643 │   │   else:                                                                │
         │   644 │   │   │   match = cls._REGEX.match(version)                                │
         │   645 │   │   if match is None:                                                    │
         │ ❱ 646 │   │   │   raise ValueError(f"{version} is not valid SemVer string")        │
         │   647 │   │                                                                        │
         │   648 │   │   matched_version_parts: Dict[str, Any] = match.groupdict()            │
         │   649 │   │   if not matched_version_parts["minor"]:                               │
         ╰────────────────────────────────────────────────────────────────────────────────────╯
         ValueError: ~>1.5.0 is not valid SemVer string

Changes

This PR proposes to add a simple utility function for parsing the Terraform version. The function tries to remove version constraint characters and parse the version. If any exceptions are raised, the function will return 1.5.7 as suggested by the previous code. This is not a perfectly accurate approach to version constraint handling, but it will at least help to avoid exceptions and set an approximate Terraform version.

This PR includes tests to cover the new function.

Related

Terraform versions in Terraform Cloud workspaces may have version
constraints. The exporter raises an exception on version constraints.

For example, exporting a Terraform Cloud workspace set to Terraform
`~>1.5.0` raises `ValueError: ~>1.5.0 is not valid SemVer string`.

This commit proposes to add a simple utility function for parsing the
Terraform version. The function tries to remove version constraint
characters and parse the version. If any exceptions are raised, the
function will return "1.5.7" as suggested by the previous code.

https://opentofu.org/docs/language/expressions/version-constraints/
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.

1 participant