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()
|
||||
},
|
||||
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"
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(`Missing TowerOps API Token`),
|
||||
ExpectError: regexp.MustCompile(`(Missing TowerOps API Token|token.*is required)`),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package provider
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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())
|
||||
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())
|
||||
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)
|
||||
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())
|
||||
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) {
|
||||
var siteID string
|
||||
var mu sync.Mutex
|
||||
|
|
@ -505,3 +452,173 @@ resource "towerops_site" "test" {
|
|||
}
|
||||
`, 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