Skip to content

Commit dc114e7

Browse files
committed
feat: enhance resource handling in generator config for Terraform provider
1 parent ab9d6cc commit dc114e7

2 files changed

Lines changed: 93 additions & 21 deletions

File tree

Scripts/TerraformProvider/GenerateProvider.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ async function main() {
2626
// --output <output/for/provider_code_spec.json> \
2727
// <path/to/openapi_spec.json>
2828

29+
// Get the Go path and construct the full path to the tfplugingen-openapi binary
30+
const goPath = execSync('go env GOPATH', { encoding: 'utf8' }).trim();
31+
const tfplugigenBinaryPath = path.join(goPath, 'bin', 'tfplugingen-openapi');
32+
2933
const generatorConfigPath = path.resolve(__dirname, '../../Terraform/terraform-provider-generator-config.yaml');
3034
const outputPath = path.resolve(__dirname, '../../Terraform/terraform-provider-code');
3135
const openApiSpecPathForCodegen = path.resolve(__dirname, '../../Terraform/openapi.json');
32-
const command = `tfplugingen-openapi generate --config ${generatorConfigPath} --output ${outputPath} ${openApiSpecPathForCodegen}`;
36+
const command = `"${tfplugigenBinaryPath}" generate --config "${generatorConfigPath}" --output "${outputPath}" "${openApiSpecPathForCodegen}"`;
3337

3438
try {
3539
execSync(command, { stdio: 'inherit' });

Scripts/TerraformProvider/GeneratorConfig.ts

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,39 +41,83 @@ export default class GeneratorConfig {
4141

4242
const operationId = op.operationId.toLowerCase();
4343
const isReadOperation = operationId.startsWith('get') || operationId.startsWith('list') || operationId.startsWith('count') || operationId.includes('read') || operationId.includes('fetch');
44-
const isWriteOperation = operationId.startsWith('create') || operationId.startsWith('add') || operationId.startsWith('update') || operationId.startsWith('put') || operationId.includes('save');
44+
const isCreateOperation = operationId.startsWith('create') || operationId.startsWith('add') || (method.toLowerCase() === 'post');
45+
const isUpdateOperation = operationId.startsWith('update') || operationId.startsWith('put') || (method.toLowerCase() === 'put');
4546
const isDeleteOperation = operationId.startsWith('delete') || operationId.includes('remove');
4647

4748
if (isReadOperation) {
4849
// Generate data source for read operations
49-
const dsName = operationId.replace(/^(get|list|count|read|fetch)/i, '').toLowerCase() || pathKey.replace(/[\/{\}]/g, '').replace(/\//g, '_');
50-
const cleanDsName = dsName.replace(/^_+|_+$/g, '');
51-
if (cleanDsName) {
52-
if (!config.data_sources[cleanDsName]) config.data_sources[cleanDsName] = {};
53-
config.data_sources[cleanDsName]['read'] = { path: pathKey, method: method.toUpperCase() };
50+
const dsName = this.extractResourceNameFromPath(pathKey).toLowerCase();
51+
if (dsName) {
52+
if (!config.data_sources[dsName]) config.data_sources[dsName] = {};
53+
config.data_sources[dsName]['read'] = { path: pathKey, method: method.toUpperCase() };
5454
}
55-
} else if (isWriteOperation || (!isReadOperation && !isDeleteOperation)) {
56-
// Generate resource for write operations (create, update) or other operations
57-
const resourceName = operationId.replace(/^(create|put|add|update)/i, '').toLowerCase() || pathKey.replace(/[\/{\}]/g, '').replace(/\//g, '_');
58-
const cleanResourceName = resourceName.replace(/^_+|_+$/g, '');
59-
if (cleanResourceName) {
60-
if (!config.resources[cleanResourceName]) config.resources[cleanResourceName] = {};
61-
const operationType = isWriteOperation && operationId.includes('update') ? 'put' : method.toLowerCase();
62-
config.resources[cleanResourceName][operationType] = { path: pathKey, method: method.toUpperCase() };
55+
56+
// Also add as resource read operation
57+
const resourceName = this.extractResourceNameFromPath(pathKey).toLowerCase();
58+
if (resourceName) {
59+
if (!config.resources[resourceName]) config.resources[resourceName] = {};
60+
config.resources[resourceName]['read'] = { path: pathKey, method: method.toUpperCase() };
61+
}
62+
} else if (isCreateOperation) {
63+
// Generate resource for create operations
64+
const resourceName = this.extractResourceNameFromPath(pathKey).toLowerCase();
65+
if (resourceName) {
66+
if (!config.resources[resourceName]) config.resources[resourceName] = {};
67+
config.resources[resourceName]['create'] = { path: pathKey, method: method.toUpperCase() };
68+
}
69+
} else if (isUpdateOperation) {
70+
// Generate resource for update operations
71+
const resourceName = this.extractResourceNameFromPath(pathKey).toLowerCase();
72+
if (resourceName) {
73+
if (!config.resources[resourceName]) config.resources[resourceName] = {};
74+
config.resources[resourceName]['update'] = { path: pathKey, method: method.toUpperCase() };
6375
}
6476
} else if (isDeleteOperation) {
65-
// Handle delete operations - try to attach to existing resources or create new ones
66-
const resourceName = operationId.replace(/^(delete|remove)/i, '').toLowerCase() || pathKey.replace(/[\/{\}]/g, '').replace(/\//g, '_');
67-
const cleanResourceName = resourceName.replace(/^_+|_+$/g, '');
68-
if (cleanResourceName) {
69-
if (!config.resources[cleanResourceName]) config.resources[cleanResourceName] = {};
70-
config.resources[cleanResourceName]['delete'] = { path: pathKey, method: method.toUpperCase() };
77+
// Handle delete operations
78+
const resourceName = this.extractResourceNameFromPath(pathKey).toLowerCase();
79+
if (resourceName) {
80+
if (!config.resources[resourceName]) config.resources[resourceName] = {};
81+
config.resources[resourceName]['delete'] = { path: pathKey, method: method.toUpperCase() };
7182
}
7283
}
7384
}
7485
}
7586
}
7687

88+
// Ensure every resource has both 'create' and 'read' operations
89+
// Remove resources that don't have the required operations
90+
const resourcesToRemove: string[] = [];
91+
92+
for (const [resourceName, resourceConfig] of Object.entries(config.resources)) {
93+
const resource = resourceConfig as any;
94+
95+
// If resource doesn't have 'create', try to use 'post' operation
96+
if (!resource.create && resource.post) {
97+
resource.create = resource.post;
98+
delete resource.post;
99+
}
100+
101+
// If resource doesn't have 'read', try to find it in data sources
102+
if (!resource.read) {
103+
const matchingDataSource = config.data_sources[resourceName];
104+
if (matchingDataSource && matchingDataSource.read) {
105+
resource.read = matchingDataSource.read;
106+
}
107+
}
108+
109+
// If resource still doesn't have both 'create' and 'read', remove it
110+
if (!resource.create || !resource.read) {
111+
console.log(`Removing resource '${resourceName}' - missing required operations (create: ${!!resource.create}, read: ${!!resource.read})`);
112+
resourcesToRemove.push(resourceName);
113+
}
114+
}
115+
116+
// Remove invalid resources
117+
for (const resourceName of resourcesToRemove) {
118+
delete config.resources[resourceName];
119+
}
120+
77121
// Remove empty objects
78122
if (Object.keys(config.resources).length === 0) delete config.resources;
79123
if (Object.keys(config.data_sources).length === 0) delete config.data_sources;
@@ -90,6 +134,30 @@ export default class GeneratorConfig {
90134
const outputFile = path.join(data.outputPath, data.outputFileName);
91135
fs.writeFileSync(outputFile, yamlStr, 'utf-8');
92136
}
137+
138+
/**
139+
* Extract resource name from API path.
140+
* Converts paths like "/alert-custom-field" to "alertcustomfield"
141+
* and "/alert-custom-field/{id}" to "alertcustomfield"
142+
*/
143+
private static extractResourceNameFromPath(path: string): string {
144+
// Remove leading slash and anything after the first parameter
145+
const pathParts = path.replace(/^\//, '').split('/');
146+
let resourcePath = pathParts[0] || '';
147+
148+
// Handle paths that end with specific patterns like /count, /get-list, etc.
149+
if (resourcePath.includes('-count') || resourcePath.includes('-get-list')) {
150+
resourcePath = resourcePath.replace(/-count$|-get-list$/, '');
151+
}
152+
153+
// Convert kebab-case to snake_case and remove special characters
154+
const resourceName = resourcePath
155+
.replace(/-/g, '') // Remove hyphens
156+
.replace(/[^a-zA-Z0-9]/g, '') // Remove any other special characters
157+
.toLowerCase();
158+
159+
return resourceName;
160+
}
93161
}
94162

95163

0 commit comments

Comments
 (0)