github.com/go-chef/chef@v0.30.1/node.go (about) 1 package chef 2 3 import ( 4 "errors" 5 "fmt" 6 ) 7 8 var ( 9 ErrPathNotFound = errors.New("attribute path not found") 10 ErrNoPathProvided = errors.New("no path was provided") 11 ) 12 13 type NodeService struct { 14 client *Client 15 } 16 17 // Node represents the native Go version of the deserialized Node type 18 type Node struct { 19 Name string `json:"name"` 20 Environment string `json:"chef_environment,omitempty"` 21 ChefType string `json:"chef_type,omitempty"` 22 AutomaticAttributes map[string]interface{} `json:"automatic,omitempty"` 23 NormalAttributes map[string]interface{} `json:"normal,omitempty"` 24 DefaultAttributes map[string]interface{} `json:"default,omitempty"` 25 OverrideAttributes map[string]interface{} `json:"override,omitempty"` 26 JsonClass string `json:"json_class,omitempty"` 27 //TODO: use the RunList struct for this 28 RunList []string `json:"run_list,omitempty"` 29 PolicyName string `json:"policy_name,omitempty"` 30 PolicyGroup string `json:"policy_group,omitempty"` 31 } 32 33 // GetAttribute will fetch an attribute from the provided path considering the right attribute precedence. 34 func (e *Node) GetAttribute(paths ...string) (interface{}, error) { 35 if len(paths) <= 0 { 36 return nil, ErrNoPathProvided 37 } 38 39 // this follows the Chef attribute precedence: https://docs.chef.io/attribute_precedence/ 40 attrList := []map[string]interface{}{e.AutomaticAttributes, e.OverrideAttributes, e.NormalAttributes, e.DefaultAttributes} 41 42 for _, attrs := range attrList { 43 attr, err := lookupAttribute(attrs, paths...) 44 if err != nil { 45 if errors.Is(err, ErrPathNotFound) { 46 continue 47 } 48 49 return nil, err 50 } 51 52 return attr, nil 53 } 54 55 return nil, ErrPathNotFound 56 } 57 58 // looks up a complete path in the provided attribute map. 59 func lookupAttribute(attrs map[string]interface{}, paths ...string) (interface{}, error) { 60 if len(paths) <= 0 { 61 return nil, ErrPathNotFound 62 } 63 64 currentPath, remainingPaths := paths[0], paths[1:] 65 66 if attr, ok := attrs[currentPath]; ok { 67 if len(remainingPaths) <= 0 { 68 return attr, nil // we are at the last provided part of the path 69 } 70 71 // otherwise keep looking until we reach the end 72 return lookupAttribute(attr.(map[string]interface{}), remainingPaths...) 73 } 74 75 return nil, ErrPathNotFound 76 } 77 78 type NodeResult struct { 79 Uri string `json:"uri"` 80 } 81 82 // NewNode is the Node constructor method 83 func NewNode(name string) (node Node) { 84 node = Node{ 85 Name: name, 86 Environment: "_default", 87 ChefType: "node", 88 JsonClass: "Chef::Node", 89 } 90 return 91 } 92 93 // List lists the nodes in the Chef server. 94 // 95 // Chef API docs: https://docs.chef.io/api_chef_server.html#nodes 96 func (e *NodeService) List() (data map[string]string, err error) { 97 err = e.client.magicRequestDecoder("GET", "nodes", nil, &data) 98 return 99 } 100 101 // Get gets a node from the Chef server. 102 // 103 // Chef API docs: https://docs.chef.io/api_chef_server.html#nodes-name 104 func (e *NodeService) Get(name string) (node Node, err error) { 105 url := fmt.Sprintf("nodes/%s", name) 106 err = e.client.magicRequestDecoder("GET", url, nil, &node) 107 return 108 } 109 110 // Head gets a node from the Chef server. Does not return a json body. 111 // 112 // Chef API docs: https://docs.chef.io/api_chef_server.html#nodes-name 113 func (e *NodeService) Head(name string) (err error) { 114 url := fmt.Sprintf("nodes/%s", name) 115 err = e.client.magicRequestDecoder("HEAD", url, nil, nil) 116 return 117 } 118 119 // Post creates a Node on the chef server 120 // 121 // Chef API docs: https://docs.chef.io/api_chef_server.html#nodes 122 func (e *NodeService) Post(node Node) (data *NodeResult, err error) { 123 body, err := JSONReader(node) 124 if err != nil { 125 return 126 } 127 128 err = e.client.magicRequestDecoder("POST", "nodes", body, &data) 129 return 130 } 131 132 // Put updates a node on the Chef server. 133 // 134 // Chef API docs: https://docs.chef.io/api_chef_server.html#nodes-name 135 // TODO: We might want to change the name. name and data should be separate structures 136 func (e *NodeService) Put(n Node) (node Node, err error) { 137 url := fmt.Sprintf("nodes/%s", n.Name) 138 body, err := JSONReader(n) 139 if err != nil { 140 return 141 } 142 143 err = e.client.magicRequestDecoder("PUT", url, body, &node) 144 return 145 } 146 147 // Delete removes a node on the Chef server 148 // 149 // Chef API docs: https://docs.chef.io/api_chef_server.html#nodes-name 150 func (e *NodeService) Delete(name string) (err error) { 151 err = e.client.magicRequestDecoder("DELETE", "nodes/"+name, nil, nil) 152 return 153 }