github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  
    12  // Current agent config format is defined as follows:
    13  // # format <version>\n   (very first line; <version> is 1.18 or later)
    14  // <config-encoded-as-yaml>
    15  // All of this is saved in a single agent.conf file.
    16  //
    17  // Juju only supports upgrading from single steps, so Juju only needs
    18  // to know about the current format and the format of the previous
    19  // stable release. For convenience, the format name includes the
    20  // version number of the stable release that it will be released with.
    21  // Once this release has happened, the format should be considered
    22  // FIXED, and should no longer be modified. If changes are necessary
    23  // to the format, a new format should be created.
    24  //
    25  // We don't need to create new formats for each release, the version
    26  // number is just a convenience for us to know which stable release
    27  // introduced that format.
    28  
    29  var formats = make(map[string]formatter)
    30  
    31  // The formatter defines the two methods needed by the formatters for
    32  // translating to and from the internal, format agnostic, structure.
    33  type formatter interface {
    34  	version() string
    35  	unmarshal(data []byte) (*configInternal, error)
    36  }
    37  
    38  func registerFormat(format formatter) {
    39  	formats[format.version()] = format
    40  }
    41  
    42  // Once a new format version is introduced:
    43  // - Create a formatter for the new version (including a marshal() method);
    44  // - Call registerFormat in the new format's init() function.
    45  // - Change this to point to the new format;
    46  //
    47  // When a new format version is introduced there is going to need to be some
    48  // refactoring around the config writing when provisioning a machine as the
    49  // controller may well understand a config format that the model does not. So
    50  // when generating the agent.conf for the model's machine, it needs to be
    51  // written out in a format that the model can understand. Right now it will be
    52  // written out in the format that the controller understands, and that will
    53  // not continue to be correct.
    54  
    55  // currentFormat holds the current agent config version's formatter.
    56  var currentFormat = format_2_0
    57  
    58  // formatPrefix is prefix of the first line in an agent config file.
    59  const formatPrefix = "# format "
    60  
    61  func getFormatter(version string) (formatter, error) {
    62  	version = strings.TrimSpace(version)
    63  	format, ok := formats[version]
    64  	if !ok {
    65  		return nil, fmt.Errorf("unknown agent config format %q", version)
    66  	}
    67  	return format, nil
    68  }
    69  
    70  func parseConfigData(data []byte) (formatter, *configInternal, error) {
    71  	i := bytes.IndexByte(data, '\n')
    72  	if i == -1 {
    73  		return nil, nil, fmt.Errorf("invalid agent config format: %s", string(data))
    74  	}
    75  	version, configData := string(data[0:i]), data[i+1:]
    76  	if !strings.HasPrefix(version, formatPrefix) {
    77  		return nil, nil, fmt.Errorf("malformed agent config format %q", version)
    78  	}
    79  	version = strings.TrimPrefix(version, formatPrefix)
    80  	format, err := getFormatter(version)
    81  	if err != nil {
    82  		return nil, nil, err
    83  	}
    84  	config, err := format.unmarshal(configData)
    85  	if err != nil {
    86  		return nil, nil, err
    87  	}
    88  	return format, config, nil
    89  }