mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
import { TerraformProviderConfig, OpenAPISpec } from "./Types";
|
|
import { FileGenerator } from "./FileGenerator";
|
|
import { StringUtils } from "./StringUtils";
|
|
import { GoCodeGenerator } from "./GoCodeGenerator";
|
|
|
|
export class ProviderGenerator {
|
|
private config: TerraformProviderConfig;
|
|
private spec: OpenAPISpec;
|
|
private fileGenerator: FileGenerator;
|
|
|
|
public constructor(config: TerraformProviderConfig, spec: OpenAPISpec) {
|
|
this.config = config;
|
|
this.spec = spec;
|
|
this.fileGenerator = new FileGenerator(config.outputDir);
|
|
}
|
|
|
|
public async generateProvider(): Promise<void> {
|
|
await this.generateProviderGo();
|
|
await this.generateClientGo();
|
|
await this.generateConfigGo();
|
|
}
|
|
|
|
private async generateProviderGo(): Promise<void> {
|
|
const providerGoContent: string = `package provider
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
|
"github.com/hashicorp/terraform-plugin-framework/provider"
|
|
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
)
|
|
|
|
// Ensure provider defined types fully satisfy framework interfaces.
|
|
var _ provider.Provider = &${StringUtils.toPascalCase(this.config.providerName)}Provider{}
|
|
|
|
// ${StringUtils.toPascalCase(this.config.providerName)}Provider defines the provider implementation.
|
|
type ${StringUtils.toPascalCase(this.config.providerName)}Provider struct {
|
|
// version is set to the provider version on release, "dev" when the
|
|
// provider is built and ran locally, and "test" when running acceptance
|
|
// testing.
|
|
version string
|
|
}
|
|
|
|
// ${StringUtils.toPascalCase(this.config.providerName)}ProviderModel describes the provider data model.
|
|
type ${StringUtils.toPascalCase(this.config.providerName)}ProviderModel struct {
|
|
OneuptimeUrl types.String \`tfsdk:"oneuptime_url"\`
|
|
ApiKey types.String \`tfsdk:"api_key"\`
|
|
}
|
|
|
|
func (p *${StringUtils.toPascalCase(this.config.providerName)}Provider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
|
|
resp.TypeName = "${this.config.providerName}"
|
|
resp.Version = p.version
|
|
}
|
|
|
|
func (p *${StringUtils.toPascalCase(this.config.providerName)}Provider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
MarkdownDescription: "${GoCodeGenerator.escapeString(this.spec.info.description || `Terraform provider for ${this.config.providerName}`)}",
|
|
|
|
Attributes: map[string]schema.Attribute{
|
|
"oneuptime_url": schema.StringAttribute{
|
|
MarkdownDescription: "The ${this.config.providerName} URL (without /api path). Defaults to 'oneuptime.com' if not specified. The provider automatically appends '/api' to the URL.",
|
|
Optional: true,
|
|
},
|
|
"api_key": schema.StringAttribute{
|
|
MarkdownDescription: "API key for authentication",
|
|
Required: true,
|
|
Sensitive: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p *${StringUtils.toPascalCase(this.config.providerName)}Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
|
|
var data ${StringUtils.toPascalCase(this.config.providerName)}ProviderModel
|
|
|
|
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Configuration values are now available.
|
|
var oneuptimeUrl string
|
|
var apiKey string
|
|
|
|
if data.OneuptimeUrl.IsUnknown() {
|
|
// Cannot connect to client with an unknown value
|
|
resp.Diagnostics.AddWarning(
|
|
"Unable to create client",
|
|
"Cannot use unknown value as oneuptime_url",
|
|
)
|
|
return
|
|
}
|
|
|
|
if data.OneuptimeUrl.IsNull() {
|
|
oneuptimeUrl = os.Getenv("${StringUtils.toConstantCase(this.config.providerName)}_URL")
|
|
if oneuptimeUrl == "" {
|
|
oneuptimeUrl = "oneuptime.com"
|
|
}
|
|
} else {
|
|
oneuptimeUrl = data.OneuptimeUrl.ValueString()
|
|
}
|
|
|
|
if data.ApiKey.IsUnknown() {
|
|
// Cannot connect to client with an unknown value
|
|
resp.Diagnostics.AddWarning(
|
|
"Unable to create client",
|
|
"Cannot use unknown value as api_key",
|
|
)
|
|
return
|
|
}
|
|
|
|
if data.ApiKey.IsNull() {
|
|
apiKey = os.Getenv("${StringUtils.toConstantCase(this.config.providerName)}_API_KEY")
|
|
if apiKey == "" {
|
|
resp.Diagnostics.AddError(
|
|
"Missing API Key",
|
|
"API key is required for authentication. "+
|
|
"Please provide it via the api_key attribute or the ${StringUtils.toConstantCase(this.config.providerName)}_API_KEY environment variable.",
|
|
)
|
|
return
|
|
}
|
|
} else {
|
|
apiKey = data.ApiKey.ValueString()
|
|
}
|
|
|
|
client, err := NewClient(oneuptimeUrl, apiKey)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Unable to Create ${StringUtils.toPascalCase(this.config.providerName)} API Client",
|
|
"An unexpected error occurred when creating the ${StringUtils.toPascalCase(this.config.providerName)} API client. "+
|
|
"If the error is not clear, please contact the provider developers.\\n\\n"+
|
|
"${StringUtils.toPascalCase(this.config.providerName)} Client Error: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
resp.DataSourceData = client
|
|
resp.ResourceData = client
|
|
|
|
tflog.Info(ctx, "Configured ${StringUtils.toPascalCase(this.config.providerName)} client", map[string]any{"success": true})
|
|
}
|
|
|
|
func (p *${StringUtils.toPascalCase(this.config.providerName)}Provider) Resources(ctx context.Context) []func() resource.Resource {
|
|
return GetResources()
|
|
}
|
|
|
|
func (p *${StringUtils.toPascalCase(this.config.providerName)}Provider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
|
return GetDataSources()
|
|
}
|
|
|
|
func New(version string) func() provider.Provider {
|
|
return func() provider.Provider {
|
|
return &${StringUtils.toPascalCase(this.config.providerName)}Provider{
|
|
version: version,
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
await this.fileGenerator.writeFileInDir(
|
|
"internal/provider",
|
|
"provider.go",
|
|
providerGoContent,
|
|
);
|
|
}
|
|
|
|
private async generateClientGo(): Promise<void> {
|
|
const clientGoContent: string = `package provider
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Client represents the API client for ${this.config.providerName}
|
|
type Client struct {
|
|
BaseURL string
|
|
HTTPClient *http.Client
|
|
ApiKey string
|
|
}
|
|
|
|
// NewClient creates a new API client
|
|
func NewClient(oneuptimeUrl, apiKey string) (*Client, error) {
|
|
// Ensure the oneuptimeUrl has the correct scheme
|
|
if !strings.HasPrefix(oneuptimeUrl, "http://") && !strings.HasPrefix(oneuptimeUrl, "https://") {
|
|
oneuptimeUrl = "https://" + oneuptimeUrl
|
|
}
|
|
|
|
// Append /api to the oneuptimeUrl
|
|
if !strings.HasSuffix(oneuptimeUrl, "/api") {
|
|
oneuptimeUrl = strings.TrimSuffix(oneuptimeUrl, "/") + "/api"
|
|
}
|
|
|
|
// Parse and validate the URL
|
|
parsedURL, err := url.Parse(oneuptimeUrl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid oneuptime_url: %w", err)
|
|
}
|
|
|
|
client := &Client{
|
|
BaseURL: parsedURL.String(),
|
|
HTTPClient: &http.Client{
|
|
Timeout: time.Second * 30,
|
|
},
|
|
ApiKey: apiKey,
|
|
}
|
|
|
|
return client, nil
|
|
}
|
|
|
|
// DoRequest performs an HTTP request
|
|
func (c *Client) DoRequest(method, path string, body interface{}) (*http.Response, error) {
|
|
// Construct the full URL
|
|
fullURL := c.BaseURL + path
|
|
|
|
var bodyReader io.Reader
|
|
if body != nil {
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
|
}
|
|
bodyReader = bytes.NewBuffer(jsonBody)
|
|
}
|
|
|
|
req, err := http.NewRequest(method, fullURL, bodyReader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
// Set authentication
|
|
if c.ApiKey != "" {
|
|
req.Header.Set("APIKey", c.ApiKey)
|
|
}
|
|
|
|
resp, err := c.HTTPClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to execute request: %w", err)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Get performs a GET request
|
|
func (c *Client) Get(path string) (*http.Response, error) {
|
|
return c.DoRequest("GET", path, nil)
|
|
}
|
|
|
|
// Post performs a POST request
|
|
func (c *Client) Post(path string, body interface{}) (*http.Response, error) {
|
|
return c.DoRequest("POST", path, body)
|
|
}
|
|
|
|
// Put performs a PUT request
|
|
func (c *Client) Put(path string, body interface{}) (*http.Response, error) {
|
|
return c.DoRequest("PUT", path, body)
|
|
}
|
|
|
|
// Patch performs a PATCH request
|
|
func (c *Client) Patch(path string, body interface{}) (*http.Response, error) {
|
|
return c.DoRequest("PATCH", path, body)
|
|
}
|
|
|
|
// Delete performs a DELETE request
|
|
func (c *Client) Delete(path string) (*http.Response, error) {
|
|
return c.DoRequest("DELETE", path, nil)
|
|
}
|
|
|
|
// PostWithSelect performs a POST request with select parameter to fetch full object
|
|
func (c *Client) PostWithSelect(path string, selectParam interface{}) (*http.Response, error) {
|
|
requestBody := map[string]interface{}{
|
|
"select": selectParam,
|
|
}
|
|
return c.DoRequest("POST", path, requestBody)
|
|
}
|
|
|
|
// ParseResponse parses an HTTP response into a struct
|
|
func (c *Client) ParseResponse(resp *http.Response, target interface{}) error {
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 400 {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
if target == nil {
|
|
return nil
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
if len(body) == 0 {
|
|
return nil
|
|
}
|
|
|
|
err = json.Unmarshal(body, target)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
`;
|
|
|
|
await this.fileGenerator.writeFileInDir(
|
|
"internal/provider",
|
|
"client.go",
|
|
clientGoContent,
|
|
);
|
|
}
|
|
|
|
private async generateConfigGo(): Promise<void> {
|
|
const configGoContent: string = `package provider
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
|
)
|
|
|
|
// Config holds the provider configuration
|
|
type Config struct {
|
|
OneuptimeUrl string
|
|
ApiKey string
|
|
Client *Client
|
|
}
|
|
|
|
// NewConfig creates a new configuration from the provider model
|
|
func NewConfig(ctx context.Context, model ${StringUtils.toPascalCase(this.config.providerName)}ProviderModel) (*Config, diag.Diagnostics) {
|
|
var diags diag.Diagnostics
|
|
|
|
config := &Config{}
|
|
|
|
// Set oneuptime_url
|
|
if model.OneuptimeUrl.IsNull() {
|
|
config.OneuptimeUrl = os.Getenv("${StringUtils.toConstantCase(this.config.providerName)}_URL")
|
|
if config.OneuptimeUrl == "" {
|
|
config.OneuptimeUrl = "oneuptime.com"
|
|
}
|
|
} else {
|
|
config.OneuptimeUrl = model.OneuptimeUrl.ValueString()
|
|
}
|
|
|
|
// Set API key
|
|
if model.ApiKey.IsNull() {
|
|
config.ApiKey = os.Getenv("${StringUtils.toConstantCase(this.config.providerName)}_API_KEY")
|
|
if config.ApiKey == "" {
|
|
diags.AddError(
|
|
"Missing API Key",
|
|
"API key is required for authentication. "+
|
|
"Please provide it via the api_key attribute or the ${StringUtils.toConstantCase(this.config.providerName)}_API_KEY environment variable.",
|
|
)
|
|
return nil, diags
|
|
}
|
|
} else {
|
|
config.ApiKey = model.ApiKey.ValueString()
|
|
}
|
|
|
|
// Create client
|
|
client, err := NewClient(config.OneuptimeUrl, config.ApiKey)
|
|
if err != nil {
|
|
diags.AddError(
|
|
"Unable to Create API Client",
|
|
"An unexpected error occurred when creating the API client. "+
|
|
"If the error is not clear, please contact the provider developers.\\n\\n"+
|
|
"Client Error: "+err.Error(),
|
|
)
|
|
return nil, diags
|
|
}
|
|
|
|
config.Client = client
|
|
return config, diags
|
|
}
|
|
`;
|
|
|
|
await this.fileGenerator.writeFileInDir(
|
|
"internal/provider",
|
|
"config.go",
|
|
configGoContent,
|
|
);
|
|
}
|
|
}
|