kibana/fleet.go (601 lines of code) (raw):
// 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 kibana
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/elastic/elastic-agent-libs/upgrade/details"
)
// The full documentation for the Kibana API can be found on
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json
const (
fleetAgentAPI = "/api/fleet/agents/%s"
fleetAgentPoliciesAPI = "/api/fleet/agent_policies"
fleetAgentPolicyAPI = "/api/fleet/agent_policies/%s"
fleetAgentsAPI = "/api/fleet/agents"
fleetAgentsDeleteAPI = "/api/fleet/agent_policies/delete"
fleetEnrollmentAPIKeysAPI = "/api/fleet/enrollment_api_keys" //nolint:gosec // no API key being leaked here
fleetFleetServerHostsAPI = "/api/fleet/fleet_server_hosts"
fleetPackagePoliciesAPI = "/api/fleet/package_policies"
fleetUnEnrollAgentAPI = "/api/fleet/agents/%s/unenroll"
fleetUninstallTokensAPI = "/api/fleet/uninstall_tokens" //nolint:gosec // NOT the "Potential hardcoded credentials"
fleetUpgradeAgentAPI = "/api/fleet/agents/%s/upgrade"
fleetAgentDownloadSourcesAPI = "/api/fleet/agent_download_sources"
fleetProxiesAPI = "/api/fleet/proxies"
)
// Constant booleans for convenience for dealing with *bool
var (
bt bool = true
bf bool = false
TRUE *bool = &bt
FALSE *bool = &bf
)
//
// Agent Policies
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Elastic%20Agent%20policies
//
const (
// MonitoringEnabledLogs specifies log monitoring
MonitoringEnabledLogs MonitoringEnabledOption = "logs"
// MonitoringEnabledMetrics specifies metrics monitoring
MonitoringEnabledMetrics MonitoringEnabledOption = "metrics"
)
type policyResp struct {
Item PolicyResponse `json:"item"`
}
// MonitoringEnabledOption is a Kibana JSON value that specifies the various monitoring option types
type MonitoringEnabledOption string
type AgentPolicy struct {
ID string `json:"id,omitempty"`
// Name of the policy. Required to create a policy.
Name string `json:"name"`
// Namespace of the policy. Required to create a policy.
Namespace string `json:"namespace"`
Description string `json:"description,omitempty"`
MonitoringEnabled []MonitoringEnabledOption `json:"monitoring_enabled,omitempty"`
DataOutputID string `json:"data_output_id,omitempty"`
MonitoringOutputID string `json:"monitoring_output_id,omitempty"`
FleetServerHostID string `json:"fleet_server_host_id,omitempty"`
DownloadSourceID string `json:"download_source_id,omitempty"`
UnenrollTimeout int `json:"unenroll_timeout,omitempty"`
InactivityTImeout int `json:"inactivity_timeout,omitempty"`
AgentFeatures []map[string]interface{} `json:"agent_features,omitempty"`
Overrides map[string]interface{} `json:"overrides,omitempty"`
IsProtected bool `json:"is_protected"`
}
type PolicyResponse struct {
AgentPolicy `json:",inline"`
UpdatedOn time.Time `json:"updated_on"`
UpdatedBy string `json:"updated_by"`
Revision int `json:"revision"`
IsProtected bool `json:"is_protected"`
PackagePolicies []map[string]interface{} `json:"package_policies"`
}
// AgentPolicyUpdateRequest is the JSON object for requesting an updated policy
// Unlike the Agent create and response structures, the update request does not contain an ID field.
type AgentPolicyUpdateRequest struct {
// Name of the policy. Required in an update request.
Name string `json:"name"`
// Namespace of the policy. Required in an update request.
Namespace string `json:"namespace"`
Description string `json:"description,omitempty"`
MonitoringEnabled []MonitoringEnabledOption `json:"monitoring_enabled,omitempty"`
DataOutputID string `json:"data_output_id,omitempty"`
MonitoringOutputID string `json:"monitoring_output_id,omitempty"`
FleetServerHostID string `json:"fleet_server_host_id,omitempty"`
DownloadSourceID string `json:"download_source_id,omitempty"`
UnenrollTimeout int `json:"unenroll_timeout,omitempty"`
InactivityTImeout int `json:"inactivity_timeout,omitempty"`
AgentFeatures []map[string]interface{} `json:"agent_features,omitempty"`
Overrides map[string]interface{} `json:"overrides,omitempty"`
IsProtected *bool `json:"is_protected,omitempty"` // Optional bool for compatibility with the older pre 8.9.0 stack
}
// CreatePolicy creates a new agent policy with the given config
func (client *Client) CreatePolicy(ctx context.Context, request AgentPolicy) (r PolicyResponse, err error) {
reqBody, err := json.Marshal(request)
if err != nil {
return r, fmt.Errorf("unable to marshal create policy request into JSON: %w", err)
}
resp, err := client.Connection.SendWithContext(ctx, http.MethodPost, fleetAgentPoliciesAPI, nil, nil, bytes.NewReader(reqBody))
if err != nil {
return r, fmt.Errorf("error calling create policy API: %w", err)
}
defer resp.Body.Close()
var polResp policyResp
err = readJSONResponse(resp, &polResp)
return polResp.Item, err
}
type DownloadSource struct {
Name string `json:"name"`
Host string `json:"host"`
IsDefault bool `json:"is_default"`
ProxyID interface{} `json:"proxy_id"`
}
type DownloadSourceResponse struct {
Item struct {
ID string `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
IsDefault bool `json:"is_default"`
ProxyID string `json:"proxy_id"`
} `json:"item"`
}
func (client *Client) CreateDownloadSource(ctx context.Context, source DownloadSource) (DownloadSourceResponse, error) {
reqBody, err := json.Marshal(source)
if err != nil {
return DownloadSourceResponse{},
fmt.Errorf("unable to marshal DownloadSource into JSON: %w", err)
}
resp, err := client.Connection.SendWithContext(
ctx,
http.MethodPost,
fleetAgentDownloadSourcesAPI,
nil,
nil,
bytes.NewReader(reqBody))
if err != nil {
return DownloadSourceResponse{},
fmt.Errorf("error calling Agent Binary Download Sources API: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var respBody string
if bs, err := io.ReadAll(resp.Body); err != nil {
respBody = "could not read response body"
} else {
respBody = string(bs)
}
client.log.Errorw(
"could not create download source, kibana returned "+resp.Status,
"http.response.body.content", respBody)
return DownloadSourceResponse{},
fmt.Errorf("could not create download source, kibana returned %s. response body: %s: %w",
resp.Status, respBody, err)
}
body := DownloadSourceResponse{}
if err = json.NewDecoder(resp.Body).Decode(&body); err != nil {
return DownloadSourceResponse{},
fmt.Errorf("failed parsing download source response: %w", err)
}
return body, nil
}
// GetPolicy returns the policy with 'policy_id' id.
func (client *Client) GetPolicy(ctx context.Context, id string) (r PolicyResponse, err error) {
apiURL := fmt.Sprintf(fleetAgentPolicyAPI, id)
resp, err := client.Connection.SendWithContext(ctx, http.MethodGet, apiURL, nil, nil, nil)
if err != nil {
return r, fmt.Errorf("error calling get policy API: %w", err)
}
defer resp.Body.Close()
var polResp policyResp
err = readJSONResponse(resp, &polResp)
return polResp.Item, err
}
// UpdatePolicy updates an existing agent policy.
func (client *Client) UpdatePolicy(ctx context.Context, id string, request AgentPolicyUpdateRequest) (r PolicyResponse, err error) {
reqBody, err := json.Marshal(request)
if err != nil {
return r, fmt.Errorf("unable to marshal update policy request into JSON: %w", err)
}
apiURL := fmt.Sprintf(fleetAgentPolicyAPI, id)
resp, err := client.Connection.SendWithContext(ctx, http.MethodPut, apiURL, nil, nil, bytes.NewReader(reqBody))
if err != nil {
return r, fmt.Errorf("error calling update policy API: %w", err)
}
defer resp.Body.Close()
var polResp policyResp
err = readJSONResponse(resp, &polResp)
return polResp.Item, err
}
// DeletePolicy deletes the policy with the given ID
func (client *Client) DeletePolicy(ctx context.Context, id string) error {
var delRequest = struct {
AgentPolicyID string `json:"agentPolicyId"`
}{
AgentPolicyID: id,
}
reqBody, err := json.Marshal(delRequest)
if err != nil {
return fmt.Errorf("unable to marshal delete policy request into JSON: %w", err)
}
resp, err := client.Connection.SendWithContext(ctx, http.MethodPost, fleetAgentsDeleteAPI, nil, nil, bytes.NewReader(reqBody))
if err != nil {
return fmt.Errorf("error calling delete policy API: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("unable to delete policy; API returned status code [%d] and error reading response: %w", resp.StatusCode, err)
}
return fmt.Errorf("unable to delete policy; API returned status code [%d] and body [%s]", resp.StatusCode, string(respBody))
}
return nil
}
//
// Create Enrollment API Key
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Fleet%20enrollment%20API%20keys
//
type CreateEnrollmentAPIKeyRequest struct {
Name string `json:"name"`
PolicyID string `json:"policy_id"`
}
type CreateEnrollmentAPIKeyResponse struct {
Active bool `json:"active"`
APIKey string `json:"api_key"`
APIKeyID string `json:"api_key_id"`
ID string `json:"id"`
Name string `json:"name"`
PolicyID string `json:"policy_id"`
}
// CreateEnrollmentAPIKey creates an enrollment API key
func (client *Client) CreateEnrollmentAPIKey(ctx context.Context, request CreateEnrollmentAPIKeyRequest) (r CreateEnrollmentAPIKeyResponse, err error) {
reqBody, err := json.Marshal(request)
if err != nil {
return r, fmt.Errorf("unable to marshal create enrollment API key request into JSON: %w", err)
}
resp, err := client.Connection.SendWithContext(ctx, http.MethodPost, fleetEnrollmentAPIKeysAPI, nil, nil, bytes.NewReader(reqBody))
if err != nil {
return r, fmt.Errorf("error calling create enrollment API key API: %w", err)
}
defer resp.Body.Close()
var enrollResp struct {
Item CreateEnrollmentAPIKeyResponse `json:"item"`
}
err = readJSONResponse(resp, &enrollResp)
return enrollResp.Item, err
}
//
// Elastic Agents
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Elastic%20Agents
//
// AgentCommon represents common agent data used across Agent APIs
type AgentCommon struct {
Active bool `json:"active"`
Status string `json:"status"`
Agent struct {
ID string `json:"id"`
Version string `json:"version"`
} `json:"agent"`
LocalMetadata struct {
Host struct {
Hostname string `json:"hostname"`
} `json:"host"`
Elastic struct {
Agent struct {
FIPS bool `json:"fips"`
} `json:"agent"`
} `json:"elastic"`
} `json:"local_metadata"`
PolicyID string `json:"policy_id"`
PolicyRevision int `json:"policy_revision"`
UpgradeDetails *AgentUpgradeDetails `json:"upgrade_details"`
}
type AgentUpgradeDetails struct {
TargetVersion string `json:"target_version"`
State string `json:"state"`
ActionID string `json:"action_id"`
Metadata struct {
ScheduledAt *time.Time `json:"scheduled_at"`
DownloadPercent float64 `json:"download_percent"`
DownloadRate details.DownloadRate `json:"download_rate"`
FailedState string `json:"failed_state"`
ErrorMsg string `json:"error_msg"`
} `json:"metadata"`
}
type AgentExisting struct {
ID string `json:"id"`
AgentCommon `json:",inline"`
}
type ListAgentsRequest struct {
// For future use
}
type ListAgentsResponse struct {
Items []AgentExisting `json:"items"`
}
// ListAgents returns a list of agents known to Kibana
func (client *Client) ListAgents(ctx context.Context, _ ListAgentsRequest) (r ListAgentsResponse, err error) {
resp, err := client.Connection.SendWithContext(ctx, http.MethodGet, fleetAgentsAPI, nil, nil, nil)
if err != nil {
return r, fmt.Errorf("error calling list agents API: %w", err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
return r, err
}
type GetAgentRequest struct {
ID string
}
type GetAgentResponse AgentExisting
// GetAgent fetches data for an agent
func (client *Client) GetAgent(ctx context.Context, request GetAgentRequest) (r GetAgentResponse, err error) {
apiURL := fmt.Sprintf(fleetAgentAPI, request.ID)
resp, err := client.Connection.SendWithContext(ctx, http.MethodGet, apiURL, nil, nil, nil)
if err != nil {
return r, fmt.Errorf("error calling get agent API: %w", err)
}
defer resp.Body.Close()
var agentResp struct {
Item GetAgentResponse `json:"item"`
}
err = readJSONResponse(resp, &agentResp)
return agentResp.Item, err
}
//
// Elastic Agent actions
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Elastic%20Agent%20actions
//
type UnEnrollAgentRequest struct {
ID string `json:"-"` // ID is not part of the request body send to the Fleet API
Revoke bool `json:"revoke"`
}
type UnEnrollAgentResponse struct {
// For future use
}
// UnEnrollAgent removes the agent from fleet
func (client *Client) UnEnrollAgent(ctx context.Context, request UnEnrollAgentRequest) (r UnEnrollAgentResponse, err error) {
reqBody, err := json.Marshal(request)
if err != nil {
return r, fmt.Errorf("unable to marshal unenroll agent request into JSON: %w", err)
}
apiURL := fmt.Sprintf(fleetUnEnrollAgentAPI, request.ID)
resp, err := client.Connection.SendWithContext(ctx, http.MethodPost, apiURL, nil, nil, bytes.NewReader(reqBody))
if err != nil {
return r, fmt.Errorf("error calling unenroll agent API: %w", err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
return r, err
}
type UpgradeAgentRequest struct {
ID string `json:"-"` // ID is not part of the request body send to the Fleet API
Version string `json:"version"`
SourceURI string `json:"source_uri"`
Force bool `json:"force"`
}
type UpgradeAgentResponse struct {
// For future use
}
// UpgradeAgent upgrades the requested agent
func (client *Client) UpgradeAgent(ctx context.Context, request UpgradeAgentRequest) (r UpgradeAgentResponse, err error) {
reqBody, err := json.Marshal(request)
if err != nil {
return r, fmt.Errorf("unable to marshal upgrade agent request into JSON: %w", err)
}
apiURL := fmt.Sprintf(fleetUpgradeAgentAPI, request.ID)
resp, err := client.Connection.SendWithContext(ctx, http.MethodPost, apiURL, nil, nil, bytes.NewReader(reqBody))
if err != nil {
return r, fmt.Errorf("error calling upgrade agent API: %w", err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
return r, err
}
//
// Fleet Server Hosts
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Fleet%20Server%20hosts
//
type FleetServerHost struct {
ID string `json:"id"`
Name string `json:"name"`
HostURLs []string `json:"host_urls"`
IsDefault bool `json:"is_default"`
IsInternal bool `json:"is_internal"`
IsPreconfigured bool `json:"is_preconfigured"`
ProxyID string `json:"proxy_id"`
}
type ListFleetServerHostsRequest struct {
HostURLs []string `json:"host_urls"`
ID string `json:"id"`
IsDefault bool `json:"is_default"`
IsInternal bool `json:"is_internal"`
Name string `json:"name"`
ProxyID string `json:"proxy_id"`
}
type ListFleetServerHostsResponse struct {
Items []FleetServerHost `json:"items"`
}
type FleetServerHostsResponse struct {
Item struct {
ID string `json:"id"`
HostUrls []string `json:"host_urls"`
IsDefault bool `json:"is_default"`
IsInternal bool `json:"is_internal"`
IsPreconfigured bool `json:"is_preconfigured"`
Name string `json:"name"`
ProxyID string `json:"proxy_id"`
} `json:"item"`
}
// ListFleetServerHosts returns a list of fleet server hosts
func (client *Client) ListFleetServerHosts(ctx context.Context, _ ListFleetServerHostsRequest) (r ListFleetServerHostsResponse, err error) {
resp, err := client.Connection.SendWithContext(ctx, http.MethodGet, fleetFleetServerHostsAPI, nil, nil, nil)
if err != nil {
return r, fmt.Errorf("error calling list fleet server hosts API: %w", err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
return r, err
}
// CreateFleetServerHosts creates a new Fleet Server host
func (client *Client) CreateFleetServerHosts(ctx context.Context, req ListFleetServerHostsRequest) (FleetServerHostsResponse, error) {
bs, err := json.Marshal(req)
if err != nil {
return FleetServerHostsResponse{}, fmt.Errorf("could not marshal ListFleetServerHostsRequest: %w", err)
}
resp, err := client.Connection.SendWithContext(ctx, http.MethodPost,
fleetFleetServerHostsAPI,
nil, nil, bytes.NewReader(bs))
if err != nil {
return FleetServerHostsResponse{}, fmt.Errorf("error calling new fleet server hosts API: %w", err)
}
defer resp.Body.Close()
var fleetResp FleetServerHostsResponse
err = readJSONResponse(resp, &fleetResp)
return fleetResp, err
}
type GetFleetServerHostRequest struct {
ID string
}
type GetFleetServerHostResponse FleetServerHost
func (client *Client) GetFleetServerHost(ctx context.Context, request GetFleetServerHostRequest) (r GetFleetServerHostResponse, err error) {
apiURL := fleetFleetServerHostsAPI + "/" + request.ID
resp, err := client.Connection.SendWithContext(ctx, http.MethodGet, apiURL, nil, nil, nil)
if err != nil {
return r, fmt.Errorf("error calling get fleet server hosts API: %w", err)
}
defer resp.Body.Close()
var fleetResp struct {
Item GetFleetServerHostResponse `json:"item"`
}
err = readJSONResponse(resp, &fleetResp)
return fleetResp.Item, err
}
//
// Fleet Package Policy
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Fleet%20package%20policies
//
type PackagePolicyRequest struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Namespace string `json:"namespace"`
PolicyID string `json:"policy_id"`
Package PackagePolicyRequestPackage `json:"package"`
Vars map[string]interface{} `json:"vars"`
Inputs []map[string]interface{} `json:"inputs"`
Force bool `json:"force"`
}
type PackagePolicyRequestPackage struct {
Name string `json:"name"`
Version string `json:"version"`
}
type PackagePolicyResponse struct {
Item PackagePolicy `json:"item"`
}
type PackagePolicy struct {
ID string `json:"id,omitempty"`
Revision int `json:"revision"`
Enabled bool `json:"enabled"`
Inputs []map[string]interface{} `json:"inputs"`
Package PackagePolicyRequestPackage `json:"package"`
Namespace string `json:"namespace"`
OutputID string `json:"output_id"`
PolicyID string `json:"policy_id"`
Name string `json:"name"`
Description string `json:"description"`
}
type DeletePackagePolicyResponse struct {
ID string `json:"id"`
}
// InstallFleetPackage uses the Fleet package policies API install an integration package as specified in the request.
// Note that the package policy ID and Name must be globally unique across all installed packages.
func (client *Client) InstallFleetPackage(ctx context.Context, req PackagePolicyRequest) (r PackagePolicyResponse, err error) {
reqBytes, err := json.Marshal(&req)
if err != nil {
return r, fmt.Errorf("marshalling request json: %w", err)
}
resp, err := client.Connection.SendWithContext(ctx,
http.MethodPost,
fleetPackagePoliciesAPI,
nil,
nil,
bytes.NewReader(reqBytes),
)
if err != nil {
return r, fmt.Errorf("posting %s: %w", fleetPackagePoliciesAPI, err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
return r, err
}
// DeleteFleetPackage deletes integration with packagePolicyID from the policy ID
func (client *Client) DeleteFleetPackage(ctx context.Context, packagePolicyID string) (r DeletePackagePolicyResponse, err error) {
u, err := url.JoinPath(fleetPackagePoliciesAPI, packagePolicyID)
if err != nil {
return r, err
}
resp, err := client.Connection.SendWithContext(ctx,
http.MethodDelete,
u,
nil,
nil,
nil,
)
if err != nil {
return r, fmt.Errorf("DELETE %s: %w", u, err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
return r, err
}
//
// Fleet Proxies
// see full documentation on:
// https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/main/oas_docs/bundle.json#/Fleet%20proxies
//
type ProxiesRequest struct {
Certificate string `json:"certificate"`
CertificateAuthorities string `json:"certificate_authorities"`
CertificateKey string `json:"certificate_key"`
ID string `json:"id"`
Name string `json:"name"`
ProxyHeaders map[string]string `json:"proxy_headers"`
URL string `json:"url"`
}
type ProxiesResponse struct {
Item struct {
Certificate string `json:"certificate"`
CertificateAuthorities string `json:"certificate_authorities"`
CertificateKey string `json:"certificate_key"`
ID string `json:"id"`
IsPreconfigured bool `json:"is_preconfigured"`
Name string `json:"name"`
ProxyHeaders map[string]string `json:"proxy_headers"`
URL string `json:"url"`
} `json:"item"`
}
// CreateFleetProxy creates a new proxy
func (client *Client) CreateFleetProxy(ctx context.Context, req ProxiesRequest) (ProxiesResponse, error) {
// if `proxy_headers` is `null` 8.x kibana/Fleet will return a 400 - Bad request
if req.ProxyHeaders == nil {
req.ProxyHeaders = map[string]string{}
}
bs, err := json.Marshal(req)
if err != nil {
return ProxiesResponse{}, fmt.Errorf("could not marshal ListFleetServerHostsRequest: %w", err)
}
r, err := client.Connection.SendWithContext(ctx, http.MethodPost,
fleetProxiesAPI, nil, nil,
bytes.NewReader(bs),
)
if err != nil {
return ProxiesResponse{}, err
}
defer r.Body.Close()
resp := ProxiesResponse{}
err = readJSONResponse(r, &resp)
return resp, err
}
type UninstallTokenResponse struct {
Items []UninstallTokenItem `json:"items"`
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"perPage"`
}
type UninstallTokenItem struct {
ID string `json:"id"`
PolicyID string `json:"policy_id"`
Token string `json:"token"`
CreatedAt string `json:"created_at"`
}
type uninstallTokenValueResponse struct {
Item UninstallTokenItem `json:"item"`
}
// GetPolicyUninstallTokens Retrieves the policy uninstall tokens
func (client *Client) GetPolicyUninstallTokens(ctx context.Context, policyID string) (r UninstallTokenResponse, err error) {
// Fetch uninstall token for the policy
// /api/fleet/uninstall_tokens?policyId={policyId}&page=1&perPage=1000
q := make(url.Values)
q.Add("policyId", policyID)
q.Add("page", "1")
q.Add("perPage", "1000")
resp, err := client.Connection.SendWithContext(ctx,
http.MethodGet,
fleetUninstallTokensAPI,
q,
nil,
nil,
)
if err != nil {
return r, fmt.Errorf("getting %s, policyID %s: %w", fleetUninstallTokensAPI, policyID, err)
}
defer resp.Body.Close()
err = readJSONResponse(resp, &r)
if err != nil {
return r, err
}
// Resolve token values for token ID
for i := 0; i < len(r.Items); i++ {
tokRes, err := client.GetUninstallToken(ctx, r.Items[i].ID)
if err != nil {
return r, err
}
r.Items[i] = tokRes
}
return r, nil
}
// GetUninstallToken return uninstall token value for the given token ID
func (client *Client) GetUninstallToken(ctx context.Context, tokenID string) (r UninstallTokenItem, err error) {
u, err := url.JoinPath(fleetUninstallTokensAPI, tokenID)
if err != nil {
return r, err
}
resp, err := client.Connection.SendWithContext(ctx,
http.MethodGet,
u,
nil,
nil,
nil,
)
if err != nil {
return r, fmt.Errorf("getting %s: %w", u, err)
}
defer resp.Body.Close()
var res uninstallTokenValueResponse
err = readJSONResponse(resp, &res)
if err != nil {
return r, err
}
return res.Item, nil
}
func readJSONResponse(r *http.Response, v any) error {
b, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("reading response body: %w", err)
}
if r.StatusCode != http.StatusOK {
return extractError(b)
}
err = json.Unmarshal(b, v)
if err != nil {
return fmt.Errorf("unmarshalling response json: %w", err)
}
return nil
}