// Copyright (c) EZSCALE. // SPDX-License-Identifier: MPL-2.0 package provider import ( "context" "encoding/json" "fmt" "terraform-provider-virtfusion/internal/client" "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" ) var _ datasource.DataSource = &ServerFirewallDataSource{} var _ datasource.DataSourceWithConfigure = &ServerFirewallDataSource{} // NewServerFirewallDataSource returns a new data source for reading server firewall information. func NewServerFirewallDataSource() datasource.DataSource { return &ServerFirewallDataSource{} } // ServerFirewallDataSource defines the data source implementation. type ServerFirewallDataSource struct { client *client.Client } // ServerFirewallDataSourceModel describes the data source data model. type ServerFirewallDataSourceModel struct { ServerID types.Int64 `tfsdk:"server_id"` InterfaceName types.String `tfsdk:"interface_name"` Enabled types.Bool `tfsdk:"enabled"` Rules types.List `tfsdk:"rules"` } func (d *ServerFirewallDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_server_firewall" } func (d *ServerFirewallDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "Use this data source to read firewall information for a VirtFusion server.", Attributes: map[string]schema.Attribute{ "server_id": schema.Int64Attribute{ MarkdownDescription: "The server ID to read firewall information for.", Required: true, }, "interface_name": schema.StringAttribute{ MarkdownDescription: "The network interface name. Defaults to `eth0`.", Optional: true, Computed: true, }, "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether the firewall is enabled.", Computed: true, }, "rules": schema.ListNestedAttribute{ MarkdownDescription: "The firewall rules.", Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "action": schema.StringAttribute{ MarkdownDescription: "The action for the rule (e.g. `accept`, `drop`).", Computed: true, }, "direction": schema.StringAttribute{ MarkdownDescription: "The direction for the rule (e.g. `in`, `out`).", Computed: true, }, "protocol": schema.StringAttribute{ MarkdownDescription: "The protocol for the rule (e.g. `tcp`, `udp`).", Computed: true, }, "port": schema.StringAttribute{ MarkdownDescription: "The port or port range for the rule.", Computed: true, }, "ip": schema.StringAttribute{ MarkdownDescription: "The IP address or CIDR for the rule.", Computed: true, }, }, }, }, }, } } func (d *ServerFirewallDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { if req.ProviderData == nil { return } c, ok := req.ProviderData.(*client.Client) if !ok { resp.Diagnostics.AddError( "Unexpected Data Source Configure Type", fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), ) return } d.client = c } func (d *ServerFirewallDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var data ServerFirewallDataSourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } // Default interface_name to "eth0" if not set. interfaceName := "eth0" if !data.InterfaceName.IsNull() && !data.InterfaceName.IsUnknown() && data.InterfaceName.ValueString() != "" { interfaceName = data.InterfaceName.ValueString() } data.InterfaceName = types.StringValue(interfaceName) result, err := d.client.Get(ctx, fmt.Sprintf("/servers/%d/firewall/%s", data.ServerID.ValueInt64(), interfaceName)) if err != nil { resp.Diagnostics.AddError("Error Reading Server Firewall", err.Error()) return } var fwResp client.FirewallResponse if err := json.Unmarshal(result, &fwResp); err != nil { resp.Diagnostics.AddError("Error Parsing Server Firewall Response", err.Error()) return } data.Enabled = types.BoolValue(fwResp.Data.Enabled) ruleObjects := make([]attr.Value, len(fwResp.Data.Rules)) for i, rule := range fwResp.Data.Rules { obj, diags := types.ObjectValue( firewallRuleAttrTypes(), map[string]attr.Value{ "action": types.StringValue(rule.Action), "direction": types.StringValue(rule.Direction), "protocol": types.StringValue(rule.Protocol), "port": types.StringValue(rule.Port), "ip": types.StringValue(rule.IP), }, ) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } ruleObjects[i] = obj } rulesList, diags := types.ListValue(types.ObjectType{AttrTypes: firewallRuleAttrTypes()}, ruleObjects) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } data.Rules = rulesList resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) }