github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/vagrant/dev.go (about)

     1  package vagrant
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/otto/app"
    12  	"github.com/hashicorp/otto/directory"
    13  	"github.com/hashicorp/otto/helper/router"
    14  )
    15  
    16  // DevOptions is the configuration struct used for Dev.
    17  type DevOptions struct {
    18  	// Dir is the path to the directory with the Vagrantfile. This
    19  	// will default to `#{ctx.Dir}/dev` if empty.
    20  	Dir string
    21  
    22  	// DataDir is the path to the directory where Vagrant should store its data.
    23  	// Defaults to `#{ctx.LocalDir/vagrant}` if empty.
    24  	DataDir string
    25  
    26  	// Layer, if non-nil, will be the set of layers that this environment
    27  	// builds on top of. If this is set, then the layers will be managed
    28  	// automatically by this.
    29  	//
    30  	// If this is nil, then layers won't be used.
    31  	Layer *Layered
    32  
    33  	// Instructions are help text that is shown after creating the
    34  	// development environment.
    35  	Instructions string
    36  }
    37  
    38  // Dev can be used as an implementation of app.App.Dev to automatically
    39  // handle creating a development environment and forwarding commands down
    40  // to Vagrant.
    41  func Dev(opts *DevOptions) *router.Router {
    42  	return &router.Router{
    43  		Actions: map[string]router.Action{
    44  			"": &router.SimpleAction{
    45  				ExecuteFunc:  opts.actionUp,
    46  				SynopsisText: actionUpSyn,
    47  				HelpText:     strings.TrimSpace(actionUpHelp),
    48  			},
    49  
    50  			"address": &router.SimpleAction{
    51  				ExecuteFunc:  opts.actionAddress,
    52  				SynopsisText: actionAddressSyn,
    53  				HelpText:     strings.TrimSpace(actionAddressHelp),
    54  			},
    55  
    56  			"destroy": &router.SimpleAction{
    57  				ExecuteFunc:  opts.actionDestroy,
    58  				SynopsisText: actionDestroySyn,
    59  				HelpText:     strings.TrimSpace(actionDestroyHelp),
    60  			},
    61  
    62  			"halt": &router.SimpleAction{
    63  				ExecuteFunc:  opts.actionHalt,
    64  				SynopsisText: actionHaltSyn,
    65  				HelpText:     strings.TrimSpace(actionHaltHelp),
    66  			},
    67  
    68  			"layers": &router.SimpleAction{
    69  				ExecuteFunc:  opts.actionLayers,
    70  				SynopsisText: actionLayersSyn,
    71  				HelpText:     strings.TrimSpace(actionLayersHelp),
    72  			},
    73  
    74  			"ssh": &router.SimpleAction{
    75  				ExecuteFunc:  opts.actionSSH,
    76  				SynopsisText: actionSSHSyn,
    77  				HelpText:     strings.TrimSpace(actionSSHHelp),
    78  			},
    79  
    80  			"vagrant": &router.SimpleAction{
    81  				ExecuteFunc:  opts.actionRaw,
    82  				SynopsisText: actionVagrantSyn,
    83  				HelpText:     strings.TrimSpace(actionVagrantHelp),
    84  			},
    85  		},
    86  	}
    87  }
    88  
    89  func (opts *DevOptions) Vagrant(ctx *app.Context) *Vagrant {
    90  	dir := opts.Dir
    91  	if dir == "" {
    92  		dir = filepath.Join(ctx.Dir, "dev")
    93  	}
    94  	dataDir := opts.DataDir
    95  	if dataDir == "" {
    96  		dataDir = filepath.Join(ctx.LocalDir, "vagrant")
    97  	}
    98  	result := &Vagrant{
    99  		Dir:     dir,
   100  		DataDir: dataDir,
   101  		Ui:      ctx.Ui,
   102  	}
   103  
   104  	// If we have a layered environment we want to configure every environment
   105  	// with the layer information so that we can call arbitrary commands.
   106  	if opts.Layer != nil {
   107  		if err := opts.Layer.ConfigureEnv(result); err != nil {
   108  			// This shouldn't fail
   109  			panic(err)
   110  		}
   111  	}
   112  
   113  	return result
   114  }
   115  
   116  func (opts *DevOptions) actionAddress(rctx router.Context) error {
   117  	ctx := rctx.(*app.Context)
   118  	ctx.Ui.Raw(ctx.DevIPAddress + "\n")
   119  	return nil
   120  }
   121  
   122  func (opts *DevOptions) actionDestroy(rctx router.Context) error {
   123  	ctx := rctx.(*app.Context)
   124  	project := Project(&ctx.Shared)
   125  	if err := project.InstallIfNeeded(); err != nil {
   126  		return err
   127  	}
   128  
   129  	ctx.Ui.Header("Destroying the local development environment...")
   130  	vagrant := opts.Vagrant(ctx)
   131  
   132  	// If the Vagrant directory doesn't exist, then we're already deleted.
   133  	// So we just verify here that it exists and then call destroy only
   134  	// if it does.
   135  	log.Printf("[DEBUG] vagrant: verifying data dir exists: %s", vagrant.DataDir)
   136  	_, err := os.Stat(vagrant.DataDir)
   137  	if err != nil && !os.IsNotExist(err) {
   138  		log.Printf("[ERROR] vagrant: err: %s", err)
   139  		return err
   140  	}
   141  	if err == nil {
   142  		if err := vagrant.Execute("destroy", "-f"); err != nil {
   143  			return err
   144  		}
   145  		ctx.Ui.Raw("\n")
   146  	}
   147  
   148  	// Store the dev status into the directory. We just do this before
   149  	// since there are a lot of cases where Vagrant fails but still imported.
   150  	// We just override any prior dev.
   151  	ctx.Ui.Header("Deleting development environment metadata...")
   152  	if opts.Layer != nil {
   153  		if err := opts.Layer.RemoveEnv(vagrant); err != nil {
   154  			return fmt.Errorf(
   155  				"Error preparing dev environment: %s", err)
   156  		}
   157  	}
   158  
   159  	if err := ctx.Directory.DeleteDev(opts.devLookup(ctx)); err != nil {
   160  		return fmt.Errorf(
   161  			"Error deleting dev environment metadata: %s", err)
   162  	}
   163  
   164  	if err := opts.sshCache(ctx).Delete(); err != nil {
   165  		return fmt.Errorf(
   166  			"Error cleaning SSH cache: %s", err)
   167  	}
   168  
   169  	ctx.Ui.Header("[green]Development environment has been destroyed!")
   170  	return nil
   171  }
   172  
   173  func (opts *DevOptions) actionHalt(rctx router.Context) error {
   174  	ctx := rctx.(*app.Context)
   175  	project := Project(&ctx.Shared)
   176  	if err := project.InstallIfNeeded(); err != nil {
   177  		return err
   178  	}
   179  
   180  	ctx.Ui.Header("Halting the the local development environment...")
   181  
   182  	if err := opts.Vagrant(ctx).Execute("halt"); err != nil {
   183  		return err
   184  	}
   185  
   186  	ctx.Ui.Header("[green]Development environment halted!")
   187  
   188  	return nil
   189  }
   190  
   191  func (opts *DevOptions) actionRaw(rctx router.Context) error {
   192  	ctx := rctx.(*app.Context)
   193  	project := Project(&ctx.Shared)
   194  	if err := project.InstallIfNeeded(); err != nil {
   195  		return err
   196  	}
   197  
   198  	ctx.Ui.Header(fmt.Sprintf(
   199  		"Executing: 'vagrant %s'", strings.Join(ctx.ActionArgs, " ")))
   200  
   201  	if err := opts.Vagrant(ctx).Execute(ctx.ActionArgs...); err != nil {
   202  		return err
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  func (opts *DevOptions) actionSSH(rctx router.Context) error {
   209  	ctx := rctx.(*app.Context)
   210  
   211  	dev, err := ctx.Directory.GetDev(opts.devLookup(ctx))
   212  	if err != nil {
   213  		return err
   214  	}
   215  	if dev == nil {
   216  		return fmt.Errorf(
   217  			"The development environment hasn't been created yet! Please\n" +
   218  				"create the development environmet by running `otto dev` before\n" +
   219  				"attempting to SSH.")
   220  	}
   221  
   222  	project := Project(&ctx.Shared)
   223  	if err := project.InstallIfNeeded(); err != nil {
   224  		return err
   225  	}
   226  
   227  	ctx.Ui.Header("Executing SSH. This may take a few seconds...")
   228  	return opts.sshCache(ctx).Exec(true)
   229  }
   230  
   231  func (opts *DevOptions) actionUp(rctx router.Context) error {
   232  	ctx := rctx.(*app.Context)
   233  	project := Project(&ctx.Shared)
   234  	if err := project.InstallIfNeeded(); err != nil {
   235  		return err
   236  	}
   237  
   238  	// If we are layered, then let the user know we're going to use
   239  	// a layer development environment...
   240  	if opts.Layer != nil {
   241  		pending, err := opts.Layer.Pending()
   242  		if err != nil {
   243  			return fmt.Errorf("Error checking dev layer status: %s", err)
   244  		}
   245  
   246  		if len(pending) > 0 {
   247  			ctx.Ui.Header("Creating development environment layers...")
   248  			ctx.Ui.Message(
   249  				"Otto uses layers to speed up building development environments.\n" +
   250  					"Each layer only needs to be built once. We've detected that the\n" +
   251  					"layers below aren't created yet. These will be built this time.\n" +
   252  					"Future development envirionments will use the cached versions\n" +
   253  					"to be much, much faster.")
   254  		}
   255  
   256  		if err := opts.Layer.Build(&ctx.Shared); err != nil {
   257  			return fmt.Errorf(
   258  				"Error building dev environment layers: %s", err)
   259  		}
   260  	}
   261  
   262  	// Output some info the user prior to running
   263  	ctx.Ui.Header(
   264  		"Creating local development environment with Vagrant if it doesn't exist...")
   265  
   266  	// Store the dev status into the directory. We just do this before
   267  	// since there are a lot of cases where Vagrant fails but still imported.
   268  	// We just override any prior dev.
   269  	dev := &directory.Dev{Lookup: directory.Lookup{AppID: ctx.Appfile.ID}}
   270  	dev.MarkReady()
   271  	if err := ctx.Directory.PutDev(dev); err != nil {
   272  		return fmt.Errorf(
   273  			"Error saving dev environment metadata: %s", err)
   274  	}
   275  
   276  	// Run it!
   277  	vagrant := opts.Vagrant(ctx)
   278  	if opts.Layer != nil {
   279  		if err := opts.Layer.ConfigureEnv(vagrant); err != nil {
   280  			return fmt.Errorf(
   281  				"Error preparing dev environment: %s", err)
   282  		}
   283  
   284  		// Configure the environment as ready
   285  		if err := opts.Layer.SetEnv(vagrant, envStateReady); err != nil {
   286  			return fmt.Errorf(
   287  				"Error preparing dev environment: %s", err)
   288  		}
   289  	}
   290  	if err := vagrant.Execute("up"); err != nil {
   291  		return err
   292  	}
   293  
   294  	// Cache the SSH info
   295  	ctx.Ui.Header("Caching SSH credentials from Vagrant...")
   296  	if err := opts.sshCache(ctx).Cache(); err != nil {
   297  		return err
   298  	}
   299  
   300  	// Success, let the user know whats up
   301  	ctx.Ui.Header("[green]Development environment successfully created!")
   302  	ctx.Ui.Message(fmt.Sprintf("IP address: %s", ctx.DevIPAddress))
   303  	if opts.Instructions != "" {
   304  		ctx.Ui.Message("\n" + opts.Instructions)
   305  	}
   306  
   307  	return nil
   308  }
   309  
   310  func (opts *DevOptions) actionLayers(rctx router.Context) error {
   311  	if opts.Layer == nil {
   312  		return fmt.Errorf(
   313  			"This development environment does not use layers.\n" +
   314  				"This command can only be used to manage development\n" +
   315  				"environments with layers.")
   316  	}
   317  
   318  	ctx := rctx.(*app.Context)
   319  	fs := flag.NewFlagSet("otto", flag.ContinueOnError)
   320  	graph := fs.Bool("graph", false, "show graph")
   321  	prune := fs.Bool("prune", false, "prune unused layers")
   322  	if err := fs.Parse(rctx.RouteArgs()); err != nil {
   323  		return err
   324  	}
   325  
   326  	// Graph?
   327  	if *graph {
   328  		graph, err := opts.Layer.Graph()
   329  		if err != nil {
   330  			return err
   331  		}
   332  
   333  		ctx.Ui.Raw(graph.String() + "\n")
   334  		return nil
   335  	}
   336  
   337  	// Prune?
   338  	if *prune {
   339  		ctx.Ui.Header("Pruning any outdated or unused layers...")
   340  		count, err := opts.Layer.Prune(&ctx.Shared)
   341  		if err != nil {
   342  			return err
   343  		}
   344  		if count == 0 {
   345  			ctx.Ui.Message("No outdated or unused layers were found!")
   346  		} else {
   347  			ctx.Ui.Message(fmt.Sprintf(
   348  				"[green]Pruned %d outdated or unused layers!", count))
   349  		}
   350  
   351  		return nil
   352  	}
   353  
   354  	// We're just listing the layers. Eventually we probably should
   355  	// output status or something more useful here.
   356  	for _, l := range opts.Layer.Layers {
   357  		ctx.Ui.Raw(l.ID + "\n")
   358  	}
   359  
   360  	return nil
   361  }
   362  
   363  func (opts *DevOptions) devLookup(ctx *app.Context) *directory.Dev {
   364  	return &directory.Dev{Lookup: directory.Lookup{AppID: ctx.Appfile.ID}}
   365  }
   366  
   367  func (opts *DevOptions) sshCache(ctx *app.Context) *SSHCache {
   368  	return &SSHCache{
   369  		Path:    filepath.Join(ctx.CacheDir, "dev_ssh_cache"),
   370  		Vagrant: opts.Vagrant(ctx),
   371  	}
   372  }
   373  
   374  // Synopsis text for actions
   375  const (
   376  	actionAddressSyn = "Shows the address to reach the development environment"
   377  	actionUpSyn      = "Starts the development environment"
   378  	actionDestroySyn = "Destroy the development environment"
   379  	actionHaltSyn    = "Halts the development environment"
   380  	actionLayersSyn  = "Manage the layers of this development environment"
   381  	actionSSHSyn     = "SSH into the development environment"
   382  	actionVagrantSyn = "Run arbitrary Vagrant commands"
   383  )
   384  
   385  // Help text for actions
   386  const actionUpHelp = `
   387  Usage: otto dev
   388  
   389    Builds and starts the development environment.
   390  
   391    The development environment runs locally via Vagrant. Otto manages
   392    Vagrant for you. All upstream dependencies will automatically be started
   393    and running within the development environment.
   394  
   395    At the end of running this command, help text will be shown that tell
   396    you how to interact with the build environment.
   397  `
   398  
   399  const actionDestroyHelp = `
   400  Usage: otto dev destroy
   401  
   402    Destroys the development environment.
   403  
   404    This command will stop and delete the development environment.
   405    Any data that was put onto the development environment will be deleted,
   406    except for your own project's code (the directory and any subdirectories
   407    where the Appfile exists).
   408  
   409  `
   410  
   411  const actionHaltHelp = `
   412  Usage: otto dev halt
   413  
   414    Halts the development environment.
   415  
   416    This command will stop the development environment. The environment can then
   417    be started again with 'otto dev'.
   418  
   419  `
   420  
   421  const actionLayersHelp = `
   422  Usage: otto dev layers [options]
   423  
   424    Manage the development environment layers.
   425  
   426    WARNING: This is an advanced, low level command. You shouldn't need this
   427    command. It is meant to give you the ability to get out of a bad situation
   428    if Otto mis-manages your layers. If you run into a scenario where you need
   429    to use this, please report a bug to Otto so we can think of others ways
   430    around it.
   431  
   432    This command will manage the layers of the development environment.
   433    Otto uses layers as a mechanism for caching parts of the development
   434    environment that aren't often updated. This makes "otto dev" faster
   435    after the first call.
   436  
   437    If no options are given, the layers will be listed that this development
   438    environment uses. If multiple conflicting options are given, the first
   439    in alphabetical order is processed. For example, if both "-graph" and
   440    "-prune" are specified, the graph will be shown.
   441  
   442  Options:
   443  
   444    -graph       Show the full layer graph for Otto
   445    -prune       Delete all unused or outdated layers
   446  
   447  `
   448  
   449  const actionSSHHelp = `
   450  Usage: otto dev ssh
   451  
   452    Connect to the development environment via SSH.
   453  
   454    The development environment typically is headless, meaning that the
   455    preferred way to access it is SSH. This command will automatically SSH
   456    you into the development environment.
   457  
   458  `
   459  
   460  const actionAddressHelp = `
   461  Usage: otto dev address
   462  
   463    Output the address to connect to the development environment.
   464  
   465    The development environment is configured with a static IP address.
   466    This command outputs that address so you can reach it. If you want to
   467    SSH into the development environment, use 'otto dev ssh'. This address
   468    is meant for reaching running services such as in a web browser.
   469  
   470  `
   471  
   472  const actionVagrantHelp = `
   473  Usage: otto dev vagrant [command...]
   474  
   475    Run arbitrary Vagrant commands against the development environment.
   476  
   477    This is for advanced users who know and are comfortable with Vagrant.
   478    In average day to day usage, this command isn't needed.
   479  
   480    Because the development environment is backed by Vagrant, this command
   481    lets you access it directly via Vagrant. For example, if you want to
   482    run "vagrant ssh-config" against the environment, you can use
   483    "otto dev vagrant ssh-config"
   484  
   485  `