PseudoDynamic.Terraform.Toolset
Implement Terraform provider plugins, including resources and data sources, in best C# manner.
Install / Use
/learn @pseudo-dynamic/PseudoDynamic.Terraform.ToolsetREADME
PseudoDynamic.Terraform.Plugin.Sdk
:warning: Due to the fact that this side-project has literally no interest I retire this project. Even the smallest feature request for a tiny CLI called terraform-plugin-docs seems to be ignored. ^^
This library wants to take charge and allows you to write a provider in C# with ease. Before I begin, yes, this library has a lot of similarities with terraform-plugin-framework!
This is the main goal that this library should fullfill:
- Write resources and data sources
Please keep in mind, that this library is in the very early development stage and is anything but finished. Please, consider to help! :relaxed:
By saying this, let me give you a short list of protocol features that are or are not currently implemented:
- Provider
- :heavy_check_mark: GetProviderSchema
- :heavy_check_mark: ValidateProviderConfig
- :heavy_check_mark: ConfigureProvider
- Data Source
- :heavy_check_mark: ValidateDataResourceConfig
- :heavy_check_mark: ReadDataSource
- Resources
- :x: UpgradeResourceState
- :x: ImportResourceState
- :heavy_check_mark: ReadResource
- :heavy_check_mark: ValidateResourceConfig
- :heavy_check_mark: PlanResourceChange
- :heavy_check_mark: ApplyResourceChange
- Terraform Types
- :x: Tuples
- :heavy_check_mark: All others are supported
Still fine? Then continue with the usage examples.
Example
It may be easier to have a concrete example before proceeding with the usage, so here a minimal setup to get started:
https://github.com/pseudo-dynamic/Terraform.Provider.Scaffolding
Usage
To make use of this library, you need to install the NuGet package.
[![Nuget][nuget-package-badge]][nuget-package]
After you installed the package, you have to decide for a protocol version. At the current time only protocol v5 is available. In this library you have access to these by creating a protocol specification:
IPluginServerSpecification.NewProtocolV5()
You use it to create a plugin server:
var webHost = new WebHostBuilder()
.UseTerraformPluginServer(IPluginServerSpecification.NewProtocolV5())
.Build();
This is an advanced provider configuration with a provider, one resource, one data source and the usage of provider meta schema:
var webHost = new WebHostBuilder()
.UseTerraformPluginServer(IPluginServerSpecification.NewProtocolV5()
// Feel free to take a look at the other overload methods.
.UseProvider<ProviderMetaSchema>(providerName, provider =>
{
provider.SetProvider<ProviderImpl>();
provider.AddResource<ResourceImpl>();
provider.AddDataSource<DataSourceImpl>();
}))
.Build();
await webHost.RunAsync();
[Block]
class ProviderMetaSchema {}
[Block]
class ProviderSchema {}
/* If using constructor, the constructor parameters are resolved by service provider. */
class ProviderImpl : Provider<ProviderSchema>
{
public override Task ValidateConfig(IValidateConfigContext<ProviderSchema> context) => base.ValidateConfig(context);
public override Task Configure(IConfigureContext<ProviderSchema> context) => base.Configure(context);
}
[Block]
class ResourceSchema
{
}
/* If using constructor, the constructor parameters are resolved by service provider. */
internal class ResourceImpl : Resource<ResourceSchema, ProviderMetaSchema>
{
public override string TypeName => "sum_a_b";
// MigrateState and ImportState are omitted because not yet functional
public override Task ReviseState(IReviseStateContext<ResourceSchema, ProviderMetaSchema> context) => base.ReviseState(context);
public override Task ValidateConfig(IValidateConfigContext<ResourceSchema> context) => base.ValidateConfig(context);
public override Task Apply(IApplyContext<ResourceSchema, ProviderMetaSchema> context) => base.Apply(context);
public override Task Plan(IPlanContext<ResourceSchema, ProviderMetaSchema> context) => base.Plan(context);
}
[Block]
internal class DataSourceSchema {}
/* If using constructor, the constructor parameters are resolved by service provider. */
internal class DataSourceImpl : DataSource<DataSourceSchema, ProviderMetaSchema>
{
public override string TypeName => "sum_x_y";
public override Task ValidateConfig(IValidateConfigContext<DataSourceSchema> context) => base.ValidateConfig(context);
public override Task Read(IReadContext<DataSourceSchema, ProviderMetaSchema> context) => base.Read(context);
}
Supported C# Mappings
Property names are taken as attribute names and property names are automatically converted to kebab_case.
:bulb: If you prefer using a custom attribute name, then use NameAttribute.
[Object]
class AnObject {
/* Can contain everything like a block except nested blocks. */
}
[Block]
class AnotherSchema { }
[Block]
class Schema {
// By using object, you can decode parts of it at runtime.
// See section for Terraform dynamic.
public object Dynamic { get; set; }
// String decoded as UTF8.
public string String { get; set; }
/* Here a list of all supported primitives:
* Byte, SByte, UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double
*/
public int Number { get; set; }
// For numbers bigger than UInt64.
public BigInteger BigNumber { get;set;}
// A boolean.
public bool Boolean { get; set; }
// List of any supported type.
public IList<> List { get; set; }
// Unique set of any supported type.
public ISet<> List { get; set; }
// Map of any supported type. The key must be of type string.
public IDictionary<string,> List { get; set; }
public AnObject Object { get; set; }
// A nested block where only a single block definition is allowed.
[NestedBlock]
public AnotherSchema List { get; set; }
// A list of nested blocks.
[NestedBlock]
public IList<AnotherSchema> List { get; set; }
// A unique set of nested blocks.
[NestedBlock]
public ISet<AnotherSchema> List { get; set; }
// A map of nested blocks. The key must be of type string.
[NestedBlock]
public IDictionary<string, AnotherSchema> List { get; set; }
}
As you may have seen, only public properties with public getter and public setter are considered as attributes. You can omit the setter if you specify an equivalent as constructor parameter. The names may only differ in upper and lower case. Read more about schema class constructor.
Optional Terraform Attributes
Whether an attribute is treated as optional or not is dependent on the fact whether the property type is nullable or not.
:exclamation: This is only true if nullability analysis is enabled, otherwise the attributes are optional by default.
:bulb: To enable nullability analysis, please use
<Nullable>enable</Nullable>in your .csproj-file or use#nullable enableinside the source code, primarily where the schema classes are defined.
:bulb: If you make your property type non-nullable, but the attribute is still opional, then you can use
OptionalAttributeto enforce the attribute being optional.
:exclamation: Currently Nullable<> is not supported.
Assuming nullability analysis is enabled, the attribute equivalent of public AnObject Object { get; set; } is required and the attribute equivalent of public AnObject? Object { get; set; } is optional.
:exclamation: Because the nullability analysis feature should be always enabled, I do not support
RequiredAttribute.
Supported C# Attributes
- Class Attribtues
- TupleAttribute (no function yet)
- ObjectAttribute => make class equivalent to Terraform object type
- BlockAttribute => make class equivalent to Terraform block type
- Class Constructor Attributes
- BlockConstructorAttribute => prefer a constructor in case of two
- Propertiy Attributes
- AttributeIgnoreAttribute => do not treat property as attribute
- ComputedAttribute => mark attribute as computed
- DeprecatedAttribute => mark attribute as deprecated
- DescriptionKindAttribute => description kind of XML comment
- NameAttribute => use custom name
- NestedBlockAttribute => mark attribute as nested block
- OptionalAttribute => mark attribute as optional <br/>Why is there no RequiredAttribute? See optional Terraform attributes
- SensitiveAttribute => mark attribute as sensitive
- ValueAttribute => overwrite implicit Terraform type determination
Unknown Terraform Values
What about unknown values? Just wrap any type with ITerraformValue<> or TerraformValue<>. By doing so, you are able to differentiate between unknown and null values.
The only types you cannot wrap with ITerraformValue<> are
- :x: lists of nested blocks (e.g.
ITerraformValue<IList<ANestedBlock>>), - :x: sets of nested blocks (e.g.
ITerraformValue<ISet<ANestedBlock>>) or - :x: map of nested blocks (e.g.
ITerraformValue<IDictionary<ANestedBlock>>), but - :heavy_check_mark: nested blocks inside collections can be wrapped (e.g.
IList<ITerraformValue<ANestedBlock>>)
Dynamic Terraform Values
If you use System.Object as type, you can decode it by an instance of ITerraformDynamicDecoder, which you can access through any context that provides data. For example:
public override Task Plan(IPlanContext<,> context)
{
if (context.DynamicDecoder.TryDecode... // Use of dynamic decoder
}
Schema Class Constructor
If you use an constructor for objects or blocks, your constructor parame
Related Skills
node-connect
354.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
