-
Notifications
You must be signed in to change notification settings - Fork 118
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
Add new subcommand to dump Agent Policies #862
Changes from 27 commits
4b3a1c6
e8be6c8
ab1f310
469fe8c
9bb50ff
c33295d
d813be9
c175000
62e51fc
80a61b0
898f15f
fb226d9
7e17639
862883f
d126235
47fcf74
0bbcc90
c9af623
5aa71f3
3fa942b
2184cec
58487b5
3334135
d872ec2
ac2b8ca
7251ad6
cc1a245
eb1fe46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,44 +5,65 @@ | |
package cmd | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/elastic/elastic-package/internal/cobraext" | ||
"github.com/elastic/elastic-package/internal/dump" | ||
"github.com/elastic/elastic-package/internal/elasticsearch" | ||
"github.com/elastic/elastic-package/internal/kibana" | ||
) | ||
|
||
const dumpLongDescription = `Use this command as a exploratory tool to dump assets relevant for the package.` | ||
const dumpLongDescription = `Use this command as an exploratory tool to dump resources from Elastic Stack (objects installed as part of package and agent policies).` | ||
|
||
const dumpInstalledObjectsLongDescription = `Use this command to dump objects installed by Fleet as part of a package. | ||
|
||
Use this command as a exploratory tool to dump objects as they are installed by Fleet when installing a package. Dumped objects are stored in files as they are returned by APIs of the stack, without any processing.` | ||
Use this command as an exploratory tool to dump objects as they are installed by Fleet when installing a package. Dumped objects are stored in files as they are returned by APIs of the stack, without any processing.` | ||
|
||
const dumpAgentPoliciesLongDescription = `Use this command to dump agent policies created by Fleet as part of a package installation. | ||
|
||
Use this command as an exploratory tool to dump agent policies as they are created by Fleet when installing a package. Dumped agent policies are stored in files as they are returned by APIs of the stack, without any processing. | ||
|
||
If no flag is provided, by default this command dumps all agent policies created by Fleet. | ||
|
||
If --package flag is provided, this command dumps all agent policies that the given package has been assigned to it.` | ||
|
||
func setupDumpCommand() *cobraext.Command { | ||
dumpInstalledObjectsCmd := &cobra.Command{ | ||
Use: "installed-objects", | ||
Short: "Dump objects installed in the stack", | ||
Long: dumpInstalledObjectsLongDescription, | ||
RunE: dumpInstalledObjectsCmd, | ||
RunE: dumpInstalledObjectsCmdAction, | ||
} | ||
dumpInstalledObjectsCmd.Flags().Bool(cobraext.TLSSkipVerifyFlagName, false, cobraext.TLSSkipVerifyFlagDescription) | ||
dumpInstalledObjectsCmd.Flags().StringP(cobraext.PackageFlagName, cobraext.PackageFlagShorthand, "", cobraext.PackageFlagDescription) | ||
dumpInstalledObjectsCmd.MarkFlagRequired(cobraext.PackageFlagName) | ||
|
||
dumpAgentPoliciesCmd := &cobra.Command{ | ||
Use: "agent-policies", | ||
Short: "Dump agent policies defined in the stack", | ||
Long: dumpAgentPoliciesLongDescription, | ||
RunE: dumpAgentPoliciesCmdAction, | ||
} | ||
dumpAgentPoliciesCmd.Flags().StringP(cobraext.AgentPolicyFlagName, "", "", cobraext.AgentPolicyDescription) | ||
dumpAgentPoliciesCmd.Flags().StringP(cobraext.PackageFlagName, cobraext.PackageFlagShorthand, "", cobraext.PackageFlagDescription) | ||
|
||
cmd := &cobra.Command{ | ||
Use: "dump", | ||
Short: "Dump package assets", | ||
Long: dumpLongDescription, | ||
} | ||
cmd.PersistentFlags().StringP(cobraext.PackageFlagName, cobraext.PackageFlagShorthand, "", cobraext.PackageFlagDescription) | ||
cmd.MarkFlagRequired(cobraext.PackageFlagName) | ||
cmd.PersistentFlags().StringP(cobraext.DumpOutputFlagName, "o", "package-dump", cobraext.DumpOutputFlagDescription) | ||
|
||
cmd.AddCommand(dumpInstalledObjectsCmd) | ||
cmd.AddCommand(dumpAgentPoliciesCmd) | ||
|
||
return cobraext.NewCommand(cmd, cobraext.ContextGlobal) | ||
} | ||
|
||
func dumpInstalledObjectsCmd(cmd *cobra.Command, args []string) error { | ||
func dumpInstalledObjectsCmdAction(cmd *cobra.Command, args []string) error { | ||
packageName, err := cmd.Flags().GetString(cobraext.PackageFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.PackageFlagName) | ||
|
@@ -76,3 +97,66 @@ func dumpInstalledObjectsCmd(cmd *cobra.Command, args []string) error { | |
cmd.Printf("Dumped %d installed objects for package %s to %s\n", n, packageName, outputPath) | ||
return nil | ||
} | ||
|
||
func dumpAgentPoliciesCmdAction(cmd *cobra.Command, args []string) error { | ||
packageName, err := cmd.Flags().GetString(cobraext.PackageFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.PackageFlagName) | ||
} | ||
|
||
agentPolicy, err := cmd.Flags().GetString(cobraext.AgentPolicyFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.AgentPolicyFlagName) | ||
} | ||
|
||
outputPath, err := cmd.Flags().GetString(cobraext.DumpOutputFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.DumpOutputFlagName) | ||
} | ||
|
||
tlsSkipVerify, _ := cmd.Flags().GetBool(cobraext.TLSSkipVerifyFlagName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side question to @jsoriano: With your change enforcing SSL mode, will such options (like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this flag should continue working after the changes in #847. |
||
|
||
var clientOptions []kibana.ClientOption | ||
if tlsSkipVerify { | ||
clientOptions = append(clientOptions, kibana.TLSSkipVerify()) | ||
} | ||
kibanaClient, err := kibana.NewClient(clientOptions...) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to initialize Kibana client") | ||
} | ||
|
||
switch { | ||
case agentPolicy != "" && packageName != "": | ||
return fmt.Errorf("agent-policy and package parameters cannot be set at the same time") | ||
case agentPolicy != "": | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dumper := dump.NewAgentPoliciesDumper(kibanaClient) | ||
err = dumper.DumpByName(cmd.Context(), outputPath, agentPolicy) | ||
if err != nil { | ||
return errors.Wrap(err, "dump failed") | ||
} | ||
cmd.Printf("Dumped agent policy %s to %s\n", agentPolicy, outputPath) | ||
case packageName != "": | ||
dumper := dump.NewAgentPoliciesDumper(kibanaClient) | ||
count, err := dumper.DumpByPackage(cmd.Context(), outputPath, packageName) | ||
if err != nil { | ||
return errors.Wrap(err, "dump failed") | ||
} | ||
if count != 0 { | ||
cmd.Printf("Dumped %d agent policies filtering by package name %s to %s\n", count, packageName, outputPath) | ||
} else { | ||
cmd.Printf("No agent policies were found filtering by package name %s\n", packageName) | ||
} | ||
default: | ||
dumper := dump.NewAgentPoliciesDumper(kibanaClient) | ||
count, err := dumper.DumpAll(cmd.Context(), outputPath) | ||
if err != nil { | ||
return errors.Wrap(err, "dump failed") | ||
} | ||
if count != 0 { | ||
cmd.Printf("Dumped %d agent policies to %s\n", count, outputPath) | ||
} else { | ||
cmd.Printf("No agent policies were found\n") | ||
} | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License; | ||
// you may not use this file except in compliance with the Elastic License. | ||
|
||
package dump | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"path/filepath" | ||
|
||
"github.com/elastic/elastic-package/internal/common" | ||
"github.com/elastic/elastic-package/internal/kibana" | ||
) | ||
|
||
const AgentPoliciesDumpDir = "agent_policies" | ||
|
||
// AgentPoliciesDumper discovers and dumps agent policies in Fleet | ||
type AgentPoliciesDumper struct { | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
client *kibana.Client | ||
} | ||
|
||
type AgentPolicy struct { | ||
name string | ||
raw json.RawMessage | ||
} | ||
|
||
func (p AgentPolicy) Name() string { | ||
return p.name | ||
} | ||
|
||
func (p AgentPolicy) JSON() []byte { | ||
return p.raw | ||
} | ||
|
||
// NewAgentPoliciesDumper creates an AgentPoliciesDumper | ||
func NewAgentPoliciesDumper(client *kibana.Client) *AgentPoliciesDumper { | ||
return &AgentPoliciesDumper{ | ||
client: client, | ||
} | ||
} | ||
|
||
func (d *AgentPoliciesDumper) getAgentPolicy(ctx context.Context, name string) (*AgentPolicy, error) { | ||
policy, err := d.client.GetRawPolicy(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &AgentPolicy{name: name, raw: policy}, nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) DumpByName(ctx context.Context, dir, name string) error { | ||
agentPolicy, err := d.getAgentPolicy(ctx, name) | ||
if err != nil { | ||
return fmt.Errorf("failed to get agent policy: %w", err) | ||
} | ||
|
||
dir = filepath.Join(dir, AgentPoliciesDumpDir) | ||
err = dumpJSONResource(dir, agentPolicy) | ||
if err != nil { | ||
return fmt.Errorf("failed to dump agent policy %s: %w", agentPolicy.Name(), err) | ||
} | ||
return nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) getAllAgentPolicies(ctx context.Context) ([]AgentPolicy, error) { | ||
return d.getAgentPoliciesFilteredByPackage(ctx, "") | ||
} | ||
|
||
type packagePolicy struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
Package struct { | ||
Name string `json:"name"` | ||
Title string `json:"title"` | ||
Version string `json:"version"` | ||
} `json:"package"` | ||
} | ||
|
||
func getPackagesUsingAgentPolicy(packagePolicies []packagePolicy) []string { | ||
var packageNames []string | ||
for _, packagePolicy := range packagePolicies { | ||
packageNames = append(packageNames, packagePolicy.Package.Name) | ||
} | ||
return packageNames | ||
} | ||
|
||
func (d *AgentPoliciesDumper) getAgentPoliciesFilteredByPackage(ctx context.Context, packageName string) ([]AgentPolicy, error) { | ||
rawPolicies, err := d.client.ListRawPolicies() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var policyPackages struct { | ||
ID string `json:"id"` | ||
PackagePolicies []packagePolicy `json:"package_policies"` | ||
} | ||
|
||
var policies []AgentPolicy | ||
|
||
for _, policy := range rawPolicies { | ||
err = json.Unmarshal(policy, &policyPackages) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get Agent Policy ID: %w", err) | ||
} | ||
if packageName != "" { | ||
packageNames := getPackagesUsingAgentPolicy(policyPackages.PackagePolicies) | ||
if !common.StringSliceContains(packageNames, packageName) { | ||
continue | ||
} | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
agentPolicy := AgentPolicy{name: policyPackages.ID, raw: policy} | ||
policies = append(policies, agentPolicy) | ||
} | ||
return policies, nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) DumpAll(ctx context.Context, dir string) (count int, err error) { | ||
agentPolicies, err := d.getAllAgentPolicies(ctx) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to get agent policy: %w", err) | ||
} | ||
|
||
dir = filepath.Join(dir, AgentPoliciesDumpDir) | ||
for _, agentPolicy := range agentPolicies { | ||
err := dumpJSONResource(dir, agentPolicy) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to dump agent policy %s: %w", agentPolicy.Name(), err) | ||
} | ||
} | ||
return len(agentPolicies), nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) DumpByPackage(ctx context.Context, dir, packageName string) (count int, err error) { | ||
agentPolicies, err := d.getAgentPoliciesFilteredByPackage(ctx, packageName) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to get agent policy: %w", err) | ||
} | ||
|
||
dir = filepath.Join(dir, AgentPoliciesDumpDir) | ||
for _, agentPolicy := range agentPolicies { | ||
err := dumpJSONResource(dir, agentPolicy) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to dump agent policy %s: %w", agentPolicy.Name(), err) | ||
} | ||
} | ||
return len(agentPolicies), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Maybe we can add more guidance to the
dump
description.agent-policies
would be another supported feature :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I've updated the dump description as:
Would you add anything else here?