github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/loggo"
    11  	"github.com/juju/utils"
    12  	"launchpad.net/gnuflag"
    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"
    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  	loggo.RegisterWriter("toolsmetadata", cmd.NewCommandLogWriter("juju.environs.tools", context.Stdout, context.Stderr), loggo.INFO)
    99  	defer loggo.RemoveWriter("toolsmetadata")
   100  	if c.metadataDir == "" {
   101  		c.metadataDir = osenv.JujuXDGDataHome()
   102  	} else {
   103  		c.metadataDir = context.AbsPath(c.metadataDir)
   104  	}
   105  
   106  	sourceStorage, err := filestorage.NewFileStorageReader(c.metadataDir)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	// We now store the tools in a directory named after their stream, but the
   112  	// legacy behaviour is to store all tools in a single "releases" directory.
   113  	toolsDir := c.stream
   114  	if c.stream == "" {
   115  		fmt.Fprintf(context.Stdout, "No stream specified, defaulting to released tools in the releases directory.\n")
   116  		c.stream = envtools.ReleasedStream
   117  		toolsDir = envtools.LegacyReleaseDirectory
   118  	}
   119  	fmt.Fprintf(context.Stdout, "Finding tools in %s for stream %s.\n", c.metadataDir, c.stream)
   120  	toolsList, err := envtools.ReadList(sourceStorage, toolsDir, -1, -1)
   121  	if err == envtools.ErrNoTools {
   122  		var source string
   123  		source, err = envtools.ToolsURL(envtools.DefaultBaseURL)
   124  		if err != nil {
   125  			return err
   126  		}
   127  		toolsList, err = envtools.FindToolsForCloud(toolsDataSources(source), simplestreams.CloudSpec{}, c.stream, -1, -1, coretools.Filter{})
   128  	}
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	targetStorage, err := filestorage.NewFileStorageWriter(c.metadataDir)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	writeMirrors := envtools.DoNotWriteMirrors
   138  	if c.public {
   139  		writeMirrors = envtools.WriteMirrors
   140  	}
   141  	return mergeAndWriteMetadata(targetStorage, toolsDir, c.stream, c.clean, toolsList, writeMirrors)
   142  }
   143  
   144  func toolsDataSources(urls ...string) []simplestreams.DataSource {
   145  	dataSources := make([]simplestreams.DataSource, len(urls))
   146  	for i, url := range urls {
   147  		dataSources[i] = simplestreams.NewURLSignedDataSource(
   148  			"local source",
   149  			url,
   150  			juju.JujuPublicKey,
   151  			utils.VerifySSLHostnames,
   152  			simplestreams.CUSTOM_CLOUD_DATA,
   153  			false)
   154  	}
   155  	return dataSources
   156  }
   157  
   158  // This is essentially the same as tools.MergeAndWriteMetadata, but also
   159  // resolves metadata for existing tools by fetching them and computing
   160  // size/sha256 locally.
   161  func mergeAndWriteMetadata(
   162  	stor storage.Storage, toolsDir, stream string, clean bool, toolsList coretools.List, writeMirrors envtools.ShouldWriteMirrors,
   163  ) error {
   164  	existing, err := envtools.ReadAllMetadata(stor)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if clean {
   169  		delete(existing, stream)
   170  	}
   171  	metadata := envtools.MetadataFromTools(toolsList, toolsDir)
   172  	var mergedMetadata []*envtools.ToolsMetadata
   173  	if mergedMetadata, err = envtools.MergeMetadata(metadata, existing[stream]); err != nil {
   174  		return err
   175  	}
   176  	if err = envtools.ResolveMetadata(stor, toolsDir, mergedMetadata); err != nil {
   177  		return err
   178  	}
   179  	existing[stream] = mergedMetadata
   180  	return envtools.WriteMetadata(stor, existing, []string{stream}, writeMirrors)
   181  }