diff --git a/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service.go b/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service.go index d68c2754..77ea359a 100644 --- a/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service.go +++ b/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service.go @@ -3,6 +3,8 @@ package ephemeralenvironments import ( "math" + "github.com/OctopusDeploy/go-octopusdeploy/v2/internal" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/constants" v2 "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments/v2" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments/v2/environments" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient" @@ -96,3 +98,37 @@ func Deprovision(client newclient.Client, spaceID string, environmentId string) return newclient.Add[DeprovisionEphemeralEnvironmentResponse](client, path, spaceID, body) } + +// GetStatus returns the provisioning status of an ephemeral environment for a project. +func GetStatus(client newclient.Client, spaceID string, environmentId string, projectId string) (*EphemeralEnvironmentStatus, error) { + spaceID, err := internal.GetSpaceID(spaceID, client.GetSpaceID()) + if err != nil { + return nil, err + } + + if internal.IsEmpty(environmentId) { + return nil, internal.CreateRequiredParameterIsEmptyError(constants.ParameterID) + } + + if internal.IsEmpty(projectId) { + return nil, internal.CreateRequiredParameterIsEmptyError(constants.ParameterProjectID) + } + + path, err := client.URITemplateCache().Expand(v2.GetEphemeralEnvironmentStatusTemplate, map[string]any{ + "id": environmentId, + "projectId": projectId, + "spaceId": spaceID, + }) + + if err != nil { + return nil, err + } + + return newclient.Get[EphemeralEnvironmentStatus](client.HttpSession(), path) +} + +// Delete deletes an ephemeral environment without running its deprovisioning runbook. +// Use it for environments that cannot be deprovisioned, for example when provisioning failed. +func Delete(client newclient.Client, spaceID string, environmentId string) error { + return newclient.DeleteByID(client, v2.DeleteEphemeralEnvironmentTemplate, spaceID, environmentId) +} diff --git a/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service_test.go b/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service_test.go new file mode 100644 index 00000000..a0155e7d --- /dev/null +++ b/pkg/environments/v2/ephemeralenvironments/ephemeral_environment_service_test.go @@ -0,0 +1,91 @@ +package ephemeralenvironments + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient" +) + +func newTestClient(t *testing.T, handler http.HandlerFunc) newclient.Client { + server := httptest.NewServer(handler) + t.Cleanup(server.Close) + + baseURL, err := url.Parse(server.URL) + require.NoError(t, err) + + return newclient.NewClientS(&newclient.HttpSession{ + HttpClient: server.Client(), + BaseURL: baseURL, + DefaultHeaders: map[string]string{}, + }, "Spaces-1") +} + +func TestGetStatus(t *testing.T) { + var method, requested string + client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + method = r.Method + requested = r.URL.RequestURI() + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"Status":"ProvisioningFailed"}`)) + }) + + status, err := GetStatus(client, "Spaces-1", "Environments-1001", "Projects-1") + require.NoError(t, err) + + assert.Equal(t, http.MethodGet, method) + assert.Equal(t, "/api/Spaces-1/projects/Projects-1/environments/ephemeral/Environments-1001/status", requested) + assert.Equal(t, "ProvisioningFailed", status.Status) +} + +func TestGetStatusReturnsServerError(t *testing.T) { + client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"ErrorMessage":"not found"}`)) + }) + + status, err := GetStatus(client, "Spaces-1", "Environments-1001", "Projects-1") + assert.Error(t, err) + assert.Nil(t, status) +} + +func TestGetStatusRequiresIDs(t *testing.T) { + client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + t.Errorf("no request expected, got %s %s", r.Method, r.URL.RequestURI()) + }) + + _, err := GetStatus(client, "Spaces-1", "", "Projects-1") + assert.Error(t, err) + + _, err = GetStatus(client, "Spaces-1", "Environments-1001", "") + assert.Error(t, err) +} + +func TestDelete(t *testing.T) { + var method, requested string + client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + method = r.Method + requested = r.URL.RequestURI() + w.WriteHeader(http.StatusOK) + }) + + err := Delete(client, "Spaces-1", "Environments-1001") + require.NoError(t, err) + + assert.Equal(t, http.MethodDelete, method) + assert.Equal(t, "/api/Spaces-1/environments/Environments-1001", requested) +} + +func TestDeleteRequiresID(t *testing.T) { + client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + t.Errorf("no request expected, got %s %s", r.Method, r.URL.RequestURI()) + }) + + assert.Error(t, Delete(client, "Spaces-1", "")) +} diff --git a/pkg/environments/v2/ephemeralenvironments/types.go b/pkg/environments/v2/ephemeralenvironments/types.go index 17677a61..fab6b4b0 100644 --- a/pkg/environments/v2/ephemeralenvironments/types.go +++ b/pkg/environments/v2/ephemeralenvironments/types.go @@ -26,3 +26,8 @@ type DeprovisioningRunbookRun struct { RunbookRunID string `json:"RunbookRunId"` TaskId string `json:"TaskId"` } + +// EphemeralEnvironmentStatus is the provisioning status of an ephemeral environment for a project. +type EphemeralEnvironmentStatus struct { + Status string `json:"Status"` +} diff --git a/pkg/environments/v2/route.go b/pkg/environments/v2/route.go index 91b95b12..ba5daddf 100644 --- a/pkg/environments/v2/route.go +++ b/pkg/environments/v2/route.go @@ -5,4 +5,6 @@ const ( CreateEphemeralEnvironmentTemplate = "/api/{spaceId}/projects/{projectId}/environments/ephemeral" DeprovisionEphemeralEnvironmentForProjectTemplate = "/api/{spaceId}/projects/{projectId}/environments/ephemeral/{id}/deprovision" DeprovisionEphemeralEnvironmentTemplate = "/api/{spaceId}/environments/ephemeral/{id}/deprovision" + GetEphemeralEnvironmentStatusTemplate = "/api/{spaceId}/projects/{projectId}/environments/ephemeral/{id}/status" + DeleteEphemeralEnvironmentTemplate = "/api/{spaceId}/environments/{id}" ) diff --git a/test/e2e/ephemeral_environment_service_test.go b/test/e2e/ephemeral_environment_service_test.go index d44f537a..af7156a8 100644 --- a/test/e2e/ephemeral_environment_service_test.go +++ b/test/e2e/ephemeral_environment_service_test.go @@ -246,3 +246,76 @@ func DeprovisionEphemeralEnvironment(t *testing.T, client *client.Client, enviro return deprovisionResponse.DeprovisioningRuns } + +func TestEnvironmentServiceGetStatus(t *testing.T) { + client := getOctopusClient() + require.NotNil(t, client) + + space := GetDefaultSpace(t, client) + require.NotNil(t, space) + + lifecycle := CreateTestLifecycle(t, client) + require.NotNil(t, lifecycle) + defer DeleteTestLifecycle(t, client, lifecycle) + + projectGroup := CreateTestProjectGroup(t, client) + require.NotNil(t, projectGroup) + defer DeleteTestProjectGroup(t, client, projectGroup) + + project := CreateTestProject(t, client, space, lifecycle, projectGroup) + require.NotNil(t, project) + defer DeleteTestProject(t, client, project) + + parentEnvironment := CreateParentEnvironment(t, client) + require.NotNil(t, parentEnvironment) + defer DeleteParentEnvironment(t, client, parentEnvironment) + + ephemeralChannel := CreateEphemeralTestChannel(t, client, project, parentEnvironment) + require.NotNil(t, ephemeralChannel) + defer DeleteTestChannel(t, client, ephemeralChannel) + + createdEnvironmentId := CreateEphemeralEnvironment(t, client, project) + defer DeprovisionEphemeralEnvironment(t, client, &createdEnvironmentId) + + status, err := ephemeralenvironments.GetStatus(client, client.GetSpaceID(), createdEnvironmentId, project.GetID()) + require.NoError(t, err) + require.NotNil(t, status) + require.NotEmpty(t, status.Status) +} + +func TestEnvironmentServiceDeleteEphemeralEnvironment(t *testing.T) { + client := getOctopusClient() + require.NotNil(t, client) + + space := GetDefaultSpace(t, client) + require.NotNil(t, space) + + lifecycle := CreateTestLifecycle(t, client) + require.NotNil(t, lifecycle) + defer DeleteTestLifecycle(t, client, lifecycle) + + projectGroup := CreateTestProjectGroup(t, client) + require.NotNil(t, projectGroup) + defer DeleteTestProjectGroup(t, client, projectGroup) + + project := CreateTestProject(t, client, space, lifecycle, projectGroup) + require.NotNil(t, project) + defer DeleteTestProject(t, client, project) + + parentEnvironment := CreateParentEnvironment(t, client) + require.NotNil(t, parentEnvironment) + defer DeleteParentEnvironment(t, client, parentEnvironment) + + ephemeralChannel := CreateEphemeralTestChannel(t, client, project, parentEnvironment) + require.NotNil(t, ephemeralChannel) + defer DeleteTestChannel(t, client, ephemeralChannel) + + createdEnvironmentId := CreateEphemeralEnvironment(t, client, project) + + err := ephemeralenvironments.Delete(client, client.GetSpaceID(), createdEnvironmentId) + require.NoError(t, err) + + deletedEnvironment, err := ephemeralenvironments.GetByID(client, client.GetSpaceID(), createdEnvironmentId) + require.NoError(t, err) + require.Nil(t, deletedEnvironment, "ephemeral environment should be deleted") +}