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

Add new subcommand to dump Agent Policies #862

Merged
merged 28 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4b3a1c6
Rename dump installed objects action
mrodm Jun 21, 2022
e8be6c8
Add new subcommand to dump agent policies
mrodm Jun 22, 2022
ab1f310
Add parameter to get agent policies using some package
mrodm Jun 22, 2022
469fe8c
Rephrase long descriptions
mrodm Jun 22, 2022
9bb50ff
Remove debug prints
mrodm Jun 22, 2022
c33295d
Comment reviews
mrodm Jun 23, 2022
d813be9
Set required package flag just for installed-objects subcommand
mrodm Jun 23, 2022
c175000
Add pagination support to list all agent policies
mrodm Jun 23, 2022
62e51fc
Remove debug statements
mrodm Jun 23, 2022
80a61b0
Changes from review
mrodm Jun 23, 2022
898f15f
Rename dumpInstalledObject to be more generic
mrodm Jun 23, 2022
fb226d9
Reuse agent filtering method to get all policies
mrodm Jun 23, 2022
7e17639
Remove caching for agent policies
mrodm Jun 23, 2022
862883f
Rename Ingest Manager to Fleet
mrodm Jun 27, 2022
d126235
Update dump command descriptions
mrodm Jun 27, 2022
47fcf74
Update README
mrodm Jun 27, 2022
0bbcc90
Merge 'origin/main' branch into dump_agent_policies
mrodm Jun 27, 2022
c9af623
Added tests for agent-policies dump subcommand
mrodm Jun 27, 2022
5aa71f3
Use get public method in client_test
mrodm Jun 27, 2022
3fa942b
Set post,put,delete methods public as Get
mrodm Jun 27, 2022
2184cec
Rename method to list all agent policies
mrodm Jun 27, 2022
58487b5
Move functions to dump a generic json resource to its own file
mrodm Jun 28, 2022
3334135
Replace question mark char in files too
mrodm Jun 28, 2022
d872ec2
Fix default path url name
mrodm Jun 28, 2022
ac2b8ca
Use formatter package to dump JSONs - rewritten all dumped files
mrodm Jun 28, 2022
7251ad6
Set again get,post,put,delete methods as private
mrodm Jun 28, 2022
cc1a245
Fix
mrodm Jun 28, 2022
eb1fe46
Revert "Use formatter package to dump JSONs - rewritten all dumped fi…
mrodm Jun 28, 2022
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ For details on how to create a new package, review the [HOWTO guide](https://git

_Context: global_

Use this command as a exploratory tool to dump assets relevant for the package.
Use this command as an exploratory tool to dump resources from Elastic Stack (objects installed as part of package and agent policies).

### `elastic-package export`

Expand Down
96 changes: 90 additions & 6 deletions cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor

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 :)

Copy link
Contributor Author

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:

const dumpLongDescription = `Use this command as an exploratory tool to dump resources from Elastic Stack (objects installed as part of package and agent policies).`

Would you add anything else here?

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)
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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 tlsSkipVerify) work? I'm thinking about the use case when a developer wants to dump agent policies from some random Kibana, not booted with elastic-package.

Copy link
Member

Choose a reason for hiding this comment

The 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
}
3 changes: 3 additions & 0 deletions internal/cobraext/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const (

// Flag names and descriptions used by CLI commands
const (
AgentPolicyFlagName = "agent-policy"
AgentPolicyDescription = "name of the agent policy"

BuildZipFlagName = "zip"
BuildZipFlagDescription = "archive the built package"

Expand Down
150 changes: 150 additions & 0 deletions internal/dump/agentpolicies.go
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
}
Loading