Skip to content

Commit c5f987a

Browse files
committed
WIP
1 parent 43f676d commit c5f987a

File tree

6 files changed

+506
-0
lines changed

6 files changed

+506
-0
lines changed

app/models/ecosystem/terraform.rb

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# frozen_string_literal: true
2+
3+
module Ecosystem
4+
class Terraform < Base
5+
6+
def registry_url(package, version = nil)
7+
base_url = "https://registry.terraform.io/modules"
8+
parts = package.name.split('/')
9+
if parts.length == 3
10+
namespace, name, provider = parts
11+
"#{base_url}/#{namespace}/#{name}/#{provider}" + (version ? "/#{version}" : "")
12+
else
13+
"#{base_url}/#{package.name}"
14+
end
15+
end
16+
17+
def download_url(package, version = nil)
18+
return nil unless version.present?
19+
20+
parts = package.name.split('/')
21+
return nil unless parts.length == 3
22+
23+
namespace, name, provider = parts
24+
"https://registry.terraform.io/v1/modules/#{namespace}/#{name}/#{provider}/#{version}/download"
25+
end
26+
27+
def documentation_url(package, _version = nil)
28+
registry_url(package)
29+
end
30+
31+
def install_command(package, version = nil)
32+
parts = package.name.split('/')
33+
return nil unless parts.length == 3
34+
35+
module_source = package.name
36+
module_source += "?version=#{version}" if version
37+
38+
"terraform init # with module \"example\" { source = \"#{module_source}\" }"
39+
end
40+
41+
def check_status_url(package)
42+
parts = package.name.split('/')
43+
return nil unless parts.length == 3
44+
45+
namespace, name, provider = parts
46+
"https://registry.terraform.io/v1/modules/#{namespace}/#{name}/#{provider}/versions"
47+
end
48+
49+
def check_status(package)
50+
url = check_status_url(package)
51+
return nil unless url
52+
53+
begin
54+
json = get_json(url)
55+
versions = json.dig('modules', 0, 'versions')
56+
return "removed" if !versions || versions.empty?
57+
nil
58+
rescue StandardError
59+
"removed"
60+
end
61+
end
62+
63+
def all_package_names
64+
# Use the v2 API that the Terraform Registry website uses
65+
all_names = []
66+
page = 1
67+
page_size = 100
68+
69+
loop do
70+
begin
71+
response = get_json("https://registry.terraform.io/v2/modules?page[size]=#{page_size}&page[number]=#{page}")
72+
modules = response.dig('data') || []
73+
74+
break if modules.empty?
75+
76+
modules.each do |mod|
77+
full_name = mod.dig('attributes', 'full-name')
78+
all_names << full_name if full_name
79+
end
80+
81+
# Check if there's a next page
82+
next_page = response.dig('links', 'next')
83+
break unless next_page
84+
85+
page += 1
86+
87+
# Safety break to prevent infinite loops (limit to first 50 pages)
88+
break if page > 50
89+
rescue
90+
break
91+
end
92+
end
93+
94+
all_names.uniq
95+
rescue
96+
[]
97+
end
98+
99+
def recently_updated_package_names
100+
# Get recently updated modules using v2 API sorted by recent activity
101+
begin
102+
response = get_json("https://registry.terraform.io/v2/modules?include=latest-version&page[size]=100&page[number]=1&sort=-updated")
103+
modules = response.dig('data') || []
104+
105+
modules.map do |mod|
106+
mod.dig('attributes', 'full-name')
107+
end.compact
108+
rescue
109+
[]
110+
end
111+
end
112+
113+
def fetch_package_metadata(name)
114+
parts = name.split('/')
115+
return nil unless parts.length == 3
116+
117+
namespace, module_name, provider = parts
118+
get_json("https://registry.terraform.io/v1/modules/#{namespace}/#{module_name}/#{provider}")
119+
rescue
120+
nil
121+
end
122+
123+
def map_package_metadata(package)
124+
return false unless package.present?
125+
126+
{
127+
name: package['id'],
128+
description: package['description'],
129+
homepage: repo_fallback(package['source'], nil),
130+
repository_url: repo_fallback(package['source'], nil),
131+
keywords_array: [],
132+
licenses: 'Unknown',
133+
namespace: package['namespace'],
134+
downloads: package['downloads'] || 0,
135+
downloads_period: 'total',
136+
versions: package['versions'] || [],
137+
metadata: {
138+
'provider' => package['provider'],
139+
'verified' => package['verified'] || false,
140+
'trusted' => package['trusted'] || false,
141+
'latest_version' => package['versions']&.first.is_a?(Hash) ? package['versions'].first['version'] : package['versions']&.first,
142+
'owner' => package['owner']
143+
}
144+
}
145+
end
146+
147+
def versions_metadata(pkg_metadata, existing_version_numbers = [])
148+
return [] unless pkg_metadata[:versions]
149+
150+
pkg_metadata[:versions]
151+
.select { |v| v['version'].present? }
152+
.reject { |v| existing_version_numbers.include?(v['version']) }
153+
.map do |version|
154+
{
155+
number: version['version'],
156+
published_at: version['published_at'],
157+
licenses: pkg_metadata[:licenses] || 'Unknown',
158+
metadata: {
159+
'submodules' => version['submodules'],
160+
'providers' => version['providers'],
161+
}
162+
}
163+
end
164+
end
165+
166+
def self.purl_type
167+
'terraform'
168+
end
169+
170+
def purl(package, version = nil)
171+
# Terraform modules have namespace/name/provider format
172+
parts = package.name.split('/')
173+
return nil unless parts.length == 3
174+
175+
namespace, name, provider = parts
176+
177+
PackageURL.new(
178+
type: 'terraform',
179+
namespace: "#{namespace}/#{provider}",
180+
name: name,
181+
version: version.try(:number)
182+
).to_s
183+
rescue
184+
nil
185+
end
186+
187+
private
188+
189+
def registry_url_from_parts(namespace, name, provider)
190+
"https://registry.terraform.io/modules/#{namespace}/#{name}/#{provider}"
191+
end
192+
end
193+
end

db/seeds.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
{name: 'github actions', url: 'https://github.com/marketplace/actions/', ecosystem: 'actions', github: 'actions', default: true},
3434
{name: 'pkg.adelielinux.org', url: "https://pkg.adelielinux.org/current", ecosystem: "adelie", github: "AdelieLinux", default: true, metadata: {repos: ['system', 'user']}},
3535
{name: 'bioconductor.org', url: 'https://bioconductor.org', ecosystem: 'bioconductor', github: 'Bioconductor', default: true},
36+
{name: 'registry.terraform.io', url: 'https://registry.terraform.io', ecosystem: 'terraform', github: 'hashicorp', default: true},
3637
]
3738

3839
default_registries.each do |data|
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"id": "terraform-aws-modules/vpc/aws",
3+
"owner": "terraform-aws-modules",
4+
"namespace": "terraform-aws-modules",
5+
"name": "vpc",
6+
"provider": "aws",
7+
"description": "Terraform module which creates VPC resources on AWS",
8+
"source": "https://github.com/terraform-aws-modules/terraform-aws-vpc",
9+
"published_at": "2023-06-02T14:30:00Z",
10+
"downloads": 1500000,
11+
"verified": true,
12+
"trusted": true,
13+
"versions": [
14+
{
15+
"version": "5.0.0",
16+
"published_at": "2023-06-02T14:30:00Z",
17+
"submodules": [
18+
{
19+
"name": "complete-sg",
20+
"path": "modules/complete-sg"
21+
}
22+
],
23+
"providers": [
24+
"aws"
25+
],
26+
"dependencies": []
27+
},
28+
{
29+
"version": "4.0.2",
30+
"published_at": "2023-05-01T10:00:00Z",
31+
"submodules": [],
32+
"providers": [
33+
"aws"
34+
],
35+
"dependencies": []
36+
}
37+
]
38+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"modules": [
3+
{
4+
"id": "terraform-aws-modules/vpc/aws",
5+
"owner": "terraform-aws-modules",
6+
"namespace": "terraform-aws-modules",
7+
"name": "vpc",
8+
"provider": "aws",
9+
"description": "Terraform module which creates VPC resources on AWS",
10+
"source": "https://github.com/terraform-aws-modules/terraform-aws-vpc",
11+
"published_at": "2023-06-02T14:30:00Z",
12+
"downloads": 1500000,
13+
"verified": true,
14+
"trusted": true,
15+
"versions": [
16+
{
17+
"version": "5.0.0",
18+
"published_at": "2023-06-02T14:30:00Z",
19+
"submodules": [
20+
{
21+
"name": "complete-sg",
22+
"path": "modules/complete-sg"
23+
}
24+
],
25+
"providers": [
26+
"aws"
27+
],
28+
"dependencies": []
29+
},
30+
{
31+
"version": "4.0.2",
32+
"published_at": "2023-05-01T10:00:00Z",
33+
"submodules": [],
34+
"providers": [
35+
"aws"
36+
],
37+
"dependencies": []
38+
}
39+
]
40+
}
41+
]
42+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"data": [
3+
{
4+
"id": "terraform-aws-modules/vpc/aws",
5+
"type": "modules",
6+
"attributes": {
7+
"full-name": "terraform-aws-modules/vpc/aws",
8+
"namespace": "terraform-aws-modules",
9+
"name": "vpc",
10+
"provider": "aws"
11+
}
12+
},
13+
{
14+
"id": "hashicorp/consul/aws",
15+
"type": "modules",
16+
"attributes": {
17+
"full-name": "hashicorp/consul/aws",
18+
"namespace": "hashicorp",
19+
"name": "consul",
20+
"provider": "aws"
21+
}
22+
}
23+
],
24+
"links": {
25+
"self": "https://registry.terraform.io/v2/modules?page[number]=1&page[size]=100",
26+
"next": "https://registry.terraform.io/v2/modules?page[number]=2&page[size]=100"
27+
}
28+
}

0 commit comments

Comments
 (0)