├── LICENSE ├── README.md ├── augeas.go └── error.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dominik Honnef 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go bindings for Augeas 2 | 3 | This package provides Go bindings for [Augeas](http://augeas.net/), 4 | the configuration editing tool. 5 | 6 | ## Installation 7 | 8 | ```sh 9 | go get honnef.co/go/augeas 10 | ``` 11 | 12 | ## Documentation 13 | 14 | Documentation can be found at 15 | [godoc.org](http://godoc.org/honnef.co/go/augeas). 16 | 17 | ## Examples 18 | ### Simple example 19 | 20 | ```go 21 | package main 22 | 23 | import ( 24 | "honnef.co/go/augeas" 25 | 26 | "fmt" 27 | ) 28 | 29 | func main() { 30 | ag, err := augeas.New("/", "", augeas.None) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | // There is also Augeas.Version(), but we're demonstrating Get 36 | // here. 37 | version, err := ag.Get("/augeas/version") 38 | fmt.Println(version, err) 39 | } 40 | ``` 41 | 42 | ### Extended example 43 | 44 | An extended example that fetches all host entries from /etc/hosts can 45 | be found [in the playground](http://play.golang.org/p/aDjm4RWBvP). 46 | -------------------------------------------------------------------------------- /augeas.go: -------------------------------------------------------------------------------- 1 | // Package augeas provides Go bindings for Augeas, the configuration 2 | // editing tool. 3 | // 4 | // For more information on Augeas itself, check out http://augeas.net/ 5 | package augeas // import "honnef.co/go/augeas" 6 | 7 | // #cgo pkg-config: libxml-2.0 augeas 8 | // #include 9 | // #include 10 | import "C" 11 | import ( 12 | "unsafe" 13 | ) 14 | 15 | // Flag describes flags that influence the behaviour of Augeas when 16 | // passed to New. 17 | type Flag uint 18 | 19 | // Bits or'ed together to modify the behavior of Augeas. 20 | const ( 21 | // Keep the original file with a .augsave extension 22 | SaveBackup = 1 << iota 23 | 24 | // Save changes into a file with extension .augnew, and do not 25 | // overwrite the original file. Takes precedence over SaveBackup 26 | SaveNewFile 27 | 28 | // Typecheck lenses; since it can be very expensive it is not done 29 | // by default 30 | TypeCheck 31 | 32 | // Do not use the built-in load path for modules 33 | NoStdinc 34 | 35 | // Make save a no-op process, just record what would have changed 36 | SaveNoop 37 | 38 | // Do not load the tree automatically 39 | NoLoad 40 | 41 | NoModlAutoload 42 | 43 | // Track the span in the input of nodes 44 | EnableSpan 45 | 46 | // Do not close automatically when encountering error during 47 | // initialization 48 | NoErrClose 49 | 50 | None Flag = 0 51 | ) 52 | 53 | // Augeas encapsulates an Augeas handle. 54 | type Augeas struct { 55 | handle *C.augeas 56 | } 57 | 58 | // A Span describes the position of a node in the file it was parsed 59 | // from. 60 | type Span struct { 61 | Filename string 62 | LabelStart uint 63 | LabelEnd uint 64 | ValueStart uint 65 | ValueEnd uint 66 | SpanStart uint 67 | SpanEnd uint 68 | } 69 | 70 | // New creates a new Augeas handle, specifying the file system root, a 71 | // list of module directories and flags. 72 | // 73 | // Call the Close method once done with the handle. 74 | func New(root, loadPath string, flags Flag) (Augeas, error) { 75 | cRoot := C.CString(root) 76 | defer C.free(unsafe.Pointer(cRoot)) 77 | cLoadPath := C.CString(loadPath) 78 | defer C.free(unsafe.Pointer(cLoadPath)) 79 | 80 | handle := C.aug_init(cRoot, cLoadPath, C.uint(flags)) 81 | if flags&NoErrClose > 0 { 82 | a := Augeas{handle} 83 | return a, a.error() 84 | } 85 | if handle == nil { 86 | return Augeas{}, Error{CouldNotInitialize, "Could not initialize Augeas tree", "", ""} 87 | } 88 | 89 | return Augeas{handle}, nil 90 | } 91 | 92 | // DefineVariable defines a variable whose value is the result of 93 | // evaluating the expression. If a variable with the name already 94 | // exists, it will be replaced. Context will not be applied to the 95 | // expression. 96 | // 97 | // If the expression is empty, the variable will be removed. 98 | // 99 | // Path variables can be used in path expressions later on by prefixing 100 | // them with '$'. 101 | // 102 | // Returns the number of nodes if the expression evaluates to a 103 | // nodeset. 104 | func (a Augeas) DefineVariable(name, expression string) (int, error) { 105 | cName := C.CString(name) 106 | defer C.free(unsafe.Pointer(cName)) 107 | 108 | var cExpression *C.char 109 | if expression != "" { 110 | cExpression = C.CString(expression) 111 | defer C.free(unsafe.Pointer(cExpression)) 112 | } 113 | 114 | ret := C.aug_defvar(a.handle, cName, cExpression) 115 | 116 | if ret == -1 { 117 | return 0, a.error() 118 | } 119 | 120 | return int(ret), nil 121 | } 122 | 123 | // RemoveVariable removes a variable previously defined by 124 | // DefineVariable. 125 | func (a Augeas) RemoveVariable(name string) error { 126 | _, err := a.DefineVariable(name, "") 127 | return err 128 | } 129 | 130 | // DefineNode defines a variable whose value is the result of 131 | // evaluating the expression, which must not be empty and evaluate to 132 | // a nodeset. If a variable with the name already exists, it will be 133 | // replaced. 134 | // 135 | // If the expression evaluates to an empty nodeset, a node is created. 136 | // 137 | // Returns the number of nodes in the nodeset and whether a node has 138 | // been created or of it already existed. 139 | func (a Augeas) DefineNode(name, expression, value string) (num int, created bool, err error) { 140 | cName := C.CString(name) 141 | defer C.free(unsafe.Pointer(cName)) 142 | 143 | cExpression := C.CString(expression) 144 | defer C.free(unsafe.Pointer(cExpression)) 145 | 146 | cValue := C.CString(value) 147 | defer C.free(unsafe.Pointer(cValue)) 148 | 149 | var cCreated C.int 150 | cNum := C.aug_defnode(a.handle, cName, cExpression, cValue, &cCreated) 151 | num = int(cNum) 152 | created = cCreated == 1 153 | if cNum == -1 { 154 | err = a.error() 155 | return 156 | } 157 | 158 | return 159 | } 160 | 161 | // Get looks up the value associated with a path. 162 | // 163 | // Returns an error if there are no or too many matching nodes, or if 164 | // the path is not a legal path expression. 165 | func (a Augeas) Get(path string) (value string, err error) { 166 | cPath := C.CString(path) 167 | defer C.free(unsafe.Pointer(cPath)) 168 | 169 | var cValue *C.char 170 | ret := C.aug_get(a.handle, cPath, &cValue) 171 | 172 | if ret == 1 { 173 | return C.GoString(cValue), nil 174 | } else if ret == 0 { 175 | return "", Error{NoMatch, "No matching node", "", ""} 176 | } else if ret < 0 { 177 | return "", a.error() 178 | } 179 | 180 | panic("Unexpected return value") 181 | } 182 | 183 | // GetAll gets all values associated with a path. 184 | func (a Augeas) GetAll(path string) (values []string, err error) { 185 | paths, err := a.Match(path) 186 | if err != nil { 187 | return 188 | } 189 | 190 | for _, path := range paths { 191 | value, err := a.Get(path) 192 | if err != nil { 193 | return values, err 194 | } 195 | 196 | values = append(values, value) 197 | } 198 | 199 | return 200 | } 201 | 202 | // Set the value associated with a path. Intermediate entries are 203 | // created if they don't exist. 204 | func (a Augeas) Set(path, value string) error { 205 | cPath := C.CString(path) 206 | defer C.free(unsafe.Pointer(cPath)) 207 | 208 | cValue := C.CString(value) 209 | defer C.free(unsafe.Pointer(cValue)) 210 | 211 | ret := C.aug_set(a.handle, cPath, cValue) 212 | 213 | if ret == -1 { 214 | return a.error() 215 | } 216 | 217 | return nil 218 | } 219 | 220 | // Clear clears the value associated with a path. Intermediate entries 221 | // are created if they don't exist. 222 | func (a Augeas) Clear(path string) error { 223 | cPath := C.CString(path) 224 | defer C.free(unsafe.Pointer(cPath)) 225 | 226 | ret := C.aug_set(a.handle, cPath, nil) 227 | 228 | if ret == -1 { 229 | return a.error() 230 | } 231 | 232 | return nil 233 | } 234 | 235 | // SetMultiple sets the value of multiple nodes in one operation. Find 236 | // or create a node matching sub by interpreting sub as a path 237 | // expression relative to each node matching base. sub may be empty, 238 | // in which case all the nodes matching base will be modified. 239 | // 240 | // Returns the number of modified nodes. 241 | func (a Augeas) SetMultiple(base, sub, value string) (int, error) { 242 | cBase := C.CString(base) 243 | defer C.free(unsafe.Pointer(cBase)) 244 | 245 | var cSub *C.char 246 | if sub != "" { 247 | cSub := C.CString(sub) 248 | defer C.free(unsafe.Pointer(cSub)) 249 | } 250 | 251 | cValue := C.CString(value) 252 | defer C.free(unsafe.Pointer(cValue)) 253 | 254 | ret := C.aug_setm(a.handle, cBase, cSub, cValue) 255 | 256 | if ret == -1 { 257 | return 0, a.error() 258 | } 259 | 260 | return int(ret), nil 261 | } 262 | 263 | // Span gets the span according to input file of the node associated 264 | // with a path. If the node is associated with a file, the filename, 265 | // label and value start and end positions are set. 266 | func (a Augeas) Span(path string) (Span, error) { 267 | cPath := C.CString(path) 268 | defer C.free(unsafe.Pointer(cPath)) 269 | 270 | var cFilename *C.char 271 | var labelStart, labelEnd, valueStart, valueEnd, spanStart, spanEnd C.uint 272 | var span Span 273 | 274 | ret := C.aug_span(a.handle, cPath, &cFilename, 275 | &labelStart, &labelEnd, 276 | &valueStart, &valueEnd, 277 | &spanStart, &spanEnd) 278 | 279 | if ret == -1 { 280 | return span, a.error() 281 | } 282 | 283 | span.LabelStart = uint(labelStart) 284 | span.LabelEnd = uint(labelEnd) 285 | span.ValueStart = uint(valueStart) 286 | span.ValueEnd = uint(valueEnd) 287 | span.SpanStart = uint(spanStart) 288 | span.SpanEnd = uint(spanEnd) 289 | span.Filename = C.GoString(cFilename) 290 | C.free(unsafe.Pointer(cFilename)) 291 | 292 | return span, nil 293 | } 294 | 295 | // Insert creates a new sibling for a path by inserting into the 296 | // tree just before the path if before is true or just after it if 297 | // before is false. 298 | // 299 | // The path must match exactly one existing node in the tree, and the 300 | // label must not contain a '/', '*' or end with a bracketed index 301 | // '[N]'. 302 | func (a Augeas) Insert(path, label string, before bool) error { 303 | cPath := C.CString(path) 304 | defer C.free(unsafe.Pointer(cPath)) 305 | 306 | cLabel := C.CString(label) 307 | defer C.free(unsafe.Pointer(cLabel)) 308 | 309 | var cBefore C.int 310 | if before { 311 | cBefore = 1 312 | } else { 313 | cBefore = 0 314 | } 315 | 316 | ret := C.aug_insert(a.handle, cPath, cLabel, cBefore) 317 | if ret == -1 { 318 | return a.error() 319 | } 320 | 321 | return nil 322 | } 323 | 324 | // Remove removes a path and all its children. Returns the number of 325 | // entries removed. All nodes that match the given path, and their 326 | // descendants, are removed. 327 | func (a Augeas) Remove(path string) (num int) { 328 | cPath := C.CString(path) 329 | defer C.free(unsafe.Pointer(cPath)) 330 | 331 | return int(C.aug_rm(a.handle, cPath)) 332 | } 333 | 334 | // Move moves the node src to dst. src must match exactly one node in 335 | // the tree. dst must either match exactly one node in the tree, or 336 | // may not exist yet. If dst exists already, it and all its 337 | // descendants are deleted. If dst does not exist yet, it and all its 338 | // missing ancestors are created. 339 | // 340 | // Note that the node src always becomes the node dst: when you move /a/b 341 | // to /x, the node /a/b is now called /x, no matter whether /x existed 342 | // initially or not. 343 | func (a Augeas) Move(source, destination string) error { 344 | cSource := C.CString(source) 345 | defer C.free(unsafe.Pointer(cSource)) 346 | 347 | cDestination := C.CString(destination) 348 | defer C.free(unsafe.Pointer(cDestination)) 349 | 350 | ret := C.aug_mv(a.handle, cSource, cDestination) 351 | 352 | if ret == -1 { 353 | return a.error() 354 | } 355 | 356 | return nil 357 | } 358 | 359 | // Match returns all paths matching a given path. The returned paths 360 | // are sufficiently qualified to make sure that they match exactly one 361 | // node in the current tree. 362 | // 363 | // Path expressions use a very simple subset of XPath: the path 364 | // consists of a number of segments, separated by '/'; each segment can 365 | // either be a '*', matching any tree node, or a string, optionally 366 | // followed by an index in brackets, matching tree nodes labelled with 367 | // exactly that string. If no index is specified, the expression matches 368 | // all nodes with that label; the index can be a positive number N, which 369 | // matches exactly the Nth node with that label (counting from 1), or the 370 | // special expression 'last()' which matches the last node with the given 371 | // label. All matches are done in fixed positions in the tree, and nothing 372 | // matches more than one path segment. 373 | func (a Augeas) Match(path string) (matches []string, err error) { 374 | cPath := C.CString(path) 375 | defer C.free(unsafe.Pointer(cPath)) 376 | 377 | var cMatches **C.char 378 | cNum := C.aug_match(a.handle, cPath, &cMatches) 379 | num := int(cNum) 380 | 381 | if num < 0 { 382 | return nil, a.error() 383 | } 384 | 385 | q := unsafe.Pointer(cMatches) 386 | for i := 0; i < num; i++ { 387 | p := (**C.char)(q) 388 | matches = append(matches, C.GoString(*p)) 389 | C.free(unsafe.Pointer(*p)) 390 | q = unsafe.Pointer(uintptr(q) + unsafe.Sizeof(uintptr(0))) 391 | } 392 | 393 | C.free(unsafe.Pointer(cMatches)) 394 | 395 | return 396 | } 397 | 398 | // Label gets the label associated with a path. 399 | // 400 | // Returns an error if there are no or too many matching nodes, or if 401 | // the path is not a legal path expression. 402 | func (a Augeas) Label(path string) (value string, err error) { 403 | cPath := C.CString(path) 404 | defer C.free(unsafe.Pointer(cPath)) 405 | 406 | var cValue *C.char 407 | ret := C.aug_label(a.handle, cPath, &cValue) 408 | 409 | if ret == 1 { 410 | return C.GoString(cValue), nil 411 | } else if ret == 0 { 412 | return "", Error{NoMatch, "No matching node", "", ""} 413 | } else if ret < 0 { 414 | return "", a.error() 415 | } 416 | 417 | panic("Unexpected return value") 418 | } 419 | 420 | // Save writes all pending changes to disk. 421 | func (a Augeas) Save() error { 422 | ret := C.aug_save(a.handle) 423 | if ret == -1 { 424 | return a.error() 425 | } 426 | 427 | return nil 428 | } 429 | 430 | // Load loads files into the tree. Which files to load and what lenses 431 | // to use on them is specified under /augeas/load in the tree; each 432 | // entry /augeas/load/NAME specifies a 'transform', by having itself 433 | // exactly one child 'lens' and any number of children labelled 'incl' 434 | // and 'excl'. The value of NAME has no meaning. 435 | // 436 | // The 'lens' grandchild of /augeas/load specifies which lens to use, 437 | // and can either be the fully qualified name of a lens 'Module.lens' 438 | // or '@Module'. The latter form means that the lens from the 439 | // transform marked for autoloading in MODULE should be used. 440 | // 441 | // The 'incl' and 'excl' grandchildren of /augeas/load indicate which 442 | // files to transform. Their value are used as glob patterns. Any file 443 | // that matches at least one 'incl' pattern and no 'excl' pattern is 444 | // transformed. The order of 'incl' and 'excl' entries is irrelevant. 445 | // 446 | // When New is first called, it populates /augeas/load with the 447 | // transforms marked for autoloading in all the modules it finds. 448 | // 449 | // Before loading any files, Load will remove everything underneath 450 | // /augeas/files and /files, regardless of whether any entries have 451 | // been modified or not. 452 | // 453 | // Note that success includes the case where some files could not be 454 | // loaded. Details of such files can be found as '/augeas//error'. 455 | func (a Augeas) Load() error { 456 | ret := C.aug_load(a.handle) 457 | if ret == -1 { 458 | return a.error() 459 | } 460 | 461 | return nil 462 | } 463 | 464 | // Transform adds a transform for a lens/file couple. 465 | func (a Augeas) Transform(lens, file string, excl bool) error { 466 | cLens := C.CString(lens) 467 | defer C.free(unsafe.Pointer(cLens)) 468 | cFile := C.CString(file) 469 | defer C.free(unsafe.Pointer(cFile)) 470 | 471 | var cExcl C.int 472 | if excl { 473 | cExcl = 1 474 | } 475 | ret := C.aug_transform(a.handle, cLens, cFile, cExcl) 476 | if ret == -1 { 477 | return a.error() 478 | } 479 | 480 | return nil 481 | } 482 | 483 | // LoadFile loads a single file to the Augeas tree. 484 | func (a Augeas) LoadFile(file string) error { 485 | cFile := C.CString(file) 486 | defer C.free(unsafe.Pointer(cFile)) 487 | 488 | ret := C.aug_load_file(a.handle, cFile) 489 | if ret == -1 { 490 | return a.error() 491 | } 492 | 493 | return nil 494 | } 495 | 496 | // Close closes the Augeas instance and frees any storage associated 497 | // with it. After closing, the handle is invalid and can not be 498 | // used for any more operations. 499 | func (a Augeas) Close() { 500 | C.aug_close(a.handle) 501 | } 502 | 503 | // Version returns the Augeas version. 504 | func (a Augeas) Version() string { 505 | val, _ := a.Get("/augeas/version") 506 | return val 507 | } 508 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package augeas 2 | 3 | // #cgo pkg-config: libxml-2.0 augeas 4 | // #include 5 | import "C" 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | // ErrorCode is used to differentiate between the different errors 11 | // returned by Augeas. Positive values are from Augeas itself, while 12 | // negative values are specific to these bindings. 13 | type ErrorCode int 14 | 15 | // The possible error codes stored in Error.Code. 16 | const ( 17 | CouldNotInitialize ErrorCode = -2 18 | NoMatch = -1 19 | 20 | // No error 21 | NoError = 0 22 | 23 | // Out of memory 24 | ENOMEM 25 | 26 | // Internal (to augeas itself) error (bug) 27 | EINTERNAL 28 | 29 | // Invalid path expression 30 | EPATHX 31 | 32 | // No match for path expression 33 | ENOMATCH 34 | 35 | // Too many matches for path expression 36 | EMMATCH 37 | 38 | // Syntax error in lens file 39 | ESYNTAX 40 | 41 | // Lens lookup failed 42 | ENOLENS 43 | 44 | // Multiple transforms 45 | EMXFM 46 | 47 | // No span for this node 48 | ENOSPAN 49 | 50 | // Cannot move node into its descendant 51 | EMVDESC 52 | 53 | // Failed to execute command 54 | ECMDRUN 55 | 56 | // Invalid argument in function call 57 | EBADARG 58 | ) 59 | 60 | // Error encapsulates errors returned by Augeas. 61 | type Error struct { 62 | Code ErrorCode 63 | 64 | // Human-readable error message 65 | Message string 66 | 67 | // Human-readable message elaborating the error. For example, when 68 | // the error code is AUG_EPATHX, this will explain how the path 69 | // expression is invalid 70 | MinorMessage string 71 | 72 | // Details about the error. For example, for AUG_EPATHX, indicates 73 | // where in the path expression the error occurred. 74 | Details string 75 | } 76 | 77 | func (err Error) Error() string { 78 | return fmt.Sprintf("Message: %s - Minor message: %s - Details: %s", 79 | err.Message, err.MinorMessage, err.Details) 80 | } 81 | 82 | func (a Augeas) error() error { 83 | code := a.errorCode() 84 | if code == NoError { 85 | return nil 86 | } 87 | 88 | return Error{code, a.errorMessage(), a.errorMinorMessage(), a.errorDetails()} 89 | } 90 | 91 | func (a Augeas) errorCode() ErrorCode { 92 | return ErrorCode(C.aug_error(a.handle)) 93 | } 94 | 95 | func (a Augeas) errorMessage() string { 96 | return C.GoString(C.aug_error_message(a.handle)) 97 | } 98 | 99 | func (a Augeas) errorMinorMessage() string { 100 | return C.GoString(C.aug_error_minor_message(a.handle)) 101 | } 102 | 103 | func (a Augeas) errorDetails() string { 104 | return C.GoString(C.aug_error_details(a.handle)) 105 | } 106 | --------------------------------------------------------------------------------