Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reintroduce fleetctl as tool #13444

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions tools/cmd/fleetctl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# fleetctl

`fleetctl` provides a minimal CLI for interacting with the Fleet API,
for use in development and testing. This tool may be useful for
manipulating APM integration config vars that are not shown in the
APM integration policy editor.

# Examples

## Listing policies

`fleetctl -u <kibana_url> list-policies`

## Updating vars

`fleetctl -u <kibana_url> set-policy-var <ID> tail_sampling_storage_limit=30MB`

## Updating arbitrary config

This command can be used to set arbitrary configuration understood by APM Server,
where that configuration has no corresponding integration package var.

`fleetctl -u <kibana_url> set-policy-config <ID> apm-server.max_concurrent_decoders=300`

187 changes: 187 additions & 0 deletions tools/cmd/fleetctl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// 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 main

import (
"errors"
"fmt"
"log"
"net/url"
"reflect"
"strings"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

"github.com/elastic/apm-server/systemtest/fleettest"
)

var client *fleettest.Client

func parseKV(s ...string) (map[string]string, error) {
out := make(map[string]string)
for _, s := range s {
i := strings.IndexRune(s, '=')
if i < 0 {
return nil, errors.New("missing '='; expected format k=v")
}
k, vstr := s[:i], s[i+1:]
out[k] = vstr
}
return out, nil
}

var listPackagePoliciesCommand = &cobra.Command{
Use: "list-policies",
Short: "List Fleet integration package policies",
RunE: func(cmd *cobra.Command, _ []string) error {
policies, err := client.ListPackagePolicies()
if err != nil {
return fmt.Errorf("failed to fetch package policies: %w", err)
}
return yaml.NewEncoder(cmd.OutOrStdout()).Encode(policies)
},
}

var setPolicyVarCommand = &cobra.Command{
Use: "set-policy-var <policy-id> <k=v [k=v...]>",
Short: "Set config vars for a Fleet integration package policy",
Args: cobra.MinimumNArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
setVars, err := parseKV(args[1:]...)
if err != nil {
return err
}

policy, err := client.PackagePolicy(args[0])
if err != nil {
return fmt.Errorf("failed to fetch package policy: %w", err)
}
if len(policy.Inputs) != 1 {
return fmt.Errorf("expected 1 input, got %d", len(policy.Inputs))
}

for k, v := range setVars {
varObj, ok := policy.Inputs[0].Vars[k].(map[string]interface{})
if !ok {
return fmt.Errorf("var %q not found in package policy", k)
}
value := varObj["value"]
ptrZero := reflect.New(reflect.TypeOf(value))
if err := yaml.Unmarshal([]byte(v), ptrZero.Interface()); err != nil {
return fmt.Errorf("failed to unmarshal var %q: %w", k, err)
}
varObj["value"] = ptrZero.Elem().Interface()
}

if err := client.UpdatePackagePolicy(policy); err != nil {
return fmt.Errorf("failed to update policy: %w", err)
}
return nil
},
}

var setPolicyConfigCommand = &cobra.Command{
Use: "set-policy-config <policy-id> <k=v [k=v...]>",
Short: "Set arbitrary config for a Fleet integration package policy",
Args: cobra.MinimumNArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
config, err := parseKV(args[1:]...)
if err != nil {
return err
}

policy, err := client.PackagePolicy(args[0])
if err != nil {
return fmt.Errorf("failed to fetch package policy: %w", err)
}
if len(policy.Inputs) != 1 {
return fmt.Errorf("expected 1 input, got %d", len(policy.Inputs))
}

merge := func(k string, v interface{}, to map[string]interface{}) {
for {
before, after, found := strings.Cut(k, ".")
if !found {
to[before] = v
return
}
m, ok := to[before].(map[string]interface{})
if !ok {
m = make(map[string]interface{})
to[before] = m
}
k = after
to = m
}
}

existing := policy.Inputs[0].Config
if existing == nil {
existing = make(map[string]interface{})
policy.Inputs[0].Config = existing
}
for k, v := range config {
var value interface{}
if err := yaml.Unmarshal([]byte(v), &value); err != nil {
return fmt.Errorf("failed to unmarshal var %q: %w", k, err)
}
// Each top-level key's value is nested under "value".
if before, after, ok := strings.Cut(k, "."); ok {
k = strings.Join([]string{before, "value", after}, ".")
} else {
k = before + ".value"
}
merge(k, value, existing)
}

if err := client.UpdatePackagePolicy(policy); err != nil {
return fmt.Errorf("failed to update policy: %w", err)
}
return nil
},
}

func main() {
var (
kibanaURL string
kibanaUser string
kibanaPass string
)
rootCommand := &cobra.Command{Use: "fleetctl",
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
u, err := url.Parse(kibanaURL)
if err != nil {
return err
}
u.User = url.UserPassword(kibanaUser, kibanaPass)
client = fleettest.NewClient(u.String())
return nil
},
}
rootCommand.AddCommand(listPackagePoliciesCommand)
rootCommand.AddCommand(setPolicyVarCommand)
rootCommand.AddCommand(setPolicyConfigCommand)
rootCommand.PersistentFlags().StringVarP(&kibanaURL, "kibana", "u", "http://localhost:5601", "URL of the Kibana server")
rootCommand.PersistentFlags().StringVar(&kibanaUser, "user", "admin", "Username to use for Kibana authentication")
rootCommand.PersistentFlags().StringVar(&kibanaPass, "pass", "changeme", "Password to use for Kibana authentication")

if err := rootCommand.Execute(); err != nil {
log.Fatal(err)
}
}
32 changes: 19 additions & 13 deletions tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ go 1.22
toolchain go1.22.1

require (
github.com/elastic/apm-tools v0.0.0-20230828065051-3f799314cc8b
github.com/elastic/apm-server/systemtest v0.0.0-20240619105031-9cc99842f83f
github.com/elastic/apm-tools v0.0.0-20240607105915-a4f490dc6959
github.com/elastic/go-licenser v0.4.2
github.com/elastic/gobench v0.0.0-20220608141032-f30bc57e329c
github.com/goreleaser/nfpm/v2 v2.37.1
github.com/josephspurrier/goversioninfo v1.4.0
github.com/spf13/cobra v1.8.0
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c
github.com/terraform-docs/terraform-docs v0.18.0
go.elastic.co/go-licence-detector v0.6.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/gotestsum v1.12.0
honnef.co/go/tools v0.4.7
)
Expand Down Expand Up @@ -47,14 +50,16 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/dnephin/pflag v1.0.7 // indirect
github.com/elastic/elastic-transport-go/v8 v8.3.0 // indirect
github.com/elastic/go-elasticsearch/v8 v8.8.1 // indirect
github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect
github.com/elastic/go-elasticsearch/v8 v8.14.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.12.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobuffalo/here v0.6.7 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand Down Expand Up @@ -112,38 +117,39 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/terraform-docs/terraform-config-inspect v0.0.0-20210728164355-9c1f178932fa // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/tools/go/vcs v0.1.0-deprecated // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/xurls/v2 v2.5.0 // indirect
)

Expand Down
Loading