Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ jobs:
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -qq -y dokku
sudo dokku plugin:install-dependencies --core
- name: install dokku redis plugin
run: sudo dokku plugin:install https://github.com/dokku/dokku-redis.git redis
- name: run integration tests
run: sudo go test -v -count=1 -run TestIntegration ./tasks/
28 changes: 28 additions & 0 deletions docs/dokku_service_create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# dokku_service_create

Creates or destroys a dokku service

## Create a redis service named my-redis

```yaml
dokku_service_create:
service: redis
name: my-redis
```

## Create a postgres service named my-db

```yaml
dokku_service_create:
service: postgres
name: my-db
```

## Destroy a redis service named my-redis

```yaml
dokku_service_create:
service: redis
name: my-redis
state: absent
```
87 changes: 87 additions & 0 deletions tasks/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tasks
import (
"omakase/subprocess"
"os"
"strings"
"testing"
)

Expand All @@ -25,6 +26,31 @@ func skipIfNoDokkuT(t *testing.T) {
}
}

func dokkuPluginInstalled(plugin string) bool {
result, err := subprocess.CallExecCommand(subprocess.ExecCommandInput{
Command: "dokku",
Args: []string{"plugin:list"},
})
if err != nil {
return false
}

for _, line := range strings.Split(result.StdoutContents(), "\n") {
fields := strings.Fields(line)
if len(fields) > 0 && fields[0] == plugin {
return true
}
}
return false
}

func skipIfPluginMissingT(t *testing.T, plugin string) {
t.Helper()
if !dokkuPluginInstalled(plugin) {
t.Skipf("skipping integration test: dokku plugin %q not installed", plugin)
}
}

func TestIntegrationAppCreateAndDestroy(t *testing.T) {
skipIfNoDokkuT(t)

Expand Down Expand Up @@ -934,3 +960,64 @@ func TestIntegrationMultiTaskWorkflow(t *testing.T) {
}
}
}

func TestIntegrationServiceCreateAndDestroy(t *testing.T) {
skipIfNoDokkuT(t)
skipIfPluginMissingT(t, "redis")

serviceName := "omakase-test-service"
serviceType := "redis"

// ensure clean state
destroyService(serviceType, serviceName)

// create the service
task := ServiceCreateTask{Service: serviceType, Name: serviceName, State: StatePresent}
result := task.Execute()
if result.Error != nil {
t.Fatalf("failed to create service: %v", result.Error)
}
if result.State != StatePresent {
t.Errorf("expected state 'present', got '%s'", result.State)
}
if !result.Changed {
t.Error("expected changed=true for new service creation")
}

// creating again should be idempotent
result = task.Execute()
if result.Error != nil {
t.Fatalf("idempotent create failed: %v", result.Error)
}
if result.Changed {
t.Error("expected changed=false for existing service")
}
if result.State != StatePresent {
t.Errorf("expected state 'present', got '%s'", result.State)
}

// destroy the service
destroyTask := ServiceCreateTask{Service: serviceType, Name: serviceName, State: StateAbsent}
result = destroyTask.Execute()
if result.Error != nil {
t.Fatalf("failed to destroy service: %v", result.Error)
}
if result.State != StateAbsent {
t.Errorf("expected state 'absent', got '%s'", result.State)
}
if !result.Changed {
t.Error("expected changed=true for service destruction")
}

// destroying again should be idempotent
result = destroyTask.Execute()
if result.Error != nil {
t.Fatalf("idempotent destroy failed: %v", result.Error)
}
if result.Changed {
t.Error("expected changed=false for nonexistent service")
}
if result.State != StateAbsent {
t.Errorf("expected state 'absent', got '%s'", result.State)
}
}
81 changes: 81 additions & 0 deletions tasks/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func TestRegisteredTasksExist(t *testing.T) {
"dokku_proxy_toggle",
"dokku_resource_limit",
"dokku_resource_reserve",
"dokku_service_create",
"dokku_storage_ensure",
"dokku_storage_mount",
}
Expand Down Expand Up @@ -626,3 +627,83 @@ func TestGetTasksResourceReserveTaskParsedCorrectly(t *testing.T) {
t.Error("ClearBefore = false, want true (YAML value should be preserved)")
}
}

func TestGetTasksServiceCreateTaskParsedCorrectly(t *testing.T) {
data := []byte(`---
- tasks:
- name: create redis service
dokku_service_create:
service: redis
name: my-redis
`)
context := map[string]interface{}{}

tasks, err := GetTasks(data, context)
if err != nil {
t.Fatalf("GetTasks failed: %v", err)
}

task := tasks.Get("create redis service")
if task == nil {
t.Fatal("task 'create redis service' not found")
}

scTask, ok := task.(*ServiceCreateTask)
if !ok {
st, ok2 := task.(ServiceCreateTask)
if !ok2 {
t.Fatalf("task is not a ServiceCreateTask (type is %T)", task)
}
scTask = &st
}

if scTask.Service != "redis" {
t.Errorf("Service = %q, want %q", scTask.Service, "redis")
}
if scTask.Name != "my-redis" {
t.Errorf("Name = %q, want %q", scTask.Name, "my-redis")
}
if scTask.DesiredState() != StatePresent {
t.Errorf("expected default state 'present', got %q", scTask.DesiredState())
}
}

func TestGetTasksServiceCreateWithTemplateContext(t *testing.T) {
data := []byte(`---
- tasks:
- name: create {{ .service_type }} service
dokku_service_create:
service: {{ .service_type }}
name: {{ .service_name }}
`)
context := map[string]interface{}{
"service_type": "postgres",
"service_name": "my-db",
}

tasks, err := GetTasks(data, context)
if err != nil {
t.Fatalf("GetTasks failed: %v", err)
}

task := tasks.Get("create postgres service")
if task == nil {
t.Fatal("task 'create postgres service' not found")
}

scTask, ok := task.(*ServiceCreateTask)
if !ok {
st, ok2 := task.(ServiceCreateTask)
if !ok2 {
t.Fatalf("task is not a ServiceCreateTask (type is %T)", task)
}
scTask = &st
}

if scTask.Service != "postgres" {
t.Errorf("Service = %q, want %q", scTask.Service, "postgres")
}
if scTask.Name != "my-db" {
t.Errorf("Name = %q, want %q", scTask.Name, "my-db")
}
}
Loading
Loading