2021-12-20 23:51:51 -08:00
|
|
|
#!/usr/bin/env python3
|
2020-01-10 04:31:19 -08:00
|
|
|
|
2021-12-30 01:56:19 -08:00
|
|
|
# Copyright 2022 Google LLC
|
2020-01-10 04:31:19 -08:00
|
|
|
#
|
|
|
|
# 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
|
|
|
|
#
|
|
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
'''Generate tables for Terraform root module files, outputs and variables.
|
|
|
|
|
|
|
|
This tool generates nicely formatted Markdown tables from Terraform source
|
|
|
|
code, that can be used in root modules README files. It makes a few assumptions
|
|
|
|
on the code structure:
|
|
|
|
|
|
|
|
- that outputs are only in `outputs.tf`
|
|
|
|
- that variables are only in `variables.tf`
|
|
|
|
- that code has been formatted via `terraform fmt`
|
|
|
|
|
|
|
|
The tool supports annotations using the `tfdoc:scope:key value` syntax.
|
|
|
|
Annotations are rendered in the optional file table by default, and in the
|
|
|
|
outputs and variables tables if the `--extra-fields` flag is used. Currently
|
|
|
|
supported annotations are:
|
|
|
|
|
|
|
|
- `tfdoc:file:description`
|
|
|
|
- `tfdoc:output:consumers`
|
|
|
|
- `tfdoc:variable:source`
|
|
|
|
|
|
|
|
Tables can optionally be directly injected/replaced in README files by using
|
|
|
|
the tags in the `MARK_BEGIN` and `MARK_END` constants, and setting the
|
|
|
|
`--replace` flag.
|
|
|
|
'''
|
|
|
|
|
2020-01-10 04:31:19 -08:00
|
|
|
import collections
|
|
|
|
import enum
|
Merge development branch (#44)
* VPN-HA module initial commit
* Added readme for net-vpn-ha module
* Update readme, add simple description
* Merge new modules list and environments foundation example (#30)
* gke-cluster
* net-vpc module and tests
* add TODO to net-vpc module
* add minimal README files with input/output variables to gke and net-vpc modules
* BigQuery Module (#24)
* Bigquery Module
* Added README file
* Added type hints
* gke-cluster
* net-vpc module and tests
* add TODO to net-vpc module
* add minimal README files with input/output variables to gke and net-vpc modules
* BigQuery Module (#24)
* Bigquery Module
* Added README file
* Added type hints
* GCS module
* net vpc module: improve secondary range outputs
* net vpc module: add serve project registration
* project module
* move bigquery module to not-ready folder
* folders module
* rename project module's iam variables
* slight tweak to folder module outputs
* gcs module
* simplify net-vpc module variables
* fix module tests configurations, fix net-vpc module tests
* add pydoc utility
* add/update module READMEs
* add/update module READMEs
* add/update module READMEs
* improve variable type summary generation in tfdoc
* tfdoc: add support for replacing doc in README.md files
* improve module READMEs
* net-vpc-firewall module
* add support for sensitive output attribute in tfdoc
* remove empty function from tfdoc
* render variable type as code in tfdoc
* update module READMEs
* net address module
* net cloudnat module
* remove redundant variable from net-cloudnat module
* vpc module: add support for peering, use network name as subnet name prefix
* net-vpn-static module
* net-vpn-static module README
* net-vpn-static module README
* tfdoc: fix error on undeclared variable type
* dns module
* set version for all modules
* kms module (untested)
* change kms key self links output to map, fix gcs and kms iam variable descriptions
* fix kms module
* update kms module readme
* simplify local iam pairs in modules
* service accounts module (unfinished)
* work on service accounts module
* project module: add gcr service account
* project module: update outputs in README
* first working version of the iam service accounts module
* iam service accounts module: extra checks in locals
* modules/net-cloudnat: reorder variables
* modules/net-vpn-dynamic: initial import (untested)
* modules/net-vpn-dynamic: first working version
* modules/net-vpn-dynamic: add outputs for auto-created router
* modules/net-vpn-dynamic: update README
* modules/net-[vpn,cloudnat]: clean up variable,s remove prefix
* modules/net-vpn-dynamic: add advertisement configuration to tunnel bgp peer, refactor variables
* tfdoc: add tooltips for variable types and defaults
* modules: update README variables and outputs
* tfdoc: improve variable default rendering
* modules: update README variables and outputs
* modules/net-vpc: minimal output refactoring
* modules/vm-cos: initial import, base resources working, no outputs
* modules/vm-cos: add variable descriptions
* tfdoc: fix parsing in type and default blocks
* modules/vm-cos: fix README
* tfdoc: fix parsing in type and default blocks
* modules/vm-cos: fix README
* modules/compute-vm: initial working import (not fully tested)
* modules/vm-cos: move to not-ready
* tfdoc: fix variable defaults formatting
* modules: update README files with tfdoc fixes
* modules: add initial examples
* gke-nodepool: initial import, untested
* gke nodepool: add README, fix location variable, set node count default to 1
* gke cluster: fix private cluster variables
* gke nodepool: fix README title
* gke cluster: add output for cluster location
* gke nodepool: add missing variables for project id and cluster name, remove default from location variable, fix gke version assignment
* gke nodepool: update README
* net-cloudnat: fix router name when creating default router
* fix variables used for address and router optional creation
* vpn dynamic: fix README
* modules/net-vpn-dynamic: fix router name output
* modules/compute-vm: remove unused variable
* modules/compute-vm-cos-coredns: initial import
* Update foundations modules versions (#26)
* update foundations modules versions
* update Terraform version to v0.12.19 in CI test configuration
* backport tfdoc from Ludo's branch (#27)
* Update docs using tfdoc format (#28)
* update README files
* set all types on variables
* foundations/environments: move log filter to a variable, use org for xpn by default
* foundations/environments: do not use liens by default
* modules/ntp-vpc: better shared_vpc_host variable description
* modules/logging-sinks: initial version
* modules/logging-sinks: streamline options in sinks variable
* modules/compute-vm-cos-coredns: add support for additional files
* modules/folders: rename from 'folder'
* modules/logging-sinks: fix circular dependencies and improve variables
* modules/project: remove extra variable
* modules/bigquery: new module with dataset support only
* foundations/environments: refactor using local modules
* modules/bigquery: better variables, README description and example
* modules: fix a few READMEs
Co-authored-by: Julio Castillo <juliocc@gmail.com>
* modules/net-vpc: README description and examples
* modules/net-vpc: tweak README description and examples
* modules/net-vpc: tweak README description and examples
* modules/net-vpc-firewall: change tag-based rule default ranges, improve README examples and description
* modules/compute-vm: README changes
* modules/compute-vm: use an object for the service account variable, update README
* modules/compute-vm: update README variables table
* modules/compute-vm: add TODO list to README
* modules/compute-vm: add TODO list to README
* modules/compute-vm: add outputs for service account
* modules/net-cloudnat: README
* modules/net-cloudnat: README
* modules/net-cloudnat: add router_create variable
* modules/compute-vm: simplify service account variables
* modules/net-vpn-dynamic: fix README example, use local secret for both empty string and null
* modules/net-vpn-dynamic: improve README example
* modules/gke-cluster: minimal README tweaks
* modules/kms: fix ephemeral keys resource name
* modules/iam-service-accounts: add storage roles
* modules/gke-nodepool: fix node default scopes
* New project variable to prevent deletion of default network (#32)
* New project variable to prevent deletion of default network
This is a workaround to fix
terraform-google-modules/cloud-foundation-fabric#31 while the GCP
terraform provider is fixed
* Add TODOs to remove workarounds in the project module
* Fix Cloud Build files
* modules/gke-nodepool: add monitoring scope to defaults
* modules/iam-service-accounts: add support for IAM bindings onthe service accounts
* playground module in sandbox, remove not ready modules
* Fix ci configurations in development branch (#33)
* try fixing ci confgurations
* add exclusion match to ci boilerplate check
* add skip boilerplate comment to compute-vm-cos-coredns template fragment
* modules/gke-cluster: fix boilerplate in outputs
* Simplify tests, re-enable CI
* add instance group support to compute-vm, start tests refactoring
* modules/compute-vm: group fixes, tests
* modules/compute-vm: minimal test beautification
* simplify top-level pytest fixture
* modules/dns: tests and minor tweaks
* fix missing boilerplate in tests
* re-add requirements file to tests folder
* re-enable tests in ci build configuration
* Folder module tests and fixes (#38)
* folder tests wip
* modules/folders: tests and tweaks
* update folders and compute-vm README files
* modules/gcs: tests and minor tweaks
* Create README.md
* Update README.md
* Update README.md
* Update README.md
* Added docker image for strongSwan
* Add support for routes and tests to net-vpc module (#39)
* modules/net-vpc: add routes (untested)
* initial tests
* modules/net-vpc: add test for flow logs
* modules/net-vpc: split tests into two separate files
* modules/net-vpc: routes test
* modules/net-vpc: test routes
* Add support for Terraform plugin cache in ci test build file (#40)
* add Terraform plugin caching to test ci build configuration
* fix mkdir in test build configuration
* trigger test check
* Refactor dynamic vpn configuration for on-prem-in-a-box module
* Fix dynamic vpn for onprem-in-a-box module
* Migrate Shared VPC example to local modules (#41)
* wip
* wip
* validated, untested
* modules/compute-vm: make service account email in locals resilient to destroy
* modules/project: make project id output depend on iam roles
* fixes
* shared-vpc tweaks
* update diagram
* update README input output tables
* modules/compute-vm: add service account IAM email output
* move GKE service account roles at the project level, add GCE service account roles
* update diagram and README
* modules/project: add extra output for IAM-dependent project id
* update modules READMEs
* minor tweaks
* modules/compute-vm: fix service account output
* remove static address from NAT
* fix container service agent binding dependency
* rename shared vpc
* Update README.md
* Update README.md
* Add static vpn gw to on-prem-in-a-box module
* Refactor hub and spoke to use new modules (#42)
* modules/compute-vm: saner defaults for service account scopes
* hub and spoke refactor, docs still missing
* complete hub and spoke
* Update README.md
* Add toolbox docker container, fix gw routing to the internet
* Add DNS Hybrid connectivity parameters
* Fix onprem dns zone for the static vpn configuration
* Added readme.md for on-prem module
* Add new line at the end of the files
* Add boilerplate for cloudbuild config files
* fix boilerplate in strongswan shell script
* Update README.md
* include missing file to fix merge conflict
* remove missing file to fix merge conflict
* include missing file to fix merge conflict (again)
* remove content from spurious file used to avoid merge conflicts
* Add net-vpc-peering module
* Initial commit for hub-and-spoke-peering infrastructure example
* Fix typos in infrastructure/ READMEs
* remove stale file
* use larger resolution version of hub and spoke diagram
* Update README.md
* Update hub-and-spoke-peerings example to use internal modules
* Add initial project tests (#46)
* modules/project: make prefix optional
* initial project module tests
* modules/project: use null for unset parent
* modules/dns: backport PR6 from the CFT dns module
* Add testing resources including on-prem-in-a-box to hub-and-spoke-peerings example
* Fix firewall rules to allow connectivity, switch to custom route advertisement for onprem -> spokes connectivity
* Move locals out of main.tf
* remove ssh tag from compute-vm variable default
* Add ssh tag to the test vms
* Update README.md
* Update README.md
* Update README.md
* Hub and spoke peering changes (#48)
* rename hub-and-spoke-vpn
* add ssh tag to shared-vpc-gke instance
* rename and rework hub and spoke peering
* fix test requirements
* align hub and spoke peering with module contents
* diagram
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* minimal fixes to onprem examples variable files
* onprem example stub, missing DNS zones and private.googleapis records onprem
* add missing boilerplate
* Update README.md
* Update README.md
* infra/onprem: add test instance and minimal outputs
* add DNS modules and resource
* infra/onprem: diagram and initial README
* minor changes to onprem module and example (#49)
* update toolbox image
* infra/onprem: add zone for private access, add metadata domain to onprem dns
* infra/onprem: onnprem service account, add testing procedure in README
* Update README.md
* infra/onprem: remove extra variable
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* infra/onprem: rename forwarder address variable
* Update README:
Added explicit --tunnel-through-iap for gcloud compute ssh commands
* Update top-level and section READMEs (#50)
* top-level README WIP
* rewrite top-level README
* change top-level README title
* remove initial quote in top-level README
* Update README.md
* Update README.md
* Update README.md
* foundations README
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* add experimental scheduled cloud function module
* scheduled cloud function module: allow disabling schedule
* business-units foundation example (#52)
* Added folder-units module.
* Business units example update (WIP)
* Update all BU modules to internal ones
* Refactoring business-units example, add billing and org IAM handling
* update projects tests for new iam additive naming
* update project README for new iam additive naming
* streamline bu example and module (#53)
Co-authored-by: Ludovico Magnocavallo <ludomagno@google.com>
* align net-vpn-ha interface with the other vpn modules
* update module README files
* Update README.md
* Update README.md
* Create CHANGELOG.md
* Refactor COS module to be generic (#51)
* Create generic COS module and update CoreDNS module to use it
* Update compute-vm-cos README
* Fix COS README
* Update COS example
* Skip boilerplate check for COS file template
* Make COS module more generic and provide preset configurations
* Update COS module documentation
* tfdoc: add support for multiple variables files
* compute-vm: split boot disk in separate variable file for cos module support
* Streamline cos modules (#54)
* tfdoc: fix bug in last commit
* compute-vm: add support for user-data
* compute-vm: restore noncos variable split
* remove compute-vm-cos-coredns
* compute-vm: revert to original state
* cos-container/coredns
* fix variables mess
* cos/coredns fixes
* cos/mysql
* remove stale compute-vm-cos module
* add test instance to cos modules
* tfdoc: add support for multiple output files
* cos: add initial READMEs
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* Update README.md
* add test apply fixture
* cos-coredns: tested
* Update README.md
* Fix typo
* cos-coredns: refactor README
* Update README.md
* test yaml validity in cos modules tests
* cos mysql tests
* cos mysql: refactor and test (disk tests missing)
* onprem: fix Coredns
* cos mysql: additional disk working
* cos modules: fix instance disks for no instance
* update some modules READMEs
* update some modules READMEs
* Update README.md
* Update README.md
* add simple tests for foundations/environments
* change default for org id in foundations/environments to avoid errors when none is specified
* fix null/empty organization id in foundations/environments
* fix errors when destroying on empty state in foundations/environments
* fundations/bu: fix errors when destroying with empty state
* modules/gcs: make outputs resilient on destroy with empty state
* modules/folders: make outputs resilient on destroy with empty state
* switch organization_id variable to long form in foundations/bu and modules/folders-unit
* Update README.md
* infra/shared-vpc: remove duplicate tag attribute from bastion
Co-authored-by: Aleksandr Averbukh <averbukh@google.com>
Co-authored-by: Julio Castillo <juliocc@gmail.com>
Co-authored-by: Julio Castillo <jccb@google.com>
2020-04-03 05:06:48 -07:00
|
|
|
import glob
|
2020-01-10 04:31:19 -08:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import string
|
2021-12-20 23:51:51 -08:00
|
|
|
import urllib.parse
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
import click
|
|
|
|
|
|
|
|
|
2021-12-30 01:56:19 -08:00
|
|
|
__version__ = '2.1.0'
|
2021-12-20 23:51:51 -08:00
|
|
|
|
|
|
|
|
|
|
|
# TODO(ludomagno): decide if we want to support variables*.tf and outputs*.tf
|
|
|
|
|
|
|
|
|
|
|
|
FILE_DESC_DEFAULTS = {
|
|
|
|
'main.tf': 'Module-level locals and resources.',
|
|
|
|
'outputs.tf': 'Module outputs.',
|
|
|
|
'providers.tf': 'Provider configurations.',
|
|
|
|
'variables.tf': 'Module variables.',
|
|
|
|
'versions.tf': 'Version pins.',
|
|
|
|
}
|
|
|
|
FILE_RE_MODULES = re.compile(
|
|
|
|
r'(?sm)module\s*"[^"]+"\s*\{[^\}]*?source\s*=\s*"([^"]+)"'
|
|
|
|
)
|
|
|
|
FILE_RE_RESOURCES = re.compile(
|
|
|
|
r'(?sm)resource\s*"([^"]+)"'
|
|
|
|
)
|
|
|
|
HEREDOC_RE = re.compile(r'(?sm)^<<\-?END(\s*.*?)\s*END$')
|
2020-01-10 04:31:19 -08:00
|
|
|
MARK_BEGIN = '<!-- BEGIN TFDOC -->'
|
|
|
|
MARK_END = '<!-- END TFDOC -->'
|
2021-12-20 23:51:51 -08:00
|
|
|
OUT_ENUM = enum.Enum('O', 'OPEN ATTR ATTR_DATA CLOSE COMMENT TXT SKIP')
|
|
|
|
OUT_RE = re.compile(r'''(?smx)
|
|
|
|
# output open
|
|
|
|
(?:^\s*output\s*"([^"]+)"\s*\{\s*$) |
|
|
|
|
# attribute
|
|
|
|
(?:^\s{2}([a-z]+)\s*=\s*"?(.*?)"?\s*$) |
|
|
|
|
# output close
|
|
|
|
(?:^\s?(\})\s*$) |
|
|
|
|
# comment
|
|
|
|
(?:^\s*\#\s*(.*?)\s*$) |
|
|
|
|
# anything else
|
|
|
|
(?:^(.*?)$)
|
2020-01-10 04:31:19 -08:00
|
|
|
''')
|
2021-12-20 23:51:51 -08:00
|
|
|
OUT_TEMPLATE = ('description', 'value', 'sensitive')
|
|
|
|
TAG_RE = re.compile(r'(?sm)^\s*#\stfdoc:([^:]+:\S+)\s+(.*?)\s*$')
|
|
|
|
UNESCAPED = string.digits + string.ascii_letters + ' .,;:_-'
|
|
|
|
VAR_ENUM = enum.Enum(
|
|
|
|
'V', 'OPEN ATTR ATTR_DATA SKIP CLOSE COMMENT TXT')
|
|
|
|
VAR_RE = re.compile(r'''(?smx)
|
|
|
|
# variable open
|
|
|
|
(?:^\s*variable\s*"([^"]+)"\s*\{\s*$) |
|
|
|
|
# attribute
|
|
|
|
(?:^\s{2}([a-z]+)\s*=\s*"?(.*?)"?\s*$) |
|
|
|
|
# validation
|
|
|
|
(?:^\s+validation\s*(\{)\s*$) |
|
|
|
|
# variable close
|
|
|
|
(?:^\s?(\})\s*$) |
|
|
|
|
# comment
|
|
|
|
(?:^\s*\#\s*(.*?)\s*$) |
|
|
|
|
# anything else
|
|
|
|
(?:^(.*?)$)
|
2020-01-10 04:31:19 -08:00
|
|
|
''')
|
2021-12-20 23:51:51 -08:00
|
|
|
VAR_RE_TYPE = re.compile(r'([\(\{\}\)])')
|
|
|
|
VAR_TEMPLATE = ('default', 'description', 'type')
|
|
|
|
|
|
|
|
|
|
|
|
File = collections.namedtuple('File', 'name description modules resources')
|
|
|
|
Output = collections.namedtuple('Output',
|
|
|
|
'name description sensitive consumers')
|
|
|
|
Variable = collections.namedtuple(
|
|
|
|
'Variable', 'name description type default required source')
|
|
|
|
|
|
|
|
|
|
|
|
# parsing functions
|
|
|
|
|
|
|
|
|
|
|
|
def _extract_tags(body):
|
|
|
|
'Extract and return tfdocs tags from content.'
|
|
|
|
return {k: v for k, v in TAG_RE.findall(body)}
|
|
|
|
|
|
|
|
|
|
|
|
def _parse(body, enum=VAR_ENUM, re=VAR_RE, template=VAR_TEMPLATE):
|
|
|
|
'Low-level parsing function for outputs and variables.'
|
|
|
|
item = context = None
|
|
|
|
for m in re.finditer(body):
|
|
|
|
token = enum(m.lastindex)
|
|
|
|
data = m.group(m.lastindex)
|
|
|
|
# print(token, m.groups())
|
|
|
|
if token == enum.OPEN:
|
|
|
|
item = {'name': data, 'tags': {}}
|
|
|
|
item.update({k: [] for k in template})
|
|
|
|
context = None
|
|
|
|
elif token == enum.CLOSE:
|
|
|
|
if item:
|
|
|
|
yield item
|
|
|
|
item = context = None
|
|
|
|
elif token == enum.ATTR_DATA:
|
|
|
|
if not item:
|
|
|
|
continue
|
|
|
|
context = m.group(m.lastindex-1)
|
|
|
|
item[context].append(data)
|
|
|
|
elif token == enum.SKIP:
|
|
|
|
context = token
|
|
|
|
elif token == enum.COMMENT:
|
|
|
|
if item and data.startswith('tfdoc:'):
|
|
|
|
k, v = data.split(' ', 1)
|
|
|
|
item['tags'][k[6:]] = v
|
|
|
|
elif token == enum.TXT:
|
|
|
|
if context and context != enum.SKIP:
|
|
|
|
item[context].append(data)
|
|
|
|
|
|
|
|
|
2021-12-30 01:56:19 -08:00
|
|
|
def parse_files(basepath, exclude_files=None):
|
2021-12-20 23:51:51 -08:00
|
|
|
'Return a list of File named tuples in root module at basepath.'
|
2021-12-30 01:56:19 -08:00
|
|
|
exclude_files = exclude_files or []
|
2021-12-20 23:51:51 -08:00
|
|
|
for name in glob.glob(os.path.join(basepath, '*tf')):
|
2021-12-30 01:56:19 -08:00
|
|
|
shortname = os.path.basename(name)
|
|
|
|
if shortname in exclude_files:
|
|
|
|
continue
|
2021-12-20 23:51:51 -08:00
|
|
|
try:
|
|
|
|
with open(name) as file:
|
|
|
|
body = file.read()
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
raise SystemExit(f'Cannot read file {name}: {e}')
|
|
|
|
tags = _extract_tags(body)
|
|
|
|
description = tags.get(
|
|
|
|
'file:description', FILE_DESC_DEFAULTS.get(shortname))
|
|
|
|
modules = set(
|
|
|
|
os.path.basename(urllib.parse.urlparse(m).path)
|
|
|
|
for m in FILE_RE_MODULES.findall(body)
|
|
|
|
)
|
|
|
|
resources = set(FILE_RE_RESOURCES.findall(body))
|
|
|
|
yield File(shortname, description, modules, resources)
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
def parse_outputs(basepath):
|
|
|
|
'Return a list of Output named tuples for root module outputs.tf.'
|
|
|
|
try:
|
|
|
|
with open(os.path.join(basepath, 'outputs.tf')) as file:
|
|
|
|
body = file.read()
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
raise SystemExit(f'No outputs file in {basepath}.')
|
|
|
|
for item in _parse(body, enum=OUT_ENUM, re=OUT_RE, template=OUT_TEMPLATE):
|
|
|
|
yield Output(name=item['name'], description=''.join(item['description']),
|
|
|
|
sensitive=item['sensitive'] != [],
|
|
|
|
consumers=item['tags'].get('output:consumers', ''))
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
def parse_variables(basepath):
|
|
|
|
'Return a list of Output named tuples for root module variables.tf.'
|
|
|
|
try:
|
|
|
|
with open(os.path.join(basepath, 'variables.tf')) as file:
|
|
|
|
body = file.read()
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
raise SystemExit(f'No variables file in {basepath}.')
|
|
|
|
for item in _parse(body):
|
|
|
|
# print(item)
|
|
|
|
default = HEREDOC_RE.sub(r'\1', '\n'.join(item['default']))
|
|
|
|
required = not item['default']
|
|
|
|
vtype = '\n'.join(item['type'])
|
|
|
|
if not required and default != 'null' and vtype == 'string':
|
|
|
|
default = f'"{default}"'
|
|
|
|
yield Variable(name=item['name'], description=''.join(item['description']),
|
|
|
|
type=vtype, default=default,
|
|
|
|
required=required,
|
|
|
|
source=item['tags'].get('variable:source', ''))
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
# formatting functions
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
def _escape(s):
|
|
|
|
'Basic, minimal HTML escaping'
|
|
|
|
return ''.join(c if c in UNESCAPED else ('&#%s;' % ord(c)) for c in s)
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
|
2021-12-22 06:44:43 -08:00
|
|
|
def format_doc(outputs, variables, files, show_extra=False):
|
2021-12-20 23:51:51 -08:00
|
|
|
'Return formatted document.'
|
|
|
|
buffer = []
|
|
|
|
if files:
|
2021-12-30 01:56:19 -08:00
|
|
|
buffer += ['', '## Files', '']
|
|
|
|
buffer += list(format_files(files))
|
2021-12-20 23:51:51 -08:00
|
|
|
if variables:
|
2021-12-30 01:56:19 -08:00
|
|
|
buffer += ['', '## Variables', '']
|
|
|
|
buffer += list(format_variables(variables, show_extra))
|
2021-12-20 23:51:51 -08:00
|
|
|
if outputs:
|
2021-12-30 01:56:19 -08:00
|
|
|
buffer += ['', '## Outputs', '']
|
|
|
|
buffer += list(format_outputs(outputs, show_extra))
|
|
|
|
if buffer:
|
|
|
|
buffer.append('')
|
2021-12-20 23:51:51 -08:00
|
|
|
return '\n'.join(buffer)
|
2020-01-10 04:31:19 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
def format_files(items):
|
|
|
|
'Format files table.'
|
|
|
|
items.sort(key=lambda i: i.name)
|
|
|
|
yield '| name | description | modules | resources |'
|
|
|
|
yield '|---|---|---|---|'
|
|
|
|
for i in items:
|
|
|
|
modules = resources = ''
|
|
|
|
if i.modules:
|
|
|
|
modules = '<code>%s</code>' % '</code> · <code>'.join(
|
|
|
|
sorted(i.modules))
|
|
|
|
if i.resources:
|
|
|
|
resources = '<code>%s</code>' % '</code> · <code>'.join(
|
|
|
|
sorted(i.resources))
|
|
|
|
yield f'| [{i.name}](./{i.name}) | {i.description} | {modules} | {resources} |'
|
|
|
|
|
|
|
|
|
|
|
|
def format_outputs(items, show_extra=True):
|
|
|
|
'Format outputs table.'
|
|
|
|
if not items:
|
|
|
|
return
|
|
|
|
items.sort(key=lambda i: i.name)
|
|
|
|
yield '| name | description | sensitive |' + (
|
|
|
|
' consumers |' if show_extra else ''
|
|
|
|
)
|
|
|
|
yield '|---|---|:---:|' + (
|
|
|
|
'---|' if show_extra else ''
|
|
|
|
)
|
|
|
|
for i in items:
|
|
|
|
consumers = i.consumers or ''
|
|
|
|
if consumers:
|
|
|
|
consumers = '<code>%s</code>' % '</code> · <code>'.join(
|
|
|
|
consumers.split())
|
|
|
|
sensitive = '✓' if i.sensitive else ''
|
|
|
|
format = f'| {i.name} | {i.description or ""} | {sensitive} |'
|
|
|
|
format += f' {consumers} |' if show_extra else ''
|
|
|
|
yield format
|
|
|
|
|
|
|
|
|
|
|
|
def format_variables(items, show_extra=True):
|
|
|
|
'Format variables table.'
|
|
|
|
if not items:
|
|
|
|
return
|
|
|
|
items.sort(key=lambda i: i.name)
|
|
|
|
items.sort(key=lambda i: i.required, reverse=True)
|
|
|
|
yield '| name | description | type | required | default |' + (
|
|
|
|
' producer |' if show_extra else ''
|
|
|
|
)
|
|
|
|
yield '|---|---|:---:|:---:|:---:|' + (
|
|
|
|
':---:|' if show_extra else ''
|
|
|
|
)
|
|
|
|
for i in items:
|
|
|
|
vars = {
|
|
|
|
'default': f'<code>{_escape(i.default)}</code>' if i.default else '',
|
|
|
|
'required': '✓' if i.required else '',
|
|
|
|
'source': f'<code>{i.source}</code>' if i.source else '',
|
|
|
|
'type': f'<code>{_escape(i.type)}</code>'
|
|
|
|
}
|
|
|
|
for k in ('default', 'type'):
|
|
|
|
title = getattr(i, k)
|
|
|
|
if '\n' in title:
|
|
|
|
value = title.split('\n')
|
|
|
|
# remove indent
|
|
|
|
title = '\n'.join([value[0]] + [l[2:] for l in value[1:]])
|
|
|
|
if len(value[0]) >= 18 or len(value[-1]) >= 18:
|
|
|
|
value = '…'
|
|
|
|
else:
|
|
|
|
value = f'{value[0]}…{value[-1].strip()}'
|
|
|
|
vars[k] = f'<code title="{_escape(title)}">{_escape(value)}</code>'
|
|
|
|
format = (
|
|
|
|
f'| {i.name} | {i.description or ""} | {vars["type"]} '
|
|
|
|
f'| {vars["required"]} | {vars["default"]} |'
|
|
|
|
)
|
|
|
|
format += f' {vars["source"]} |' if show_extra else ''
|
|
|
|
yield format
|
2020-11-07 00:12:41 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
# replace functions
|
2020-11-07 00:12:41 -08:00
|
|
|
|
|
|
|
|
2021-12-20 23:51:51 -08:00
|
|
|
def get_doc(readme):
|
|
|
|
'Check if README file is marked, and return current doc.'
|
|
|
|
m = re.search('(?sm)%s\n(.*)\n%s' % (MARK_BEGIN, MARK_END), readme)
|
2020-11-07 00:12:41 -08:00
|
|
|
if not m:
|
|
|
|
return
|
2021-12-20 23:51:51 -08:00
|
|
|
return {'doc': m.group(1), 'start': m.start(), 'end': m.end()}
|
2020-11-07 00:12:41 -08:00
|
|
|
|
|
|
|
|
2021-12-30 01:56:19 -08:00
|
|
|
def create_doc(module_path, files=False, show_extra=False, exclude_files=None):
|
2020-01-10 04:31:19 -08:00
|
|
|
try:
|
2021-12-30 01:56:19 -08:00
|
|
|
mod_files = list(parse_files(module_path, exclude_files)) if files else []
|
2021-12-20 23:51:51 -08:00
|
|
|
mod_variables = list(parse_variables(module_path))
|
|
|
|
mod_outputs = list(parse_outputs(module_path))
|
2020-01-10 04:31:19 -08:00
|
|
|
except (IOError, OSError) as e:
|
|
|
|
raise SystemExit(e)
|
2021-12-20 23:51:51 -08:00
|
|
|
return format_doc(mod_outputs, mod_variables, mod_files, show_extra)
|
|
|
|
|
|
|
|
|
|
|
|
def replace_doc(module_path, doc):
|
|
|
|
'Replace document in module\'s README.md file.'
|
|
|
|
readme_path = os.path.join(module_path, 'README.md')
|
|
|
|
try:
|
|
|
|
readme = open(readme_path).read()
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
raise SystemExit(f'Error opening README {readme_path}: {e}')
|
|
|
|
result = get_doc(readme)
|
|
|
|
if not result:
|
|
|
|
raise SystemExit(f'Mark not found in README {readme_path}')
|
|
|
|
if doc == result['doc']:
|
|
|
|
return
|
|
|
|
try:
|
2021-12-21 02:51:15 -08:00
|
|
|
open(readme_path, 'w').write('\n'.join([
|
|
|
|
readme[:result['start']],
|
|
|
|
MARK_BEGIN,
|
|
|
|
doc,
|
|
|
|
MARK_END,
|
|
|
|
readme[result['end']:]
|
|
|
|
]))
|
2021-12-20 23:51:51 -08:00
|
|
|
except (IOError, OSError) as e:
|
|
|
|
raise SystemExit(f'Error replacing README {readme_path}: {e}')
|
|
|
|
|
|
|
|
|
|
|
|
@ click.command()
|
|
|
|
@ click.argument('module', type=click.Path(exists=True))
|
2021-12-30 01:56:19 -08:00
|
|
|
@click.option('--exclude-file', '-x', multiple=True)
|
2021-12-20 23:51:51 -08:00
|
|
|
@ click.option('--files/--no-files', default=False)
|
|
|
|
@ click.option('--replace/--no-replace', default=True)
|
2021-12-30 01:56:19 -08:00
|
|
|
@ click.option('--show-extra/--no-show-extra', default=False)
|
|
|
|
def main(module=None, annotate=False, exclude_file=None, files=False, replace=True,
|
2021-12-20 23:51:51 -08:00
|
|
|
show_extra=True):
|
|
|
|
'Program entry point.'
|
2021-12-30 01:56:19 -08:00
|
|
|
doc = create_doc(module, files, show_extra, exclude_file)
|
2020-01-10 04:31:19 -08:00
|
|
|
if replace:
|
|
|
|
replace_doc(module, doc)
|
|
|
|
else:
|
|
|
|
print(doc)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|