github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/plugins/juju-metadata/toolsmetadata.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/gnuflag"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/utils"
    13  
    14  	"github.com/juju/juju/cmd/modelcmd"
    15  	"github.com/juju/juju/environs/filestorage"
    16  	"github.com/juju/juju/environs/simplestreams"
    17  	"github.com/juju/juju/environs/storage"
    18  	envtools "github.com/juju/juju/environs/tools"
    19  	"github.com/juju/juju/juju/keys"
    20  	"github.com/juju/juju/juju/osenv"
    21  	coretools "github.com/juju/juju/tools"
    22  )
    23  
    24  func newToolsMetadataCommand() cmd.Command {
    25  	return modelcmd.Wrap(&toolsMetadataCommand{})
    26  }
    27  
    28  // toolsMetadataCommand is used to generate simplestreams metadata for juju tools.
    29  type toolsMetadataCommand struct {
    30  	modelcmd.ModelCommandBase
    31  	fetch       bool
    32  	metadataDir string
    33  	stream      string
    34  	clean       bool
    35  	public      bool
    36  }
    37  
    38  var toolsMetadataDoc = `
    39  generate-tools creates simplestreams tools metadata.
    40  
    41  This command works by scanning a directory for tools tarballs from which to generate
    42  simplestreams tools metadata. The working directory is specified using the -d argument
    43  (defaults to $JUJU_DATA or if not defined $XDG_DATA_HOME/juju or if that is not defined
    44  ~/.local/share/juju). The working directory is expected to contain a named subdirectory
    45  containing tools tarballs, and is where the resulting metadata is written.
    46  
    47  The stream for which metadata is generated is specified using the --stream parameter
    48  (default is "released"). Metadata can be generated for any supported stream - released,
    49  proposed, testing, devel.
    50  
    51  Tools tarballs can are located in either a sub directory called "releases" (legacy),
    52  or a directory named after the stream. By default, if no --stream argument is provided,
    53  metadata for tools in the "released" stream is generated by scanning for tool tarballs
    54  in the "releases" directory. By specifying a stream explcitly, tools tarballs are
    55  expected to be located in a directory named after the stream.
    56  
    57  Newly generated metadata will be merged with any exisitng metadata that is already there.
    58  To first remove metadata for the specified stream before generating new metadata,
    59  use the --clean option.
    60  
    61  Examples:
    62  
    63    - generate metadata for "released" tools, looking in the "releases" directory:
    64  
    65     juju metadata generate-tools -d <workingdir>
    66  
    67    - generate metadata for "released" tools, looking in the "released" directory:
    68  
    69     juju metadata generate-tools -d <workingdir> --stream released
    70  
    71    - generate metadata for "proposed" tools, looking in the "proposed" directory:
    72  
    73     juju metadata generate-tools -d <workingdir> --stream proposed
    74  
    75    - generate metadata for "proposed" tools, first removing existing "proposed" metadata:
    76  
    77     juju metadata generate-tools -d <workingdir> --stream proposed --clean
    78  
    79  `
    80  
    81  func (c *toolsMetadataCommand) Info() *cmd.Info {
    82  	return &cmd.Info{
    83  		Name:    "generate-tools",
    84  		Purpose: "generate simplestreams tools metadata",
    85  		Doc:     toolsMetadataDoc,
    86  	}
    87  }
    88  
    89  func (c *toolsMetadataCommand) SetFlags(f *gnuflag.FlagSet) {
    90  	f.StringVar(&c.metadataDir, "d", "", "local directory in which to store metadata")
    91  	// If no stream is specified, we'll generate metadata for the legacy tools location.
    92  	f.StringVar(&c.stream, "stream", "", "simplestreams stream for which to generate the metadata")
    93  	f.BoolVar(&c.clean, "clean", false, "remove any existing metadata for the specified stream before generating new metadata")
    94  	f.BoolVar(&c.public, "public", false, "tools are for a public cloud, so generate mirrors information")
    95  }
    96  
    97  func (c *toolsMetadataCommand) Run(context *cmd.Context) error {
    98  	writer := loggo.NewMinimumLevelWriter(
    99  		cmd.NewCommandLogWriter("juju.environs.tools", context.Stdout, context.Stderr),
   100  		loggo.INFO)
   101  	loggo.RegisterWriter("toolsmetadata", writer)
   102  	defer loggo.RemoveWriter("toolsmetadata")
   103  	if c.metadataDir == "" {
   104  		c.metadataDir = osenv.JujuXDGDataHomeDir()
   105  	} else {
   106  		c.metadataDir = context.AbsPath(c.metadataDir)
   107  	}
   108  
   109  	sourceStorage, err := filestorage.NewFileStorageReader(c.metadataDir)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	// We now store the tools in a directory named after their stream, but the
   115  	// legacy behaviour is to store all tools in a single "releases" directory.
   116  	toolsDir := c.stream
   117  	if c.stream == "" {
   118  		fmt.Fprintln(context.Stdout, "No stream specified, defaulting to released tools in the releases directory.")
   119  		c.stream = envtools.ReleasedStream
   120  		toolsDir = envtools.LegacyReleaseDirectory
   121  	}
   122  	fmt.Fprintf(context.Stdout, "Finding tools in %s for stream %s.\n", c.metadataDir, c.stream)
   123  	toolsList, err := envtools.ReadList(sourceStorage, toolsDir, -1, -1)
   124  	if err == envtools.ErrNoTools {
   125  		var source string
   126  		source, err = envtools.ToolsURL(envtools.DefaultBaseURL)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		toolsList, err = envtools.FindToolsForCloud(toolsDataSources(source), simplestreams.CloudSpec{}, c.stream, -1, -1, coretools.Filter{})
   131  	}
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	writeMirrors := envtools.DoNotWriteMirrors
   141  	if c.public {
   142  		writeMirrors = envtools.WriteMirrors
   143  	}
   144  	return mergeAndWriteMetadata(targetStorage, toolsDir, c.stream, c.clean, toolsList, writeMirrors)
   145  }
   146  
   147  func toolsDataSources(urls ...string) []simplestreams.DataSource {
   148  	dataSources := make([]simplestreams.DataSource, len(urls))
   149  	for i, url := range urls {
   150  		dataSources[i] = simplestreams.NewURLSignedDataSource(
   151  			"local source",
   152  			url,
   153  			keys.JujuPublicKey,
   154  			utils.VerifySSLHostnames,
   155  			simplestreams.CUSTOM_CLOUD_DATA,
   156  			false)
   157  	}
   158  	return dataSources
   159  }
   160  
   161  // This is essentially the same as tools.MergeAndWriteMetadata, but also
   162  // resolves metadata for existing tools by fetching them and computing
   163  // size/sha256 locally.
   164  func mergeAndWriteMetadata(
   165  	stor storage.Storage, toolsDir, stream string, clean bool, toolsList coretools.List, writeMirrors envtools.ShouldWriteMirrors,
   166  ) error {
   167  	existing, err := envtools.ReadAllMetadata(stor)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	if clean {
   172  		delete(existing, stream)
   173  	}
   174  	metadata := envtools.MetadataFromTools(toolsList, toolsDir)
   175  	var mergedMetadata []*envtools.ToolsMetadata
   176  	if mergedMetadata, err = envtools.MergeMetadata(metadata, existing[stream]); err != nil {
   177  		return err
   178  	}
   179  	if err = envtools.ResolveMetadata(stor, toolsDir, mergedMetadata); err != nil {
   180  		return err
   181  	}
   182  	existing[stream] = mergedMetadata
   183  	return envtools.WriteMetadata(stor, existing, []string{stream}, writeMirrors)
   184  }