Download index.html from getreadystack/opentofu-migration-lint: direct link, hf CLI and curl.
- Browser
- Download file 38.6 kB
-
https://huggingface.co/spaces/getreadystack/opentofu-migration-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/opentofu-migration-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/opentofu-migration-lint/resolve/main/index.html
38.6 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>OpenTofu Terraform Migration — check your code for free</title> | |
| <meta name="description" content="This free tool flags Terraform backends, modules, resources, version pins and CI steps that need changes for OpenTofu."> | |
| <style> | |
| :root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512} | |
| .wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px} | |
| textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box} | |
| .bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer} | |
| input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px} | |
| table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65} | |
| .sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600} | |
| .note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa} | |
| </style> | |
| <script defer data-auto-init src="https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js"></script> | |
| <script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script> | |
| <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/opentofu-migration-lint"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static;justify-content:flex-end;margin:0 0 10px}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>OpenTofu Terraform Migration — check your code for free</h1> | |
| <p class="lede">OpenTofu migration check for Terraform files: flags HCP Terraform cloud/remote backends, app.terraform.io modules, tfe resources, BSL Terraform 1.6+ pins and CI steps, with the OpenTofu fix. Runs entirely in your browser — nothing is uploaded.</p> | |
| <textarea id="src" spellcheck="false">terraform { | |
| required_version = "~> 1.9.0" | |
| cloud { | |
| organization = "acme-platform" | |
| workspaces { | |
| name = "network-prod" | |
| } | |
| } | |
| required_providers { | |
| aws = { | |
| source = "registry.terraform.io/hashicorp/aws" | |
| version = "~> 5.80" | |
| } | |
| tfe = { | |
| source = "hashicorp/tfe" | |
| version = "~> 0.62" | |
| } | |
| } | |
| provider_meta "aws" { | |
| module_name = "network" | |
| } | |
| } | |
| data "terraform_remote_state" "core" { | |
| backend = "remote" | |
| config = { | |
| organization = "acme-platform" | |
| workspaces = { name = "core-prod" } | |
| } | |
| } | |
| module "vpc" { | |
| source = "app.terraform.io/acme-platform/vpc/aws" | |
| version = "3.4.1" | |
| cidr = "10.20.0.0/16" | |
| } | |
| resource "tfe_workspace" "network" { | |
| name = "network-prod" | |
| organization = "acme-platform" | |
| } | |
| resource "aws_route53_zone" "internal" { | |
| name = "internal.acme.example" | |
| tags = { vpc = module.vpc.vpc_id, core = data.terraform_remote_state.core.outputs.account_id } | |
| }</textarea> | |
| <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div> | |
| <div id="out" aria-live="polite"></div> | |
| <div id="paid"><strong>Free here:</strong> Every finding in the open file, with the Terraform line that blocks OpenTofu and the OpenTofu fix, free and offline. <strong>In the editor:</strong> Scan the whole workspace in one pass and export one migration report for the change ticket. — that part asks for a licence key. <a href="https://getreadystack.com/api/buy/cl/polar_cl_Y3oMOcLwT7dEf8D9a5smCQxIX6uUTIULF9pmu08r145" data-polar-checkout data-polar-checkout-theme="light">Full version: workspace sweep + migration report — $29 once</a></div> | |
| <p class="note">Same engine as the VS Code extension, byte for byte.</p> | |
| </div> | |
| <script>var OT_RULES = [ | |
| {"id": "hcp-cloud-block", "sev": "warn", "pats": [{"re": "^\\s*cloud\\s*\\{"}], | |
| "msg": "cloud block: state and runs live in HCP Terraform. The HCP Terraform Free plan stops at 500 managed resources and 1 concurrent run, and OpenTofu cannot use a cloud block and a state backend in the same configuration.", | |
| "fix": "Back up state first (terraform state pull \u003e backup.tfstate), move it to an s3, gcs, azurerm or pg backend, then run tofu init and tofu plan and expect \"No changes\"."}, | |
| {"id": "hcp-remote-backend", "sev": "warn", "pats": [{"re": "^\\s*backend\\s+\"remote\""}], | |
| "msg": "backend \"remote\": state is stored in HCP Terraform (Free plan: 500 managed resources, 1 concurrent run).", | |
| "fix": "terraform state pull \u003e backup.tfstate, switch to an s3, gcs, azurerm or pg backend, then tofu init -migrate-state and tofu plan."}, | |
| {"id": "remote-state-on-hcp", "sev": "warn", "pats": [{"re": "backend\\s*=\\s*\"remote\"", "near": "data\\s+\"terraform_remote_state\"", "within": 3}], | |
| "msg": "terraform_remote_state reads another configuration's state from HCP Terraform. OpenTofu's guide for interdependent configurations treats every terraform_remote_state link as a migration dependency.", | |
| "fix": "Migrate the producing configuration's state first, then point backend and config here at the new bucket or database."}, | |
| {"id": "hcp-private-module", "sev": "warn", "pats": [{"re": "source\\s*=\\s*\"app\\.terraform\\.io/"}], | |
| "msg": "Module from the HCP Terraform private registry (app.terraform.io). tofu init needs an HCP token to fetch it, so the module stays tied to the HCP organization.", | |
| "fix": "Publish the module from its Git repo and use source = \"git::https://\u003chost\u003e/\u003corg\u003e/\u003cmodule\u003e.git?ref=vX.Y.Z\"."}, | |
| {"id": "tfe-provider", "sev": "info", "pats": [{"re": "source\\s*=\\s*\"(registry\\.terraform\\.io/)?hashicorp/tfe\""}, {"re": "^\\s*(resource|data)\\s+\"tfe_"}], | |
| "msg": "hashicorp/tfe manages HCP Terraform itself (workspaces, variables, teams). These resources have nothing to manage once state has left HCP Terraform.", | |
| "fix": "Remove the tfe_* resources with a removed block or tofu state rm after the state move, and delete the tfe provider requirement."}, | |
| {"id": "hashicorp-registry-host", "sev": "warn", "pats": [{"re": "source\\s*=\\s*\"registry\\.terraform\\.io/"}], | |
| "msg": "Provider source written with the registry.terraform.io host. The OpenTofu migration guide's tofu init step downloads providers from the OpenTofu registry, which only applies to host-less sources.", | |
| "fix": "Write the source without a host, e.g. source = \"hashicorp/aws\", then run tofu init -upgrade and commit the new .terraform.lock.hcl."}, | |
| {"id": "required-version-pin", "sev": "error", "kind": "req_version", "when": "pin", | |
| "msg": "required_version excludes OpenTofu 1.11.14: OpenTofu 1.11 and earlier check required_version against the OpenTofu version, so tofu init stops on this Terraform pin.", | |
| "fix": "Keep the Terraform pin in versions.tf and add versions.tofu with language { compatible_with { opentofu = \"\u003e= 1.12\" } } (OpenTofu 1.12+); when both files exist OpenTofu ignores versions.tf."}, | |
| {"id": "required-version-bsl", "sev": "info", "kind": "req_version", "when": "bsl", | |
| "msg": "required_version asks for Terraform 1.6.0 or later, which is Business Source License code: the Terraform LICENSE covers \"Terraform Version 1.6.0 or later\" (Licensor: IBM).", | |
| "fix": "Record the Terraform pin for Terraform only and declare OpenTofu support in versions.tofu: language { compatible_with { opentofu = \"\u003e= 1.12\" } }."}, | |
| {"id": "provider-meta-ignored", "sev": "info", "pats": [{"re": "^\\s*provider_meta\\s+\""}], | |
| "msg": "provider_meta block: OpenTofu accepts it for compatibility but silently ignores it instead of sending its data to the provider.", | |
| "fix": "Delete it, or move the data it carries into provider or resource arguments."}, | |
| {"id": "setup-terraform-action", "sev": "warn", "pats": [{"re": "uses:\\s*['\"]?hashicorp/setup-terraform@"}], | |
| "msg": "CI installs HashiCorp Terraform (hashicorp/setup-terraform, latest v4.0.1), so pipeline runs keep executing BSL Terraform after the switch.", | |
| "fix": "uses: opentofu/setup-opentofu@v2 (latest v2.0.2) with tofu_version: 1.12.6, and rename terraform commands to tofu."}, | |
| {"id": "terraform-docker-image", "sev": "warn", "pats": [{"re": "(^\\s*FROM\\s|image:\\s*['\"]?|docker\\s+(run|pull)\\s).*\\bhashicorp/terraform(?![\\w-])"}], | |
| "msg": "Container image hashicorp/terraform: the pipeline or runner still ships HashiCorp Terraform.", | |
| "fix": "Use ghcr.io/opentofu/opentofu:1.12.6 (current stable, released 2026-08-19) and call tofu."}, | |
| {"id": "terraform-cli-call", "sev": "info", "pats": [{"re": "(^|[\\s;\u0026|(`$])terraform\\s+(init|plan|apply|destroy|validate|fmt|import|state|output|workspace|providers|test|login)\\b"}], | |
| "msg": "terraform CLI call: this step runs whatever terraform binary is on the PATH.", | |
| "fix": "Call tofu with the same subcommand (tofu init, tofu plan, tofu apply)."}, | |
| {"id": "hcp-token-env", "sev": "info", "pats": [{"re": "\\b(TF_TOKEN_app_terraform_io|TFE_TOKEN|TF_CLOUD_ORGANIZATION|TF_CLOUD_HOSTNAME)\\b"}], | |
| "msg": "HCP Terraform token or organization variable: it only serves app.terraform.io.", | |
| "fix": "After the state move, revoke the HCP team or user token and delete the variable from CI secrets."} | |
| ] | |
| ;</script> | |
| <script>/* OpenTofu migration lint: one engine for VS Code and the free web page. */ | |
| (function () { | |
| var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.OT_RULES; | |
| var TOFU_OLD = [1, 11, 14]; // newest OpenTofu that still reads required_version as its own constraint | |
| var BSL_FROM = [1, 6, 0]; // Terraform LICENSE: "Terraform Version 1.6.0 or later" | |
| function compile(r) { | |
| return (r.pats || []).map(function (p) { | |
| return { re: new RegExp(p.re), near: p.near ? new RegExp(p.near) : null, within: p.within || 5 }; | |
| }); | |
| } | |
| var COMPILED = RULES.map(function (r) { return { rule: r, pats: compile(r) }; }); | |
| function ver(s) { | |
| var v = String(s).split('.').map(function (x) { return parseInt(x, 10) || 0; }); | |
| return [v[0] || 0, v[1] || 0, v[2] || 0]; | |
| } | |
| function cmp(a, b) { for (var i = 0; i < 3; i++) { if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1; } return 0; } | |
| // one clause: ">= 1.6", "~> 1.9.0", "= 1.9.8", "1.9.8" | |
| function satisfies(v, op, s) { | |
| var t = ver(s), c = cmp(v, t), parts = String(s).split('.').length; | |
| switch (op) { | |
| case '>=': return c >= 0; | |
| case '>': return c > 0; | |
| case '<=': return c <= 0; | |
| case '<': return c < 0; | |
| case '!=': return c !== 0; | |
| case '~>': | |
| if (c < 0) return false; | |
| return parts >= 3 ? (v[0] === t[0] && v[1] === t[1]) : v[0] === t[0]; | |
| default: return c === 0; | |
| } | |
| } | |
| // pin = OpenTofu 1.11.14 fails it · bsl = its lower bound is Terraform 1.6.0+ | |
| function reqVerdict(line) { | |
| var m = /^\s*required_version\s*=\s*"([^"]+)"/.exec(line); | |
| if (!m) return {}; | |
| var out = { pin: false, bsl: false }; | |
| m[1].split(',').forEach(function (cl) { | |
| var c = /^\s*(>=|<=|~>|!=|>|<|=)?\s*v?(\d+(?:\.\d+){0,2})\s*$/.exec(cl); | |
| if (!c) return; | |
| var op = c[1] || '='; | |
| if (!satisfies(TOFU_OLD, op, c[2])) out.pin = true; | |
| if (op !== '<' && op !== '<=' && op !== '!=' && cmp(ver(c[2]), BSL_FROM) >= 0) out.bsl = true; | |
| }); | |
| return out; | |
| } | |
| function check(text, opts) { | |
| var lines = String(text || '').split(/\r?\n/); | |
| var findings = []; | |
| for (var i = 0; i < lines.length; i++) { | |
| var line = lines[i]; | |
| if (/^\s*(#|\/\/)/.test(line)) continue; | |
| var req = reqVerdict(line); | |
| for (var k = 0; k < COMPILED.length; k++) { | |
| var r = COMPILED[k].rule, hit = false; | |
| if (r.kind === 'req_version') { | |
| hit = !!req[r.when]; | |
| } else { | |
| for (var p = 0; p < COMPILED[k].pats.length && !hit; p++) { | |
| var pat = COMPILED[k].pats[p]; | |
| if (!pat.re.test(line)) continue; | |
| if (!pat.near) { hit = true; break; } | |
| for (var b = i; b >= 0 && b >= i - pat.within; b--) { | |
| if (pat.near.test(lines[b])) { hit = true; break; } | |
| } | |
| } | |
| } | |
| if (hit) findings.push({ check: r.id, sev: r.sev, msg: r.msg + ' Fix: ' + r.fix, line: i + 1 }); | |
| } | |
| } | |
| return { findings: findings }; | |
| } | |
| var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length }; | |
| if (typeof module !== 'undefined') module.exports = api; | |
| if (typeof window !== 'undefined') window.OTENGINE = api; | |
| })(); | |
| </script> | |
| <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다. | |
| ; | |
| function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } | |
| function summaryLine(res) { | |
| const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length; | |
| return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.'; | |
| } | |
| function toText(res, label) { | |
| const f = (res && res.findings) || []; | |
| const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)]; | |
| f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); }); | |
| return lines.join('\n'); | |
| } | |
| function toHtml(res) { | |
| const f = (res && res.findings) || []; | |
| if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>'; | |
| const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join(''); | |
| return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>'; | |
| } | |
| var OTREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = OTREPORT; | |
| else window.OTREPORT = OTREPORT; | |
| </script> | |
| <script> | |
| (function(){ | |
| var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today'); | |
| today.value=new Date().toISOString().slice(0,10); | |
| function run(){ try { var res=window.OTENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.OTREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } } | |
| go.onclick=run; if (src.value.trim()) run(); | |
| })(); | |
| </script> | |
| <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://open-vsx.org/extension/readystack/opentofu-migration-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'opentofu-migration-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section> | |
| <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind OpenTofu Migration Lint — Terraform every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"opentofu-migration-lint",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},true);})();</script></section> | |
| <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">Scan the whole workspace in one pass and export one migration report for the change ticket (every file, line, rule and fix in one Markdown report), $29 once, one licence key per person or team seat.</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">DOJ's Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026, which is one hour of counsel reading the Terraform BSL Additional Use Grant.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_Y3oMOcLwT7dEf8D9a5smCQxIX6uUTIULF9pmu08r145" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section> | |
| <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does OpenTofu Migration Lint check in my Terraform files?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads .tf, .tofu, GitHub Actions or GitLab YAML, shell scripts and Dockerfiles line by line against 13 rules and marks every line that ties the project to HashiCorp Terraform or HCP Terraform: cloud blocks, backend "remote", app.terraform.io modules, tfe resources, registry.terraform.io hosts, required_version pins, setup-terraform steps and hashicorp/terraform images. Each finding names the OpenTofu fix.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is OpenTofu Migration Lint for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Platform, DevOps and SRE engineers moving a Terraform code base from BSL Terraform 1.6.0 or later, or from HCP Terraform, to OpenTofu 1.12. It suits consultancies and MSPs that must not ship Terraform inside a competing hosted product, and teams whose HCP Terraform Free organization is near the 500 managed resource limit.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just grep for terraform or ask a chatbot?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A grep for terraform matches every line and ranks none. A chatbot usually says required_version is harmless, but OpenTofu 1.11 and earlier check it against the OpenTofu version, so "~> 1.9.0" stops tofu init. It also misses that app.terraform.io modules need an HCP token and that OpenTofu silently ignores provider_meta.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the licence add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free: every finding in the open file, with the line and the OpenTofu fix, in VS Code and in the web version, offline, no account. The sample 49-line main.tf gives 9 findings for free. The licence, $29 once, adds a sweep of the whole workspace in one pass and one exported Markdown migration report for the change ticket.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does the alternative cost?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The Terraform licence question is legal work: DOJ's Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026, which is one hour of counsel reading the Terraform BSL Additional Use Grant. Staying on HCP Terraform Free caps you at 500 managed resources and 1 concurrent run.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does OpenTofu Migration Lint check in my Terraform files?", "acceptedAnswer": {"@type": "Answer", "text": "It reads .tf, .tofu, GitHub Actions or GitLab YAML, shell scripts and Dockerfiles line by line against 13 rules and marks every line that ties the project to HashiCorp Terraform or HCP Terraform: cloud blocks, backend \"remote\", app.terraform.io modules, tfe resources, registry.terraform.io hosts, required_version pins, setup-terraform steps and hashicorp/terraform images. Each finding names the OpenTofu fix."}}, {"@type": "Question", "name": "Who is OpenTofu Migration Lint for?", "acceptedAnswer": {"@type": "Answer", "text": "Platform, DevOps and SRE engineers moving a Terraform code base from BSL Terraform 1.6.0 or later, or from HCP Terraform, to OpenTofu 1.12. It suits consultancies and MSPs that must not ship Terraform inside a competing hosted product, and teams whose HCP Terraform Free organization is near the 500 managed resource limit."}}, {"@type": "Question", "name": "Why not just grep for terraform or ask a chatbot?", "acceptedAnswer": {"@type": "Answer", "text": "A grep for terraform matches every line and ranks none. A chatbot usually says required_version is harmless, but OpenTofu 1.11 and earlier check it against the OpenTofu version, so \"~> 1.9.0\" stops tofu init. It also misses that app.terraform.io modules need an HCP token and that OpenTofu silently ignores provider_meta."}}, {"@type": "Question", "name": "What is free and what does the licence add?", "acceptedAnswer": {"@type": "Answer", "text": "Free: every finding in the open file, with the line and the OpenTofu fix, in VS Code and in the web version, offline, no account. The sample 49-line main.tf gives 9 findings for free. The licence, $29 once, adds a sweep of the whole workspace in one pass and one exported Markdown migration report for the change ticket."}}, {"@type": "Question", "name": "What does the alternative cost?", "acceptedAnswer": {"@type": "Answer", "text": "The Terraform licence question is legal work: DOJ's Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026, which is one hour of counsel reading the Terraform BSL Additional Use Grant. Staying on HCP Terraform Free caps you at 500 managed resources and 1 concurrent run."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:0,slug:"opentofu-migration-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/aks-eks-support-cliff-lint"><span class="yj-rel-t">AKS LTS & EKS Extended Support Lint for Terraform</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/almalinux-migration-lint"><span class="yj-rel-t">AlmaLinux Migration Lint — CentOS 7/8</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/brave-search-api-migration-check"><span class="yj-rel-t">Brave Search API Migration Check — Google Custom Search & Bing</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bulk-sender-lint"><span class="yj-rel-t">Bulk Sender Lint - the Gmail, Yahoo and Outlook.com rules, checked in your mai</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cinc-migration-check"><span class="yj-rel-t">Cinc Migration Check: Chef Infra licence gate lint</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script> | |
| <script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script> | |
| <script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div></body> | |
| </html> | |