Skip to content

Commit 6b0c5fe

Browse files
authored
Merge pull request #5 from innogames/api_function
Implement calling API functions
2 parents 1edbe71 + 5077fcb commit 6b0c5fe

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ server.Set("maintenance_mode", "true")
142142
server.Commit()
143143
```
144144

145+
### Calling API Functions
146+
147+
```go
148+
// Call a remote API function by group and function name
149+
result, err := adminapi.CallAPI("ip", "get_free", map[string]any{"network": "internal"})
150+
if err != nil {
151+
panic(err)
152+
}
153+
fmt.Printf("Free IP: %s\n", result)
154+
```
155+
145156
## Building
146157

147158
```bash

adminapi/call.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package adminapi
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
const apiEndpointCall = "/call"
9+
10+
type callRequest struct {
11+
Group string `json:"group"`
12+
Name string `json:"name"`
13+
Args []any `json:"args"`
14+
Kwargs map[string]any `json:"kwargs"`
15+
}
16+
17+
type callResponse struct {
18+
Status string `json:"status"`
19+
RetVal any `json:"retval"`
20+
Message string `json:"message"`
21+
}
22+
23+
// CallAPI calls a remote API function on the Serveradmin server.
24+
// It takes a function group, function name, and keyword arguments as a map.
25+
func CallAPI(group, function string, args map[string]any) (any, error) {
26+
req := callRequest{
27+
Group: group,
28+
Name: function,
29+
Args: []any{},
30+
Kwargs: args,
31+
}
32+
33+
resp, err := sendRequest(apiEndpointCall, req)
34+
if err != nil {
35+
return nil, err
36+
}
37+
defer resp.Body.Close()
38+
39+
var result callResponse
40+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
41+
return nil, fmt.Errorf("failed to decode call response: %w", err)
42+
}
43+
44+
if result.Status == "error" {
45+
return nil, fmt.Errorf("API call %s.%s failed: %s", group, function, result.Message)
46+
}
47+
48+
return result.RetVal, nil
49+
}

adminapi/call_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package adminapi
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestCallAPISuccess(t *testing.T) {
15+
var receivedBody callRequest
16+
17+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18+
body, _ := io.ReadAll(r.Body)
19+
json.Unmarshal(body, &receivedBody)
20+
21+
w.WriteHeader(200)
22+
w.Write([]byte(`{"status": "success", "retval": "10.0.0.1"}`))
23+
}))
24+
defer server.Close()
25+
26+
resetConfig()
27+
t.Setenv("SERVERADMIN_TOKEN", "testtoken")
28+
t.Setenv("SERVERADMIN_BASE_URL", server.URL)
29+
30+
result, err := CallAPI("ip", "get_free", map[string]any{"network": "internal"})
31+
require.NoError(t, err)
32+
assert.Equal(t, "10.0.0.1", result)
33+
34+
// Verify request structure
35+
assert.Equal(t, "ip", receivedBody.Group)
36+
assert.Equal(t, "get_free", receivedBody.Name)
37+
assert.Empty(t, receivedBody.Args)
38+
assert.Equal(t, map[string]any{"network": "internal"}, receivedBody.Kwargs)
39+
}
40+
41+
func TestCallAPIError(t *testing.T) {
42+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
43+
w.WriteHeader(200)
44+
w.Write([]byte(`{"status": "error", "message": "function not found"}`))
45+
}))
46+
defer server.Close()
47+
48+
resetConfig()
49+
t.Setenv("SERVERADMIN_TOKEN", "testtoken")
50+
t.Setenv("SERVERADMIN_BASE_URL", server.URL)
51+
52+
result, err := CallAPI("ip", "nonexistent", map[string]any{})
53+
assert.Nil(t, result)
54+
require.Error(t, err)
55+
assert.Contains(t, err.Error(), "ip.nonexistent")
56+
assert.Contains(t, err.Error(), "function not found")
57+
}
58+
59+
func TestCallAPIComplexReturnValue(t *testing.T) {
60+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
61+
w.WriteHeader(200)
62+
w.Write([]byte(`{"status": "success", "retval": {"ip": "10.0.0.1", "network": "internal"}}`))
63+
}))
64+
defer server.Close()
65+
66+
resetConfig()
67+
t.Setenv("SERVERADMIN_TOKEN", "testtoken")
68+
t.Setenv("SERVERADMIN_BASE_URL", server.URL)
69+
70+
result, err := CallAPI("ip", "get_details", map[string]any{"ip": "10.0.0.1"})
71+
require.NoError(t, err)
72+
73+
resultMap, ok := result.(map[string]any)
74+
require.True(t, ok, "expected map return value")
75+
assert.Equal(t, "10.0.0.1", resultMap["ip"])
76+
assert.Equal(t, "internal", resultMap["network"])
77+
}
78+
79+
func TestCallAPINilArgs(t *testing.T) {
80+
var receivedBody callRequest
81+
82+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83+
body, _ := io.ReadAll(r.Body)
84+
json.Unmarshal(body, &receivedBody)
85+
86+
w.WriteHeader(200)
87+
w.Write([]byte(`{"status": "success", "retval": null}`))
88+
}))
89+
defer server.Close()
90+
91+
resetConfig()
92+
t.Setenv("SERVERADMIN_TOKEN", "testtoken")
93+
t.Setenv("SERVERADMIN_BASE_URL", server.URL)
94+
95+
result, err := CallAPI("system", "ping", nil)
96+
require.NoError(t, err)
97+
assert.Nil(t, result)
98+
99+
assert.Equal(t, "system", receivedBody.Group)
100+
assert.Equal(t, "ping", receivedBody.Name)
101+
}

examples/update_example.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ func batchDeleteExample() {
9494
fmt.Printf("Deleted %d servers (commit %d)\n", len(servers), commitID)
9595
}
9696

97+
func callAPIExample() {
98+
// Call a remote API function
99+
result, err := api.CallAPI("ip", "get_free", map[string]any{"network": "internal"})
100+
checkErr(err)
101+
102+
fmt.Printf("Free IP: %s\n", result)
103+
}
104+
97105
func rollbackExample() {
98106
q, err := api.FromQuery("hostname=webserver01")
99107
checkErr(err)

0 commit comments

Comments
 (0)