github.com/judwhite/consul@v1.4.4-0.20190315202039-6ef970a191d3/command/intention/create/create.go (about)

     1  package create
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  
    10  	"github.com/hashicorp/consul/api"
    11  	"github.com/hashicorp/consul/command/flags"
    12  	"github.com/hashicorp/consul/command/intention/finder"
    13  	"github.com/mitchellh/cli"
    14  )
    15  
    16  func New(ui cli.Ui) *cmd {
    17  	c := &cmd{UI: ui}
    18  	c.init()
    19  	return c
    20  }
    21  
    22  type cmd struct {
    23  	UI    cli.Ui
    24  	flags *flag.FlagSet
    25  	http  *flags.HTTPFlags
    26  	help  string
    27  
    28  	// flags
    29  	flagAllow   bool
    30  	flagDeny    bool
    31  	flagFile    bool
    32  	flagReplace bool
    33  	flagMeta    map[string]string
    34  
    35  	// testStdin is the input for testing.
    36  	testStdin io.Reader
    37  }
    38  
    39  func (c *cmd) init() {
    40  	c.flags = flag.NewFlagSet("", flag.ContinueOnError)
    41  	c.flags.BoolVar(&c.flagAllow, "allow", false,
    42  		"Create an intention that allows when matched.")
    43  	c.flags.BoolVar(&c.flagDeny, "deny", false,
    44  		"Create an intention that denies when matched.")
    45  	c.flags.BoolVar(&c.flagFile, "file", false,
    46  		"Read intention data from one or more files.")
    47  	c.flags.BoolVar(&c.flagReplace, "replace", false,
    48  		"Replace matching intentions.")
    49  	c.flags.Var((*flags.FlagMapValue)(&c.flagMeta), "meta",
    50  		"Metadata to set on the intention, formatted as key=value. This flag "+
    51  			"may be specified multiple times to set multiple meta fields.")
    52  
    53  	c.http = &flags.HTTPFlags{}
    54  	flags.Merge(c.flags, c.http.ClientFlags())
    55  	flags.Merge(c.flags, c.http.ServerFlags())
    56  	c.help = flags.Usage(help, c.flags)
    57  }
    58  
    59  func (c *cmd) Run(args []string) int {
    60  	if err := c.flags.Parse(args); err != nil {
    61  		return 1
    62  	}
    63  
    64  	// Default to allow
    65  	if !c.flagAllow && !c.flagDeny {
    66  		c.flagAllow = true
    67  	}
    68  
    69  	// If both are specified it is an error
    70  	if c.flagAllow && c.flagDeny {
    71  		c.UI.Error("Only one of -allow or -deny may be specified.")
    72  		return 1
    73  	}
    74  
    75  	// Check for arg validation
    76  	args = c.flags.Args()
    77  	ixns, err := c.ixnsFromArgs(args)
    78  	if err != nil {
    79  		c.UI.Error(fmt.Sprintf("Error: %s", err))
    80  		return 1
    81  	}
    82  
    83  	// Create and test the HTTP client
    84  	client, err := c.http.APIClient()
    85  	if err != nil {
    86  		c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
    87  		return 1
    88  	}
    89  
    90  	// Create the finder in case we need it
    91  	find := &finder.Finder{Client: client}
    92  
    93  	// Go through and create each intention
    94  	for _, ixn := range ixns {
    95  		// If replace is set to true, then perform an update operation.
    96  		if c.flagReplace {
    97  			oldIxn, err := find.Find(ixn.SourceString(), ixn.DestinationString())
    98  			if err != nil {
    99  				c.UI.Error(fmt.Sprintf(
   100  					"Error looking up intention for replacement with source %q "+
   101  						"and destination %q: %s",
   102  					ixn.SourceString(),
   103  					ixn.DestinationString(),
   104  					err))
   105  				return 1
   106  			}
   107  			if oldIxn != nil {
   108  				// We set the ID of our intention so we overwrite it
   109  				ixn.ID = oldIxn.ID
   110  
   111  				if _, err := client.Connect().IntentionUpdate(ixn, nil); err != nil {
   112  					c.UI.Error(fmt.Sprintf(
   113  						"Error replacing intention with source %q "+
   114  							"and destination %q: %s",
   115  						ixn.SourceString(),
   116  						ixn.DestinationString(),
   117  						err))
   118  					return 1
   119  				}
   120  
   121  				// Continue since we don't want to try to insert a new intention
   122  				continue
   123  			}
   124  		}
   125  
   126  		_, _, err := client.Connect().IntentionCreate(ixn, nil)
   127  		if err != nil {
   128  			c.UI.Error(fmt.Sprintf("Error creating intention %q: %s", ixn, err))
   129  			return 1
   130  		}
   131  
   132  		c.UI.Output(fmt.Sprintf("Created: %s", ixn))
   133  	}
   134  
   135  	return 0
   136  }
   137  
   138  // ixnsFromArgs returns the set of intentions to create based on the arguments
   139  // given and the flags set. This will call ixnsFromFiles if the -file flag
   140  // was set.
   141  func (c *cmd) ixnsFromArgs(args []string) ([]*api.Intention, error) {
   142  	// If we're in file mode, load from files
   143  	if c.flagFile {
   144  		return c.ixnsFromFiles(args)
   145  	}
   146  
   147  	// From args we require exactly two
   148  	if len(args) != 2 {
   149  		return nil, fmt.Errorf("Must specify two arguments: source and destination")
   150  	}
   151  
   152  	return []*api.Intention{&api.Intention{
   153  		SourceName:      args[0],
   154  		DestinationName: args[1],
   155  		SourceType:      api.IntentionSourceConsul,
   156  		Action:          c.ixnAction(),
   157  		Meta:            c.flagMeta,
   158  	}}, nil
   159  }
   160  
   161  func (c *cmd) ixnsFromFiles(args []string) ([]*api.Intention, error) {
   162  	var result []*api.Intention
   163  	for _, path := range args {
   164  		f, err := os.Open(path)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  
   169  		var ixn api.Intention
   170  		err = json.NewDecoder(f).Decode(&ixn)
   171  		f.Close()
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  
   176  		result = append(result, &ixn)
   177  	}
   178  
   179  	return result, nil
   180  }
   181  
   182  // ixnAction returns the api.IntentionAction based on the flag set.
   183  func (c *cmd) ixnAction() api.IntentionAction {
   184  	if c.flagAllow {
   185  		return api.IntentionActionAllow
   186  	}
   187  
   188  	return api.IntentionActionDeny
   189  }
   190  
   191  func (c *cmd) Synopsis() string {
   192  	return synopsis
   193  }
   194  
   195  func (c *cmd) Help() string {
   196  	return c.help
   197  }
   198  
   199  const synopsis = "Create intentions for service connections."
   200  const help = `
   201  Usage: consul intention create [options] SRC DST
   202  Usage: consul intention create [options] -file FILE...
   203  
   204    Create one or more intentions. The data can be specified as a single
   205    source and destination pair or via a set of files when the "-file" flag
   206    is specified.
   207  
   208        $ consul intention create web db
   209  
   210    To consume data from a set of files:
   211  
   212        $ consul intention create -file one.json two.json
   213  
   214    When specifying the "-file" flag, "-" may be used once to read from stdin:
   215  
   216        $ echo "{ ... }" | consul intention create -file -
   217  
   218    An "allow" intention is created by default (whitelist). To create a
   219    "deny" intention, the "-deny" flag should be specified.
   220  
   221    If a conflicting intention is found, creation will fail. To replace any
   222    conflicting intentions, specify the "-replace" flag. This will replace any
   223    conflicting intentions with the intention specified in this command.
   224    Metadata and any other fields of the previous intention will not be
   225    preserved.
   226  
   227    Additional flags and more advanced use cases are detailed below.
   228  `