diff --git a/cmd/osbuild-weldr-tests/main.go b/cmd/osbuild-weldr-tests/main.go index 7a7002832..d04171c2a 100644 --- a/cmd/osbuild-weldr-tests/main.go +++ b/cmd/osbuild-weldr-tests/main.go @@ -3,9 +3,24 @@ package main import ( + "context" + "net" + "net/http" + "time" + "github.com/osbuild/osbuild-composer/internal/weldrcheck" ) func main() { - weldrcheck.Run("/run/weldr/api.socket") + client := &http.Client{ + // TODO This may be too short/simple for downloading images + Timeout: 60 * time.Second, + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", "/run/weldr/api.socket") + }, + }, + } + + weldrcheck.Run(client) } diff --git a/internal/client/blueprints.go b/internal/client/blueprints.go index 99ade9cef..2df7e8381 100644 --- a/internal/client/blueprints.go +++ b/internal/client/blueprints.go @@ -5,12 +5,13 @@ package client import ( "encoding/json" "fmt" + "net/http" "strings" ) // PostTOMLBlueprintV0 sends a TOML blueprint string to the API // and returns an APIResponse -func PostTOMLBlueprintV0(socket, blueprint string) (*APIResponse, error) { +func PostTOMLBlueprintV0(socket *http.Client, blueprint string) (*APIResponse, error) { body, resp, err := PostTOML(socket, "/api/v0/blueprints/new", blueprint) if resp != nil || err != nil { return resp, err @@ -20,7 +21,7 @@ func PostTOMLBlueprintV0(socket, blueprint string) (*APIResponse, error) { // PostTOMLWorkspaceV0 sends a TOML blueprint string to the API // and returns an APIResponse -func PostTOMLWorkspaceV0(socket, blueprint string) (*APIResponse, error) { +func PostTOMLWorkspaceV0(socket *http.Client, blueprint string) (*APIResponse, error) { body, resp, err := PostTOML(socket, "/api/v0/blueprints/workspace", blueprint) if resp != nil || err != nil { return resp, err @@ -30,7 +31,7 @@ func PostTOMLWorkspaceV0(socket, blueprint string) (*APIResponse, error) { // PostJSONBlueprintV0 sends a JSON blueprint string to the API // and returns an APIResponse -func PostJSONBlueprintV0(socket, blueprint string) (*APIResponse, error) { +func PostJSONBlueprintV0(socket *http.Client, blueprint string) (*APIResponse, error) { body, resp, err := PostJSON(socket, "/api/v0/blueprints/new", blueprint) if resp != nil || err != nil { return resp, err @@ -40,7 +41,7 @@ func PostJSONBlueprintV0(socket, blueprint string) (*APIResponse, error) { // PostJSONWorkspaceV0 sends a JSON blueprint string to the API // and returns an APIResponse -func PostJSONWorkspaceV0(socket, blueprint string) (*APIResponse, error) { +func PostJSONWorkspaceV0(socket *http.Client, blueprint string) (*APIResponse, error) { body, resp, err := PostJSON(socket, "/api/v0/blueprints/workspace", blueprint) if resp != nil || err != nil { return resp, err @@ -49,7 +50,7 @@ func PostJSONWorkspaceV0(socket, blueprint string) (*APIResponse, error) { } // DeleteBlueprintV0 deletes the named blueprint and returns an APIResponse -func DeleteBlueprintV0(socket, bpName string) (*APIResponse, error) { +func DeleteBlueprintV0(socket *http.Client, bpName string) (*APIResponse, error) { body, resp, err := DeleteRaw(socket, "/api/v0/blueprints/delete/"+bpName) if resp != nil || err != nil { return resp, err @@ -58,7 +59,7 @@ func DeleteBlueprintV0(socket, bpName string) (*APIResponse, error) { } // DeleteWorkspaceV0 deletes the named blueprint's workspace and returns an APIResponse -func DeleteWorkspaceV0(socket, bpName string) (*APIResponse, error) { +func DeleteWorkspaceV0(socket *http.Client, bpName string) (*APIResponse, error) { body, resp, err := DeleteRaw(socket, "/api/v0/blueprints/workspace/"+bpName) if resp != nil || err != nil { return resp, err @@ -67,7 +68,7 @@ func DeleteWorkspaceV0(socket, bpName string) (*APIResponse, error) { } // ListBlueprintsV0 returns a list of blueprint names -func ListBlueprintsV0(socket string) ([]string, *APIResponse, error) { +func ListBlueprintsV0(socket *http.Client) ([]string, *APIResponse, error) { body, resp, err := GetJSONAll(socket, "/api/v0/blueprints/list") if resp != nil || err != nil { return nil, resp, err @@ -81,7 +82,7 @@ func ListBlueprintsV0(socket string) ([]string, *APIResponse, error) { } // GetBlueprintInfoTOMLV0 returns the requested blueprint as a TOML string -func GetBlueprintInfoTOMLV0(socket, bpName string) (string, *APIResponse, error) { +func GetBlueprintInfoTOMLV0(socket *http.Client, bpName string) (string, *APIResponse, error) { body, resp, err := GetRaw(socket, "GET", "/api/v0/blueprints/info/"+bpName+"?format=toml") if resp != nil || err != nil { return "", resp, err @@ -90,7 +91,7 @@ func GetBlueprintInfoTOMLV0(socket, bpName string) (string, *APIResponse, error) } // GetBlueprintsInfoJSONV0 returns the requested blueprints and their changed state -func GetBlueprintsInfoJSONV0(socket, bpName string) (BlueprintsInfoV0, *APIResponse, error) { +func GetBlueprintsInfoJSONV0(socket *http.Client, bpName string) (BlueprintsInfoV0, *APIResponse, error) { body, resp, err := GetRaw(socket, "GET", "/api/v0/blueprints/info/"+bpName) if resp != nil || err != nil { return BlueprintsInfoV0{}, resp, err @@ -104,7 +105,7 @@ func GetBlueprintsInfoJSONV0(socket, bpName string) (BlueprintsInfoV0, *APIRespo } // GetBlueprintsChangesV0 returns the changes to the listed blueprints -func GetBlueprintsChangesV0(socket string, bpNames []string) (BlueprintsChangesV0, *APIResponse, error) { +func GetBlueprintsChangesV0(socket *http.Client, bpNames []string) (BlueprintsChangesV0, *APIResponse, error) { names := strings.Join(bpNames, ",") body, resp, err := GetRaw(socket, "GET", "/api/v0/blueprints/changes/"+names) if resp != nil || err != nil { @@ -119,7 +120,7 @@ func GetBlueprintsChangesV0(socket string, bpNames []string) (BlueprintsChangesV } // UndoBlueprintChangeV0 reverts a blueprint to a previous commit -func UndoBlueprintChangeV0(socket, blueprint, commit string) (*APIResponse, error) { +func UndoBlueprintChangeV0(socket *http.Client, blueprint, commit string) (*APIResponse, error) { request := fmt.Sprintf("/api/v0/blueprints/undo/%s/%s", blueprint, commit) body, resp, err := PostRaw(socket, request, "", nil) if resp != nil || err != nil { @@ -129,7 +130,7 @@ func UndoBlueprintChangeV0(socket, blueprint, commit string) (*APIResponse, erro } // TagBlueprintV0 tags the current blueprint commit as a new revision -func TagBlueprintV0(socket, blueprint string) (*APIResponse, error) { +func TagBlueprintV0(socket *http.Client, blueprint string) (*APIResponse, error) { body, resp, err := PostRaw(socket, "/api/v0/blueprints/tag/"+blueprint, "", nil) if resp != nil || err != nil { return resp, err @@ -138,7 +139,7 @@ func TagBlueprintV0(socket, blueprint string) (*APIResponse, error) { } // DepsolveBlueprintV0 depsolves the listed blueprint -func DepsolveBlueprintV0(socket, blueprint string) (BlueprintsDepsolveV0, *APIResponse, error) { +func DepsolveBlueprintV0(socket *http.Client, blueprint string) (BlueprintsDepsolveV0, *APIResponse, error) { body, resp, err := GetRaw(socket, "GET", "/api/v0/blueprints/depsolve/"+blueprint) if resp != nil || err != nil { return BlueprintsDepsolveV0{}, resp, err @@ -153,7 +154,7 @@ func DepsolveBlueprintV0(socket, blueprint string) (BlueprintsDepsolveV0, *APIRe // FreezeBlueprintV0 depsolves the listed blueprint and returns the blueprint with frozen package // versions -func FreezeBlueprintV0(socket, blueprint string) (BlueprintsFreezeV0, *APIResponse, error) { +func FreezeBlueprintV0(socket *http.Client, blueprint string) (BlueprintsFreezeV0, *APIResponse, error) { body, resp, err := GetRaw(socket, "GET", "/api/v0/blueprints/freeze/"+blueprint) if resp != nil || err != nil { return BlueprintsFreezeV0{}, resp, err diff --git a/internal/client/client.go b/internal/client/client.go index e2c1c73fa..f1ae7e1a0 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -4,14 +4,11 @@ package client import ( "bytes" - "context" "encoding/json" "errors" "fmt" "io/ioutil" - "net" "net/http" - "time" ) // Request handles sending the request, handling errors, returning the response @@ -22,17 +19,7 @@ import ( // // If it is successful a http.Response will be returned. If there is an error, the response will be // nil and error will be returned. -func Request(socket, method, path, body string, headers map[string]string) (*http.Response, error) { - client := http.Client{ - // TODO This may be too short/simple for downloading images - Timeout: 60 * time.Second, - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socket) - }, - }, - } - +func Request(socket *http.Client, method, path, body string, headers map[string]string) (*http.Response, error) { req, err := http.NewRequest(method, "http://localhost"+path, bytes.NewReader([]byte(body))) if err != nil { return nil, err @@ -42,7 +29,7 @@ func Request(socket, method, path, body string, headers map[string]string) (*htt req.Header.Set(h, v) } - resp, err := client.Do(req) + resp, err := socket.Do(req) if err != nil { return nil, err } @@ -112,7 +99,7 @@ func apiError(resp *http.Response) (*APIResponse, error) { // GetRaw returns raw data from a GET request // Errors from the API are returned as an APIResponse, client errors are returned as error -func GetRaw(socket, method, path string) ([]byte, *APIResponse, error) { +func GetRaw(socket *http.Client, method, path string) ([]byte, *APIResponse, error) { resp, err := Request(socket, method, path, "", map[string]string{}) if err != nil { return nil, nil, err @@ -138,7 +125,7 @@ func GetRaw(socket, method, path string) ([]byte, *APIResponse, error) { // and then with limit=TOTAL to fetch all of the results. // The path passed to GetJSONAll should not include the limit or offset query parameters // Errors from the API are returned as an APIResponse, client errors are returned as error -func GetJSONAll(socket, path string) ([]byte, *APIResponse, error) { +func GetJSONAll(socket *http.Client, path string) ([]byte, *APIResponse, error) { body, api, err := GetRaw(socket, "GET", path+"?limit=0") if api != nil || err != nil { return nil, api, err @@ -167,7 +154,7 @@ func GetJSONAll(socket, path string) ([]byte, *APIResponse, error) { // PostRaw sends a POST with raw data and returns the raw response body // Errors from the API are returned as an APIResponse, client errors are returned as error -func PostRaw(socket, path, body string, headers map[string]string) ([]byte, *APIResponse, error) { +func PostRaw(socket *http.Client, path, body string, headers map[string]string) ([]byte, *APIResponse, error) { resp, err := Request(socket, "POST", path, body, headers) if err != nil { return nil, nil, err @@ -190,21 +177,21 @@ func PostRaw(socket, path, body string, headers map[string]string) ([]byte, *API // PostTOML sends a POST with TOML data and the Content-Type header set to "text/x-toml" // Errors from the API are returned as an APIResponse, client errors are returned as error -func PostTOML(socket, path, body string) ([]byte, *APIResponse, error) { +func PostTOML(socket *http.Client, path, body string) ([]byte, *APIResponse, error) { headers := map[string]string{"Content-Type": "text/x-toml"} return PostRaw(socket, path, body, headers) } // PostJSON sends a POST with JSON data and the Content-Type header set to "application/json" // Errors from the API are returned as an APIResponse, client errors are returned as error -func PostJSON(socket, path, body string) ([]byte, *APIResponse, error) { +func PostJSON(socket *http.Client, path, body string) ([]byte, *APIResponse, error) { headers := map[string]string{"Content-Type": "application/json"} return PostRaw(socket, path, body, headers) } // DeleteRaw sends a DELETE request // Errors from the API are returned as an APIResponse, client errors are returned as error -func DeleteRaw(socket, path string) ([]byte, *APIResponse, error) { +func DeleteRaw(socket *http.Client, path string) ([]byte, *APIResponse, error) { resp, err := Request(socket, "DELETE", path, "", nil) if err != nil { return nil, nil, err diff --git a/internal/client/client_test.go b/internal/client/client_test.go index f11c79e02..4b68f0d7d 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -3,6 +3,7 @@ package client import ( + "context" "io/ioutil" "log" "net" @@ -10,6 +11,7 @@ import ( "os" "strings" "testing" + "time" test_distro "github.com/osbuild/osbuild-composer/internal/distro/fedoratest" rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd" @@ -21,9 +23,12 @@ import ( // socketPath is the path to the temporary unix domain socket var socketPath string +// socket is the client connection to the server on socketPath +var socket *http.Client + func TestRequest(t *testing.T) { // Make a request to the status route - resp, err := Request(socketPath, "GET", "/api/status", "", map[string]string{}) + resp, err := Request(socket, "GET", "/api/status", "", map[string]string{}) if err != nil { t.Fatalf("Request good route failed: %v", err) } @@ -32,7 +37,7 @@ func TestRequest(t *testing.T) { } // Make a request to a bad route - resp, err = Request(socketPath, "GET", "/invalidroute", "", map[string]string{}) + resp, err = Request(socket, "GET", "/invalidroute", "", map[string]string{}) if err != nil { t.Fatalf("Request bad route failed: %v", err) } @@ -47,7 +52,7 @@ func TestRequest(t *testing.T) { } // Make a request with a bad offset to trigger a JSON response with Status set to 400 - resp, err = Request(socketPath, "GET", "/api/v0/blueprints/list?offset=bad", "", map[string]string{}) + resp, err = Request(socket, "GET", "/api/v0/blueprints/list?offset=bad", "", map[string]string{}) if err != nil { t.Fatalf("Request bad offset failed: %v", err) } @@ -84,7 +89,7 @@ func TestAPIResponse(t *testing.T) { func TestGetRaw(t *testing.T) { // Get raw data - b, resp, err := GetRaw(socketPath, "GET", "/api/status") + b, resp, err := GetRaw(socket, "GET", "/api/status") if err != nil { t.Fatalf("GetRaw failed with a client error: %v", err) } @@ -95,7 +100,7 @@ func TestGetRaw(t *testing.T) { t.Fatal("GetRaw returned an empty string") } // Get an API error - b, resp, err = GetRaw(socketPath, "GET", "/api/v0/blueprints/list?offset=bad") + b, resp, err = GetRaw(socket, "GET", "/api/v0/blueprints/list?offset=bad") if err != nil { t.Fatalf("GetRaw bad request failed with a client error: %v", err) } @@ -114,7 +119,7 @@ func TestGetRaw(t *testing.T) { func TestGetJSONAll(t *testing.T) { // Get all the projects - b, resp, err := GetJSONAll(socketPath, "/api/v0/projects/list") + b, resp, err := GetJSONAll(socket, "/api/v0/projects/list") if err != nil { t.Fatalf("GetJSONAll failed with a client error: %v", err) } @@ -126,7 +131,7 @@ func TestGetJSONAll(t *testing.T) { } // Run it on a route that doesn't support offset/limit - b, resp, err = GetJSONAll(socketPath, "/api/status") + b, resp, err = GetJSONAll(socket, "/api/status") if err == nil { t.Fatalf("GetJSONAll bad route failed: %v", b) } @@ -137,7 +142,7 @@ func TestGetJSONAll(t *testing.T) { func TestPostRaw(t *testing.T) { // There are no routes that accept raw POST w/o Content-Type so this ends up testing the error path - b, resp, err := PostRaw(socketPath, "/api/v0/blueprints/new", "nobody", nil) + b, resp, err := PostRaw(socket, "/api/v0/blueprints/new", "nobody", nil) if err != nil { t.Fatalf("PostRaw bad request failed with a client error: %v", err) } @@ -158,7 +163,7 @@ func TestPostTOML(t *testing.T) { blueprint := `name = "test-blueprint" description = "TOML test blueprint" version = "0.0.1"` - b, resp, err := PostTOML(socketPath, "/api/v0/blueprints/new", blueprint) + b, resp, err := PostTOML(socket, "/api/v0/blueprints/new", blueprint) if err != nil { t.Fatalf("PostTOML client failed: %v", err) } @@ -174,7 +179,7 @@ func TestPostJSON(t *testing.T) { blueprint := `{"name": "test-blueprint", "description": "JSON test blueprint", "version": "0.0.1"}` - b, resp, err := PostJSON(socketPath, "/api/v0/blueprints/new", blueprint) + b, resp, err := PostJSON(socket, "/api/v0/blueprints/new", blueprint) if err != nil { t.Fatalf("PostJSON client failed: %v", err) } @@ -216,6 +221,16 @@ func TestMain(m *testing.M) { } }() + // Setup client connection to socketPath + socket = &http.Client{ + Timeout: 60 * time.Second, + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }, + }, + } + // Run the tests os.Exit(m.Run()) } diff --git a/internal/client/weldr.go b/internal/client/weldr.go index 711c898de..dcb9feede 100644 --- a/internal/client/weldr.go +++ b/internal/client/weldr.go @@ -4,10 +4,11 @@ package client import ( "encoding/json" + "net/http" ) // GetStatusV0 makes a GET request to /api/status and returns the v0 response as a StatusResponseV0 -func GetStatusV0(socket string) (reply StatusV0, resp *APIResponse, err error) { +func GetStatusV0(socket *http.Client) (reply StatusV0, resp *APIResponse, err error) { body, resp, err := GetRaw(socket, "GET", "/api/status") if resp != nil || err != nil { return reply, resp, err diff --git a/internal/weldrcheck/blueprints.go b/internal/weldrcheck/blueprints.go index f65bf293a..e00d454fb 100644 --- a/internal/weldrcheck/blueprints.go +++ b/internal/weldrcheck/blueprints.go @@ -4,6 +4,7 @@ package weldrcheck import ( "log" + "net/http" "reflect" "sort" "strings" @@ -14,7 +15,7 @@ import ( ) type checkBlueprintsV0 struct { - socket string + socket *http.Client } // Run will execute the API V0 Blueprint check functions diff --git a/internal/weldrcheck/check.go b/internal/weldrcheck/check.go index b22edd681..9a075908a 100644 --- a/internal/weldrcheck/check.go +++ b/internal/weldrcheck/check.go @@ -4,6 +4,7 @@ package weldrcheck import ( "log" + "net/http" "os" "sort" "strconv" @@ -32,7 +33,7 @@ func isStringInSlice(slice []string, s string) bool { // Run the API V0 checks against the server // Return true if all the checks pass -func runV0Checks(socket string) (pass bool) { +func runV0Checks(socket *http.Client) (pass bool) { pass = true bpv0 := checkBlueprintsV0{socket} @@ -47,7 +48,7 @@ func runV0Checks(socket string) (pass bool) { } // Run the V1 checks against the server -func runV1Checks(socket string) (pass bool) { +func runV1Checks(socket *http.Client) (pass bool) { pass = true if pass { @@ -60,14 +61,9 @@ func runV1Checks(socket string) (pass bool) { // Run executes all of the weldr API checks against a running API server // This is designed to run against any WELDR API server, not just osbuild-composer -func Run(socket string) { - log.Printf("Running API check on %s", socket) +func Run(socket *http.Client) { + log.Print("Running API check") - // Does the socket exist? - if _, err := os.Stat(socket); os.IsNotExist(err) { - log.Printf("ERROR: API socket %s is missing", socket) - os.Exit(1) - } // Does the server respond to /api/status? status, resp, err := client.GetStatusV0(socket) if err != nil {