mirror of
https://github.com/imjasonh/terraform-provider-melange
synced 2026-07-20 04:58:24 +00:00
169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package provider
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework/attr"
|
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
"github.com/wolfi-dev/wolfictl/pkg/dag"
|
|
)
|
|
|
|
// Ensure provider defined types fully satisfy framework interfaces.
|
|
var _ datasource.DataSource = &GraphDataSource{}
|
|
|
|
func NewGraphDataSource() datasource.DataSource {
|
|
return &GraphDataSource{}
|
|
}
|
|
|
|
// GraphDataSource defines the data source implementation.
|
|
type GraphDataSource struct {
|
|
popts ProviderOpts
|
|
}
|
|
|
|
// GraphDataSourceModel describes the data source data model.
|
|
type GraphDataSourceModel struct {
|
|
Dir types.String `tfsdk:"dir"`
|
|
Deps types.Map `tfsdk:"deps"`
|
|
Id types.String `tfsdk:"id"`
|
|
}
|
|
|
|
func (d *GraphDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_graph"
|
|
}
|
|
|
|
func (d *GraphDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
// This description is used by the documentation generator and the language server.
|
|
MarkdownDescription: "Graph data source",
|
|
|
|
Attributes: map[string]schema.Attribute{
|
|
"dir": schema.StringAttribute{
|
|
MarkdownDescription: "Dir to load configs from (overrides provider dir)",
|
|
Optional: true,
|
|
},
|
|
"deps": schema.MapAttribute{
|
|
MarkdownDescription: "Map of dependencies: this -> [needs]",
|
|
Computed: true,
|
|
ElementType: basetypes.ListType{
|
|
ElemType: basetypes.StringType{},
|
|
},
|
|
},
|
|
"id": schema.StringAttribute{
|
|
MarkdownDescription: "Graph identifier",
|
|
Computed: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *GraphDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
|
// Prevent panic if the provider has not been configured.
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
popts, ok := req.ProviderData.(*ProviderOpts)
|
|
if !ok || popts == nil {
|
|
resp.Diagnostics.AddError("Client Error", "invalid provider data")
|
|
return
|
|
}
|
|
d.popts = *popts
|
|
}
|
|
|
|
func (d *GraphDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
var data GraphDataSourceModel
|
|
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
dir := d.popts.dir
|
|
if data.Dir.ValueString() != "" {
|
|
dir = data.Dir.ValueString()
|
|
}
|
|
|
|
tflog.Trace(ctx, fmt.Sprintf("dir: %s", dir))
|
|
|
|
pkgs, err := dag.NewPackages(os.DirFS(dir), dir, filepath.Join(dir, "pipelines"))
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to load packages: %v", err))
|
|
return
|
|
}
|
|
if len(pkgs.Packages()) == 0 {
|
|
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("no packages found in %s", dir))
|
|
return
|
|
}
|
|
|
|
g, err := dag.NewGraph(pkgs,
|
|
dag.WithRepos(d.popts.repositories...),
|
|
dag.WithKeys(d.popts.keyring...),
|
|
)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to build graph: %v", err))
|
|
return
|
|
}
|
|
|
|
tflog.Trace(ctx, fmt.Sprintf("configs: %v", pkgs.PackageNames()))
|
|
|
|
// Only include local packages
|
|
g, err = g.Filter(dag.FilterLocal())
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to filter graph to local packages: %v", err))
|
|
return
|
|
}
|
|
// Only return main packages (configs)
|
|
g, err = g.Filter(dag.OnlyMainPackages(pkgs))
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to filter graph to configs: %v", err))
|
|
return
|
|
}
|
|
m, err := g.Graph.AdjacencyMap()
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("failed to get graph adjacency map: %v", err))
|
|
return
|
|
}
|
|
out := map[string]attr.Value{}
|
|
for k, v := range m {
|
|
k, _, _ := strings.Cut(k, ":")
|
|
var vels []string
|
|
for vv := range v {
|
|
vv, _, _ := strings.Cut(vv, ":")
|
|
vels = append(vels, vv)
|
|
}
|
|
sort.Strings(vels)
|
|
var els []attr.Value
|
|
for _, vv := range vels {
|
|
els = append(els, basetypes.NewStringValue(vv))
|
|
}
|
|
out[k] = basetypes.NewListValueMust(basetypes.StringType{}, els)
|
|
}
|
|
data.Deps = basetypes.NewMapValueMust(basetypes.ListType{
|
|
ElemType: basetypes.StringType{},
|
|
}, out)
|
|
|
|
// ID is the sha256 of the JSON-serialized dep graph,
|
|
// to ensure the data source changes if any config changes in a way that changes the graph.
|
|
b, err := json.Marshal(data.Deps)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Client Error", "failed to marshal configs")
|
|
return
|
|
}
|
|
data.Id = types.StringValue(fmt.Sprintf("%x", sha256.Sum256(b)))
|
|
|
|
// Save data into Terraform state
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
}
|