github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/builtin/providers/rundeck/resource_project.go (about) 1 package rundeck 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 "strings" 8 9 "github.com/hashicorp/terraform/helper/schema" 10 11 "github.com/apparentlymart/go-rundeck-api/rundeck" 12 ) 13 14 var projectConfigAttributes = map[string]string{ 15 "project.name": "name", 16 "project.description": "description", 17 "service.FileCopier.default.provider": "default_node_file_copier_plugin", 18 "service.NodeExecutor.default.provider": "default_node_executor_plugin", 19 "project.ssh-authentication": "ssh_authentication_type", 20 "project.ssh-key-storage-path": "ssh_key_storage_path", 21 "project.ssh-keypath": "ssh_key_file_path", 22 } 23 24 func resourceRundeckProject() *schema.Resource { 25 return &schema.Resource{ 26 Create: CreateProject, 27 Update: UpdateProject, 28 Delete: DeleteProject, 29 Exists: ProjectExists, 30 Read: ReadProject, 31 32 Schema: map[string]*schema.Schema{ 33 "name": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 Description: "Unique name for the project", 37 ForceNew: true, 38 }, 39 40 "description": &schema.Schema{ 41 Type: schema.TypeString, 42 Optional: true, 43 Description: "Description of the project to be shown in the Rundeck UI", 44 Default: "Managed by Terraform", 45 }, 46 47 "ui_url": &schema.Schema{ 48 Type: schema.TypeString, 49 Required: false, 50 Computed: true, 51 }, 52 53 "resource_model_source": &schema.Schema{ 54 Type: schema.TypeList, 55 Required: true, 56 Elem: &schema.Resource{ 57 Schema: map[string]*schema.Schema{ 58 "type": &schema.Schema{ 59 Type: schema.TypeString, 60 Required: true, 61 Description: "Name of the resource model plugin to use", 62 }, 63 "config": &schema.Schema{ 64 Type: schema.TypeMap, 65 Required: true, 66 Description: "Configuration parameters for the selected plugin", 67 }, 68 }, 69 }, 70 }, 71 72 "default_node_file_copier_plugin": &schema.Schema{ 73 Type: schema.TypeString, 74 Optional: true, 75 Default: "jsch-scp", 76 }, 77 78 "default_node_executor_plugin": &schema.Schema{ 79 Type: schema.TypeString, 80 Optional: true, 81 Default: "jsch-ssh", 82 }, 83 84 "ssh_authentication_type": &schema.Schema{ 85 Type: schema.TypeString, 86 Optional: true, 87 Default: "privateKey", 88 }, 89 90 "ssh_key_storage_path": &schema.Schema{ 91 Type: schema.TypeString, 92 Optional: true, 93 }, 94 95 "ssh_key_file_path": &schema.Schema{ 96 Type: schema.TypeString, 97 Optional: true, 98 }, 99 100 "extra_config": &schema.Schema{ 101 Type: schema.TypeMap, 102 Optional: true, 103 Description: "Additional raw configuration parameters to include in the project configuration, with dots replaced with slashes in the key names due to limitations in Terraform's config language.", 104 }, 105 }, 106 } 107 } 108 109 func CreateProject(d *schema.ResourceData, meta interface{}) error { 110 client := meta.(*rundeck.Client) 111 112 // Rundeck's model is a little inconsistent in that we can create 113 // a project via a high-level structure but yet we must update 114 // the project via its raw config properties. 115 // For simplicity's sake we create a bare minimum project here 116 // and then delegate to UpdateProject to fill in the rest of the 117 // configuration via the raw config properties. 118 119 project, err := client.CreateProject(&rundeck.Project{ 120 Name: d.Get("name").(string), 121 }) 122 123 if err != nil { 124 return err 125 } 126 127 d.SetId(project.Name) 128 d.Set("id", project.Name) 129 130 return UpdateProject(d, meta) 131 } 132 133 func UpdateProject(d *schema.ResourceData, meta interface{}) error { 134 client := meta.(*rundeck.Client) 135 136 // In Rundeck, updates are always in terms of the low-level config 137 // properties map, so we need to transform our data structure 138 // into the equivalent raw properties. 139 140 projectName := d.Id() 141 142 updateMap := map[string]string{} 143 144 slashReplacer := strings.NewReplacer("/", ".") 145 if extraConfig := d.Get("extra_config"); extraConfig != nil { 146 for k, v := range extraConfig.(map[string]interface{}) { 147 updateMap[slashReplacer.Replace(k)] = v.(string) 148 } 149 } 150 151 for configKey, attrKey := range projectConfigAttributes { 152 v := d.Get(attrKey).(string) 153 if v != "" { 154 updateMap[configKey] = v 155 } 156 } 157 158 for i, rmsi := range d.Get("resource_model_source").([]interface{}) { 159 rms := rmsi.(map[string]interface{}) 160 pluginType := rms["type"].(string) 161 ci := rms["config"].(map[string]interface{}) 162 attrKeyPrefix := fmt.Sprintf("resources.source.%v.", i+1) 163 typeKey := attrKeyPrefix + "type" 164 configKeyPrefix := fmt.Sprintf("%vconfig.", attrKeyPrefix) 165 updateMap[typeKey] = pluginType 166 for k, v := range ci { 167 updateMap[configKeyPrefix+k] = v.(string) 168 } 169 } 170 171 err := client.SetProjectConfig(projectName, updateMap) 172 173 if err != nil { 174 return err 175 } 176 177 return ReadProject(d, meta) 178 } 179 180 func ReadProject(d *schema.ResourceData, meta interface{}) error { 181 client := meta.(*rundeck.Client) 182 183 name := d.Id() 184 project, err := client.GetProject(name) 185 186 if err != nil { 187 return err 188 } 189 190 for configKey, attrKey := range projectConfigAttributes { 191 d.Set(projectConfigAttributes[configKey], nil) 192 if v, ok := project.Config[configKey]; ok { 193 d.Set(attrKey, v) 194 // Remove this key so it won't get included in extra_config 195 // later. 196 delete(project.Config, configKey) 197 } 198 } 199 200 resourceSourceMap := map[int]interface{}{} 201 configMaps := map[int]interface{}{} 202 for configKey, v := range project.Config { 203 if strings.HasPrefix(configKey, "resources.source.") { 204 nameParts := strings.Split(configKey, ".") 205 206 if len(nameParts) < 4 { 207 continue 208 } 209 210 index, err := strconv.Atoi(nameParts[2]) 211 if err != nil { 212 continue 213 } 214 215 if _, ok := resourceSourceMap[index]; !ok { 216 configMap := map[string]interface{}{} 217 configMaps[index] = configMap 218 resourceSourceMap[index] = map[string]interface{}{ 219 "config": configMap, 220 } 221 } 222 223 switch nameParts[3] { 224 case "type": 225 if len(nameParts) != 4 { 226 continue 227 } 228 m := resourceSourceMap[index].(map[string]interface{}) 229 m["type"] = v 230 case "config": 231 if len(nameParts) != 5 { 232 continue 233 } 234 m := configMaps[index].(map[string]interface{}) 235 m[nameParts[4]] = v 236 default: 237 continue 238 } 239 240 // Remove this key so it won't get included in extra_config 241 // later. 242 delete(project.Config, configKey) 243 } 244 } 245 246 resourceSources := []map[string]interface{}{} 247 resourceSourceIndices := []int{} 248 for k := range resourceSourceMap { 249 resourceSourceIndices = append(resourceSourceIndices, k) 250 } 251 sort.Ints(resourceSourceIndices) 252 253 for _, index := range resourceSourceIndices { 254 resourceSources = append(resourceSources, resourceSourceMap[index].(map[string]interface{})) 255 } 256 d.Set("resource_model_source", resourceSources) 257 258 extraConfig := map[string]string{} 259 dotReplacer := strings.NewReplacer(".", "/") 260 for k, v := range project.Config { 261 extraConfig[dotReplacer.Replace(k)] = v 262 } 263 d.Set("extra_config", extraConfig) 264 265 d.Set("name", project.Name) 266 d.Set("ui_url", project.URL) 267 268 return nil 269 } 270 271 func ProjectExists(d *schema.ResourceData, meta interface{}) (bool, error) { 272 client := meta.(*rundeck.Client) 273 274 name := d.Id() 275 _, err := client.GetProject(name) 276 277 if _, ok := err.(rundeck.NotFoundError); ok { 278 return false, nil 279 } 280 281 if err != nil { 282 return false, err 283 } 284 285 return true, nil 286 } 287 288 func DeleteProject(d *schema.ResourceData, meta interface{}) error { 289 client := meta.(*rundeck.Client) 290 291 name := d.Id() 292 return client.DeleteProject(name) 293 }