[0-9]+)(?.*)") } |
46 | select(.semver.pre == "") |
47 | {
48 | version: .version,
49 | group: (.semver.major + "." + .semver.minor),
50 | major: .semver.major | tonumber,
51 | minor: .semver.minor | tonumber,
52 | patch: .semver.patch | tonumber
53 | }
54 | ] | group_by(.group) | [ .[] | sort_by(.patch) | .[-1] | .version ] |
55 | sort_by(.|split(".")|map(tonumber)) | .[-2:]')" >> "$GITHUB_OUTPUT"
56 | outputs:
57 | terraform: ${{ steps.versions.outputs.terraform }}
58 |
59 | test:
60 | runs-on: ubuntu-latest
61 | needs: versions
62 | timeout-minutes: 15
63 | strategy:
64 | fail-fast: false
65 | max-parallel: 1
66 | matrix:
67 | terraform: ${{ fromJSON(needs.versions.outputs.terraform) }}
68 | concurrency:
69 | group: test
70 | cancel-in-progress: false
71 | steps:
72 | - uses: actions/checkout@v5
73 | - uses: actions/setup-go@v6
74 | with:
75 | go-version-file: go.mod
76 | check-latest: true
77 | cache: true
78 | - run: make testacc
79 | env:
80 | TF_ACC_TERRAFORM_VERSION: ${{ matrix.terraform }}
81 | OHDEAR_TOKEN: ${{ secrets.OHDEAR_TOKEN }}
82 | OHDEAR_TEAM_ID: 6944
83 |
--------------------------------------------------------------------------------
/internal/provider/provider.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
11 |
12 | "github.com/articulate/terraform-provider-ohdear/internal/runtime"
13 | "github.com/articulate/terraform-provider-ohdear/pkg/ohdear"
14 | )
15 |
16 | func init() {
17 | schema.DescriptionKind = schema.StringMarkdown
18 |
19 | // add defaults on to the exported descriptions if present
20 | schema.SchemaDescriptionBuilder = func(s *schema.Schema) string {
21 | desc := s.Description
22 | if s.Default != nil {
23 | desc += fmt.Sprintf(" Defaults to `%v`.", s.Default)
24 | }
25 | if s.Deprecated != "" {
26 | desc += " __Deprecated__: " + s.Deprecated
27 | }
28 | return strings.TrimSpace(desc)
29 | }
30 | }
31 |
32 | func New() *schema.Provider {
33 | return &schema.Provider{
34 | Schema: map[string]*schema.Schema{
35 | "api_token": {
36 | Type: schema.TypeString,
37 | Required: true,
38 | Description: "Oh Dear API token. If not set, uses `OHDEAR_TOKEN` env var.",
39 | DefaultFunc: schema.EnvDefaultFunc("OHDEAR_TOKEN", nil),
40 | },
41 | "api_url": {
42 | Type: schema.TypeString,
43 | Optional: true,
44 | Description: "Oh Dear API URL. If not set, uses `OHDEAR_API_URL` env var. Defaults to `https://ohdear.app`.",
45 | ValidateFunc: validation.IsURLWithHTTPorHTTPS,
46 | DefaultFunc: schema.EnvDefaultFunc("OHDEAR_API_URL", "https://ohdear.app"),
47 | },
48 | "team_id": {
49 | Type: schema.TypeInt,
50 | Optional: true,
51 | Description: "The default team ID to use for sites. If not set, uses `OHDEAR_TEAM_ID` env var.",
52 | DefaultFunc: schema.EnvDefaultFunc("OHDEAR_TEAM_ID", 0),
53 | },
54 | },
55 | ResourcesMap: map[string]*schema.Resource{
56 | "ohdear_site": resourceOhdearSite(),
57 | "ohdear_monitor": resourceOhdearMonitor(),
58 | },
59 | ConfigureContextFunc: providerConfigure,
60 | }
61 | }
62 |
63 | func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
64 | ua := fmt.Sprintf(
65 | "terraform-provider-ohdear/%s (https://github.com/articulate/terraform-provider-ohdear)",
66 | runtime.Version,
67 | )
68 | client := ohdear.NewClient(d.Get("api_url").(string), d.Get("api_token").(string))
69 | client.SetUserAgent(ua)
70 |
71 | return &Config{
72 | client: client,
73 | teamID: d.Get("team_id").(int),
74 | }, nil
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/ohdear/site_test.go:
--------------------------------------------------------------------------------
1 | package ohdear
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "testing"
7 |
8 | "github.com/jarcoal/httpmock"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestGetSite(t *testing.T) {
14 | _, reset := mocklog()
15 | t.Cleanup(reset)
16 | t.Cleanup(httpmock.DeactivateAndReset)
17 |
18 | resp, err := httpmock.NewJsonResponder(200, map[string]interface{}{
19 | "id": 1234,
20 | "type": "http",
21 | "url": "https://example.com",
22 | "team_id": 5678,
23 | "checks": []map[string]interface{}{
24 | {
25 | "id": 12,
26 | "type": "uptime",
27 | "enabled": true,
28 | },
29 | {
30 | "id": 34,
31 | "type": "performance",
32 | "enabled": false,
33 | },
34 | },
35 | })
36 | require.NoError(t, err)
37 | httpmock.RegisterResponder("GET", "https://ohdear.test/api/monitors/1234", resp)
38 |
39 | client := NewClient("https://ohdear.test", "")
40 | httpmock.ActivateNonDefault(client.GetClient())
41 |
42 | site, err := client.GetSite(1234)
43 | require.NoError(t, err)
44 | assert.Equal(t, 1234, site.ID)
45 | assert.Equal(t, "https://example.com", site.URL)
46 | assert.Equal(t, 5678, site.TeamID)
47 | assert.Len(t, site.Checks, 2)
48 | assert.Equal(t, 12, site.Checks[0].ID)
49 | assert.Equal(t, "uptime", site.Checks[0].Type)
50 | assert.True(t, site.Checks[0].Enabled)
51 | assert.Equal(t, "performance", site.Checks[1].Type)
52 | assert.False(t, site.Checks[1].Enabled)
53 | }
54 |
55 | func TestAddSite(t *testing.T) {
56 | _, reset := mocklog()
57 | t.Cleanup(reset)
58 | t.Cleanup(httpmock.DeactivateAndReset)
59 |
60 | httpmock.RegisterResponder("POST", "https://ohdear.test/api/monitors",
61 | func(req *http.Request) (*http.Response, error) {
62 | body, err := io.ReadAll(req.Body)
63 | require.NoError(t, err)
64 | assert.JSONEq(
65 | t,
66 | `{"checks":["uptime","performance","broken_links"],"team_id":5678,"url":"https://example.com/new","type":"http"}`,
67 | string(body),
68 | )
69 |
70 | return httpmock.NewJsonResponse(200, map[string]interface{}{
71 | "id": 4321,
72 | "type": "http",
73 | "url": "https://example.com/new",
74 | "team_id": 5678,
75 | "checks": []map[string]interface{}{
76 | {
77 | "id": 12,
78 | "type": "uptime",
79 | "enabled": true,
80 | },
81 | },
82 | })
83 | })
84 |
85 | client := NewClient("https://ohdear.test", "")
86 | httpmock.ActivateNonDefault(client.GetClient())
87 |
88 | site, err := client.AddSite("https://example.com/new", 5678, []string{"uptime", "performance", "broken_links"})
89 | require.NoError(t, err)
90 | assert.Equal(t, 4321, site.ID)
91 | assert.Equal(t, "https://example.com/new", site.URL)
92 | }
93 |
94 | func TestRemoveSite(t *testing.T) {
95 | _, reset := mocklog()
96 | t.Cleanup(reset)
97 | t.Cleanup(httpmock.DeactivateAndReset)
98 |
99 | httpmock.RegisterResponder("DELETE", "https://ohdear.test/api/monitors/1234", httpmock.NewStringResponder(204, ""))
100 |
101 | client := NewClient("https://ohdear.test", "")
102 | httpmock.ActivateNonDefault(client.GetClient())
103 |
104 | err := client.RemoveSite(1234)
105 | require.NoError(t, err)
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/ohdear/monitor_test.go:
--------------------------------------------------------------------------------
1 | package ohdear
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "testing"
7 |
8 | "github.com/jarcoal/httpmock"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestGetMonitor(t *testing.T) {
14 | _, reset := mocklog()
15 | t.Cleanup(reset)
16 | t.Cleanup(httpmock.DeactivateAndReset)
17 |
18 | resp, err := httpmock.NewJsonResponder(200, map[string]interface{}{
19 | "id": 1234,
20 | "url": "https://example.com",
21 | "team_id": 5678,
22 | "checks": []map[string]interface{}{
23 | {
24 | "id": 12,
25 | "type": "uptime",
26 | "enabled": true,
27 | },
28 | {
29 | "id": 34,
30 | "type": "performance",
31 | "enabled": false,
32 | },
33 | },
34 | })
35 | require.NoError(t, err)
36 | httpmock.RegisterResponder("GET", "https://ohdear.test/api/monitors/1234", resp)
37 |
38 | client := NewClient("https://ohdear.test", "")
39 | httpmock.ActivateNonDefault(client.GetClient())
40 |
41 | monitor, err := client.GetMonitor(1234)
42 | require.NoError(t, err)
43 | assert.Equal(t, 1234, monitor.ID)
44 | assert.Equal(t, "https://example.com", monitor.URL)
45 | assert.Equal(t, 5678, monitor.TeamID)
46 | assert.Len(t, monitor.Checks, 2)
47 | assert.Equal(t, 12, monitor.Checks[0].ID)
48 | assert.Equal(t, "uptime", monitor.Checks[0].Type)
49 | assert.True(t, monitor.Checks[0].Enabled)
50 | assert.Equal(t, "performance", monitor.Checks[1].Type)
51 | assert.False(t, monitor.Checks[1].Enabled)
52 | }
53 |
54 | func TestAddMonitor(t *testing.T) {
55 | _, reset := mocklog()
56 | t.Cleanup(reset)
57 | t.Cleanup(httpmock.DeactivateAndReset)
58 |
59 | httpmock.RegisterResponder("POST", "https://ohdear.test/api/monitors",
60 | func(req *http.Request) (*http.Response, error) {
61 | body, err := io.ReadAll(req.Body)
62 | require.NoError(t, err)
63 | assert.JSONEq(
64 | t,
65 | `{"checks":["uptime","performance","broken_links"],"team_id":5678,"url":"https://example.com/new","type":"http"}`,
66 | string(body),
67 | )
68 |
69 | return httpmock.NewJsonResponse(200, map[string]interface{}{
70 | "id": 4321,
71 | "url": "https://example.com/new",
72 | "team_id": 5678,
73 | "checks": []map[string]interface{}{
74 | {
75 | "id": 12,
76 | "type": "uptime",
77 | "enabled": true,
78 | },
79 | },
80 | })
81 | })
82 |
83 | client := NewClient("https://ohdear.test", "")
84 | httpmock.ActivateNonDefault(client.GetClient())
85 |
86 | monitor, err := client.AddMonitor("https://example.com/new", 5678, []string{"uptime", "performance", "broken_links"})
87 | require.NoError(t, err)
88 | assert.Equal(t, 4321, monitor.ID)
89 | assert.Equal(t, "https://example.com/new", monitor.URL)
90 | }
91 |
92 | func TestRemoveMonitor(t *testing.T) {
93 | _, reset := mocklog()
94 | t.Cleanup(reset)
95 | t.Cleanup(httpmock.DeactivateAndReset)
96 |
97 | httpmock.RegisterResponder("DELETE", "https://ohdear.test/api/monitors/1234", httpmock.NewStringResponder(204, ""))
98 |
99 | client := NewClient("https://ohdear.test", "")
100 | httpmock.ActivateNonDefault(client.GetClient())
101 |
102 | err := client.RemoveMonitor(1234)
103 | require.NoError(t, err)
104 | }
105 |
--------------------------------------------------------------------------------
/pkg/ohdear/client_test.go:
--------------------------------------------------------------------------------
1 | package ohdear
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "os"
7 | "strconv"
8 | "testing"
9 | "time"
10 |
11 | "github.com/jarcoal/httpmock"
12 | "github.com/stretchr/testify/assert"
13 | "github.com/stretchr/testify/require"
14 | )
15 |
16 | func TestClient(t *testing.T) {
17 | token := os.Getenv("OHDEAR_TOKEN")
18 | teamID := os.Getenv("OHDEAR_TEAM_ID")
19 | if token == "" || teamID == "" || os.Getenv("TF_ACC") == "" {
20 | t.Skip("Integration tests skipped unless 'OHDEAR_TOKEN', 'OHDEAR_TEAM_ID', and 'TF_ACC' env vars are set")
21 | }
22 |
23 | if os.Getenv("SKIP_INTEGRATION_TESTS") != "" {
24 | t.Skip("Integration tests skipped because SKIP_INTEGRATION_TESTS env var set")
25 | }
26 |
27 | team, err := strconv.Atoi(teamID)
28 | require.NoError(t, err)
29 |
30 | url := os.Getenv("OHDEAR_API_URL")
31 | if url == "" {
32 | url = "https://ohdear.app"
33 | }
34 |
35 | _, reset := mocklog()
36 | // if we defer, we get log leakage from the other cleanup function
37 | t.Cleanup(reset)
38 |
39 | ua := "terraform-provider-ohdear/TEST (https://github.com/articulate/terraform-provider-ohdear) integration-tests"
40 | client := NewClient(url, token)
41 | client.SetDebug(false)
42 | client.SetUserAgent(ua)
43 |
44 | create, err := client.AddSite("https://example.com", team, []string{"uptime"})
45 | require.NoError(t, err)
46 |
47 | // make sure we remove the site even if tests fail
48 | t.Cleanup(func() {
49 | var e *Error
50 | if !errors.As(err, &e) {
51 | t.Fatal("site was not removed from OhDear")
52 | }
53 |
54 | if e.Response.StatusCode() != 404 {
55 | t.Fatal("site was not removed from Oh Dear")
56 | }
57 | })
58 |
59 | uptime, enabled := getCheckInfo(create)
60 |
61 | assert.Equal(t, "https://example.com", create.URL)
62 | assert.ElementsMatch(t, []string{"uptime", "performance"}, enabled)
63 |
64 | // get the site
65 | site, err := client.GetSite(create.ID)
66 | require.NoError(t, err)
67 | assert.Equal(t, create, site)
68 |
69 | // disable the uptime check
70 | err = client.DisableCheck(uptime)
71 | require.NoError(t, err)
72 | update, err := client.GetSite(site.ID)
73 | require.NoError(t, err)
74 | _, enabled = getCheckInfo(update)
75 | assert.Empty(t, enabled)
76 |
77 | // enable the uptime check
78 | err = client.EnableCheck(uptime)
79 | require.NoError(t, err)
80 | update, err = client.GetSite(site.ID)
81 | require.NoError(t, err)
82 | _, enabled = getCheckInfo(update)
83 | assert.ElementsMatch(t, []string{"uptime", "performance"}, enabled)
84 |
85 | // delete the site
86 | err = client.RemoveSite(site.ID)
87 | require.NoError(t, err)
88 |
89 | // verify it was deleted (wait because sometimes it takes the api a second to update)
90 | time.Sleep(5 * time.Second)
91 | removed, err := client.GetSite(site.ID)
92 | assert.Nil(t, removed)
93 |
94 | var e *Error
95 | require.ErrorAs(t, err, &e)
96 | assert.Equal(t, 404, e.Response.StatusCode())
97 | }
98 |
99 | func TestSetUserAgent(t *testing.T) {
100 | _, reset := mocklog()
101 | t.Cleanup(reset)
102 | t.Cleanup(httpmock.DeactivateAndReset)
103 |
104 | httpmock.RegisterResponder("GET", "https://ohdear.test/ping",
105 | func(req *http.Request) (*http.Response, error) {
106 | assert.Equal(t, "application/json", req.Header.Get("Accept"))
107 | assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
108 | assert.Equal(t, "user-agent/test", req.Header.Get("User-Agent"))
109 |
110 | return httpmock.NewStringResponse(200, ""), nil
111 | })
112 |
113 | client := NewClient("https://ohdear.test", "")
114 | httpmock.ActivateNonDefault(client.GetClient())
115 |
116 | client.SetUserAgent("user-agent/test")
117 | _, err := client.R().Get("/ping")
118 | require.NoError(t, err)
119 | }
120 |
121 | func getCheckInfo(s *Site) (int, []string) {
122 | uptime := 0
123 | enabled := []string{}
124 | for _, check := range s.Checks {
125 | if check.Enabled {
126 | enabled = append(enabled, check.Type)
127 | }
128 | if check.Type == "uptime" {
129 | uptime = check.ID
130 | }
131 | }
132 |
133 | return uptime, enabled
134 | }
135 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/articulate/terraform-provider-ohdear
2 |
3 | go 1.24.0
4 |
5 | require (
6 | github.com/go-resty/resty/v2 v2.16.5
7 | github.com/hashicorp/terraform-plugin-docs v0.23.0
8 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1
9 | github.com/jarcoal/httpmock v1.4.1
10 | github.com/stretchr/testify v1.11.1
11 | )
12 |
13 | require (
14 | github.com/BurntSushi/toml v1.2.1 // indirect
15 | github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
16 | github.com/Masterminds/goutils v1.1.1 // indirect
17 | github.com/Masterminds/semver/v3 v3.2.0 // indirect
18 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect
19 | github.com/ProtonMail/go-crypto v1.1.6 // indirect
20 | github.com/agext/levenshtein v1.2.2 // indirect
21 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
22 | github.com/armon/go-radix v1.0.0 // indirect
23 | github.com/bgentry/speakeasy v0.1.0 // indirect
24 | github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
25 | github.com/cloudflare/circl v1.6.1 // indirect
26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
27 | github.com/fatih/color v1.16.0 // indirect
28 | github.com/golang/protobuf v1.5.4 // indirect
29 | github.com/google/go-cmp v0.7.0 // indirect
30 | github.com/google/uuid v1.6.0 // indirect
31 | github.com/hashicorp/cli v1.1.7 // indirect
32 | github.com/hashicorp/errwrap v1.1.0 // indirect
33 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect
34 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
35 | github.com/hashicorp/go-cty v1.5.0 // indirect
36 | github.com/hashicorp/go-hclog v1.6.3 // indirect
37 | github.com/hashicorp/go-multierror v1.1.1 // indirect
38 | github.com/hashicorp/go-plugin v1.7.0 // indirect
39 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
40 | github.com/hashicorp/go-uuid v1.0.3 // indirect
41 | github.com/hashicorp/go-version v1.7.0 // indirect
42 | github.com/hashicorp/hc-install v0.9.2 // indirect
43 | github.com/hashicorp/hcl/v2 v2.24.0 // indirect
44 | github.com/hashicorp/logutils v1.0.0 // indirect
45 | github.com/hashicorp/terraform-exec v0.23.1 // indirect
46 | github.com/hashicorp/terraform-json v0.27.1 // indirect
47 | github.com/hashicorp/terraform-plugin-go v0.29.0 // indirect
48 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
49 | github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
50 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect
51 | github.com/hashicorp/yamux v0.1.2 // indirect
52 | github.com/huandu/xstrings v1.3.3 // indirect
53 | github.com/imdario/mergo v0.3.15 // indirect
54 | github.com/mattn/go-colorable v0.1.14 // indirect
55 | github.com/mattn/go-isatty v0.0.20 // indirect
56 | github.com/mattn/go-runewidth v0.0.9 // indirect
57 | github.com/mitchellh/copystructure v1.2.0 // indirect
58 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect
59 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect
60 | github.com/mitchellh/mapstructure v1.5.0 // indirect
61 | github.com/mitchellh/reflectwalk v1.0.2 // indirect
62 | github.com/oklog/run v1.1.0 // indirect
63 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
64 | github.com/posener/complete v1.2.3 // indirect
65 | github.com/shopspring/decimal v1.3.1 // indirect
66 | github.com/spf13/cast v1.5.0 // indirect
67 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
68 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
69 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
70 | github.com/yuin/goldmark v1.7.7 // indirect
71 | github.com/yuin/goldmark-meta v1.1.0 // indirect
72 | github.com/zclconf/go-cty v1.17.0 // indirect
73 | go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
74 | golang.org/x/crypto v0.42.0 // indirect
75 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
76 | golang.org/x/mod v0.27.0 // indirect
77 | golang.org/x/net v0.43.0 // indirect
78 | golang.org/x/sync v0.17.0 // indirect
79 | golang.org/x/sys v0.36.0 // indirect
80 | golang.org/x/text v0.29.0 // indirect
81 | golang.org/x/tools v0.36.0 // indirect
82 | google.golang.org/appengine v1.6.8 // indirect
83 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
84 | google.golang.org/grpc v1.75.1 // indirect
85 | google.golang.org/protobuf v1.36.9 // indirect
86 | gopkg.in/yaml.v2 v2.3.0 // indirect
87 | gopkg.in/yaml.v3 v3.0.1 // indirect
88 | )
89 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [3.0.2](https://github.com/articulate/terraform-provider-ohdear/compare/v3.0.1...v3.0.2) (2025-10-08)
4 |
5 |
6 | ### Dependency Updates
7 |
8 | * bump the terraform group with 2 updates ([#171](https://github.com/articulate/terraform-provider-ohdear/issues/171)) ([f9944fd](https://github.com/articulate/terraform-provider-ohdear/commit/f9944fd54530f0266a275b4bada072b755f32ba7))
9 |
10 | ## [3.0.1](https://github.com/articulate/terraform-provider-ohdear/compare/v3.0.0...v3.0.1) (2025-09-02)
11 |
12 |
13 | ### Dependency Updates
14 |
15 | * bump the terraform group across 1 directory with 2 updates ([#162](https://github.com/articulate/terraform-provider-ohdear/issues/162)) ([c6aa173](https://github.com/articulate/terraform-provider-ohdear/commit/c6aa173b6d6432a287a844c39a218088b5db2c2c))
16 | * bump the test group with 2 updates ([#167](https://github.com/articulate/terraform-provider-ohdear/issues/167)) ([601268f](https://github.com/articulate/terraform-provider-ohdear/commit/601268f35d7114037bdc948300f0baa59915fa8a))
17 |
18 | ## [3.0.0](https://github.com/articulate/terraform-provider-ohdear/compare/v2.3.0...v3.0.0) (2025-08-28)
19 |
20 |
21 | ### ⚠ BREAKING CHANGES
22 |
23 | * deprecate ohdear_site in favor of new ohdear_monitor ([#165](https://github.com/articulate/terraform-provider-ohdear/issues/165))
24 |
25 | ### Features
26 |
27 | * deprecate ohdear_site in favor of new ohdear_monitor ([#165](https://github.com/articulate/terraform-provider-ohdear/issues/165)) ([935bdc0](https://github.com/articulate/terraform-provider-ohdear/commit/935bdc0b1f351de92156edc971e3fb5407cb5794))
28 |
29 | ## [2.3.0](https://github.com/articulate/terraform-provider-ohdear/compare/v2.2.4...v2.3.0) (2025-05-08)
30 |
31 |
32 | ### Features
33 |
34 | * upgrade golangci-lint to v2 ([#155](https://github.com/articulate/terraform-provider-ohdear/issues/155)) ([f8c71e7](https://github.com/articulate/terraform-provider-ohdear/commit/f8c71e7465bd6e7c8b33ae80e02af2c38b40d8c2))
35 |
36 |
37 | ### Dependency Updates
38 |
39 | * bump github.com/go-resty/resty/v2 from 2.16.2 to 2.16.5 ([#150](https://github.com/articulate/terraform-provider-ohdear/issues/150)) ([b2abd2b](https://github.com/articulate/terraform-provider-ohdear/commit/b2abd2b8a5f752e95ed5a6ad8191f0bebfa2182a))
40 | * bump github.com/jarcoal/httpmock from 1.3.1 to 1.4.0 in the test group ([#157](https://github.com/articulate/terraform-provider-ohdear/issues/157)) ([9486df3](https://github.com/articulate/terraform-provider-ohdear/commit/9486df35bf2eea4e504ed04950c610ae1d90dc1b))
41 | * bump the terraform group with 2 updates ([#152](https://github.com/articulate/terraform-provider-ohdear/issues/152)) ([9555346](https://github.com/articulate/terraform-provider-ohdear/commit/9555346de8d58320f75fd5440255edad880d3a41))
42 |
43 | ## [2.2.4](https://github.com/articulate/terraform-provider-ohdear/compare/v2.2.3...v2.2.4) (2024-12-04)
44 |
45 |
46 | ### Dependency Updates
47 |
48 | * bump github.com/go-resty/resty/v2 from 2.15.3 to 2.16.2 ([#145](https://github.com/articulate/terraform-provider-ohdear/issues/145)) ([73f4759](https://github.com/articulate/terraform-provider-ohdear/commit/73f4759f6972662f1ce9765df2e55dcf4cb15f0e))
49 | * bump github.com/hashicorp/terraform-plugin-docs from 0.19.4 to 0.20.1 in the terraform group ([#143](https://github.com/articulate/terraform-provider-ohdear/issues/143)) ([2afc59e](https://github.com/articulate/terraform-provider-ohdear/commit/2afc59ee8d35d0be52a4e747b4cc0393cb63e324))
50 | * bump github.com/stretchr/testify from 1.9.0 to 1.10.0 in the test group ([#144](https://github.com/articulate/terraform-provider-ohdear/issues/144)) ([e3a37fe](https://github.com/articulate/terraform-provider-ohdear/commit/e3a37fee61efae433d33aaadee54b8e7ec2f86bd))
51 |
52 | ## [2.2.3](https://github.com/articulate/terraform-provider-ohdear/compare/v2.2.2...v2.2.3) (2024-11-14)
53 |
54 |
55 | ### Dependency Updates
56 |
57 | * bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.34.0 to 2.35.0 in the terraform group ([#140](https://github.com/articulate/terraform-provider-ohdear/issues/140)) ([c287ddd](https://github.com/articulate/terraform-provider-ohdear/commit/c287dddfda55c98c0907c819579a6d85ca640634))
58 |
59 | ## [2.2.2](https://github.com/articulate/terraform-provider-ohdear/compare/v2.2.1...v2.2.2) (2024-10-25)
60 |
61 |
62 | ### Continuous Integration
63 |
64 | * **release:** create goreleaser workflow that runs on tags ([#137](https://github.com/articulate/terraform-provider-ohdear/issues/137)) ([38be960](https://github.com/articulate/terraform-provider-ohdear/commit/38be96010719ad3e1a220e6456171ce86d8798ae))
65 |
66 | ## [2.2.1](https://github.com/articulate/terraform-provider-ohdear/compare/v2.2.0...v2.2.1) (2024-10-25)
67 |
68 |
69 | ### Continuous Integration
70 |
71 | * **release:** create tag for goreleaser ([#135](https://github.com/articulate/terraform-provider-ohdear/issues/135)) ([66c4937](https://github.com/articulate/terraform-provider-ohdear/commit/66c493735fa86d71be5e23f7c73d1798adbb5579))
72 |
73 | ## [2.2.0](https://github.com/articulate/terraform-provider-ohdear/compare/v2.1.13...v2.2.0) (2024-10-25)
74 |
75 |
76 | ### Features
77 |
78 | * **golang:** bumps to version 1.23 ([f8b0244](https://github.com/articulate/terraform-provider-ohdear/commit/f8b0244ba83492e74ff2b4bb232cacf0f8bcbfdc))
79 |
80 |
81 | ### Dependency Updates
82 |
83 | * bump github.com/go-resty/resty/v2 from 2.13.1 to 2.15.3 ([#131](https://github.com/articulate/terraform-provider-ohdear/issues/131)) ([2d71685](https://github.com/articulate/terraform-provider-ohdear/commit/2d716850f8742e7bf2205f99e54633c96bfb97c0))
84 |
--------------------------------------------------------------------------------
/internal/provider/resource_monitor.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
13 |
14 | "github.com/articulate/terraform-provider-ohdear/pkg/ohdear"
15 | )
16 |
17 | func resourceOhdearMonitor() *schema.Resource {
18 | return &schema.Resource{
19 | Description: "`ohdear_monitor` manages a monitor in Oh Dear.",
20 | CreateContext: resourceOhdearMonitorCreate,
21 | ReadContext: resourceOhdearMonitorRead,
22 | DeleteContext: resourceOhdearMonitorDelete,
23 | UpdateContext: resourceOhdearMonitorUpdate,
24 | Schema: map[string]*schema.Schema{
25 | "url": {
26 | Type: schema.TypeString,
27 | Required: true,
28 | ForceNew: true,
29 | Description: "URL of the monitor to be checked.",
30 | ValidateFunc: validation.IsURLWithHTTPorHTTPS,
31 | },
32 | "team_id": {
33 | Type: schema.TypeInt,
34 | Optional: true,
35 | Computed: true,
36 | ForceNew: true,
37 | Description: "ID of the team for this monitor. If not set, will use `team_id` configured in provider.",
38 | },
39 | "checks": {
40 | Type: schema.TypeList,
41 | Description: "Set the checks enabled for the monitor. If block is not present, it will enable all checks.",
42 | Optional: true,
43 | Computed: true,
44 | MaxItems: 1,
45 | Elem: &schema.Resource{
46 | Schema: map[string]*schema.Schema{
47 | ohdear.UptimeCheck: {
48 | Type: schema.TypeBool,
49 | Description: "Enable uptime checks.",
50 | Optional: true,
51 | },
52 | ohdear.BrokenLinksCheck: {
53 | Type: schema.TypeBool,
54 | Description: "Enable broken link checks.",
55 | Optional: true,
56 | },
57 | ohdear.CertificateHealthCheck: {
58 | Type: schema.TypeBool,
59 | Description: "Enable certificate health checks. Requires the url to use https.",
60 | Optional: true,
61 | },
62 | ohdear.CertificateTransparencyCheck: {
63 | Type: schema.TypeBool,
64 | Description: "Enable certificate transparency checks. Requires the url to use https.",
65 | Optional: true,
66 | Deprecated: "This check was removed by OhDear and will be removed " +
67 | "in a future major release.",
68 | DiffSuppressFunc: func(_, _, _ string, _ *schema.ResourceData) bool {
69 | return true
70 | },
71 | },
72 | ohdear.MixedContentCheck: {
73 | Type: schema.TypeBool,
74 | Description: "Enable mixed content checks.",
75 | Optional: true,
76 | },
77 | ohdear.PerformanceCheck: {
78 | Type: schema.TypeBool,
79 | Description: "Enable performance checks.",
80 | Optional: true,
81 | Deprecated: "This check was merged with the 'uptime' check by OhDear and will be removed " +
82 | "in a future major release.",
83 | DiffSuppressFunc: func(_, _, _ string, _ *schema.ResourceData) bool {
84 | return true
85 | },
86 | },
87 | ohdear.DNSCheck: {
88 | Type: schema.TypeBool,
89 | Description: "Enable DNS checks.",
90 | Default: false,
91 | Optional: true,
92 | },
93 | },
94 | },
95 | },
96 | },
97 | CustomizeDiff: resourceOhdearMonitorDiff,
98 | Importer: &schema.ResourceImporter{
99 | StateContext: schema.ImportStatePassthroughContext,
100 | },
101 | }
102 | }
103 |
104 | func getMonitorID(d *schema.ResourceData) (int, error) {
105 | id, err := strconv.Atoi(d.Id())
106 | if err != nil {
107 | return id, fmt.Errorf("corrupted resource ID in terraform state, Oh Dear only supports integer IDs. Err: %w", err)
108 | }
109 | return id, err
110 | }
111 |
112 | func resourceOhdearMonitorDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
113 | checks := d.Get("checks").([]interface{})
114 | if len(checks) == 0 {
115 | isHTTPS := strings.HasPrefix(d.Get("url").(string), "https")
116 | checks = append(checks, map[string]bool{
117 | ohdear.UptimeCheck: true,
118 | ohdear.BrokenLinksCheck: true,
119 | ohdear.CertificateHealthCheck: isHTTPS,
120 | ohdear.MixedContentCheck: isHTTPS,
121 | ohdear.DNSCheck: false, // TODO: turn to true on next major release (breaking change)
122 | })
123 |
124 | if err := d.SetNew("checks", checks); err != nil {
125 | return err
126 | }
127 | }
128 |
129 | // set team_id from provider default if not provided
130 | if d.Get("team_id") == 0 {
131 | return d.SetNew("team_id", meta.(*Config).teamID)
132 | }
133 |
134 | return nil
135 | }
136 |
137 | func resourceOhdearMonitorCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
138 | log.Println("[DEBUG] Calling Create lifecycle function for monitor")
139 |
140 | client := meta.(*Config).client
141 | monitor, err := client.AddMonitor(d.Get("url").(string), d.Get("team_id").(int), checksWanted(d))
142 | if err != nil {
143 | return diagErrorf(err, "Could not add monitor to Oh Dear")
144 | }
145 |
146 | d.SetId(strconv.Itoa(monitor.ID))
147 |
148 | return resourceOhdearMonitorRead(ctx, d, meta)
149 | }
150 |
151 | func resourceOhdearMonitorRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
152 | log.Printf("[DEBUG] Calling Read lifecycle function for monitor %s\n", d.Id())
153 |
154 | id, err := getMonitorID(d)
155 | if err != nil {
156 | return diag.FromErr(err)
157 | }
158 |
159 | client := meta.(*Config).client
160 | monitor, err := client.GetMonitor(id)
161 | if err != nil {
162 | return diagErrorf(err, "Could not find monitor %d in Oh Dear", id)
163 | }
164 |
165 | checks := checkStateMapFromMonitor(monitor)
166 | if err := d.Set("checks", []interface{}{checks}); err != nil {
167 | return diag.FromErr(err)
168 | }
169 |
170 | if err := d.Set("url", monitor.URL); err != nil {
171 | return diag.FromErr(err)
172 | }
173 |
174 | if err := d.Set("team_id", monitor.TeamID); err != nil {
175 | return diag.FromErr(err)
176 | }
177 |
178 | return nil
179 | }
180 |
181 | func resourceOhdearMonitorDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
182 | log.Printf("[DEBUG] Calling Delete lifecycle function for monitor %s\n", d.Id())
183 |
184 | id, err := getMonitorID(d)
185 | if err != nil {
186 | return diag.FromErr(err)
187 | }
188 |
189 | client := meta.(*Config).client
190 | if err = client.RemoveMonitor(id); err != nil {
191 | return diagErrorf(err, "Could not remove monitor %d from Oh Dear", id)
192 | }
193 |
194 | return nil
195 | }
196 |
197 | func resourceOhdearMonitorUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
198 | log.Printf("[DEBUG] Calling Update lifecycle function for monitor %s\n", d.Id())
199 |
200 | id, err := getMonitorID(d)
201 | if err != nil {
202 | return diag.FromErr(err)
203 | }
204 |
205 | client := meta.(*Config).client
206 | monitor, err := client.GetMonitor(id)
207 | if err != nil {
208 | return diagErrorf(err, "Could not find monitor in Oh Dear")
209 | }
210 |
211 | // Sync downstream checks with config
212 | checksWanted := checksWanted(d)
213 | for _, check := range monitor.Checks {
214 | if check.Enabled {
215 | if !contains(checksWanted, check.Type) {
216 | if err := client.DisableCheck(check.ID); err != nil {
217 | return diagErrorf(err, "Could not remove check to monitor in Oh Dear")
218 | }
219 | }
220 | } else {
221 | if contains(checksWanted, check.Type) {
222 | if err := client.EnableCheck(check.ID); err != nil {
223 | return diagErrorf(err, "Could not add check to monitor in Oh Dear")
224 | }
225 | }
226 | }
227 | }
228 |
229 | return resourceOhdearMonitorRead(ctx, d, meta)
230 | }
231 |
232 | func checkStateMapFromMonitor(monitor *ohdear.Monitor) map[string]bool {
233 | result := make(map[string]bool)
234 | for _, check := range monitor.Checks {
235 | if contains(ohdear.AllChecks, check.Type) {
236 | result[check.Type] = check.Enabled
237 | }
238 | }
239 |
240 | return result
241 | }
242 |
--------------------------------------------------------------------------------
/internal/provider/resource_site.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
13 |
14 | "github.com/articulate/terraform-provider-ohdear/pkg/ohdear"
15 | )
16 |
17 | func resourceOhdearSite() *schema.Resource {
18 | return &schema.Resource{
19 | Description: "`ohdear_site` manages a site in Oh Dear.",
20 | DeprecationMessage: "`ohdear_site` is deprecated and will be removed in a future major release. " +
21 | "Please use `ohdear_monitor` instead, which now supports all site functionality.",
22 |
23 | CreateContext: resourceOhdearSiteCreate,
24 | ReadContext: resourceOhdearSiteRead,
25 | DeleteContext: resourceOhdearSiteDelete,
26 | UpdateContext: resourceOhdearSiteUpdate,
27 | Schema: map[string]*schema.Schema{
28 | "url": {
29 | Type: schema.TypeString,
30 | Required: true,
31 | ForceNew: true,
32 | Description: "URL of the site to be checked.",
33 | ValidateFunc: validation.IsURLWithHTTPorHTTPS,
34 | },
35 | "team_id": {
36 | Type: schema.TypeInt,
37 | Optional: true,
38 | Computed: true,
39 | ForceNew: true,
40 | Description: "ID of the team for this site. If not set, will use `team_id` configured in provider.",
41 | },
42 | "checks": {
43 | Type: schema.TypeList,
44 | Description: "Set the checks enabled for the site. If block is not present, it will enable all checks.",
45 | Optional: true,
46 | Computed: true,
47 | MaxItems: 1,
48 | Elem: &schema.Resource{
49 | Schema: map[string]*schema.Schema{
50 | ohdear.UptimeCheck: {
51 | Type: schema.TypeBool,
52 | Description: "Enable uptime checks.",
53 | Optional: true,
54 | },
55 | ohdear.BrokenLinksCheck: {
56 | Type: schema.TypeBool,
57 | Description: "Enable broken link checks.",
58 | Optional: true,
59 | },
60 | ohdear.CertificateHealthCheck: {
61 | Type: schema.TypeBool,
62 | Description: "Enable certificate health checks. Requires the url to use https.",
63 | Optional: true,
64 | },
65 | ohdear.CertificateTransparencyCheck: {
66 | Type: schema.TypeBool,
67 | Description: "Enable certificate transparency checks. Requires the url to use https.",
68 | Optional: true,
69 | Deprecated: "This check was removed by OhDear and will be removed " +
70 | "in a future major release.",
71 | DiffSuppressFunc: func(_, _, _ string, _ *schema.ResourceData) bool {
72 | return true
73 | },
74 | },
75 | ohdear.MixedContentCheck: {
76 | Type: schema.TypeBool,
77 | Description: "Enable mixed content checks.",
78 | Optional: true,
79 | },
80 | ohdear.PerformanceCheck: {
81 | Type: schema.TypeBool,
82 | Description: "Enable performance checks.",
83 | Optional: true,
84 | Deprecated: "This check was merged with the 'uptime' check by OhDear and will be removed " +
85 | "in a future major release.",
86 | DiffSuppressFunc: func(_, _, _ string, _ *schema.ResourceData) bool {
87 | return true
88 | },
89 | },
90 | ohdear.DNSCheck: {
91 | Type: schema.TypeBool,
92 | Description: "Enable DNS checks.",
93 | Default: false,
94 | Optional: true,
95 | },
96 | },
97 | },
98 | },
99 | },
100 | CustomizeDiff: resourceOhdearSiteDiff,
101 | Importer: &schema.ResourceImporter{
102 | StateContext: schema.ImportStatePassthroughContext,
103 | },
104 | }
105 | }
106 |
107 | func getSiteID(d *schema.ResourceData) (int, error) {
108 | id, err := strconv.Atoi(d.Id())
109 | if err != nil {
110 | return id, fmt.Errorf("corrupted resource ID in terraform state, Oh Dear only supports integer IDs. Err: %w", err)
111 | }
112 | return id, err
113 | }
114 |
115 | func resourceOhdearSiteDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error {
116 | checks := d.Get("checks").([]interface{})
117 | if len(checks) == 0 {
118 | isHTTPS := strings.HasPrefix(d.Get("url").(string), "https")
119 | checks = append(checks, map[string]bool{
120 | ohdear.UptimeCheck: true,
121 | ohdear.BrokenLinksCheck: true,
122 | ohdear.CertificateHealthCheck: isHTTPS,
123 | ohdear.MixedContentCheck: isHTTPS,
124 | ohdear.DNSCheck: false, // TODO: turn to true on next major release (breaking change)
125 | })
126 |
127 | if err := d.SetNew("checks", checks); err != nil {
128 | return err
129 | }
130 | }
131 |
132 | // set team_id from provider default if not provided
133 | if d.Get("team_id") == 0 {
134 | return d.SetNew("team_id", meta.(*Config).teamID)
135 | }
136 |
137 | return nil
138 | }
139 |
140 | func resourceOhdearSiteCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
141 | log.Println("[DEBUG] Calling Create lifecycle function for site")
142 |
143 | client := meta.(*Config).client
144 | site, err := client.AddSite(d.Get("url").(string), d.Get("team_id").(int), checksWanted(d))
145 | if err != nil {
146 | return diagErrorf(err, "Could not add site to Oh Dear")
147 | }
148 |
149 | d.SetId(strconv.Itoa(site.ID))
150 |
151 | return resourceOhdearSiteRead(ctx, d, meta)
152 | }
153 |
154 | func resourceOhdearSiteRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
155 | log.Printf("[DEBUG] Calling Read lifecycle function for site %s\n", d.Id())
156 |
157 | id, err := getSiteID(d)
158 | if err != nil {
159 | return diag.FromErr(err)
160 | }
161 |
162 | client := meta.(*Config).client
163 | site, err := client.GetSite(id)
164 | if err != nil {
165 | return diagErrorf(err, "Could not find site %d in Oh Dear", id)
166 | }
167 |
168 | checks := checkStateMapFromSite(site)
169 | if err := d.Set("checks", []interface{}{checks}); err != nil {
170 | return diag.FromErr(err)
171 | }
172 |
173 | if err := d.Set("url", site.URL); err != nil {
174 | return diag.FromErr(err)
175 | }
176 |
177 | if err := d.Set("team_id", site.TeamID); err != nil {
178 | return diag.FromErr(err)
179 | }
180 |
181 | return nil
182 | }
183 |
184 | func resourceOhdearSiteDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
185 | log.Printf("[DEBUG] Calling Delete lifecycle function for site %s\n", d.Id())
186 |
187 | id, err := getSiteID(d)
188 | if err != nil {
189 | return diag.FromErr(err)
190 | }
191 |
192 | client := meta.(*Config).client
193 | if err = client.RemoveSite(id); err != nil {
194 | return diagErrorf(err, "Could not remove site %d from Oh Dear", id)
195 | }
196 |
197 | return nil
198 | }
199 |
200 | func resourceOhdearSiteUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
201 | log.Printf("[DEBUG] Calling Update lifecycle function for site %s\n", d.Id())
202 |
203 | id, err := getSiteID(d)
204 | if err != nil {
205 | return diag.FromErr(err)
206 | }
207 |
208 | client := meta.(*Config).client
209 | site, err := client.GetSite(id)
210 | if err != nil {
211 | return diagErrorf(err, "Could not find site in Oh Dear")
212 | }
213 |
214 | // Sync downstream checks with config
215 | checksWanted := checksWanted(d)
216 | for _, check := range site.Checks {
217 | if check.Enabled {
218 | if !contains(checksWanted, check.Type) {
219 | if err := client.DisableCheck(check.ID); err != nil {
220 | return diagErrorf(err, "Could not remove check to site in Oh Dear")
221 | }
222 | }
223 | } else {
224 | if contains(checksWanted, check.Type) {
225 | if err := client.EnableCheck(check.ID); err != nil {
226 | return diagErrorf(err, "Could not add check to site in Oh Dear")
227 | }
228 | }
229 | }
230 | }
231 |
232 | return resourceOhdearSiteRead(ctx, d, meta)
233 | }
234 |
235 | func checkStateMapFromSite(site *ohdear.Site) map[string]bool {
236 | result := make(map[string]bool)
237 | for _, check := range site.Checks {
238 | if contains(ohdear.AllChecks, check.Type) {
239 | result[check.Type] = check.Enabled
240 | }
241 | }
242 |
243 | return result
244 | }
245 |
246 | func checksWanted(d *schema.ResourceData) []string {
247 | checks := []string{}
248 | schema := d.Get("checks").([]interface{})[0].(map[string]interface{})
249 | for check, enabled := range schema {
250 | if enabled.(bool) {
251 | checks = append(checks, check)
252 | }
253 | }
254 |
255 | return checks
256 | }
257 |
--------------------------------------------------------------------------------
/internal/provider/resource_monitor_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 | "testing"
9 | "time"
10 |
11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
13 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
14 |
15 | "github.com/articulate/terraform-provider-ohdear/pkg/ohdear"
16 | )
17 |
18 | func TestAccOhdearMonitor(t *testing.T) {
19 | name := acctest.RandomWithPrefix("tf-acc-test")
20 | url := "https://example.com/" + name
21 | resourceName := "ohdear_monitor." + name
22 | updatedURL := url + "/new"
23 |
24 | resource.Test(t, resource.TestCase{
25 | PreCheck: func() { testAccPreCheck(t) },
26 | IDRefreshName: resourceName,
27 | ProviderFactories: testAccProviderFactories,
28 | CheckDestroy: testAccCheckMonitorDestroy,
29 | Steps: []resource.TestStep{
30 | {
31 | Config: testAccOhdearMonitorConfigBasic(name, url),
32 | Check: resource.ComposeAggregateTestCheckFunc(
33 | testAccEnsureMonitorExists(resourceName),
34 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
35 | resource.TestCheckResourceAttr(resourceName, "url", url),
36 | testAccEnsureChecksEnabled(resourceName, []string{
37 | "uptime", "broken_links", "certificate_health",
38 | "mixed_content", "performance",
39 | }),
40 | testAccEnsureChecksDisabled(resourceName, []string{"dns"}),
41 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
42 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "true"),
43 | resource.TestCheckResourceAttr(resourceName, "checks.0.certificate_health", "true"),
44 | resource.TestCheckResourceAttr(resourceName, "checks.0.mixed_content", "true"),
45 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
46 | resource.TestCheckResourceAttr(resourceName, "checks.0.dns", "false"),
47 | ),
48 | },
49 | {
50 | ResourceName: resourceName,
51 | ImportState: true,
52 | ImportStateVerify: true,
53 | },
54 | {
55 | Config: testAccOhdearMonitorConfigBasic(name, updatedURL),
56 | Check: resource.ComposeAggregateTestCheckFunc(
57 | testAccEnsureMonitorExists(resourceName),
58 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
59 | resource.TestCheckResourceAttr(resourceName, "url", updatedURL),
60 | ),
61 | },
62 | },
63 | })
64 | }
65 |
66 | func TestAccOhdearMonitor_EnableDisableChecks(t *testing.T) {
67 | name := acctest.RandomWithPrefix("tf-acc-test")
68 | url := "https://example.com/" + name
69 | resourceName := "ohdear_monitor." + name
70 |
71 | resource.Test(t, resource.TestCase{
72 | PreCheck: func() { testAccPreCheck(t) },
73 | IDRefreshName: resourceName,
74 | ProviderFactories: testAccProviderFactories,
75 | CheckDestroy: testAccCheckMonitorDestroy,
76 | Steps: []resource.TestStep{
77 | {
78 | Config: testAccOhdearMonitorConfigChecks(name, url, map[string]bool{"uptime": true, "broken_links": true}),
79 | Check: resource.ComposeAggregateTestCheckFunc(
80 | testAccEnsureMonitorExists(resourceName),
81 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
82 | resource.TestCheckResourceAttr(resourceName, "url", url),
83 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
84 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "true"),
85 | resource.TestCheckResourceAttr(resourceName, "checks.0.certificate_health", "false"),
86 | resource.TestCheckResourceAttr(resourceName, "checks.0.mixed_content", "false"),
87 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
88 | resource.TestCheckResourceAttr(resourceName, "checks.0.dns", "false"),
89 | testAccEnsureChecksEnabled(resourceName, []string{"uptime", "broken_links"}),
90 | testAccEnsureChecksDisabled(resourceName, []string{"mixed_content"}),
91 | ),
92 | },
93 | {
94 | Config: testAccOhdearMonitorConfigChecks(name, url, map[string]bool{"uptime": true}),
95 | Check: resource.ComposeAggregateTestCheckFunc(
96 | resource.TestCheckResourceAttr(resourceName, "url", url),
97 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
98 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "false"),
99 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
100 | testAccEnsureChecksEnabled(resourceName, []string{"uptime"}),
101 | testAccEnsureChecksDisabled(resourceName, []string{"broken_links"}),
102 | ),
103 | },
104 | {
105 | Config: testAccOhdearMonitorConfigChecks(name, url, map[string]bool{"uptime": false}),
106 | Check: resource.ComposeAggregateTestCheckFunc(
107 | resource.TestCheckResourceAttr(resourceName, "url", url),
108 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "false"),
109 | testAccEnsureChecksDisabled(resourceName, []string{"uptime", "broken_links"}),
110 | ),
111 | },
112 | },
113 | })
114 | }
115 |
116 | func TestAccOhdearMonitor_TeamID(t *testing.T) {
117 | name := acctest.RandomWithPrefix("tf-acc-test")
118 | url := "https://example.com/" + name
119 | resourceName := "ohdear_monitor." + name
120 |
121 | resource.Test(t, resource.TestCase{
122 | PreCheck: func() { testAccPreCheck(t) },
123 | IDRefreshName: resourceName,
124 | ProviderFactories: testAccProviderFactories,
125 | CheckDestroy: testAccCheckMonitorDestroy,
126 | Steps: []resource.TestStep{
127 | {
128 | Config: testAccOhdearMonitorConfigBasic(name, url),
129 | Check: resource.ComposeAggregateTestCheckFunc(
130 | testAccEnsureMonitorExists(resourceName),
131 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
132 | resource.TestCheckResourceAttr(resourceName, "url", url),
133 | ),
134 | },
135 | {
136 | Config: testAccOhdearMonitorConfigTeamID(name, url, "1"),
137 | PlanOnly: true,
138 | ExpectNonEmptyPlan: true,
139 | Check: resource.ComposeAggregateTestCheckFunc(
140 | resource.TestCheckResourceAttr(resourceName, "team_id", "1"),
141 | ),
142 | },
143 | },
144 | })
145 | }
146 |
147 | func TestAccOhdearMonitor_HTTPDefaults(t *testing.T) {
148 | name := acctest.RandomWithPrefix("tf-acc-test")
149 | url := "http://example.com/" + name
150 | resourceName := "ohdear_monitor." + name
151 |
152 | resource.Test(t, resource.TestCase{
153 | PreCheck: func() { testAccPreCheck(t) },
154 | IDRefreshName: resourceName,
155 | ProviderFactories: testAccProviderFactories,
156 | CheckDestroy: testAccCheckMonitorDestroy,
157 | Steps: []resource.TestStep{
158 | {
159 | Config: testAccOhdearMonitorConfigBasic(name, url),
160 | PlanOnly: true,
161 | ExpectNonEmptyPlan: true,
162 | Check: resource.ComposeAggregateTestCheckFunc(
163 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
164 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "true"),
165 | resource.TestCheckResourceAttr(resourceName, "checks.0.certificate_health", "false"),
166 | resource.TestCheckResourceAttr(resourceName, "checks.0.mixed_content", "false"),
167 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
168 | resource.TestCheckResourceAttr(resourceName, "checks.0.dns", "false"),
169 | ),
170 | },
171 | },
172 | })
173 | }
174 |
175 | // Checks
176 |
177 | func doesMonitorExist(strID string) (bool, error) {
178 | client := testAccProvider.Meta().(*Config).client
179 | id, _ := strconv.Atoi(strID)
180 | if _, err := client.GetMonitor(id); err != nil {
181 | var e *ohdear.Error
182 | if errors.As(err, &e) && e.Response.StatusCode() == 404 {
183 | return false, nil
184 | }
185 |
186 | return false, err
187 | }
188 |
189 | return true, nil
190 | }
191 |
192 | func testAccCheckMonitorDestroy(s *terraform.State) error {
193 | for _, rs := range s.RootModule().Resources {
194 | if rs.Type != "ohdear_monitor" {
195 | continue
196 | }
197 |
198 | // give the API time to update
199 | time.Sleep(5 * time.Second)
200 |
201 | exists, err := doesMonitorExist(rs.Primary.ID)
202 | if err != nil {
203 | return err
204 | }
205 |
206 | if exists {
207 | return fmt.Errorf("monitor still exists in Oh Dear: %s", rs.Primary.ID)
208 | }
209 | }
210 | return nil
211 | }
212 |
213 | func testAccEnsureMonitorExists(name string) resource.TestCheckFunc {
214 | return func(s *terraform.State) error {
215 | rs, ok := s.RootModule().Resources[name]
216 | if !ok {
217 | return fmt.Errorf("resource not found: %s", name)
218 | }
219 |
220 | exists, err := doesMonitorExist(rs.Primary.ID)
221 | if err != nil {
222 | return err
223 | }
224 |
225 | if !exists {
226 | return fmt.Errorf("resource not found: %s", name)
227 | }
228 |
229 | return nil
230 | }
231 | }
232 |
233 | // Configs
234 |
235 | func testAccOhdearMonitorConfigBasic(name, url string) string {
236 | return fmt.Sprintf(`
237 | resource "ohdear_monitor" "%s" {
238 | url = "%s"
239 | }
240 | `, name, url)
241 | }
242 |
243 | func testAccOhdearMonitorConfigTeamID(name, url, team string) string {
244 | return fmt.Sprintf(`
245 | resource "ohdear_monitor" "%s" {
246 | team_id = %s
247 | url = "%s"
248 | }
249 | `, name, team, url)
250 | }
251 |
252 | func testAccOhdearMonitorConfigChecks(name, url string, checks map[string]bool) string {
253 | block := []string{}
254 | for check, enabled := range checks {
255 | block = append(block, fmt.Sprintf("%s = %t", check, enabled))
256 | }
257 |
258 | return fmt.Sprintf(`
259 | resource "ohdear_monitor" "%s" {
260 | url = "%s"
261 |
262 | checks {
263 | %s
264 | }
265 | }
266 | `, name, url, strings.Join(block, "\n "))
267 | }
268 |
--------------------------------------------------------------------------------
/internal/provider/resource_site_test.go:
--------------------------------------------------------------------------------
1 | package provider
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | "strings"
9 | "testing"
10 | "time"
11 |
12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
13 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
14 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
15 |
16 | "github.com/articulate/terraform-provider-ohdear/pkg/ohdear"
17 | )
18 |
19 | var teamID string
20 |
21 | func init() {
22 | teamID = os.Getenv("OHDEAR_TEAM_ID")
23 | }
24 |
25 | func TestAccOhdearSite(t *testing.T) {
26 | name := acctest.RandomWithPrefix("tf-acc-test")
27 | url := "https://example.com/" + name
28 | resourceName := "ohdear_site." + name
29 | updatedURL := url + "/new"
30 |
31 | resource.Test(t, resource.TestCase{
32 | PreCheck: func() { testAccPreCheck(t) },
33 | IDRefreshName: resourceName,
34 | ProviderFactories: testAccProviderFactories,
35 | CheckDestroy: testAccCheckSiteDestroy,
36 | Steps: []resource.TestStep{
37 | {
38 | Config: testAccOhdearSiteConfigBasic(name, url),
39 | Check: resource.ComposeAggregateTestCheckFunc(
40 | testAccEnsureSiteExists(resourceName),
41 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
42 | resource.TestCheckResourceAttr(resourceName, "url", url),
43 | testAccEnsureChecksEnabled(resourceName, []string{
44 | "uptime", "broken_links", "certificate_health",
45 | "mixed_content", "performance",
46 | }),
47 | testAccEnsureChecksDisabled(resourceName, []string{"dns"}),
48 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
49 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "true"),
50 | resource.TestCheckResourceAttr(resourceName, "checks.0.certificate_health", "true"),
51 | resource.TestCheckResourceAttr(resourceName, "checks.0.mixed_content", "true"),
52 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
53 | resource.TestCheckResourceAttr(resourceName, "checks.0.dns", "false"),
54 | ),
55 | },
56 | {
57 | ResourceName: resourceName,
58 | ImportState: true,
59 | ImportStateVerify: true,
60 | },
61 | {
62 | Config: testAccOhdearSiteConfigBasic(name, updatedURL),
63 | Check: resource.ComposeAggregateTestCheckFunc(
64 | testAccEnsureSiteExists(resourceName),
65 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
66 | resource.TestCheckResourceAttr(resourceName, "url", updatedURL),
67 | ),
68 | },
69 | },
70 | })
71 | }
72 |
73 | func TestAccOhdearSite_EnableDisableChecks(t *testing.T) {
74 | name := acctest.RandomWithPrefix("tf-acc-test")
75 | url := "https://example.com/" + name
76 | resourceName := "ohdear_site." + name
77 |
78 | resource.Test(t, resource.TestCase{
79 | PreCheck: func() { testAccPreCheck(t) },
80 | IDRefreshName: resourceName,
81 | ProviderFactories: testAccProviderFactories,
82 | CheckDestroy: testAccCheckSiteDestroy,
83 | Steps: []resource.TestStep{
84 | {
85 | Config: testAccOhdearSiteConfigChecks(name, url, map[string]bool{"uptime": true, "broken_links": true}),
86 | Check: resource.ComposeAggregateTestCheckFunc(
87 | testAccEnsureSiteExists(resourceName),
88 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
89 | resource.TestCheckResourceAttr(resourceName, "url", url),
90 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
91 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "true"),
92 | resource.TestCheckResourceAttr(resourceName, "checks.0.certificate_health", "false"),
93 | resource.TestCheckResourceAttr(resourceName, "checks.0.mixed_content", "false"),
94 | testAccEnsureChecksEnabled(resourceName, []string{"uptime", "broken_links"}),
95 | testAccEnsureChecksDisabled(resourceName, []string{"mixed_content"}),
96 | ),
97 | },
98 | {
99 | Config: testAccOhdearSiteConfigChecks(name, url, map[string]bool{"uptime": true}),
100 | Check: resource.ComposeAggregateTestCheckFunc(
101 | resource.TestCheckResourceAttr(resourceName, "url", url),
102 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
103 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "false"),
104 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
105 | testAccEnsureChecksEnabled(resourceName, []string{"uptime"}),
106 | testAccEnsureChecksDisabled(resourceName, []string{"broken_links"}),
107 | ),
108 | },
109 | {
110 | Config: testAccOhdearSiteConfigChecks(name, url, map[string]bool{"uptime": false}),
111 | Check: resource.ComposeAggregateTestCheckFunc(
112 | resource.TestCheckResourceAttr(resourceName, "url", url),
113 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "false"),
114 | testAccEnsureChecksDisabled(resourceName, []string{"uptime", "broken_links"}),
115 | ),
116 | },
117 | },
118 | })
119 | }
120 |
121 | func TestAccOhdearSite_TeamID(t *testing.T) {
122 | name := acctest.RandomWithPrefix("tf-acc-test")
123 | url := "https://example.com/" + name
124 | resourceName := "ohdear_site." + name
125 |
126 | resource.Test(t, resource.TestCase{
127 | PreCheck: func() { testAccPreCheck(t) },
128 | IDRefreshName: resourceName,
129 | ProviderFactories: testAccProviderFactories,
130 | CheckDestroy: testAccCheckSiteDestroy,
131 | Steps: []resource.TestStep{
132 | {
133 | Config: testAccOhdearSiteConfigBasic(name, url),
134 | Check: resource.ComposeAggregateTestCheckFunc(
135 | testAccEnsureSiteExists(resourceName),
136 | resource.TestCheckResourceAttr(resourceName, "team_id", teamID),
137 | resource.TestCheckResourceAttr(resourceName, "url", url),
138 | ),
139 | },
140 | {
141 | Config: testAccOhdearSiteConfigTeamID(name, url, "1"),
142 | PlanOnly: true,
143 | ExpectNonEmptyPlan: true,
144 | Check: resource.ComposeAggregateTestCheckFunc(
145 | resource.TestCheckResourceAttr(resourceName, "team_id", "1"),
146 | ),
147 | },
148 | },
149 | })
150 | }
151 |
152 | func TestAccOhdearSite_HTTPDefaults(t *testing.T) {
153 | name := acctest.RandomWithPrefix("tf-acc-test")
154 | url := "http://example.com/" + name
155 | resourceName := "ohdear_site." + name
156 |
157 | resource.Test(t, resource.TestCase{
158 | PreCheck: func() { testAccPreCheck(t) },
159 | IDRefreshName: resourceName,
160 | ProviderFactories: testAccProviderFactories,
161 | CheckDestroy: testAccCheckSiteDestroy,
162 | Steps: []resource.TestStep{
163 | {
164 | Config: testAccOhdearSiteConfigBasic(name, url),
165 | PlanOnly: true,
166 | ExpectNonEmptyPlan: true,
167 | Check: resource.ComposeAggregateTestCheckFunc(
168 | resource.TestCheckResourceAttr(resourceName, "checks.0.uptime", "true"),
169 | resource.TestCheckResourceAttr(resourceName, "checks.0.broken_links", "true"),
170 | resource.TestCheckResourceAttr(resourceName, "checks.0.certificate_health", "false"),
171 | resource.TestCheckResourceAttr(resourceName, "checks.0.mixed_content", "false"),
172 | resource.TestCheckResourceAttr(resourceName, "checks.0.performance", "true"),
173 | resource.TestCheckResourceAttr(resourceName, "checks.0.dns", "false"),
174 | ),
175 | },
176 | },
177 | })
178 | }
179 |
180 | // Checks
181 |
182 | func doesSiteExists(strID string) (bool, error) {
183 | client := testAccProvider.Meta().(*Config).client
184 | id, _ := strconv.Atoi(strID)
185 | if _, err := client.GetSite(id); err != nil {
186 | var e *ohdear.Error
187 | if errors.As(err, &e) && e.Response.StatusCode() == 404 {
188 | return false, nil
189 | }
190 |
191 | return false, err
192 | }
193 |
194 | return true, nil
195 | }
196 |
197 | func testAccCheckSiteDestroy(s *terraform.State) error {
198 | for _, rs := range s.RootModule().Resources {
199 | if rs.Type != "ohdear_site" {
200 | continue
201 | }
202 |
203 | // give the API time to update
204 | time.Sleep(5 * time.Second)
205 |
206 | exists, err := doesSiteExists(rs.Primary.ID)
207 | if err != nil {
208 | return err
209 | }
210 |
211 | if exists {
212 | return fmt.Errorf("site still exists in Oh Dear: %s", rs.Primary.ID)
213 | }
214 | }
215 | return nil
216 | }
217 |
218 | func testAccEnsureSiteExists(name string) resource.TestCheckFunc {
219 | return func(s *terraform.State) error {
220 | rs, ok := s.RootModule().Resources[name]
221 | if !ok {
222 | return fmt.Errorf("resource not found: %s", name)
223 | }
224 |
225 | exists, err := doesSiteExists(rs.Primary.ID)
226 | if err != nil {
227 | return err
228 | }
229 |
230 | if !exists {
231 | return fmt.Errorf("resource not found: %s", name)
232 | }
233 |
234 | return nil
235 | }
236 | }
237 |
238 | func testAccEnsureChecksEnabled(name string, checksWanted []string) resource.TestCheckFunc {
239 | return func(s *terraform.State) error {
240 | client := testAccProvider.Meta().(*Config).client
241 |
242 | missingErr := fmt.Errorf("resource not found: %s", name)
243 | rs, ok := s.RootModule().Resources[name]
244 | if !ok {
245 | return missingErr
246 | }
247 | siteID, _ := strconv.Atoi(rs.Primary.ID)
248 | site, _ := client.GetSite(siteID)
249 |
250 | for _, check := range checksWanted {
251 | enabled := isCheckEnabled(site, check)
252 | if !enabled {
253 | return fmt.Errorf("Check %s not enabled for site %s", check, name)
254 | }
255 | }
256 |
257 | return nil
258 | }
259 | }
260 |
261 | // TODO: merge with enabled (take map of boolean to check all at once)
262 | func testAccEnsureChecksDisabled(name string, checksWanted []string) resource.TestCheckFunc {
263 | return func(s *terraform.State) error {
264 | client := testAccProvider.Meta().(*Config).client
265 |
266 | rs, ok := s.RootModule().Resources[name]
267 | if !ok {
268 | return fmt.Errorf("resource not found: %s", name)
269 | }
270 |
271 | siteID, _ := strconv.Atoi(rs.Primary.ID)
272 | site, err := client.GetSite(siteID)
273 | if err != nil {
274 | return err
275 | }
276 |
277 | for _, check := range checksWanted {
278 | if isCheckEnabled(site, check) {
279 | return fmt.Errorf("check %s not enabled for site %s", check, name)
280 | }
281 | }
282 |
283 | return nil
284 | }
285 | }
286 |
287 | // isCheckEnabled checks the site retrieved from OhDear to see whether the
288 | // specified check is present and enabled
289 | func isCheckEnabled(site *ohdear.Site, checkName string) bool {
290 | for _, aCheck := range site.Checks {
291 | if aCheck.Type == checkName && aCheck.Enabled == true {
292 | return true
293 | }
294 | }
295 |
296 | return false
297 | }
298 |
299 | // Configs
300 |
301 | func testAccOhdearSiteConfigBasic(name, url string) string {
302 | return fmt.Sprintf(`
303 | resource "ohdear_site" "%s" {
304 | url = "%s"
305 | }
306 | `, name, url)
307 | }
308 |
309 | func testAccOhdearSiteConfigTeamID(name, url, team string) string {
310 | return fmt.Sprintf(`
311 | resource "ohdear_site" "%s" {
312 | team_id = %s
313 | url = "%s"
314 | }
315 | `, name, team, url)
316 | }
317 |
318 | func testAccOhdearSiteConfigChecks(name, url string, checks map[string]bool) string {
319 | block := []string{}
320 | for check, enabled := range checks {
321 | block = append(block, fmt.Sprintf("%s = %t", check, enabled))
322 | }
323 |
324 | return fmt.Sprintf(`
325 | resource "ohdear_site" "%s" {
326 | url = "%s"
327 |
328 | checks {
329 | %s
330 | }
331 | }
332 | `, name, url, strings.Join(block, "\n "))
333 | }
334 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
3 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
4 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
5 | github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
6 | github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
7 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
8 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
9 | github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
10 | github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
11 | github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
12 | github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
13 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
14 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
15 | github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
16 | github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
17 | github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
18 | github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
19 | github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
20 | github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
21 | github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
22 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
23 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
24 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
25 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
26 | github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
27 | github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
28 | github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
29 | github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
30 | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
31 | github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
32 | github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
33 | github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
36 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
37 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
38 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
39 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
40 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
41 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
42 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
43 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
44 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
45 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
46 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
47 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
48 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
49 | github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
50 | github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
51 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
52 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
53 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
54 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
55 | github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
56 | github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
57 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
58 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
59 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
60 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
61 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
62 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
63 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
64 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
65 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
66 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
67 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
68 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
69 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
70 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
71 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
72 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
73 | github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
74 | github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
75 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
76 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
77 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
78 | github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU=
79 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
80 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
81 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
82 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
83 | github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0=
84 | github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM=
85 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
86 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
87 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
88 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
89 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
90 | github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
91 | github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
92 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
93 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
94 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
95 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
96 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
97 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
98 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
99 | github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24=
100 | github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I=
101 | github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
102 | github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
103 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
104 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
105 | github.com/hashicorp/terraform-exec v0.23.1 h1:diK5NSSDXDKqHEOIQefBMu9ny+FhzwlwV0xgUTB7VTo=
106 | github.com/hashicorp/terraform-exec v0.23.1/go.mod h1:e4ZEg9BJDRaSalGm2z8vvrPONt0XWG0/tXpmzYTf+dM=
107 | github.com/hashicorp/terraform-json v0.27.1 h1:zWhEracxJW6lcjt/JvximOYyc12pS/gaKSy/wzzE7nY=
108 | github.com/hashicorp/terraform-json v0.27.1/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
109 | github.com/hashicorp/terraform-plugin-docs v0.23.0 h1:sipnfD4/9EJBg9zekym+s1H6qmLAKJHhGWBwvN9v/hE=
110 | github.com/hashicorp/terraform-plugin-docs v0.23.0/go.mod h1:J4b5AtMRgJlDrwCQz+G4hKABgHY5m56PnsRmdAzBwW8=
111 | github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
112 | github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
113 | github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
114 | github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
115 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4=
116 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1/go.mod h1:GQhpKVvvuwzD79e8/NZ+xzj+ZpWovdPAe8nfV/skwNU=
117 | github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
118 | github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
119 | github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
120 | github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
121 | github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
122 | github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
123 | github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
124 | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
125 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
126 | github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
127 | github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
128 | github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
129 | github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
130 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
131 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
132 | github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
133 | github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
134 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
135 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
136 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
137 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
138 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
139 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
140 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
141 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
142 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
143 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
144 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
145 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
146 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
147 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
148 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
149 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
150 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
151 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
152 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
153 | github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI=
154 | github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
155 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
156 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
157 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
158 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
159 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
160 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
161 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
162 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
163 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
164 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
165 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
166 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
167 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
168 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
169 | github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
170 | github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
171 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
172 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
173 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
174 | github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
175 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
176 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
177 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
178 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
179 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
180 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
181 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
182 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
183 | github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
184 | github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
185 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
186 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
187 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
188 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
189 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
190 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
191 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
192 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
193 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
194 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
195 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
196 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
197 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
198 | github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
199 | github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
200 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
201 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
202 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
203 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
204 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
205 | github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU=
206 | github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
207 | github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
208 | github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
209 | github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
210 | github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
211 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
212 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
213 | go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
214 | go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
215 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
216 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
217 | go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
218 | go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
219 | go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
220 | go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
221 | go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
222 | go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
223 | go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
224 | go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
225 | go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
226 | go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
227 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
228 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
229 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
230 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
231 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
232 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
233 | golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
234 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
235 | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
236 | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
237 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
238 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
239 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
240 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
241 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
242 | golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
243 | golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
244 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
245 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
246 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
247 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
248 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
249 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
250 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
251 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
252 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
253 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
254 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
255 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
256 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
257 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
258 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
259 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
260 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
261 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
262 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
263 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
264 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
265 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
266 | golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
267 | golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
268 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
269 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
270 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
271 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
272 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
273 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
274 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
275 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
276 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
277 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
278 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
279 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
280 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
281 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
282 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
283 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
284 | gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
285 | gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
286 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
287 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
288 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
289 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
290 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
291 | google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
292 | google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
293 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
294 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
295 | google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
296 | google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
297 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
298 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
299 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
300 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
301 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
302 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
303 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
304 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
305 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
306 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
307 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
308 |
--------------------------------------------------------------------------------