github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 }