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 }