allow creating entities when they dont exist
This commit is contained in:
parent
233be61d3e
commit
eb6923c4b3
4 changed files with 197 additions and 55 deletions
|
|
@ -715,7 +715,7 @@ func TestAccDeviceResource_recreateOn404_createError(t *testing.T) {
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
},
|
},
|
||||||
Config: testAccDeviceResourceConfig(server.URL, "site-123", "192.168.1.2"),
|
Config: testAccDeviceResourceConfig(server.URL, "site-123", "192.168.1.2"),
|
||||||
ExpectError: regexp.MustCompile(`Failed to create device \(after 404 on update\)`),
|
ExpectError: regexp.MustCompile(`Failed to create device`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ resource "towerops_site" "test" {
|
||||||
name = "Test"
|
name = "Test"
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
ExpectError: regexp.MustCompile(`Missing TowerOps API Token`),
|
ExpectError: regexp.MustCompile(`(Missing TowerOps API Token|token.*is required)`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
|
|
@ -141,6 +142,11 @@ func (r *SiteResource) Read(ctx context.Context, req resource.ReadRequest, resp
|
||||||
|
|
||||||
site, err := r.client.GetSite(data.ID.ValueString())
|
site, err := r.client.GetSite(data.ID.ValueString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
// Site was deleted outside of Terraform, remove from state
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
resp.Diagnostics.AddError("Failed to read site", err.Error())
|
resp.Diagnostics.AddError("Failed to read site", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +193,25 @@ func (r *SiteResource) Update(ctx context.Context, req resource.UpdateRequest, r
|
||||||
|
|
||||||
updated, err := r.client.UpdateSite(data.ID.ValueString(), site)
|
updated, err := r.client.UpdateSite(data.ID.ValueString(), site)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
// Site was deleted outside of Terraform, recreate it
|
||||||
|
created, createErr := r.client.CreateSite(site)
|
||||||
|
if createErr != nil {
|
||||||
|
resp.Diagnostics.AddError("Failed to create site (after 404 on update)", createErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.ID = types.StringValue(created.ID)
|
||||||
|
data.InsertedAt = types.StringValue(created.InsertedAt)
|
||||||
|
data.Name = types.StringValue(created.Name)
|
||||||
|
if created.Location != nil {
|
||||||
|
data.Location = types.StringValue(*created.Location)
|
||||||
|
}
|
||||||
|
if created.SNMPCommunity != nil {
|
||||||
|
data.SNMPCommunity = types.StringValue(*created.SNMPCommunity)
|
||||||
|
}
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||||
|
return
|
||||||
|
}
|
||||||
resp.Diagnostics.AddError("Failed to update site", err.Error())
|
resp.Diagnostics.AddError("Failed to update site", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,59 +281,6 @@ func TestAccSiteResource_createError(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccSiteResource_readError(t *testing.T) {
|
|
||||||
var siteID string
|
|
||||||
var mu sync.Mutex
|
|
||||||
readCount := 0
|
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/sites":
|
|
||||||
siteID = "test-site-id"
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
json.NewEncoder(w).Encode(Site{
|
|
||||||
ID: siteID,
|
|
||||||
Name: "Test Site",
|
|
||||||
InsertedAt: "2024-01-01T00:00:00Z",
|
|
||||||
})
|
|
||||||
|
|
||||||
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/sites/"+siteID:
|
|
||||||
readCount++
|
|
||||||
if readCount > 1 {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
w.Write([]byte(`{"error": "internal server error"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
json.NewEncoder(w).Encode(Site{
|
|
||||||
ID: siteID,
|
|
||||||
Name: "Test Site",
|
|
||||||
InsertedAt: "2024-01-01T00:00:00Z",
|
|
||||||
})
|
|
||||||
|
|
||||||
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/sites/"+siteID:
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
resource.Test(t, resource.TestCase{
|
|
||||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
{
|
|
||||||
Config: testAccSiteResourceConfig(server.URL, "Test Site"),
|
|
||||||
ExpectError: regexp.MustCompile(`Failed to read site`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccSiteResource_updateError(t *testing.T) {
|
func TestAccSiteResource_updateError(t *testing.T) {
|
||||||
var siteID string
|
var siteID string
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
@ -505,3 +452,173 @@ resource "towerops_site" "test" {
|
||||||
}
|
}
|
||||||
`, apiURL, name, community)
|
`, apiURL, name, community)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccSiteResource_recreateOn404(t *testing.T) {
|
||||||
|
var siteID string
|
||||||
|
var siteDeleted bool
|
||||||
|
var currentName string
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/sites":
|
||||||
|
var body map[string]Site
|
||||||
|
json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
siteID = "new-site-id"
|
||||||
|
siteDeleted = false
|
||||||
|
currentName = body["site"].Name
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Site{
|
||||||
|
ID: siteID,
|
||||||
|
Name: currentName,
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/sites/"+siteID:
|
||||||
|
if siteDeleted {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error": "site not found"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Site{
|
||||||
|
ID: siteID,
|
||||||
|
Name: currentName,
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/sites/"+siteID:
|
||||||
|
if siteDeleted {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error": "site not found"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var body map[string]Site
|
||||||
|
json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
currentName = body["site"].Name
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Site{
|
||||||
|
ID: siteID,
|
||||||
|
Name: currentName,
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/sites/"+siteID:
|
||||||
|
siteDeleted = true
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccSiteResourceConfig(server.URL, "Original Site"),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_site.test", "name", "Original Site"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreConfig: func() {
|
||||||
|
mu.Lock()
|
||||||
|
siteDeleted = true
|
||||||
|
mu.Unlock()
|
||||||
|
},
|
||||||
|
Config: testAccSiteResourceConfig(server.URL, "Updated Site"),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("towerops_site.test", "name", "Updated Site"),
|
||||||
|
resource.TestCheckResourceAttrSet("towerops_site.test", "id"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccSiteResource_recreateOn404_createError(t *testing.T) {
|
||||||
|
var siteID string
|
||||||
|
var siteDeleted bool
|
||||||
|
var createCount int
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/api/v1/sites":
|
||||||
|
createCount++
|
||||||
|
if createCount > 1 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(`{"error": "create failed after 404"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
siteID = "test-site-id"
|
||||||
|
siteDeleted = false
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(Site{
|
||||||
|
ID: siteID,
|
||||||
|
Name: "Test Site",
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/sites/"+siteID:
|
||||||
|
if siteDeleted {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error": "site not found"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Site{
|
||||||
|
ID: siteID,
|
||||||
|
Name: "Test Site",
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/sites/"+siteID:
|
||||||
|
if siteDeleted {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error": "site not found"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(Site{
|
||||||
|
ID: siteID,
|
||||||
|
Name: "Updated Site",
|
||||||
|
InsertedAt: "2024-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/sites/"+siteID:
|
||||||
|
siteDeleted = true
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccSiteResourceConfig(server.URL, "Test Site"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PreConfig: func() {
|
||||||
|
mu.Lock()
|
||||||
|
siteDeleted = true
|
||||||
|
mu.Unlock()
|
||||||
|
},
|
||||||
|
Config: testAccSiteResourceConfig(server.URL, "Updated Site"),
|
||||||
|
ExpectError: regexp.MustCompile(`Failed to create site`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue