Skip to content

Commit ac8c048

Browse files
committed
use pagination in use-latest-module-versions policy
1 parent 89d932b commit ac8c048

File tree

3 files changed

+76
-16
lines changed

3 files changed

+76
-16
lines changed

governance/third-generation/cloud-agnostic/http-examples/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ This directory contains examples of using the [HTTP import](https://docs.hashico
33

44
Be sure to use Sentinel 0.15.2 or higher with these policies.
55

6-
These policies are essentially the same as the second-generation versions in this [directory](../../../second-generation/cloud-agnostic/http-examples), but the [use-latest-module-versions.sentinel](./use-latest-module-versions.sentinel) in this directory uses the [tfconfig/v2](https://www.terraform.io/docs/cloud/sentinel/import/tfconfig-v2.html) import instead of the older [tfconfig](https://www.terraform.io/docs/cloud/sentinel/import/tfconfig.html) import. It uses that import indirectly by calling some functions from the [tfconfig-functions](../../common-functions/tfconfig-functions) Sentinel module.
7-
86
## Policies
97
There are currently three example policies in this directory:
108
* [check-external-http-api.sentinel](./check-external-http-api.sentinel)
@@ -20,12 +18,17 @@ sentinel test -run=check -verbose
2018

2119
The second policy uses the HTTP import to call the Terraform Registry [List Modules API](https://www.terraform.io/docs/registry/api.html#list-modules) against a Terraform Cloud or Terraform Enterprise server in order to determine the most recent version of each module in the [Private Module Registry](https://www.terraform.io/docs/cloud/registry/index.html) (PMR) of an organization on that server or in the [public Terraform registry](https://registry.terraform.io). It then checks that the version constraints used in module calls allow the most recent version. This policy also uses parameters as described below.
2220

21+
Since 9/6/2021, the second policy can retrieve versions of all private modules in a PMR since it uses pagination to keep calling the List Modules API. However, the policy limits the number of modules retrieved from the public registry to 100.
22+
23+
This policy currently uses the `/api/registry/v1/modules` endpoint for private registries rather than the newer `/organizations/:organization_name/registry-modules` endpoint that can get both private and publicly curated modules. Note that publically curated modules are not available in TFE. The `/organizations/:organization_name/registry-modules` API endpoint is available in TFE since version v202106-1. We expect to create a version of this policy that will use the new API endpoint but we will keep this policy so that customers on older versions of TFE can still use it.
24+
2325
The third policy uses the HTTP import to call a [NASA API](https://api.nasa.gov/) that retrieves a list of Near Earth Objects and warns if any of them are too close for comfort. This is based on an example from this HashiCorp [blog](https://www.hashicorp.com/blog/announcing-business-aware-policies-for-terraform-cloud-and-enterprise/) that announced the HTTP import and "Business-aware Policies". This policy also uses parameters as described below.
2426

2527
## Use of Parameters in use-latest-module-versions.sentinel
26-
The [use-latest-module-versions.sentinel](./use-latest-module-versions.sentinel) policy uses four parameters:
28+
The [use-latest-module-versions.sentinel](./use-latest-module-versions.sentinel) policy uses five parameters:
2729
* `public_registry` indicates whether the public Terraform registry is being used. This is `false` by default, but could be set to `true`.
2830
* `address` gives the address of the Terraform Cloud or Terraform Enterprise server. It defaults to `app.terraform.io` which is the address of the multi-tenant Terraform Cloud server that HashiCorp runs. You must specify a value for this if using a Terraform Enterprise server.
31+
* `limit` gives the maximum number of modules to retrieve in a single call to the List Modules API endpoint. It defaults to `100` which is the maximum value that can be set.
2932
* `organization` gives the name of an [organization](https://www.terraform.io/docs/cloud/users-teams-organizations/organizations.html) on the Terraform Cloud or Terraform Enterprise server specified by `address`. You must always specify a valid organization.
3033
* `token` gives a valid Terraform Cloud API token which can be a user, team, or organization token. See the [API tokens](https://www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html) document for more information.
3134

governance/third-generation/cloud-agnostic/http-examples/use-latest-module-versions.hcl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ param "address" {
66
value = "registry.terraform.io"
77
}
88

9+
param "limit" {
10+
value = 100
11+
}
12+
913
param "organization" {
1014
value = "Azure"
1115
}

governance/third-generation/cloud-agnostic/http-examples/use-latest-module-versions.sentinel

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@
1111

1212
# Additionally, this policy uses Sentinel parameters
1313

14+
# The policy supports pagination to get unlimited number of modules from private
15+
# registries. Since the public registry has a very large number of modules,
16+
# the policy does not use pagination for it and limits the total number of
17+
# modules to 100.
18+
19+
# This policy currently uses the /api/registry/v1/modules endpoint for private
20+
# registries rather than the newer /organizations/:organization_name/registry-modules
21+
# endpoint that can get both private and publicly curated modules. Note that
22+
# publically curated modules are not available in TFE. The
23+
# /organizations/:organization_name/registry-modules endpoint is available
24+
# in TFE since version v202106-1. We expect to create a version of this policy
25+
# that will use the new API endpoint but we will keep this policy so that
26+
# customers on older versions of TFE can still use it.
27+
1428
##### Imports #####
1529

1630
# Import common-functions/tfconfig-functions/tfconfig-functions.sentinel
@@ -28,6 +42,9 @@ import "version"
2842
param public_registry default false
2943
# The address of the Terraform Cloud or Terraform Enterprise server
3044
param address default "app.terraform.io"
45+
# The limit to use when retrieving modules from a registry
46+
# This cannot be greater than 100
47+
param limit default 100
3148
# The name of the Terraform Cloud or Terraform Enterprise organization
3249
param organization
3350
# A valid Terraform Cloud or Terraform Enterprise API token
@@ -36,21 +53,34 @@ param token
3653
##### Functions #####
3754

3855
# Retrieve modules from a module registry and give their paths and latest versions
39-
retrieve_latest_module_versions = func(public_registry, address, organization, token) {
56+
retrieve_latest_module_versions = func() {
57+
58+
modules = {}
4059

4160
# Call the TFC Modules API and extract the response
61+
# Treat public registry and private registry differently
4262
if public_registry {
43-
# We are limiting the request to 20 verified modules for the
44-
# namespace in the public registry matching the organzation parameter
45-
req = http.request("https://" + address + "/v1/modules/" +
46-
organization + "?limit=20&verified=true")
63+
modules = get_public_registry_modules()
4764
} else {
48-
req = http.request("https://" + address + "/api/registry/v1/modules/" +
49-
organization)
50-
req = req.with_header("Authorization", "Bearer " + token)
65+
# private registry
66+
# Pass the empty modules map and offset=0
67+
modules = get_private_registry_modules(modules, 0)
5168
}
69+
70+
# modules is indexed by <name>/<provider> and contains most recent version
71+
return modules
72+
}
73+
74+
# Get up to `limit` verified modules from the public registry for a namespace
75+
# But pass the organization parameter
76+
get_public_registry_modules = func() {
77+
req = http.request("https://" + address + "/v1/modules/" +
78+
namespace + "?limit=" + limit + "&verified=true")
79+
80+
# Call TFE API to get modules and unmarshal results
5281
res = json.unmarshal(http.get(req).body)
5382

83+
# Initialize modules map
5484
modules = {}
5585

5686
# Iterate over the modules and extract names, providers, and latest versions
@@ -59,18 +89,41 @@ retrieve_latest_module_versions = func(public_registry, address, organization, t
5989
modules[index] = m.version
6090
}
6191

62-
# modules is indexed by <name>/<provider> and contains most recent version
92+
return modules
93+
}
94+
95+
# Get all modules from the private registry by calling itself recursively
96+
# Start by calling with modules = {}, and offset = 0
97+
get_private_registry_modules = func(modules, offset) {
98+
req = http.request("https://" + address + "/api/registry/v1/modules/" +
99+
organization + "?limit=" + string(limit) +
100+
"&offset=" + string(offset))
101+
req = req.with_header("Authorization", "Bearer " + token)
102+
103+
# Call TFE API to get modules and unmarshal results
104+
res = json.unmarshal(http.get(req).body)
105+
106+
# Iterate over the modules and extract names, providers, and latest versions
107+
for res.modules as m {
108+
index = m.namespace + "/" + m.name + "/" + m.provider
109+
modules[index] = m.version
110+
}
111+
112+
# Make recursive function call if there are more modules
113+
if res.meta.next_offset else 0 > 0 {
114+
return get_private_registry_modules(modules, res.meta.next_offset)
115+
}
116+
63117
return modules
64118
}
65119

66120
# Validate sources of modules in the registry
67-
validate_modules = func(public_registry, address, organization, token) {
121+
validate_modules = func() {
68122

69123
validated = true
70124

71125
# Get latest module versions from registry
72-
discovered_modules = retrieve_latest_module_versions(public_registry, address,
73-
organization, token)
126+
discovered_modules = retrieve_latest_module_versions()
74127

75128
# Get all module addresses
76129
allModuleAddresses = config.find_descendant_modules("")
@@ -128,7 +181,7 @@ validate_modules = func(public_registry, address, organization, token) {
128181
##### Rules #####
129182

130183
# Call the validation function
131-
modules_validated = validate_modules(public_registry, address, organization, token)
184+
modules_validated = validate_modules()
132185

133186
# Main rule
134187
main = rule {

0 commit comments

Comments
 (0)