feat(ansible): support form-urlencoded uri bodies
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
f9d8b3bc51
commit
75bafd10c8
3 changed files with 112 additions and 4 deletions
|
|
@ -1679,8 +1679,13 @@ func moduleURIWithClient(_ *Executor, client sshRunner, args map[string]any) (*T
|
|||
}
|
||||
if bodyText != "" {
|
||||
curlOpts = append(curlOpts, "-d", sprintf("%q", bodyText))
|
||||
if bodyFormat == "json" && !hasHeaderIgnoreCase(headersMap(args), "Content-Type") {
|
||||
curlOpts = append(curlOpts, "-H", "\"Content-Type: application/json\"")
|
||||
if !hasHeaderIgnoreCase(headersMap(args), "Content-Type") {
|
||||
switch bodyFormat {
|
||||
case "json":
|
||||
curlOpts = append(curlOpts, "-H", "\"Content-Type: application/json\"")
|
||||
case "form-urlencoded", "form_urlencoded", "form":
|
||||
curlOpts = append(curlOpts, "-H", "\"Content-Type: application/x-www-form-urlencoded\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
85
modules.go
85
modules.go
|
|
@ -8,6 +8,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -1298,8 +1299,13 @@ func (e *Executor) moduleURI(ctx context.Context, client sshExecutorClient, args
|
|||
}
|
||||
if bodyText != "" {
|
||||
curlOpts = append(curlOpts, "-d", sprintf("%q", bodyText))
|
||||
if bodyFormat == "json" && !hasHeaderIgnoreCase(headersMap(args), "Content-Type") {
|
||||
curlOpts = append(curlOpts, "-H", "\"Content-Type: application/json\"")
|
||||
if !hasHeaderIgnoreCase(headersMap(args), "Content-Type") {
|
||||
switch bodyFormat {
|
||||
case "json":
|
||||
curlOpts = append(curlOpts, "-H", "\"Content-Type: application/json\"")
|
||||
case "form-urlencoded", "form_urlencoded", "form":
|
||||
curlOpts = append(curlOpts, "-H", "\"Content-Type: application/x-www-form-urlencoded\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1361,11 +1367,86 @@ func renderURIBody(body any, bodyFormat string) (string, error) {
|
|||
}
|
||||
return string(data), nil
|
||||
}
|
||||
case "form-urlencoded", "form_urlencoded", "form":
|
||||
return renderURIBodyFormEncoded(body), nil
|
||||
default:
|
||||
return sprintf("%v", body), nil
|
||||
}
|
||||
}
|
||||
|
||||
func renderURIBodyFormEncoded(body any) string {
|
||||
values := url.Values{}
|
||||
|
||||
switch v := body.(type) {
|
||||
case map[string]any:
|
||||
keys := make([]string, 0, len(v))
|
||||
for key := range v {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
appendFormValue(values, key, v[key])
|
||||
}
|
||||
case map[any]any:
|
||||
keys := make([]string, 0, len(v))
|
||||
for key := range v {
|
||||
if s, ok := key.(string); ok {
|
||||
keys = append(keys, s)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
appendFormValue(values, key, v[key])
|
||||
}
|
||||
case map[string]string:
|
||||
keys := make([]string, 0, len(v))
|
||||
for key := range v {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
values.Add(key, v[key])
|
||||
}
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
if pair, ok := item.(map[string]any); ok {
|
||||
key := getStringArg(pair, "key", "")
|
||||
if key == "" {
|
||||
key = getStringArg(pair, "name", "")
|
||||
}
|
||||
if key != "" {
|
||||
appendFormValue(values, key, pair["value"])
|
||||
}
|
||||
}
|
||||
}
|
||||
case string:
|
||||
return v
|
||||
default:
|
||||
return sprintf("%v", body)
|
||||
}
|
||||
|
||||
return values.Encode()
|
||||
}
|
||||
|
||||
func appendFormValue(values url.Values, key string, value any) {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
values.Add(key, "")
|
||||
case string:
|
||||
values.Add(key, v)
|
||||
case []string:
|
||||
for _, item := range v {
|
||||
values.Add(key, item)
|
||||
}
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
values.Add(key, sprintf("%v", item))
|
||||
}
|
||||
default:
|
||||
values.Add(key, sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
func headersMap(args map[string]any) map[string]any {
|
||||
headers, _ := args["headers"].(map[string]any)
|
||||
return headers
|
||||
|
|
|
|||
|
|
@ -1251,6 +1251,28 @@ func TestModulesAdv_ModuleURI_Good_PostWithBodyAndHeaders(t *testing.T) {
|
|||
assert.True(t, mock.containsSubstring("Authorization"))
|
||||
}
|
||||
|
||||
func TestModulesAdv_ModuleURI_Good_FormURLEncodedBody(t *testing.T) {
|
||||
e, mock := newTestExecutorWithMock("host1")
|
||||
mock.expectCommand(`curl.*form\.example\.com`, "created\n201", "", 0)
|
||||
|
||||
result, err := moduleURIWithClient(e, mock, map[string]any{
|
||||
"url": "https://form.example.com/submit",
|
||||
"method": "POST",
|
||||
"body_format": "form-urlencoded",
|
||||
"body": map[string]any{
|
||||
"name": "Alice Example",
|
||||
"scope": []any{"read", "write"},
|
||||
},
|
||||
"status_code": 201,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.False(t, result.Failed)
|
||||
assert.Equal(t, 201, result.RC)
|
||||
assert.True(t, mock.containsSubstring(`-d "name=Alice+Example&scope=read&scope=write"`))
|
||||
assert.True(t, mock.containsSubstring("Content-Type: application/x-www-form-urlencoded"))
|
||||
}
|
||||
|
||||
func TestModulesAdv_ModuleURI_Good_WrongStatusCode(t *testing.T) {
|
||||
e, mock := newTestExecutorWithMock("host1")
|
||||
mock.expectCommand(`curl`, "Not Found\n404", "", 0)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue