github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/agent/format.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/juju/utils"
    12  )
    13  
    14  // Current agent config format is defined as follows:
    15  // # format <version>\n   (very first line; <version> is 1.18 or later)
    16  // <config-encoded-as-yaml>
    17  // All of this is saved in a single agent.conf file.
    18  //
    19  // Historically the format file in the agent config directory was used
    20  // to identify the method of serialization. This was used by
    21  // individual legacy (pre 1.18) format readers and writers to be able
    22  // to translate from the file format to the in-memory structure. From
    23  // version 1.18, the format is part of the agent configuration file,
    24  // so there is only a single source of truth.
    25  //
    26  // Juju only supports upgrading from single steps, so Juju only needs
    27  // to know about the current format and the format of the previous
    28  // stable release. For convenience, the format name includes the
    29  // version number of the stable release that it will be released with.
    30  // Once this release has happened, the format should be considered
    31  // FIXED, and should no longer be modified. If changes are necessary
    32  // to the format, a new format should be created.
    33  //
    34  // We don't need to create new formats for each release, the version
    35  // number is just a convenience for us to know which stable release
    36  // introduced that format.
    37  
    38  var formats = make(map[string]formatter)
    39  
    40  // The formatter defines the two methods needed by the formatters for
    41  // translating to and from the internal, format agnostic, structure.
    42  type formatter interface {
    43  	version() string
    44  	unmarshal(data []byte) (*configInternal, error)
    45  }
    46  
    47  func registerFormat(format formatter) {
    48  	formats[format.version()] = format
    49  }
    50  
    51  // Once a new format version is introduced:
    52  // - Create a formatter for the new version (including a marshal() method);
    53  // - Call registerFormat in the new format's init() function.
    54  // - Change this to point to the new format;
    55  // - Remove the marshal() method from the old format;
    56  
    57  // currentFormat holds the current agent config version's formatter.
    58  var currentFormat = format_1_18
    59  
    60  // agentConfigFilename is the default file name of used for the agent
    61  // config.
    62  const agentConfigFilename = "agent.conf"
    63  
    64  // formatPrefix is prefix of the first line in an agent config file.
    65  const formatPrefix = "# format "
    66  
    67  func writeFileCommands(filename string, contents []byte, permission int) []string {
    68  	quotedFilename := utils.ShQuote(filename)
    69  	quotedContents := utils.ShQuote(string(contents))
    70  	return []string{
    71  		fmt.Sprintf("install -m %o /dev/null %s", permission, quotedFilename),
    72  		fmt.Sprintf(`printf '%%s\n' %s > %s`, quotedContents, quotedFilename),
    73  	}
    74  }
    75  
    76  func getFormatter(version string) (formatter, error) {
    77  	version = strings.TrimSpace(version)
    78  	format, ok := formats[version]
    79  	if !ok {
    80  		return nil, fmt.Errorf("unknown agent config format %q", version)
    81  	}
    82  	return format, nil
    83  }
    84  
    85  func parseConfigData(data []byte) (formatter, *configInternal, error) {
    86  	i := bytes.IndexByte(data, '\n')
    87  	if i == -1 {
    88  		return nil, nil, fmt.Errorf("invalid agent config format: %s", string(data))
    89  	}
    90  	version, configData := string(data[0:i]), data[i+1:]
    91  	if !strings.HasPrefix(version, formatPrefix) {
    92  		return nil, nil, fmt.Errorf("malformed agent config format %q", version)
    93  	}
    94  	version = strings.TrimPrefix(version, formatPrefix)
    95  	format, err := getFormatter(version)
    96  	if err != nil {
    97  		return nil, nil, err
    98  	}
    99  	config, err := format.unmarshal(configData)
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  	return format, config, nil
   104  }