diff --git a/systemtest/fleet_test.go b/systemtest/fleet_test.go index 2cc3c33122f..fb23b412ccc 100644 --- a/systemtest/fleet_test.go +++ b/systemtest/fleet_test.go @@ -19,14 +19,17 @@ package systemtest_test import ( "context" + "errors" "fmt" "io/ioutil" + "net/http" "net/url" "path" "path/filepath" "runtime" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/wait" "go.elastic.co/apm" @@ -170,6 +173,13 @@ func cleanupFleet(t testing.TB, fleet *fleettest.Client) { } require.NoError(t, fleet.BulkUnenrollAgents(true, agentIDs...)) } - require.NoError(t, fleet.DeleteAgentPolicy(p.ID)) + // BUG(axw) the Fleet API is returning 404 when deleting agent policies + // in some circumstances: https://github.com/elastic/kibana/issues/90544 + err := fleet.DeleteAgentPolicy(p.ID) + require.Error(t, err) + var fleetError *fleettest.Error + if errors.As(err, &fleetError) { + assert.Equal(t, http.StatusNotFound, fleetError.StatusCode) + } } } diff --git a/systemtest/fleettest/client.go b/systemtest/fleettest/client.go index a20891ae998..26e2c390c08 100644 --- a/systemtest/fleettest/client.go +++ b/systemtest/fleettest/client.go @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/url" ) @@ -47,9 +46,8 @@ func (c *Client) Setup() error { return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("request failed (%s): %s", resp.Status, body) + if err := consumeResponse(resp, nil); err != nil { + return err } } return nil @@ -62,14 +60,10 @@ func (c *Client) Agents() ([]Agent, error) { return nil, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("request failed (%s): %s", resp.Status, body) - } var result struct { List []Agent `json:"list"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, err } return result.List, nil @@ -91,11 +85,7 @@ func (c *Client) BulkUnenrollAgents(force bool, agentIDs ...string) error { return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("request failed (%s): %s", resp.Status, body) - } - return nil + return consumeResponse(resp, nil) } // AgentPolicies returns the Agent Policies matching the given KQL query. @@ -112,14 +102,10 @@ func (c *Client) AgentPolicies(kuery string) ([]AgentPolicy, error) { return nil, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("request failed (%s): %s", resp.Status, body) - } var result struct { Items []AgentPolicy `json:"items"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, err } return result.Items, nil @@ -140,11 +126,7 @@ func (c *Client) DeleteAgentPolicy(id string) error { return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("request failed (%s): %s", resp.Status, body) - } - return nil + return consumeResponse(resp, nil) } // CreateAgentPolicy returns the default Agent Policy. @@ -169,14 +151,10 @@ func (c *Client) CreateAgentPolicy(name, namespace, description string) (*AgentP return nil, nil, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return nil, nil, fmt.Errorf("request failed (%s): %s", resp.Status, body) - } var result struct { Item AgentPolicy `json:"item"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, nil, err } enrollmentAPIKey, err := c.getAgentPolicyEnrollmentAPIKey(result.Item.ID) @@ -201,7 +179,7 @@ func (c *Client) getAgentPolicyEnrollmentAPIKey(policyID string) (*EnrollmentAPI var result struct { Item EnrollmentAPIKey `json:"item"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, err } return &result.Item, nil @@ -220,14 +198,10 @@ func (c *Client) enrollmentAPIKeys(kuery string) ([]EnrollmentAPIKey, error) { return nil, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("request failed (%s): %s", resp.Status, body) - } var result struct { Items []EnrollmentAPIKey `json:"list"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, err } return result.Items, nil @@ -243,7 +217,7 @@ func (c *Client) ListPackages() ([]Package, error) { var result struct { Response []Package `json:"response"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, err } return result.Response, nil @@ -256,14 +230,10 @@ func (c *Client) PackagePolicy(id string) (*PackagePolicy, error) { return nil, err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("request failed (%s): %s", resp.Status, body) - } var result struct { Item PackagePolicy `json:"item"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := consumeResponse(resp, &result); err != nil { return nil, err } return &result.Item, nil @@ -281,11 +251,7 @@ func (c *Client) CreatePackagePolicy(p PackagePolicy) error { return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("request failed (%s): %s", resp.Status, body) - } - return nil + return consumeResponse(resp, nil) } // DeletePackagePolicy deletes one or more package policies. @@ -304,11 +270,7 @@ func (c *Client) DeletePackagePolicy(ids ...string) error { return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("request failed (%s): %s", resp.Status, body) - } - return nil + return consumeResponse(resp, nil) } func (c *Client) newFleetRequest(method string, path string, body io.Reader) *http.Request { @@ -320,3 +282,19 @@ func (c *Client) newFleetRequest(method string, path string, body io.Reader) *ht req.Header.Set("kbn-xsrf", "1") return req } + +func consumeResponse(resp *http.Response, out interface{}) error { + if resp.StatusCode != http.StatusOK { + var e Error + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return err + } + return &e + } + if out != nil { + if err := json.NewDecoder(resp.Body).Decode(out); err != nil { + return err + } + } + return nil +} diff --git a/systemtest/fleettest/error.go b/systemtest/fleettest/error.go new file mode 100644 index 00000000000..2a7d0a5bcc6 --- /dev/null +++ b/systemtest/fleettest/error.go @@ -0,0 +1,31 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package fleettest + +// Error is an error type returned by Client methods on failed +// requests to the Kibana Fleet API. +type Error struct { + StatusCode int `json:"statusCode"` + ErrorCode string `json:"error"` + Message string `json:"message"` +} + +// Error returns the error message. +func (e *Error) Error() string { + return e.Message +}