From d7637edf796766c8c6e513d1c0e668972213f585 Mon Sep 17 00:00:00 2001 From: Maikel Poot Date: Mon, 18 Sep 2023 14:42:26 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 2 + docs/data-sources/a_record.md | 31 ++ docs/data-sources/aaaa_record.md | 31 ++ docs/data-sources/caa_record.md | 31 ++ docs/data-sources/cname_record.md | 31 ++ docs/data-sources/dname_record.md | 31 ++ docs/data-sources/loc_record.md | 31 ++ docs/data-sources/mx_record.md | 31 ++ docs/data-sources/naptr_record.md | 31 ++ docs/data-sources/ns_record.md | 31 ++ docs/data-sources/ptr_record.md | 31 ++ docs/data-sources/scaffolding_example.md | 30 -- docs/data-sources/spf_record.md | 31 ++ docs/data-sources/srv_record.md | 31 ++ docs/data-sources/sshfp_record.md | 31 ++ docs/data-sources/subdomain.md | 36 ++ docs/data-sources/txt_record.md | 31 ++ docs/data-sources/urlfwd_record.md | 31 ++ docs/index.md | 36 +- docs/resources/a_record.md | 31 ++ docs/resources/aaaa_record.md | 31 ++ docs/resources/caa_record.md | 31 ++ docs/resources/cname_record.md | 31 ++ docs/resources/dname_record.md | 31 ++ docs/resources/loc_record.md | 31 ++ docs/resources/mx_record.md | 31 ++ docs/resources/naptr_record.md | 31 ++ docs/resources/ns_record.md | 31 ++ docs/resources/ptr_record.md | 31 ++ docs/resources/record.md | 42 +++ docs/resources/scaffolding_example.md | 31 -- docs/resources/spf_record.md | 31 ++ docs/resources/srv_record.md | 31 ++ docs/resources/sshfp_record.md | 31 ++ docs/resources/txt_record.md | 31 ++ docs/resources/urlfwd_record.md | 31 ++ .../octodns_example/data-source.tf | 5 + .../scaffolding_example/data-source.tf | 3 - examples/provider/provider.tf | 13 +- .../resources/octodns_example/resource.tf | 12 + .../resources/scaffolding_example/resource.tf | 3 - go.mod | 10 +- go.sum | 12 + internal/models/client.go | 123 +++++++ internal/models/record.go | 263 ++++++++++++++ internal/models/rtype.go | 56 +++ internal/models/subdomain.go | 178 ++++++++++ internal/models/zone.go | 328 ++++++++++++++++++ internal/provider/example_data_source.go | 105 ------ internal/provider/example_resource.go | 187 ---------- internal/provider/models.go | 26 ++ internal/provider/provider.go | 200 +++++++++-- internal/provider/provider_test.go | 2 +- internal/provider/record_data_source.go | 218 ++++++++++++ internal/provider/record_resource.go | 318 +++++++++++++++++ internal/provider/subdomain_data_source.go | 155 +++++++++ ..._test.go => subdomain_data_source_test.go} | 15 +- internal/provider/subdomain_resource.go | 253 ++++++++++++++ ...rce_test.go => subdomain_resource_test.go} | 22 +- main.go | 4 +- 60 files changed, 3206 insertions(+), 412 deletions(-) create mode 100644 docs/data-sources/a_record.md create mode 100644 docs/data-sources/aaaa_record.md create mode 100644 docs/data-sources/caa_record.md create mode 100644 docs/data-sources/cname_record.md create mode 100644 docs/data-sources/dname_record.md create mode 100644 docs/data-sources/loc_record.md create mode 100644 docs/data-sources/mx_record.md create mode 100644 docs/data-sources/naptr_record.md create mode 100644 docs/data-sources/ns_record.md create mode 100644 docs/data-sources/ptr_record.md delete mode 100644 docs/data-sources/scaffolding_example.md create mode 100644 docs/data-sources/spf_record.md create mode 100644 docs/data-sources/srv_record.md create mode 100644 docs/data-sources/sshfp_record.md create mode 100644 docs/data-sources/subdomain.md create mode 100644 docs/data-sources/txt_record.md create mode 100644 docs/data-sources/urlfwd_record.md create mode 100644 docs/resources/a_record.md create mode 100644 docs/resources/aaaa_record.md create mode 100644 docs/resources/caa_record.md create mode 100644 docs/resources/cname_record.md create mode 100644 docs/resources/dname_record.md create mode 100644 docs/resources/loc_record.md create mode 100644 docs/resources/mx_record.md create mode 100644 docs/resources/naptr_record.md create mode 100644 docs/resources/ns_record.md create mode 100644 docs/resources/ptr_record.md create mode 100644 docs/resources/record.md delete mode 100644 docs/resources/scaffolding_example.md create mode 100644 docs/resources/spf_record.md create mode 100644 docs/resources/srv_record.md create mode 100644 docs/resources/sshfp_record.md create mode 100644 docs/resources/txt_record.md create mode 100644 docs/resources/urlfwd_record.md create mode 100644 examples/data-sources/octodns_example/data-source.tf delete mode 100644 examples/data-sources/scaffolding_example/data-source.tf create mode 100644 examples/resources/octodns_example/resource.tf delete mode 100644 examples/resources/scaffolding_example/resource.tf create mode 100644 internal/models/client.go create mode 100644 internal/models/record.go create mode 100644 internal/models/rtype.go create mode 100644 internal/models/subdomain.go create mode 100644 internal/models/zone.go delete mode 100644 internal/provider/example_data_source.go delete mode 100644 internal/provider/example_resource.go create mode 100644 internal/provider/models.go create mode 100644 internal/provider/record_data_source.go create mode 100644 internal/provider/record_resource.go create mode 100644 internal/provider/subdomain_data_source.go rename internal/provider/{example_data_source_test.go => subdomain_data_source_test.go} (57%) create mode 100644 internal/provider/subdomain_resource.go rename internal/provider/{example_resource_test.go => subdomain_resource_test.go} (58%) diff --git a/.gitignore b/.gitignore index fd3ad8e..7f5e07a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ website/node_modules *.test *.iml +tests + website/vendor # Test exclusions diff --git a/docs/data-sources/a_record.md b/docs/data-sources/a_record.md new file mode 100644 index 0000000..fe2ba00 --- /dev/null +++ b/docs/data-sources/a_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_a_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_a_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/aaaa_record.md b/docs/data-sources/aaaa_record.md new file mode 100644 index 0000000..037be50 --- /dev/null +++ b/docs/data-sources/aaaa_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_aaaa_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_aaaa_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/caa_record.md b/docs/data-sources/caa_record.md new file mode 100644 index 0000000..8d3a640 --- /dev/null +++ b/docs/data-sources/caa_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_caa_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_caa_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/cname_record.md b/docs/data-sources/cname_record.md new file mode 100644 index 0000000..83baa42 --- /dev/null +++ b/docs/data-sources/cname_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_cname_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_cname_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/dname_record.md b/docs/data-sources/dname_record.md new file mode 100644 index 0000000..81efae9 --- /dev/null +++ b/docs/data-sources/dname_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_dname_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_dname_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/loc_record.md b/docs/data-sources/loc_record.md new file mode 100644 index 0000000..d487893 --- /dev/null +++ b/docs/data-sources/loc_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_loc_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_loc_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/mx_record.md b/docs/data-sources/mx_record.md new file mode 100644 index 0000000..175e1b3 --- /dev/null +++ b/docs/data-sources/mx_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_mx_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_mx_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/naptr_record.md b/docs/data-sources/naptr_record.md new file mode 100644 index 0000000..7f2925c --- /dev/null +++ b/docs/data-sources/naptr_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_naptr_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_naptr_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/ns_record.md b/docs/data-sources/ns_record.md new file mode 100644 index 0000000..48f29ae --- /dev/null +++ b/docs/data-sources/ns_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_ns_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_ns_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/ptr_record.md b/docs/data-sources/ptr_record.md new file mode 100644 index 0000000..f551808 --- /dev/null +++ b/docs/data-sources/ptr_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_ptr_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_ptr_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/scaffolding_example.md b/docs/data-sources/scaffolding_example.md deleted file mode 100644 index 9c9de1e..0000000 --- a/docs/data-sources/scaffolding_example.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding_example Data Source - terraform-provider-scaffolding-framework" -subcategory: "" -description: |- - Example data source ---- - -# scaffolding_example (Data Source) - -Example data source - -## Example Usage - -```terraform -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` - - -## Schema - -### Optional - -- `configurable_attribute` (String) Example configurable attribute - -### Read-Only - -- `id` (String) Example identifier diff --git a/docs/data-sources/spf_record.md b/docs/data-sources/spf_record.md new file mode 100644 index 0000000..8f4446c --- /dev/null +++ b/docs/data-sources/spf_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_spf_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_spf_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/srv_record.md b/docs/data-sources/srv_record.md new file mode 100644 index 0000000..585c967 --- /dev/null +++ b/docs/data-sources/srv_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_srv_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_srv_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/sshfp_record.md b/docs/data-sources/sshfp_record.md new file mode 100644 index 0000000..96da1b4 --- /dev/null +++ b/docs/data-sources/sshfp_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_sshfp_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_sshfp_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/subdomain.md b/docs/data-sources/subdomain.md new file mode 100644 index 0000000..8eec085 --- /dev/null +++ b/docs/data-sources/subdomain.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_subdomain Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Subdomain data source +--- + +# octodns_subdomain (Data Source) + +Subdomain data source + + + + +## Schema + +### Required + +- `name` (String) Name of subdomain +- `scope` (String) Scope of zone +- `zone` (String) Zone of subdomain + +### Read-Only + +- `id` (String) Subdomain identifier +- `type` (Attributes List) (see [below for nested schema](#nestedatt--type)) + + +### Nested Schema for `type` + +Read-Only: + +- `ttl` (Number) +- `type` (String) +- `values` (List of String) diff --git a/docs/data-sources/txt_record.md b/docs/data-sources/txt_record.md new file mode 100644 index 0000000..e2b2d77 --- /dev/null +++ b/docs/data-sources/txt_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_txt_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_txt_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/data-sources/urlfwd_record.md b/docs/data-sources/urlfwd_record.md new file mode 100644 index 0000000..3e773e9 --- /dev/null +++ b/docs/data-sources/urlfwd_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_urlfwd_record Data Source - terraform-provider-octodns" +subcategory: "" +description: |- + Record data source +--- + +# octodns_urlfwd_record (Data Source) + +Record data source + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `zone` (String) Record Zone + +### Optional + +- `scope` (String) Scope of zone + +### Read-Only + +- `id` (String) Record identifier +- `ttl` (Number) +- `values` (List of String) diff --git a/docs/index.md b/docs/index.md index 8eecef4..a2fef90 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,26 +1,52 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding-framework Provider" +page_title: "octodns Provider" subcategory: "" description: |- --- -# scaffolding-framework Provider +# octodns Provider ## Example Usage ```terraform -provider "scaffolding" { - # example configuration here +provider "octodns" { + github_access_token = "ghp_xxxxxxxxxxxxx" + github_org = "example_org" + github_repo = "dns_repo" + + scope { + name = "default" + path = "zones" + } + } ``` ## Schema +### Required + +- `github_access_token` (String, Sensitive) Github personal access token +- `github_org` (String) Github personal access token +- `github_repo` (String) Github personal access token + ### Optional -- `endpoint` (String) Example provider attribute +- `git_provider` (String) Git provider, only accepted value is github +- `scope` (Block List) (see [below for nested schema](#nestedblock--scope)) + + +### Nested Schema for `scope` + +Required: + +- `path` (String) + +Optional: + +- `name` (String) diff --git a/docs/resources/a_record.md b/docs/resources/a_record.md new file mode 100644 index 0000000..fd79e23 --- /dev/null +++ b/docs/resources/a_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_a_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_a_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/aaaa_record.md b/docs/resources/aaaa_record.md new file mode 100644 index 0000000..05d2192 --- /dev/null +++ b/docs/resources/aaaa_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_aaaa_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_aaaa_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/caa_record.md b/docs/resources/caa_record.md new file mode 100644 index 0000000..127bc9c --- /dev/null +++ b/docs/resources/caa_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_caa_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_caa_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/cname_record.md b/docs/resources/cname_record.md new file mode 100644 index 0000000..25fd4f7 --- /dev/null +++ b/docs/resources/cname_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_cname_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_cname_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/dname_record.md b/docs/resources/dname_record.md new file mode 100644 index 0000000..50c60dc --- /dev/null +++ b/docs/resources/dname_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_dname_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_dname_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/loc_record.md b/docs/resources/loc_record.md new file mode 100644 index 0000000..cbd6f15 --- /dev/null +++ b/docs/resources/loc_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_loc_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_loc_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/mx_record.md b/docs/resources/mx_record.md new file mode 100644 index 0000000..bede9a3 --- /dev/null +++ b/docs/resources/mx_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_mx_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_mx_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/naptr_record.md b/docs/resources/naptr_record.md new file mode 100644 index 0000000..6e0e2b5 --- /dev/null +++ b/docs/resources/naptr_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_naptr_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_naptr_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/ns_record.md b/docs/resources/ns_record.md new file mode 100644 index 0000000..2554287 --- /dev/null +++ b/docs/resources/ns_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_ns_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_ns_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/ptr_record.md b/docs/resources/ptr_record.md new file mode 100644 index 0000000..88e0e36 --- /dev/null +++ b/docs/resources/ptr_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_ptr_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_ptr_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/record.md b/docs/resources/record.md new file mode 100644 index 0000000..64e7a24 --- /dev/null +++ b/docs/resources/record.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `zone` (String) Record Zone + +### Optional + +- `type` (Block List) (see [below for nested schema](#nestedblock--type)) + +### Read-Only + +- `id` (String) Record identifier + + +### Nested Schema for `type` + +Required: + +- `type` (String) +- `values` (List of String) + +Optional: + +- `ttl` (Number) diff --git a/docs/resources/scaffolding_example.md b/docs/resources/scaffolding_example.md deleted file mode 100644 index 47e77ed..0000000 --- a/docs/resources/scaffolding_example.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding_example Resource - terraform-provider-scaffolding-framework" -subcategory: "" -description: |- - Example resource ---- - -# scaffolding_example (Resource) - -Example resource - -## Example Usage - -```terraform -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` - - -## Schema - -### Optional - -- `configurable_attribute` (String) Example configurable attribute -- `defaulted` (String) Example configurable attribute with default value - -### Read-Only - -- `id` (String) Example identifier diff --git a/docs/resources/spf_record.md b/docs/resources/spf_record.md new file mode 100644 index 0000000..e249727 --- /dev/null +++ b/docs/resources/spf_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_spf_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_spf_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/srv_record.md b/docs/resources/srv_record.md new file mode 100644 index 0000000..265b72c --- /dev/null +++ b/docs/resources/srv_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_srv_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_srv_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/sshfp_record.md b/docs/resources/sshfp_record.md new file mode 100644 index 0000000..38f2dfe --- /dev/null +++ b/docs/resources/sshfp_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_sshfp_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_sshfp_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/txt_record.md b/docs/resources/txt_record.md new file mode 100644 index 0000000..2a7c110 --- /dev/null +++ b/docs/resources/txt_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_txt_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_txt_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/docs/resources/urlfwd_record.md b/docs/resources/urlfwd_record.md new file mode 100644 index 0000000..b37b27a --- /dev/null +++ b/docs/resources/urlfwd_record.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "octodns_urlfwd_record Resource - terraform-provider-octodns" +subcategory: "" +description: |- + Record resource +--- + +# octodns_urlfwd_record (Resource) + +Record resource + + + + +## Schema + +### Required + +- `name` (String) Record Zone +- `scope` (String) Scope of zone +- `values` (List of String) +- `zone` (String) Record Zone + +### Optional + +- `ttl` (Number) + +### Read-Only + +- `id` (String) Record identifier diff --git a/examples/data-sources/octodns_example/data-source.tf b/examples/data-sources/octodns_example/data-source.tf new file mode 100644 index 0000000..9a3e977 --- /dev/null +++ b/examples/data-sources/octodns_example/data-source.tf @@ -0,0 +1,5 @@ +data "octodns_a_record" "example" { + zone = "example.com" + scope = "default" + name = "localhost" +} diff --git a/examples/data-sources/scaffolding_example/data-source.tf b/examples/data-sources/scaffolding_example/data-source.tf deleted file mode 100644 index a852489..0000000 --- a/examples/data-sources/scaffolding_example/data-source.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 942db45..187f85f 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,3 +1,12 @@ -provider "scaffolding" { - # example configuration here +provider "octodns" { + github_access_token = "ghp_xxxxxxxxxxxxx" + github_org = "example_org" + github_repo = "dns_repo" + + scope { + name = "default" + path = "zones" + } + } + diff --git a/examples/resources/octodns_example/resource.tf b/examples/resources/octodns_example/resource.tf new file mode 100644 index 0000000..2911cc5 --- /dev/null +++ b/examples/resources/octodns_example/resource.tf @@ -0,0 +1,12 @@ +resource "octodns_a_record" "example" { + + zone = "example.com" + scope = "default" + name = "localhost" + + ttl = "3600" + values = ["127.0.0.1"] + +} + + diff --git a/examples/resources/scaffolding_example/resource.tf b/examples/resources/scaffolding_example/resource.tf deleted file mode 100644 index 9ae3f57..0000000 --- a/examples/resources/scaffolding_example/resource.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/go.mod b/go.mod index e8c9c13..8ee805e 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,17 @@ -module github.com/hashicorp/terraform-provider-scaffolding-framework +module github.com/topicusonderwijs/terraform-provider-octodns go 1.19 require ( + github.com/google/go-github/v53 v53.2.0 github.com/hashicorp/terraform-plugin-docs v0.16.0 - github.com/hashicorp/terraform-plugin-framework v1.3.2 + github.com/hashicorp/terraform-plugin-framework v1.3.5 github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.3.0 + golang.org/x/oauth2 v0.8.0 + gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -23,6 +27,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect @@ -38,6 +43,7 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.18.1 // indirect github.com/hashicorp/terraform-json v0.17.1 // indirect + github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 // indirect github.com/hashicorp/terraform-registry-address v0.2.1 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect diff --git a/go.sum b/go.sum index ef87c3a..eceeaf9 100644 --- a/go.sum +++ b/go.sum @@ -41,9 +41,14 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= +github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -84,6 +89,10 @@ github.com/hashicorp/terraform-plugin-docs v0.16.0 h1:UmxFr3AScl6Wged84jndJIfFcc github.com/hashicorp/terraform-plugin-docs v0.16.0/go.mod h1:M3ZrlKBJAbPMtNOPwHicGi1c+hZUh7/g0ifT/z7TVfA= github.com/hashicorp/terraform-plugin-framework v1.3.2 h1:aQ6GSD0CTnvoALEWvKAkcH/d8jqSE0Qq56NYEhCexUs= github.com/hashicorp/terraform-plugin-framework v1.3.2/go.mod h1:oimsRAPJOYkZ4kY6xIGfR0PHjpHLDLaknzuptl6AvnY= +github.com/hashicorp/terraform-plugin-framework v1.3.5 h1:FJ6s3CVWVAxlhiF/jhy6hzs4AnPHiflsp9KgzTGl1wo= +github.com/hashicorp/terraform-plugin-framework v1.3.5/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= github.com/hashicorp/terraform-plugin-go v0.18.0 h1:IwTkOS9cOW1ehLd/rG0y+u/TGLK9y6fGoBjXVUquzpE= github.com/hashicorp/terraform-plugin-go v0.18.0/go.mod h1:l7VK+2u5Kf2y+A+742GX0ouLut3gttudmvMgN0PA74Y= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= @@ -188,6 +197,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -226,6 +237,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/models/client.go b/internal/models/client.go new file mode 100644 index 0000000..8e3f50e --- /dev/null +++ b/internal/models/client.go @@ -0,0 +1,123 @@ +package models + +import ( + "context" + "encoding/base64" + "fmt" + "github.com/google/go-github/v53/github" + "golang.org/x/oauth2" + "strings" +) + +const ( + DEFAULT_SCOPE = "default" + DEFAULT_PATH = "" +) + +type GitClient interface { + AddScope(name string, path string) error + SetScope(name string, path string) error + GetZone(zone, scope string) (*Zone, error) +} + +type GitHubClient struct { + *github.Client + Owner string + Repo string + Scopes map[string]string + Counter int +} + +func NewGitHubClient(accessToken, owner, repo string) (GitClient, error) { + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: accessToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + return &GitHubClient{ + Client: github.NewClient(tc), + Owner: owner, + Repo: repo, + }, nil + +} + +func (g *GitHubClient) AddScope(name string, path string) error { + + if _, ok := g.Scopes[name]; ok { + return fmt.Errorf("duplicate scope name found for name `%s`", name) + } + + return g.SetScope(name, path) +} + +func (g *GitHubClient) SetScope(name string, path string) error { + + if g.Scopes == nil { + g.Scopes = make(map[string]string) + } + + if name == "" { + name = DEFAULT_SCOPE + } + + if path == "" { + path = DEFAULT_PATH + } + + if strings.HasPrefix(path, "/") || strings.HasSuffix(path, "/") { + return fmt.Errorf("scope path must not start or end with a `/`, got '%s' for scope '%s'", path, name) + } + + g.Scopes[name] = path + + return nil +} + +func (g *GitHubClient) GetZoneContents(zone, scope string) ([]byte, error) { + + var path string + var ok bool + if path, ok = g.Scopes[scope]; !ok { + return nil, fmt.Errorf("undefined scope `%s`", scope) + } + filepath := path + "/" + zone + ".yaml" + + ctx := context.Background() + fileContent, _, resp, err := g.Client.Repositories.GetContents(ctx, g.Owner, g.Repo, filepath, nil) + if err != nil { + return nil, err + } + fmt.Println("Nextpage:", resp.NextPage) + + content, err := base64.StdEncoding.DecodeString(*fileContent.Content) + if err != nil { + return nil, err + } + + return content, nil + +} + +func (g *GitHubClient) GetZone(zone, scope string) (*Zone, error) { + + if scope == "" { + scope = DEFAULT_SCOPE + } + contents, err := g.GetZoneContents(zone, scope) + if err != nil { + return nil, err + } + + z := Zone{} + + err = z.ReadYaml(contents) + if err != nil { + return nil, err + } else { + return &z, nil + } + +} diff --git a/internal/models/record.go b/internal/models/record.go new file mode 100644 index 0000000..b2e31b0 --- /dev/null +++ b/internal/models/record.go @@ -0,0 +1,263 @@ +package models + +import ( + "fmt" + "gopkg.in/yaml.v3" +) + +type Terraform struct { + Hash string `yaml:",omitempty"` +} + +type BaseRecord struct { + RecordChild *yaml.Node `yaml:"-"` + RecordNode *yaml.Node `yaml:"-"` + RecordParent *yaml.Node `yaml:"-"` + Name string + Type string + Values []RecordValue `yaml:"values" line_comment:"Enable or disable."` + TTL int + Terraform Terraform +} + +type Record struct { + BaseRecord + Values []RecordValue `yaml:"values" line_comment:"Enable or disable."` +} + +type recordYamlUnmarshal struct { + Name string `yaml:",omitempty"` + Type string `yaml:",omitempty"` + Value yaml.Node `yaml:",omitempty"` + Values yaml.Node `yaml:",omitempty"` + TTL int `yaml:",omitempty"` + Terraform Terraform `yaml:",omitempty"` +} + +func (r *Record) UpdateYaml() error { + return r.RecordChild.Encode(r) +} + +func (r *Record) ValuesAsString() []string { + var ret []string + for _, v := range r.Values { + switch r.Type { + default: + ret = append(ret, v.String()) + case TYPE_MX.String(): + ret = append(ret, v.StringMX()) + case TYPE_SRV.String(): + ret = append(ret, v.StringSRV()) + + } + + } + + return ret +} + +func (r *Record) AddType(record Record) error { + noreRecord := yaml.Node{} + + if err := noreRecord.Encode(record); err != nil { + return err + } + + if r.RecordNode.Kind == yaml.SequenceNode { + r.RecordNode.Content = append(r.RecordNode.Content, &noreRecord) + } else { + return fmt.Errorf("Can not add new type to a single typed record") + } + + return nil +} + +func (r *Record) UnmarshalYAML( + value *yaml.Node, +) error { + + //fmt.Println("Record.UnmarshalYAML") + + raw := recordYamlUnmarshal{} + + if err := value.Decode(&raw); err != nil { + return err + } + + r.TTL = raw.TTL + r.Terraform = raw.Terraform + r.Type = raw.Type + + //fmt.Println("Name:", r.Name, r.Type) + if !raw.Value.IsZero() { + rvValue := RecordValue{} + err := rvValue.UnmarshalYAML(&raw.Value) + if err != nil { + return err + } + r.Values = append(r.Values, rvValue) + } else if !raw.Values.IsZero() { + rvValues := make([]RecordValue, 0) + err := raw.Values.Decode(&rvValues) + if err != nil { + return err + } + r.Values = append(r.Values, rvValues...) + } + + //fmt.Println("Count values:", len(r.Values), r.Values) + + return nil +} + +func (r Record) MarshalYAML() (interface{}, error) { + + out := recordYamlUnmarshal{ + Type: r.Type, + TTL: r.TTL, + Terraform: r.Terraform, + } + node := yaml.Node{} + var err error + if len(r.Values) == 0 { + return nil, fmt.Errorf("0 Values encountered: %s, %s ", r.Name, r.Type) + } + if len(r.Values) > 1 { + err = node.Encode(r.Values) + node.LineComment = "Blaat" + out.Values = node + } else { + err = node.Encode(r.Values[0]) + node.LineComment = "Blaat" + out.Value = node + } + if err != nil { + return nil, err + } + + return out, nil + +} + +type baseRecordValue struct { + StringValue *string `yaml:",omitempty"` + Comment *string `yaml:"-"` + + // SSHFP + + Algorithm *int `yaml:",omitempty"` + Fingerprint *string `yaml:",omitempty"` + FingerprintType *int `yaml:"fingerprint_type,omitempty"` + + Flags *string `yaml:",omitempty"` + Tag *string `yaml:",omitempty"` + //Value string `yaml:",omitempty"` + + // SRV + Port *int `yaml:",omitempty"` + Priority *int `yaml:",omitempty"` + Target *string `yaml:",omitempty"` + Weight *int `yaml:",omitempty"` + + // MX + Exchange *string `yaml:",omitempty"` + Preference *int `yaml:",omitempty"` + Value *string `yaml:",omitempty"` + //Priority int `yaml:",omitempty"` + + // LOC + Altitude *int `yaml:",omitempty"` + Lat_degrees *int `yaml:",omitempty"` + Lat_direction *string `yaml:",omitempty"` + Lat_minutes *int `yaml:",omitempty"` + Lat_seconds *float64 `yaml:",omitempty"` + Long_degrees *int `yaml:",omitempty"` + Long_direction *string `yaml:",omitempty"` + Long_minutes *int `yaml:",omitempty"` + Long_seconds *float64 `yaml:",omitempty"` + Precision_horz *int `yaml:",omitempty"` + Precision_vert *int `yaml:",omitempty"` + Size *int `yaml:",omitempty"` + + // NAPTR + //Flags string `yaml:",omitempty"` + //Preference int `yaml:",omitempty"` + Order *int `yaml:",omitempty"` + Regexp *string `yaml:",omitempty"` + Replacement *string `yaml:",omitempty"` + Service *string `yaml:",omitempty"` + + // URLFWD + + Code *int `yaml:",omitempty"` + Masking *int `yaml:",omitempty"` + Path *string `yaml:",omitempty"` + Query *int `yaml:",omitempty"` + //Target string `yaml:",omitempty"` +} + +type RecordValue struct { + baseRecordValue +} + +func (r *RecordValue) UnmarshalYAML( + value *yaml.Node, +) error { + + if len(value.Content) == 0 { + if err := value.Decode(&r.StringValue); err != nil { + return err + } + } else { + + al := baseRecordValue{} + + if err := value.Decode(&al); err != nil { + return err + } + + r.baseRecordValue = al + + } + + return nil + +} + +func (r RecordValue) String() string { + if r.StringValue != nil { + return *r.StringValue + } else { + return "" + } +} + +func (r RecordValue) StringMX() string { + + if r.Priority != nil && r.Value != nil { + return fmt.Sprintf("%d %s", *r.Priority, *r.Value) + } else if r.Preference != nil && r.Exchange != nil { + return fmt.Sprintf("%d %s", *r.Preference, *r.Exchange) + } + + return "" +} + +func (r RecordValue) StringSRV() string { + + if r.Priority == nil || r.Weight == nil || r.Port == nil || r.Target == nil { + return "" + } else { + return fmt.Sprintf("%d %d %d %s", *r.Priority, *r.Weight, *r.Port, *r.Target) + } + +} + +func (r RecordValue) MarshalYAML() (interface{}, error) { + + if r.StringValue != nil { + return *r.StringValue, nil + } else { + return r.baseRecordValue, nil + } +} diff --git a/internal/models/rtype.go b/internal/models/rtype.go new file mode 100644 index 0000000..5b86c77 --- /dev/null +++ b/internal/models/rtype.go @@ -0,0 +1,56 @@ +package models + +import "strings" + +var ( + TYPE_A RType = RType{value: "A", enabled: true} + TYPE_AAAA RType = RType{value: "AAAA", enabled: true} + TYPE_CAA RType = RType{value: "CAA", enabled: true} + TYPE_CNAME RType = RType{value: "CNAME", enabled: true} + TYPE_DNAME RType = RType{value: "DNAME", enabled: true} + TYPE_LOC RType = RType{value: "LOC", enabled: true} + TYPE_MX RType = RType{value: "MX", enabled: true} + TYPE_NAPTR RType = RType{value: "NAPTR", enabled: true} + TYPE_NS RType = RType{value: "NS", enabled: true} + TYPE_PTR RType = RType{value: "PTR", enabled: true} + TYPE_SPF RType = RType{value: "SPF", enabled: true} + TYPE_SRV RType = RType{value: "SRV", enabled: true} + TYPE_SSHFP RType = RType{value: "SSHFP", enabled: true} + TYPE_TXT RType = RType{value: "TXT", enabled: true} + TYPE_URLFWD RType = RType{value: "URLFWD", enabled: true} + + TYPES = map[string]RType{ + TYPE_A.String(): TYPE_A, + TYPE_AAAA.String(): TYPE_AAAA, + TYPE_CAA.String(): TYPE_CAA, + TYPE_CNAME.String(): TYPE_CNAME, + TYPE_DNAME.String(): TYPE_DNAME, + TYPE_LOC.String(): TYPE_LOC, + TYPE_MX.String(): TYPE_MX, + TYPE_NAPTR.String(): TYPE_NAPTR, + TYPE_NS.String(): TYPE_NS, + TYPE_PTR.String(): TYPE_PTR, + TYPE_SPF.String(): TYPE_SPF, + TYPE_SRV.String(): TYPE_SRV, + TYPE_SSHFP.String(): TYPE_SSHFP, + TYPE_TXT.String(): TYPE_TXT, + TYPE_URLFWD.String(): TYPE_URLFWD, + } +) + +type RType struct { + value string + enabled bool +} + +func (r RType) IsEnabled() bool { + return r.enabled +} + +func (r RType) String() string { + return r.value +} + +func (r RType) LowerString() string { + return strings.ToLower(r.value) +} diff --git a/internal/models/subdomain.go b/internal/models/subdomain.go new file mode 100644 index 0000000..0ebc18a --- /dev/null +++ b/internal/models/subdomain.go @@ -0,0 +1,178 @@ +package models + +import ( + "fmt" + "gopkg.in/yaml.v3" + "strings" +) + +type Subdomain struct { + Name string + keyNode *yaml.Node + ContentNode *yaml.Node + Types map[string]*Record +} + +func (r *Subdomain) SetYaml(key, content *yaml.Node) { + + r.keyNode = key + r.ContentNode = content + + r.Name = r.keyNode.Value + r.Types = make(map[string]*Record, 0) + +} + +func (r *Subdomain) FindAllType() { + + for _, v := range TYPES { + if v.IsEnabled() == true { + _, _ = r.GetType(v.String()) + } + } + +} + +func (r *Subdomain) UpdateYaml() (err error) { + + for k, v := range r.Types { + + fmt.Println("Updating ", k) + if err = v.UpdateYaml(); err != nil { + return + } + + } + return nil + +} + +func (r *Subdomain) validateRType(rtype string) (string, error) { + + err := fmt.Errorf("%s is not a valid record type", rtype) + rtype = strings.ToUpper(strings.TrimSpace(rtype)) + + if rt, ok := TYPES[rtype]; ok && rt.IsEnabled() { + return rtype, nil + } + return "", err + +} + +func (r *Subdomain) CreateType(rtype string) (record *Record, err error) { + var rtypeValidated string + + if rtypeValidated, err = r.validateRType(rtype); err != nil { + return nil, err + } + + if _, err = r.GetType(rtypeValidated); err != nil { + if err.Error() == "type not found" { + // Can create Record Type + + emptyNode := &yaml.Node{} + record = &Record{} + record.Type = rtypeValidated + record.RecordChild = emptyNode + r.Types[rtypeValidated] = record + + switch r.ContentNode.Kind { + case yaml.MappingNode: + data := *r.ContentNode + emptyList := &yaml.Node{Kind: yaml.SequenceNode} + emptyList.Content = []*yaml.Node{r.ContentNode} + err = r.ContentNode.Encode(emptyList) + if err != nil { + fmt.Println("Err encode", err) + } + r.ContentNode.Content[0].Content = data.Content + //r.ContentNode.Content[1].Content = emptyNode.Content + + for k := range r.Types { + fmt.Println("Blaat", k) + tmp, err := r.GetType(k) + if err != nil { + fmt.Println("Err", err) + err = nil + } + r.Types[k].RecordChild = tmp.RecordChild + } + + r.ContentNode.Content = append(r.ContentNode.Content, emptyNode) + //err = r.ContentNode.Encode([]yaml.Node{*r.ContentNode, *emptyNode}) + case yaml.SequenceNode: + r.ContentNode.Content = append(r.ContentNode.Content, emptyNode) + default: + return nil, fmt.Errorf("Dont know how to add record type to a %d node", r.ContentNode.Kind) + } + + return record, nil + } + } + + return nil, fmt.Errorf("Can not create record") + +} + +func (r *Subdomain) GetType(rtype string) (record *Record, err error) { + + var rtypeValidated string + + if rtypeValidated, err = r.validateRType(rtype); err != nil { + return nil, err + } + + if _, ok := r.Types[rtypeValidated]; ok { + return r.Types[rtypeValidated], nil + } else { + yamlNode := r.findType(rtypeValidated) + if yamlNode != nil { + + record = &Record{} + record.RecordChild = yamlNode + if err = yamlNode.Decode(record); err != nil { + return nil, err + } + + r.Types[rtypeValidated] = record + return record, nil + } + } + + return nil, fmt.Errorf("type not found") + +} + +func (r *Subdomain) findType(rtype string) *yaml.Node { + + findType := func(root *yaml.Node, rtype string) *yaml.Node { + for i := 0; i < len(root.Content); i += 2 { + if root.Content[i].Value == "type" { + if root.Content[i+1].Value == strings.ToUpper(rtype) { + return root + + } + } + } + + return nil + } + + var rrecord *yaml.Node + + switch r.ContentNode.Kind { + case yaml.MappingNode: + //fmt.Println("Map") + rrecord = findType(r.ContentNode, rtype) + return rrecord + case yaml.SequenceNode: + //fmt.Println("Seq") + for y := 0; y < len(r.ContentNode.Content); y += 1 { + if rrecord = findType(r.ContentNode.Content[y], rtype); rrecord != nil { + return rrecord + } + } + } + + return nil +} diff --git a/internal/models/zone.go b/internal/models/zone.go new file mode 100644 index 0000000..fdca217 --- /dev/null +++ b/internal/models/zone.go @@ -0,0 +1,328 @@ +package models + +import ( + "bytes" + "fmt" + "gopkg.in/yaml.v3" + "os" + "strings" +) + +type Zone struct { + doc yaml.Node +} + +func (z *Zone) ReadYamlFile(filename string) error { + + //@todo: Check if file path is sane/existing/readable/etc + + fileContent, err := os.ReadFile(filename) + if err != nil { + return err + } + return z.ReadYaml(fileContent) +} + +func (z *Zone) ReadYaml(content []byte) error { + + if bytes.Contains(content, []byte("? ''\n :")) { + //fmt.Println("Problem key found") + content = bytes.Replace(content, []byte("? ''\n :"), []byte("'':\n "), 1) + } + err := yaml.Unmarshal(content, &z.doc) + if err != nil { + return err + } + + return nil + +} + +func (z *Zone) GetRecord(subdomain string, rtype string) (record *Record, err error) { + + recordChild, recordNode, recordParent, err := z.FindRecordByType(subdomain, rtype) + if err != nil { + return nil, err + } + + record = &Record{ + BaseRecord: BaseRecord{ + RecordChild: recordChild, + RecordNode: recordNode, + RecordParent: recordParent, + Name: "", + Type: "", + Values: nil, + TTL: 0, + Terraform: Terraform{}, + }, + } + + _ = recordChild + _ = recordNode + _ = recordParent + + err = recordChild.Decode(record) + if err != nil { + return + } + + return +} + +func (z *Zone) FindRecord(subdomain string) (record Subdomain, err error) { + + if subdomain == "@" { + subdomain = "" + } + + if z.doc.Kind != yaml.DocumentNode { + err = fmt.Errorf("z.doc is not a document node") + return + } + + if len(z.doc.Content[0].Content) == 0 { + err = fmt.Errorf(z.doc.Content[0].Value) + return + } + + for i := 0; i < len(z.doc.Content[0].Content); i += 2 { + /* + switch z.doc.Content[0].Kind { + case yaml.DocumentNode: + fmt.Println("DocumentNode") + case yaml.MappingNode: + fmt.Println("MappingNode") + case yaml.SequenceNode: + fmt.Println("SequenceNode") + } + */ + + // log.Print("Found: ", z.doc.Content[0].Content[i].Value, " ", subdomain, " ", z.doc.Content[0].Content[i].Value == subdomain) + + if z.doc.Content[0].Content[i].Value == subdomain { + + record.SetYaml(z.doc.Content[0].Content[i], z.doc.Content[0].Content[i+1]) + return record, nil + } + } + + return record, fmt.Errorf("subdomain not found") +} + +func (z *Zone) FindRecordByType(subdomain string, rtype string) (rrecord *yaml.Node, rcontent *yaml.Node, rparent *yaml.Node, err error) { + + if z.doc.Kind != yaml.DocumentNode { + err = fmt.Errorf("z.doc is not a document node") + return + } + + for i := 0; i < len(z.doc.Content[0].Content); i += 2 { + /* + switch z.doc.Content[0].Kind { + case yaml.DocumentNode: + fmt.Println("DocumentNode") + case yaml.MappingNode: + fmt.Println("MappingNode") + case yaml.SequenceNode: + fmt.Println("SequenceNode") + } + */ + + findType := func(root *yaml.Node, rtype string) *yaml.Node { + for i := 0; i < len(root.Content); i += 2 { + if root.Content[i].Value == "type" { + if root.Content[i+1].Value == strings.ToUpper(rtype) { + return root + + } + } + } + + return nil + } + + if z.doc.Content[0].Content[i].Value == subdomain { + + xRecord := Subdomain{} + xRecord.SetYaml(z.doc.Content[0].Content[i], z.doc.Content[0].Content[i+1]) + + rparent = z.doc.Content[0].Content[i] + rcontent = z.doc.Content[0].Content[i+1] + + _ = rparent + _ = rcontent + + //fmt.Println("Found ", subdomain) + + switch rcontent.Kind { + case yaml.MappingNode: + //fmt.Println("Map") + rrecord = findType(rcontent, rtype) + return + case yaml.SequenceNode: + //fmt.Println("Seq") + for y := 0; y < len(rcontent.Content); y += 1 { + fmt.Println("Y", y) + if rrecord = findType(rcontent.Content[y], rtype); rrecord != nil { + return + } + } + } + + return + } + + } + + return nil, nil, nil, fmt.Errorf("subdomain not found") + +} + +func (z Zone) WriteYaml() ([]byte, error) { + + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + encoder.SetIndent(2) + err := encoder.Encode(z.doc.Content[0]) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (z Zone) WriteYamlToFile(filename string) error { + + data, err := z.WriteYaml() + if err != nil { + return err + } + + err = os.WriteFile(filename, data, 0666) + if err != nil { + return err + } + return nil +} + +type OldZone struct { + Name string `yaml:"-"` + + Records []Record +} + +func (z *OldZone) UnmarshalYAML( + value *yaml.Node, +) error { + //fmt.Println("Blaats") + + var items map[string]yaml.Node + if err := value.Decode(&items); err == nil { + for k, v := range items { + if k == "" { + k = "@" + } + + var slice []yaml.Node + var object yaml.Node + if err := v.Decode(&slice); err == nil { + // Node is Slice + } else if err := v.Decode(&object); err == nil { + // Node is Single object + slice = []yaml.Node{object} + } else { + return err + } + + records, err := decodeRecords(k, slice) + if err != nil { + return err + } + _ = records + z.Records = append(z.Records, records...) + //fmt.Println("Subdomain found:", len(records)) + + } + return nil + } else { + return err + } + return nil +} + +func (z OldZone) MarshalYAML() (interface{}, error) { + + out := make(map[string][]Record, 0) + + for _, record := range z.Records { + + if _, ok := out[record.Name]; !ok { + out[record.Name] = []Record{record} + } else { + out[record.Name] = append(out[record.Name], record) + } + + } + + return out, nil + +} + +func decodeRecords(subdomain string, nodes []yaml.Node) ([]Record, error) { + + records := []Record{} + + for i := range nodes { + record, err := decodeRecord(subdomain, nodes[i]) + if err != nil { + return []Record{}, err + } + records = append(records, record) + } + + return records, nil + +} + +func decodeRecord(subdomain string, node yaml.Node) (Record, error) { + + record := Record{} + record.Name = subdomain + + err := node.Decode(&record) + + return record, err + +} + +/* +func (z *Record) UnmarshalYAML( + value *yaml.Node, +) error { + fmt.Println("2Blaat") + var name string + if err := value.Decode(&name); err == nil { + fmt.Println("2Blaat", name) + return nil + } else { + fmt.Println("2Error: ", err.Error()) + } + return nil +} +*/ +/* +func (z *OldZone) UnmarshalYAML( + + unmarshal func(interface{}) error, + + ) error { + var name string + if err := unmarshal(&name); err == nil { + fmt.Println("Blaat", name) + return nil + } else { + fmt.Println("Error: ", err.Error()) + } + return nil + } +*/ diff --git a/internal/provider/example_data_source.go b/internal/provider/example_data_source.go deleted file mode 100644 index 585b9d2..0000000 --- a/internal/provider/example_data_source.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - "fmt" - "net/http" - - "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-log/tflog" -) - -// Ensure provider defined types fully satisfy framework interfaces. -var _ datasource.DataSource = &ExampleDataSource{} - -func NewExampleDataSource() datasource.DataSource { - return &ExampleDataSource{} -} - -// ExampleDataSource defines the data source implementation. -type ExampleDataSource struct { - client *http.Client -} - -// ExampleDataSourceModel describes the data source data model. -type ExampleDataSourceModel struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Id types.String `tfsdk:"id"` -} - -func (d *ExampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_example" -} - -func (d *ExampleDataSource) 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: "Example data source", - - Attributes: map[string]schema.Attribute{ - "configurable_attribute": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute", - Optional: true, - }, - "id": schema.StringAttribute{ - MarkdownDescription: "Example identifier", - Computed: true, - }, - }, - } -} - -func (d *ExampleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*http.Client) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - d.client = client -} - -func (d *ExampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data ExampleDataSourceModel - - // Read Terraform configuration data into the model - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := d.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) - // return - // } - - // For the purposes of this example code, hardcoding a response value to - // save into the Terraform state. - data.Id = types.StringValue("example-id") - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "read a data source") - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} diff --git a/internal/provider/example_resource.go b/internal/provider/example_resource.go deleted file mode 100644 index af8c0fa..0000000 --- a/internal/provider/example_resource.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - "fmt" - "net/http" - - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -// Ensure provider defined types fully satisfy framework interfaces. -var _ resource.Resource = &ExampleResource{} -var _ resource.ResourceWithImportState = &ExampleResource{} - -func NewExampleResource() resource.Resource { - return &ExampleResource{} -} - -// ExampleResource defines the resource implementation. -type ExampleResource struct { - client *http.Client -} - -// ExampleResourceModel describes the resource data model. -type ExampleResourceModel struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Defaulted types.String `tfsdk:"defaulted"` - Id types.String `tfsdk:"id"` -} - -func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_example" -} - -func (r *ExampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - // This description is used by the documentation generator and the language server. - MarkdownDescription: "Example resource", - - Attributes: map[string]schema.Attribute{ - "configurable_attribute": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute", - Optional: true, - }, - "defaulted": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute with default value", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("example value when not configured"), - }, - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Example identifier", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - } -} - -func (r *ExampleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*http.Client) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - r.client = client -} - -func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data *ExampleResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create example, got error: %s", err)) - // return - // } - - // For the purposes of this example code, hardcoding a response value to - // save into the Terraform state. - data.Id = types.StringValue("example-id") - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a resource") - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data *ExampleResourceModel - - // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) - // return - // } - - // Save updated data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data *ExampleResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update example, got error: %s", err)) - // return - // } - - // Save updated data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data *ExampleResourceModel - - // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err)) - // return - // } -} - -func (r *ExampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) -} diff --git a/internal/provider/models.go b/internal/provider/models.go new file mode 100644 index 0000000..a736d81 --- /dev/null +++ b/internal/provider/models.go @@ -0,0 +1,26 @@ +package provider + +import "github.com/hashicorp/terraform-plugin-framework/types" + +// SubdomainModel describes the data source data model. +type SubdomainModel struct { + Zone types.String `tfsdk:"zone"` + Scope types.String `tfsdk:"scope"` + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` + Type []TypeModel `tfsdk:"type"` +} +type TypeModel struct { + Type types.String `tfsdk:"type"` + Values []types.String `tfsdk:"values"` + TTL types.Int64 `tfsdk:"ttl"` +} + +type RecordModel struct { + Zone types.String `tfsdk:"zone"` + Scope types.String `tfsdk:"scope"` + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` + Values []types.String `tfsdk:"values"` + TTL types.Int64 `tfsdk:"ttl"` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c326755..7d244a6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,49 +5,110 @@ package provider import ( "context" - "net/http" - "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/topicusonderwijs/terraform-provider-octodns/internal/models" ) -// Ensure ScaffoldingProvider satisfies various provider interfaces. -var _ provider.Provider = &ScaffoldingProvider{} +const ENVPREFIX = "OCTODNS_" + +// Ensure OctodnsProvider satisfies various provider interfaces. +var _ provider.Provider = &OctodnsProvider{} -// ScaffoldingProvider defines the provider implementation. -type ScaffoldingProvider struct { +// OctodnsProvider defines the provider implementation. +type OctodnsProvider 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 } -// ScaffoldingProviderModel describes the provider data model. -type ScaffoldingProviderModel struct { - Endpoint types.String `tfsdk:"endpoint"` +// OctodnsProviderModel describes the provider data model. +type OctodnsProviderModel struct { + GitProvider types.String `tfsdk:"git_provider"` + GithubAccessToken types.String `tfsdk:"github_access_token"` + GithubOrg types.String `tfsdk:"github_org"` + GithubRepo types.String `tfsdk:"github_repo"` + + Scopes []struct { + Name types.String `tfsdk:"name"` + Path types.String `tfsdk:"path"` + } `tfsdk:"scope"` } -func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { - resp.TypeName = "scaffolding" +func (p *OctodnsProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "octodns" resp.Version = p.version } -func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { +/* + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: ""}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := github.NewClient(tc) + + opt := &github.RepositoryListOptions{ListOptions: github.ListOptions{PerPage: 2}} + opt.Page = 2 + + //(fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) { + + fileContent, dirContent, resp, err := client.Repositories.GetContents(ctx, "topicusonderwijs", "dns-topicus.education", "overlays/pdc/zones/pdc.topicus.education.yaml", nil) + + +*/ + +func (p *OctodnsProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ - "endpoint": schema.StringAttribute{ - MarkdownDescription: "Example provider attribute", + "git_provider": schema.StringAttribute{ + MarkdownDescription: "Git provider, only accepted value is github", Optional: true, }, + "github_access_token": schema.StringAttribute{ + MarkdownDescription: "Github personal access token", + Required: true, + Sensitive: true, + }, + "github_org": schema.StringAttribute{ + MarkdownDescription: "Github personal access token", + Required: true, + }, + "github_repo": schema.StringAttribute{ + MarkdownDescription: "Github personal access token", + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "scope": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Optional: true, + }, + "path": schema.StringAttribute{ + Required: true, + }, + }, + }, + CustomType: nil, + Description: "", + MarkdownDescription: "", + DeprecationMessage: "", + Validators: nil, + }, }, } } -func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - var data ScaffoldingProviderModel +func (p *OctodnsProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data OctodnsProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -55,30 +116,121 @@ func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.Config return } + gitprovider := "github" + // Configuration values are now available. - // if data.Endpoint.IsNull() { /* ... */ } + if data.GitProvider.IsNull() { /* ... */ + gitprovider = "github" + + if data.GithubAccessToken.IsNull() { + resp.Diagnostics.AddError( + "Missing Github API access Configuration", + "While configuring the provider, the Github access token was not found in "+ + "provider configuration block github_access_token attribute.", + ) + } + if data.GithubOrg.IsNull() { + resp.Diagnostics.AddError( + "Missing Github Organisation Configuration", + "While configuring the provider, the Github Organisation was not found in "+ + "provider configuration block github_org attribute.", + ) + } + if data.GithubRepo.IsNull() { + resp.Diagnostics.AddError( + "Missing Github repo Configuration", + "While configuring the provider, the Github repo was not found in "+ + "provider configuration block github_repo attribute.", + ) + } + + } else if data.GitProvider.ValueString() != "github" { /* ... */ + resp.Diagnostics.AddWarning( + "Unsupported Git Provider Configuration", + "While configuring the provider, an invalid value was found for git_provider attribute. "+ + "Allowed values: github ", + ) + } + + var client models.GitClient + var err error + switch gitprovider { + default: + client, err = models.NewGitHubClient(data.GithubAccessToken.ValueString(), data.GithubOrg.ValueString(), data.GithubRepo.ValueString()) + } + + if err != nil { + resp.Diagnostics.AddError( + "Could not create Github client", + "While configuring the provider, the Github client failed to configure: "+ + err.Error(), + ) + } + + if len(data.Scopes) == 0 { + err = client.AddScope("default", "/zones") + } else { + for _, v := range data.Scopes { + + err = client.AddScope(v.Name.ValueString(), v.Path.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Could not add scope", err.Error()) + } + + } + } + + // Record client configuration for data sources and resources - // Example client configuration for data sources and resources - client := http.DefaultClient resp.DataSourceData = client resp.ResourceData = client } -func (p *ScaffoldingProvider) Resources(ctx context.Context) []func() resource.Resource { +func (p *OctodnsProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ - NewExampleResource, + NewSubdomainResource, + NewARecordResource, + NewAAAARecordResource, + NewCAARecordResource, + NewCNAMERecordResource, + NewDNAMERecordResource, + NewLOCRecordResource, + NewMXRecordResource, + NewNAPTRRecordResource, + NewNSRecordResource, + NewPTRRecordResource, + NewSPFRecordResource, + NewSRVRecordResource, + NewSSHFPRecordResource, + NewTXTRecordResource, + NewURLFWDRecordResource, } } -func (p *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { +func (p *OctodnsProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - NewExampleDataSource, + NewSubdomainDataSource, + NewARecordDataSource, + NewAAAARecordDataSource, + NewCAARecordDataSource, + NewCNAMERecordDataSource, + NewDNAMERecordDataSource, + NewLOCRecordDataSource, + NewMXRecordDataSource, + NewNAPTRRecordDataSource, + NewNSRecordDataSource, + NewPTRRecordDataSource, + NewSPFRecordDataSource, + NewSRVRecordDataSource, + NewSSHFPRecordDataSource, + NewTXTRecordDataSource, + NewURLFWDRecordDataSource, } } func New(version string) func() provider.Provider { return func() provider.Provider { - return &ScaffoldingProvider{ + return &OctodnsProvider{ version: version, } } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index ef6599b..756ca0c 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -15,7 +15,7 @@ import ( // CLI command executed to create a provider server to which the CLI can // reattach. var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ - "scaffolding": providerserver.NewProtocol6WithError(New("test")()), + "octodns": providerserver.NewProtocol6WithError(New("test")()), } func testAccPreCheck(t *testing.T) { diff --git a/internal/provider/record_data_source.go b/internal/provider/record_data_source.go new file mode 100644 index 0000000..36cb706 --- /dev/null +++ b/internal/provider/record_data_source.go @@ -0,0 +1,218 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/topicusonderwijs/terraform-provider-octodns/internal/models" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &SubdomainDataSource{} + +// RTYPE_A RType = "a" +func NewARecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_A} +} + +// RTYPE_AAAA RType = "aaaa" +func NewAAAARecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_AAAA} +} + +// RTYPE_CAA RType = "caa" +func NewCAARecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_CAA} +} + +// RTYPE_CNAME RType = "cname" +func NewCNAMERecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_CNAME} +} + +// RTYPE_DNAME RType = "dname" +func NewDNAMERecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_DNAME} +} + +// RTYPE_LOC RType = "loc" +func NewLOCRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_LOC} +} + +// RTYPE_MX RType = "mx" +func NewMXRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_MX} +} + +// RTYPE_NAPTR RType = "naptr" +func NewNAPTRRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_NAPTR} +} + +// RTYPE_NS RType = "ns" +func NewNSRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_NS} +} + +// RTYPE_PTR RType = "ptr" +func NewPTRRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_PTR} +} + +// RTYPE_SPF RType = "spf" +func NewSPFRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_SPF} +} + +// RTYPE_SRV RType = "srv" +func NewSRVRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_SRV} +} + +// RTYPE_SSHFP RType = "sshfp" +func NewSSHFPRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_SSHFP} +} + +// RTYPE_TXT RType = "txt" +func NewTXTRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_TXT} +} + +// RTYPE_URLFWD RType = "urlfwd" +func NewURLFWDRecordDataSource() datasource.DataSource { + return &RecordDataSource{rtype: &models.TYPE_URLFWD} +} + +func NewRecordDataSource() datasource.DataSource { + return &RecordDataSource{} +} + +// RecordDataSource defines the data source implementation. +type RecordDataSource struct { + rtype *models.RType + client *models.GitHubClient +} + +func (d *RecordDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + d.rtype.LowerString() + "_record" +} + +func (d *RecordDataSource) 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: "Record data source", + + Attributes: map[string]schema.Attribute{ + "zone": schema.StringAttribute{ + MarkdownDescription: "Record Zone", + Required: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of zone", + Optional: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Record Zone", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 256), + }, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Record identifier", + Computed: true, + }, + "values": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + }, + "ttl": schema.Int64Attribute{ + Computed: true, + }, + }, + } +} + +func (d *RecordDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*models.GitHubClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *models.GitHubClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *RecordDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data RecordModel + + if data.Name.String() == "" { + data.Name = types.StringValue("@") + } + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + + return + } + + tflog.Trace(ctx, fmt.Sprintf("==== Trying to load %s from %s/%s", data.Name.ValueString(), data.Scope.ValueString(), data.Zone.ValueString())) + + zone, err := d.client.GetZone(data.Zone.ValueString(), data.Scope.ValueString()) + tflog.Trace(ctx, fmt.Sprintf("==== After Zone ==== %s", "")) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Could not retrieve zone: %s", err.Error())) + return + } + + r, err := zone.FindRecord(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read record, got error: %s", err)) + return + } + + rt, err := r.GetType(d.rtype.String()) + + if rt.TTL > 0 { + data.TTL = types.Int64Value(int64(rt.TTL)) + } else { + data.TTL = types.Int64Null() + } + + for _, v := range rt.ValuesAsString() { + data.Values = append(data.Values, types.StringValue(v)) + } + + // For the purposes of this example code, hardcoding a response value to + // save into the Terraform state. + data.Id = types.StringValue(fmt.Sprintf("%s %s %s", data.Scope.ValueString(), data.Zone.ValueString(), data.Name.ValueString())) + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "read a data source") + + // UpdateYaml data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/record_resource.go b/internal/provider/record_resource.go new file mode 100644 index 0000000..f733821 --- /dev/null +++ b/internal/provider/record_resource.go @@ -0,0 +1,318 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/topicusonderwijs/terraform-provider-octodns/internal/models" + "log" + "strings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &RecordResource{} +var _ resource.ResourceWithImportState = &RecordResource{} + +// RTYPE_A RType = "a" +func NewARecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_A} +} + +// RTYPE_AAAA RType = "aaaa" +func NewAAAARecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_AAAA} +} + +// RTYPE_CAA RType = "caa" +func NewCAARecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_CAA} +} + +// RTYPE_CNAME RType = "cname" +func NewCNAMERecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_CNAME} +} + +// RTYPE_DNAME RType = "dname" +func NewDNAMERecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_DNAME} +} + +// RTYPE_LOC RType = "loc" +func NewLOCRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_LOC} +} + +// RTYPE_MX RType = "mx" +func NewMXRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_MX} +} + +// RTYPE_NAPTR RType = "naptr" +func NewNAPTRRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_NAPTR} +} + +// RTYPE_NS RType = "ns" +func NewNSRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_NS} +} + +// RTYPE_PTR RType = "ptr" +func NewPTRRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_PTR} +} + +// RTYPE_SPF RType = "spf" +func NewSPFRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_SPF} +} + +// RTYPE_SRV RType = "srv" +func NewSRVRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_SRV} +} + +// RTYPE_SSHFP RType = "sshfp" +func NewSSHFPRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_SSHFP} +} + +// RTYPE_TXT RType = "txt" +func NewTXTRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_TXT} +} + +// RTYPE_URLFWD RType = "urlfwd" +func NewURLFWDRecordResource() resource.Resource { + return &RecordResource{rtype: &models.TYPE_URLFWD} +} + +func NewRecordResource() resource.Resource { + return &RecordResource{rtype: nil} +} + +// RecordResource defines the resource implementation. +type RecordResource struct { + rtype *models.RType + client *models.GitHubClient +} + +func (r *RecordResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + r.rtype.LowerString() + "_record" +} + +func (r *RecordResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Record resource", + + Attributes: map[string]schema.Attribute{ + "zone": schema.StringAttribute{ + MarkdownDescription: "Record Zone", + Required: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of zone", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Record Zone", + Required: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Record identifier", + Computed: true, + }, + "values": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + }, + "ttl": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(3600), + }, + }, + } +} + +func (r *RecordResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*models.GitHubClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *RecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *SubdomainModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + zone, err := r.client.GetZone(data.Zone.ValueString(), data.Scope.ValueString()) + tflog.Trace(ctx, fmt.Sprintf("==== After Zone ==== %s", "")) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Could not retrieve zone: %s", err.Error())) + return + } + + record, err := zone.FindRecord(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read record, got error: %s", err)) + return + } + + record.FindAllType() + + //data.Type = []TypeModel{} + + for _, t := range record.Types { + + log.Print("Loop Types ", t.Type) + + rt := TypeModel{ + Type: types.StringValue(t.Type), + TTL: types.Int64Value(int64(t.TTL)), + } + for _, v := range t.ValuesAsString() { + rt.Values = append(rt.Values, types.StringValue(v)) + } + + data.Type = append(data.Type, rt) + } + + // Set ID + data.Id = types.StringValue(fmt.Sprintf("%s %s %s", data.Scope.ValueString(), data.Zone.ValueString(), data.Name.ValueString())) + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "created a resource") + + // UpdateYaml data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *RecordResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *SubdomainModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + parts := strings.Split(data.Id.ValueString(), " ") + if len(parts) != 3 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Malformed ID: %s", data.Id.String())) + return + } + + data.Scope = types.StringValue(parts[0]) + data.Zone = types.StringValue(parts[1]) + data.Name = types.StringValue(parts[2]) + + tflog.Trace(ctx, fmt.Sprintf("==== Trying to load %s from %s/%s", data.Name.ValueString(), data.Scope.ValueString(), data.Zone.ValueString())) + + zone, err := r.client.GetZone(data.Zone.ValueString(), data.Scope.ValueString()) + tflog.Trace(ctx, fmt.Sprintf("==== After Zone ==== %s", "")) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Could not retrieve zone: %s", err.Error())) + return + } + + record, err := zone.FindRecord(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read record, got error: %s", err)) + return + } + + record.FindAllType() + + for _, t := range record.Types { + + log.Print("Loop Types ", t.Type) + + rt := TypeModel{ + Type: types.StringValue(t.Type), + TTL: types.Int64Value(int64(t.TTL)), + } + for _, v := range t.ValuesAsString() { + rt.Values = append(rt.Values, types.StringValue(v)) + } + + data.Type = append(data.Type, rt) + } + // UpdateYaml updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *RecordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *SubdomainModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update record, got error: %s", err)) + // return + // } + + // UpdateYaml updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *RecordResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *SubdomainModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete record, got error: %s", err)) + // return + // } +} + +func (r *RecordResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/subdomain_data_source.go b/internal/provider/subdomain_data_source.go new file mode 100644 index 0000000..7379e40 --- /dev/null +++ b/internal/provider/subdomain_data_source.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "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-log/tflog" + "github.com/topicusonderwijs/terraform-provider-octodns/internal/models" + "log" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &SubdomainDataSource{} + +func NewSubdomainDataSource() datasource.DataSource { + return &SubdomainDataSource{} +} + +// SubdomainDataSource defines the data source implementation. +type SubdomainDataSource struct { + client *models.GitHubClient +} + +func (d *SubdomainDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_subdomain" +} + +func (d *SubdomainDataSource) 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: "Subdomain data source", + + Attributes: map[string]schema.Attribute{ + "zone": schema.StringAttribute{ + MarkdownDescription: "Zone of subdomain", + Required: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of zone", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of subdomain", + Required: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Subdomain identifier", + Computed: true, + }, + "type": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Computed: true, + }, + "values": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + }, + "ttl": schema.Int64Attribute{ + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *SubdomainDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*models.GitHubClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *models.GitHubClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *SubdomainDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SubdomainModel + + if data.Name.String() == "" { + data.Name = types.StringValue("@") + } + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + + return + } + + tflog.Trace(ctx, fmt.Sprintf("==== Trying to load %s from %s/%s", data.Name.ValueString(), data.Scope.ValueString(), data.Zone.ValueString())) + + zone, err := d.client.GetZone(data.Zone.ValueString(), data.Scope.ValueString()) + tflog.Trace(ctx, fmt.Sprintf("==== After Zone ==== %s", "")) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Could not retrieve zone: %s", err.Error())) + return + } + + r, err := zone.FindRecord(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read record, got error: %s", err)) + return + } + + r.FindAllType() + + // data.Type = make(map[string]TypeModel) + + for _, t := range r.Types { + + log.Print("Loop Types ", t.Type) + + rt := TypeModel{ + Type: types.StringValue(t.Type), + TTL: types.Int64Value(int64(t.TTL)), + } + for _, v := range t.ValuesAsString() { + rt.Values = append(rt.Values, types.StringValue(v)) + } + + data.Type = append(data.Type, rt) + } + + // For the purposes of this example code, hardcoding a response value to + // save into the Terraform state. + data.Id = types.StringValue(fmt.Sprintf("%s %s %s", data.Scope.ValueString(), data.Zone.ValueString(), data.Name.ValueString())) + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "read a data source") + + // UpdateYaml data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/example_data_source_test.go b/internal/provider/subdomain_data_source_test.go similarity index 57% rename from internal/provider/example_data_source_test.go rename to internal/provider/subdomain_data_source_test.go index 6f9aa7d..1c4cc05 100644 --- a/internal/provider/example_data_source_test.go +++ b/internal/provider/subdomain_data_source_test.go @@ -9,24 +9,27 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestAccExampleDataSource(t *testing.T) { +func TestAccRecordDataSource(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ // Read testing { - Config: testAccExampleDataSourceConfig, + Config: testAccRecordDataSourceConfig, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.scaffolding_example.test", "id", "example-id"), + resource.TestCheckResourceAttr("data.octodns_record.test", "id", "external-topicus.education-"), ), }, }, }) } -const testAccExampleDataSourceConfig = ` -data "scaffolding_example" "test" { - configurable_attribute = "example" +const testAccRecordDataSourceConfig = ` +data "octodns_record" "test" { + id="external-topicus.education-" + name="" + score="external" + zone="example.com" } ` diff --git a/internal/provider/subdomain_resource.go b/internal/provider/subdomain_resource.go new file mode 100644 index 0000000..232d829 --- /dev/null +++ b/internal/provider/subdomain_resource.go @@ -0,0 +1,253 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/topicusonderwijs/terraform-provider-octodns/internal/models" + "log" + "strings" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &SubdomainResource{} +var _ resource.ResourceWithImportState = &SubdomainResource{} + +func NewSubdomainResource() resource.Resource { + return &SubdomainResource{} +} + +// SubdomainResource defines the resource implementation. +type SubdomainResource struct { + client *models.GitHubClient +} + +func (r *SubdomainResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_record" +} + +func (r *SubdomainResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Record resource", + + Attributes: map[string]schema.Attribute{ + "zone": schema.StringAttribute{ + MarkdownDescription: "Record Zone", + Required: true, + }, + "scope": schema.StringAttribute{ + MarkdownDescription: "Scope of zone", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Record Zone", + Required: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Record identifier", + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "type": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + }, + "values": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + }, + "ttl": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(3600), + }, + }, + }, + }, + }, + } +} + +func (r *SubdomainResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*models.GitHubClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *SubdomainResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *SubdomainModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + zone, err := r.client.GetZone(data.Zone.ValueString(), data.Scope.ValueString()) + tflog.Trace(ctx, fmt.Sprintf("==== After Zone ==== %s", "")) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Could not retrieve zone: %s", err.Error())) + return + } + + record, err := zone.FindRecord(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read record, got error: %s", err)) + return + } + + record.FindAllType() + + //data.Type = []TypeModel{} + + for _, t := range record.Types { + + log.Print("Loop Types ", t.Type) + + rt := TypeModel{ + Type: types.StringValue(t.Type), + TTL: types.Int64Value(int64(t.TTL)), + } + for _, v := range t.ValuesAsString() { + rt.Values = append(rt.Values, types.StringValue(v)) + } + + data.Type = append(data.Type, rt) + } + + // Set ID + data.Id = types.StringValue(fmt.Sprintf("%s %s %s", data.Scope.ValueString(), data.Zone.ValueString(), data.Name.ValueString())) + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "created a resource") + + // UpdateYaml data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *SubdomainResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *SubdomainModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + parts := strings.Split(data.Id.ValueString(), " ") + if len(parts) != 3 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Malformed ID: %s", data.Id.String())) + return + } + + data.Scope = types.StringValue(parts[0]) + data.Zone = types.StringValue(parts[1]) + data.Name = types.StringValue(parts[2]) + + tflog.Trace(ctx, fmt.Sprintf("==== Trying to load %s from %s/%s", data.Name.ValueString(), data.Scope.ValueString(), data.Zone.ValueString())) + + zone, err := r.client.GetZone(data.Zone.ValueString(), data.Scope.ValueString()) + tflog.Trace(ctx, fmt.Sprintf("==== After Zone ==== %s", "")) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Could not retrieve zone: %s", err.Error())) + return + } + + record, err := zone.FindRecord(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read record, got error: %s", err)) + return + } + + record.FindAllType() + + for _, t := range record.Types { + + log.Print("Loop Types ", t.Type) + + rt := TypeModel{ + Type: types.StringValue(t.Type), + TTL: types.Int64Value(int64(t.TTL)), + } + for _, v := range t.ValuesAsString() { + rt.Values = append(rt.Values, types.StringValue(v)) + } + + data.Type = append(data.Type, rt) + } + // UpdateYaml updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *SubdomainResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *SubdomainModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update record, got error: %s", err)) + // return + // } + + // UpdateYaml updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *SubdomainResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *SubdomainModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete record, got error: %s", err)) + // return + // } +} + +func (r *SubdomainResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/example_resource_test.go b/internal/provider/subdomain_resource_test.go similarity index 58% rename from internal/provider/example_resource_test.go rename to internal/provider/subdomain_resource_test.go index c5464d0..a2f7f23 100644 --- a/internal/provider/example_resource_test.go +++ b/internal/provider/subdomain_resource_test.go @@ -10,36 +10,36 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestAccExampleResource(t *testing.T) { +func TestAccRecordResource(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ // Create and Read testing { - Config: testAccExampleResourceConfig("one"), + Config: testAccRecordResourceConfig("one"), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "one"), - resource.TestCheckResourceAttr("scaffolding_example.test", "defaulted", "example value when not configured"), - resource.TestCheckResourceAttr("scaffolding_example.test", "id", "example-id"), + resource.TestCheckResourceAttr("octodns_record.test", "configurable_attribute", "one"), + resource.TestCheckResourceAttr("octodns_record.test", "defaulted", "record value when not configured"), + resource.TestCheckResourceAttr("octodns_record.test", "id", "record-id"), ), }, // ImportState testing { - ResourceName: "scaffolding_example.test", + ResourceName: "octodns_record.test", ImportState: true, ImportStateVerify: true, // This is not normally necessary, but is here because this - // example code does not have an actual upstream service. + // record code does not have an actual upstream service. // Once the Read method is able to refresh information from // the upstream service, this can be removed. ImportStateVerifyIgnore: []string{"configurable_attribute", "defaulted"}, }, // Update and Read testing { - Config: testAccExampleResourceConfig("two"), + Config: testAccRecordResourceConfig("two"), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "two"), + resource.TestCheckResourceAttr("octodns_record.test", "configurable_attribute", "two"), ), }, // Delete testing automatically occurs in TestCase @@ -47,9 +47,9 @@ func TestAccExampleResource(t *testing.T) { }) } -func testAccExampleResourceConfig(configurableAttribute string) string { +func testAccRecordResourceConfig(configurableAttribute string) string { return fmt.Sprintf(` -resource "scaffolding_example" "test" { +resource "octodns_record" "test" { configurable_attribute = %[1]q } `, configurableAttribute) diff --git a/main.go b/main.go index e3d16b2..3fd0597 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/hashicorp/terraform-provider-scaffolding-framework/internal/provider" + "github.com/topicusonderwijs/terraform-provider-octodns/internal/provider" ) // Run "go generate" to format example terraform files and generate the docs for the registry/website @@ -39,7 +39,7 @@ func main() { opts := providerserver.ServeOpts{ // TODO: Update this string with the published name of your provider. - Address: "registry.terraform.io/hashicorp/scaffolding", + Address: "registry.terraform.io/topicusonderwijs/octodns", Debug: debug, }