Skip to content

Commit d92058f

Browse files
Merge 0045982 into 6985372
2 parents 6985372 + 0045982 commit d92058f

File tree

8 files changed

+298
-158
lines changed

8 files changed

+298
-158
lines changed

.changeset/selfish-beds-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hashicorp/integrations-hcl': minor
3+
---
4+
5+
Support a 'strategy' field for multiple integrations-hcl consumption paths. Includes a strategy for nomad-pack.

packages/integrations-hcl/index.ts

Lines changed: 39 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,71 @@ import * as fs from 'fs'
22
import { glob } from 'glob'
33
import * as path from 'path'
44
import { z } from 'zod'
5-
import { IntegrationsAPI, VariableGroupConfig } from './lib/generated'
6-
import HCL from './lib/hcl'
7-
import {
8-
Component,
9-
Integration,
10-
Variable,
11-
VariableGroup,
12-
} from './schemas/integration'
13-
import MetadataHCLSchema from './schemas/metadata.hcl'
14-
import { getVariablesSchema } from './schemas/variables.hcl'
5+
import { IntegrationsAPI } from './lib/generated'
6+
import { Integration } from './schemas/integration'
7+
import { loadDefaultIntegrationDirectory } from './strategies/default/load_default_directory'
8+
import { loadNomadPackIntegrationDirectory } from './strategies/nomad-pack/load_pack_directory'
159

1610
const Config = z.object({
1711
identifier: z.string(),
1812
repo_path: z.string(),
1913
version: z.string(),
14+
strategy: z.enum(['default', 'nomad-pack']).default('default').optional(),
2015
})
2116
type Config = z.infer<typeof Config>
2217

2318
export default async function LoadFilesystemIntegration(
2419
config: Config
2520
): Promise<Integration> {
26-
// Create the API client
27-
const client = new IntegrationsAPI({
21+
// Create an API client instance
22+
const apiClient = new IntegrationsAPI({
2823
BASE: process.env.INPUT_INTEGRATIONS_API_BASE_URL,
2924
})
3025

31-
// Fetch the Integration from the API that we're looking to update
26+
// Parse & Validate the Integration Identifier
3227
const [productSlug, organizationSlug, integrationSlug] =
3328
config.identifier.split('/')
34-
35-
// Throw if the identifier is invalid
3629
if (!productSlug || !organizationSlug || !integrationSlug) {
30+
// Throw if the identifier is invalid
3731
throw new Error(
3832
`Invalid integration identifier: '${config.identifier}'.` +
3933
` The expected format is 'productSlug/organizationSlug/integrationSlug'`
4034
)
4135
}
4236

43-
const organization = await client.organizations.fetchOrganization(
37+
// Validate the Organization as specified in the identifier exists
38+
const organization = await apiClient.organizations.fetchOrganization(
4439
organizationSlug
4540
)
46-
4741
if (organization.meta.status_code != 200) {
4842
throw new Error(
4943
`Organization not found for integration identifier: '${config.identifier}'`
5044
)
5145
}
5246

53-
const integrationFetchResult = await client.integrations.fetchIntegration(
47+
// Fetch the Integration from the API. We need to ensure that it exists,
48+
// and therefore has already been registered.
49+
const integrationFetchResult = await apiClient.integrations.fetchIntegration(
5450
productSlug,
5551
organization.result.id,
5652
integrationSlug
5753
)
58-
5954
if (integrationFetchResult.meta.status_code !== 200) {
6055
throw new Error(
6156
`Integration not found for integration identifier: '${config.identifier}'`
6257
)
6358
}
64-
6559
const apiIntegration = integrationFetchResult.result
6660

67-
// Parse out & validate the metadata.hcl file
68-
const repoRootDirectory = path.join(
61+
// Determine the location of the Integration & validate that it has a metadata.hcl file
62+
const integrationDirectory = path.join(
6963
config.repo_path,
7064
apiIntegration.subdirectory || ''
7165
)
72-
const metadataFilePath = path.join(repoRootDirectory, 'metadata.hcl')
66+
const metadataFilePath = path.join(integrationDirectory, 'metadata.hcl')
7367

74-
// Throw if the metadata.hcl file doesn't exist
68+
// Throw if the metadata.hcl file doesn't exist. We don't validate it at
69+
// this point beyond checking that it exists.
7570
if (!fs.existsSync(metadataFilePath)) {
7671
const matches = await glob('**/metadata.hcl', { cwd: config.repo_path })
7772
// If no metadata.hcl files were found, throw a helpful error
@@ -100,152 +95,39 @@ export default async function LoadFilesystemIntegration(
10095
}
10196
}
10297

103-
// @todo(kevinwang):
104-
// Maybe lift file content reading into HCL class and throw a more helpful error message
105-
const fileContent = fs.readFileSync(metadataFilePath, 'utf8')
106-
const hclConfig = new HCL(fileContent, MetadataHCLSchema)
107-
// throw a verbose error message with the filepath and contents
108-
if (!hclConfig.result.data) {
109-
throw new Error(
110-
hclConfig.result.error.message +
111-
'\n' +
112-
'File: ' +
113-
metadataFilePath +
114-
'\n' +
115-
fileContent
116-
)
117-
}
118-
119-
const hclIntegration = hclConfig.result.data.integration[0]
120-
121-
// Read the README
122-
let readmeContent: string | null = null
123-
if (hclIntegration.docs[0].process_docs) {
124-
const readmeFile = path.join(
125-
repoRootDirectory,
126-
hclIntegration.docs[0].readme_location
127-
)
128-
129-
// Throw if the README file doesn't exist
130-
if (!fs.existsSync(readmeFile)) {
131-
throw new Error(
132-
`The README file, ${readmeFile}, was derived from config values and integration data, but it does not exist.` +
133-
` ` +
134-
`Please double check the "readme_location" value in ${metadataFilePath}, and try again.`
135-
)
136-
}
137-
readmeContent = fs.readFileSync(readmeFile, 'utf8')
138-
}
139-
140-
// Load the Products VariableGroupConfigs so we can load any component variables
98+
// Load the Integration's product VariableGroupConfigs. This is information
99+
// that we need to parse out the Integration from the Filesystem.
141100
const variableGroupConfigs =
142-
await client.variableGroupConfigs.fetchVariableGroupConfigs(
101+
await apiClient.variableGroupConfigs.fetchVariableGroupConfigs(
143102
apiIntegration.product.slug,
144103
'100'
145104
)
146-
147105
if (variableGroupConfigs.meta.status_code !== 200) {
148106
throw new Error(
149107
`Failed to load 'variable_group' configs for product: '${apiIntegration.product.slug}'`
150108
)
151109
}
152110

153-
// Calculate each Component object
154-
const allComponents: Array<Component> = []
155-
for (let i = 0; i < hclIntegration.component.length; i++) {
156-
allComponents.push(
157-
await loadComponent(
158-
repoRootDirectory,
159-
hclIntegration.component[i].type,
160-
hclIntegration.component[i].name,
161-
hclIntegration.component[i].slug,
162-
variableGroupConfigs.result
111+
// Depending on the Strategy that is specified, we read the filesystem and coerce the
112+
// configuration to a standardized Integrations object.
113+
switch (config.strategy) {
114+
case 'nomad-pack': {
115+
return loadNomadPackIntegrationDirectory(
116+
integrationDirectory,
117+
apiIntegration.id,
118+
apiIntegration.product.slug,
119+
config.version
163120
)
164-
)
165-
}
166-
167-
// Return Integration with all defaults set
168-
return {
169-
id: apiIntegration.id,
170-
product: apiIntegration.product.slug,
171-
identifier: hclIntegration.identifier,
172-
name: hclIntegration.name,
173-
description: hclIntegration.description,
174-
current_release: {
175-
version: config.version,
176-
readme: readmeContent,
177-
components: allComponents,
178-
},
179-
flags: hclIntegration.flags,
180-
docs: hclIntegration.docs[0],
181-
hide_versions: hclIntegration.hide_versions,
182-
license: hclIntegration.license[0],
183-
integration_type: hclIntegration.integration_type,
184-
}
185-
}
186-
187-
async function loadComponent(
188-
repoRootDirectory: string,
189-
componentType: string,
190-
componentName: string,
191-
componentSlug: string,
192-
variableGroupConfigs: Array<VariableGroupConfig>
193-
): Promise<Component> {
194-
// Calculate the location of the folder where the README / variables, etc reside
195-
const componentFolder = `${repoRootDirectory}/components/${componentType}/${componentSlug}`
196-
197-
// Load the README if it exists
198-
const componentReadmeFile = `${componentFolder}/README.md`
199-
let readmeContent: string | null = null
200-
try {
201-
readmeContent = fs.readFileSync(componentReadmeFile, 'utf8')
202-
} catch (err) {
203-
// No issue, there's just no README, which is OK!
204-
}
205-
206-
// Go through each VariableGroupConfig to try see if we need to load them
207-
const variableGroups: Array<VariableGroup> = []
121+
}
208122

209-
for (let i = 0; i < variableGroupConfigs.length; i++) {
210-
const variableGroupConfig = variableGroupConfigs[i]
211-
const variableGroupFile = `${componentFolder}/${variableGroupConfig.filename}`
212-
if (fs.existsSync(variableGroupFile)) {
213-
// Load & Validate the Variable Files (parameters.hcl, outputs.hcl, etc.)
214-
const fileContent = fs.readFileSync(variableGroupFile, 'utf8')
215-
const hclConfig = new HCL(
216-
fileContent,
217-
getVariablesSchema(variableGroupConfig.stanza)
123+
default: {
124+
return loadDefaultIntegrationDirectory(
125+
integrationDirectory,
126+
apiIntegration.id,
127+
apiIntegration.product.slug,
128+
config.version,
129+
variableGroupConfigs.result
218130
)
219-
if (!hclConfig.result.data) {
220-
throw new Error(hclConfig.result.error.message)
221-
}
222-
223-
// Map the HCL File variable configuration to the Variable defaults
224-
const variables: Array<Variable> = hclConfig.result.data[
225-
variableGroupConfig.stanza
226-
].map((v) => {
227-
return {
228-
key: v.key,
229-
description: v.description ? v.description : null,
230-
type: v.type ? v.type : null,
231-
required: typeof v.required != 'undefined' ? v.required : null,
232-
default_value: v.default_value ? v.default_value : null,
233-
}
234-
})
235-
variableGroups.push({
236-
variable_group_config_id: variableGroupConfig.id,
237-
variables,
238-
})
239-
} else {
240-
console.warn(`Variable Group File '${variableGroupFile}' not found.`)
241131
}
242132
}
243-
244-
return {
245-
type: componentType,
246-
name: componentName,
247-
slug: componentSlug,
248-
readme: readmeContent,
249-
variable_groups: variableGroups,
250-
}
251133
}

0 commit comments

Comments
 (0)