rundeck_project resource type.
This commit is contained in:
parent
a42be3e6cf
commit
f0947661fb
|
@ -30,7 +30,7 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
//"rundeck_project": resourceRundeckProject(),
|
||||
"rundeck_project": resourceRundeckProject(),
|
||||
//"rundeck_job": resourceRundeckJob(),
|
||||
//"rundeck_private_key": resourceRundeckPrivateKey(),
|
||||
//"rundeck_public_key": resourceRundeckPublicKey(),
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
package rundeck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
||||
"github.com/apparentlymart/go-rundeck-api/rundeck"
|
||||
)
|
||||
|
||||
var projectConfigAttributes = map[string]string{
|
||||
"project.name": "name",
|
||||
"project.description": "description",
|
||||
"service.FileCopier.default.provider": "default_node_file_copier_plugin",
|
||||
"service.NodeExecutor.default.provider": "default_node_executor_plugin",
|
||||
"project.ssh-authentication": "ssh_authentication_type",
|
||||
"project.ssh-key-storage-path": "ssh_key_storage_path",
|
||||
"project.ssh-keypath": "ssh_key_file_path",
|
||||
}
|
||||
|
||||
func resourceRundeckProject() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: CreateProject,
|
||||
Update: UpdateProject,
|
||||
Delete: DeleteProject,
|
||||
Exists: ProjectExists,
|
||||
Read: ReadProject,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Unique name for the project",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"description": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Description of the project to be shown in the Rundeck UI",
|
||||
Default: "Managed by Terraform",
|
||||
},
|
||||
|
||||
"ui_url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: false,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"resource_model_source": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the resource model plugin to use",
|
||||
},
|
||||
"config": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Required: true,
|
||||
Description: "Configuration parameters for the selected plugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"default_node_file_copier_plugin": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "jsch-scp",
|
||||
},
|
||||
|
||||
"default_node_executor_plugin": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "jsch-ssh",
|
||||
},
|
||||
|
||||
"ssh_authentication_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "privateKey",
|
||||
},
|
||||
|
||||
"ssh_key_storage_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"ssh_key_file_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"extra_config": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
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.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreateProject(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*rundeck.Client)
|
||||
|
||||
// Rundeck's model is a little inconsistent in that we can create
|
||||
// a project via a high-level structure but yet we must update
|
||||
// the project via its raw config properties.
|
||||
// For simplicity's sake we create a bare minimum project here
|
||||
// and then delegate to UpdateProject to fill in the rest of the
|
||||
// configuration via the raw config properties.
|
||||
|
||||
project, err := client.CreateProject(&rundeck.Project{
|
||||
Name: d.Get("name").(string),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(project.Name)
|
||||
d.Set("id", project.Name)
|
||||
|
||||
return UpdateProject(d, meta)
|
||||
}
|
||||
|
||||
func UpdateProject(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*rundeck.Client)
|
||||
|
||||
// In Rundeck, updates are always in terms of the low-level config
|
||||
// properties map, so we need to transform our data structure
|
||||
// into the equivalent raw properties.
|
||||
|
||||
projectName := d.Id()
|
||||
|
||||
updateMap := map[string]string{}
|
||||
|
||||
slashReplacer := strings.NewReplacer("/", ".")
|
||||
if extraConfig := d.Get("extra_config"); extraConfig != nil {
|
||||
for k, v := range extraConfig.(map[string]interface{}) {
|
||||
updateMap[slashReplacer.Replace(k)] = v.(string)
|
||||
}
|
||||
}
|
||||
|
||||
for configKey, attrKey := range projectConfigAttributes {
|
||||
v := d.Get(attrKey).(string)
|
||||
if v != "" {
|
||||
updateMap[configKey] = v
|
||||
}
|
||||
}
|
||||
|
||||
for i, rmsi := range d.Get("resource_model_source").([]interface{}) {
|
||||
rms := rmsi.(map[string]interface{})
|
||||
pluginType := rms["type"].(string)
|
||||
ci := rms["config"].(map[string]interface{})
|
||||
attrKeyPrefix := fmt.Sprintf("resources.source.%v.", i+1)
|
||||
typeKey := attrKeyPrefix + "type"
|
||||
configKeyPrefix := fmt.Sprintf("%vconfig.", attrKeyPrefix)
|
||||
updateMap[typeKey] = pluginType
|
||||
for k, v := range ci {
|
||||
updateMap[configKeyPrefix+k] = v.(string)
|
||||
}
|
||||
}
|
||||
|
||||
err := client.SetProjectConfig(projectName, updateMap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ReadProject(d, meta)
|
||||
}
|
||||
|
||||
func ReadProject(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*rundeck.Client)
|
||||
|
||||
name := d.Id()
|
||||
project, err := client.GetProject(name)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for configKey, attrKey := range projectConfigAttributes {
|
||||
d.Set(projectConfigAttributes[configKey], nil)
|
||||
if v, ok := project.Config[configKey]; ok {
|
||||
d.Set(attrKey, v)
|
||||
// Remove this key so it won't get included in extra_config
|
||||
// later.
|
||||
delete(project.Config, configKey)
|
||||
}
|
||||
}
|
||||
|
||||
resourceSourceMap := map[int]interface{}{}
|
||||
configMaps := map[int]interface{}{}
|
||||
for configKey, v := range project.Config {
|
||||
if strings.HasPrefix(configKey, "resources.source.") {
|
||||
nameParts := strings.Split(configKey, ".")
|
||||
|
||||
if len(nameParts) < 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(nameParts[2])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := resourceSourceMap[index]; !ok {
|
||||
configMap := map[string]interface{}{}
|
||||
configMaps[index] = configMap
|
||||
resourceSourceMap[index] = map[string]interface{}{
|
||||
"config": configMap,
|
||||
}
|
||||
}
|
||||
|
||||
switch nameParts[3] {
|
||||
case "type":
|
||||
if len(nameParts) != 4 {
|
||||
continue
|
||||
}
|
||||
m := resourceSourceMap[index].(map[string]interface{})
|
||||
m["type"] = v
|
||||
case "config":
|
||||
if len(nameParts) != 5 {
|
||||
continue
|
||||
}
|
||||
m := configMaps[index].(map[string]interface{})
|
||||
m[nameParts[4]] = v
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove this key so it won't get included in extra_config
|
||||
// later.
|
||||
delete(project.Config, configKey)
|
||||
}
|
||||
}
|
||||
|
||||
resourceSources := []map[string]interface{}{}
|
||||
resourceSourceIndices := []int{}
|
||||
for k := range resourceSourceMap {
|
||||
resourceSourceIndices = append(resourceSourceIndices, k)
|
||||
}
|
||||
sort.Ints(resourceSourceIndices)
|
||||
|
||||
for _, index := range resourceSourceIndices {
|
||||
resourceSources = append(resourceSources, resourceSourceMap[index].(map[string]interface{}))
|
||||
}
|
||||
d.Set("resource_model_source", resourceSources)
|
||||
|
||||
extraConfig := map[string]string{}
|
||||
dotReplacer := strings.NewReplacer(".", "/")
|
||||
for k, v := range project.Config {
|
||||
extraConfig[dotReplacer.Replace(k)] = v
|
||||
}
|
||||
d.Set("extra_config", extraConfig)
|
||||
|
||||
d.Set("name", project.Name)
|
||||
d.Set("ui_url", project.URL)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProjectExists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
||||
client := meta.(*rundeck.Client)
|
||||
|
||||
name := d.Id()
|
||||
_, err := client.GetProject(name)
|
||||
|
||||
if _, ok := err.(rundeck.NotFoundError); ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DeleteProject(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*rundeck.Client)
|
||||
|
||||
name := d.Id()
|
||||
return client.DeleteProject(name)
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package rundeck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/apparentlymart/go-rundeck-api/rundeck"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccProject_basic(t *testing.T) {
|
||||
var project rundeck.Project
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccProjectCheckDestroy(&project),
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccProjectConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccProjectCheckExists("rundeck_project.main", &project),
|
||||
func(s *terraform.State) error {
|
||||
if expected := "terraform-acc-test-basic"; project.Name != expected {
|
||||
return fmt.Errorf("wrong name; expected %v, got %v", expected, project.Name)
|
||||
}
|
||||
if expected := "baz"; project.Config["foo.bar"] != expected {
|
||||
return fmt.Errorf("wrong foo.bar config; expected %v, got %v", expected, project.Config["foo.bar"])
|
||||
}
|
||||
if expected := "file"; project.Config["resources.source.1.type"] != expected {
|
||||
return fmt.Errorf("wrong resources.source.1.type config; expected %v, got %v", expected, project.Config["resources.source.1.type"])
|
||||
}
|
||||
return nil
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccProjectCheckDestroy(project *rundeck.Project) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*rundeck.Client)
|
||||
_, err := client.GetProject(project.Name)
|
||||
if err == nil {
|
||||
return fmt.Errorf("project still exists")
|
||||
}
|
||||
if _, ok := err.(*rundeck.NotFoundError); !ok {
|
||||
return fmt.Errorf("got something other than NotFoundError (%v) when getting project", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccProjectCheckExists(rn string, project *rundeck.Project) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[rn]
|
||||
if !ok {
|
||||
return fmt.Errorf("resource not found: %s", rn)
|
||||
}
|
||||
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("project id not set")
|
||||
}
|
||||
|
||||
client := testAccProvider.Meta().(*rundeck.Client)
|
||||
gotProject, err := client.GetProject(rs.Primary.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting project: %s", err)
|
||||
}
|
||||
|
||||
*project = *gotProject
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccProjectConfig_basic = `
|
||||
resource "rundeck_project" "main" {
|
||||
name = "terraform-acc-test-basic"
|
||||
description = "Terraform Acceptance Tests Basic Project"
|
||||
|
||||
resource_model_source {
|
||||
type = "file"
|
||||
config = {
|
||||
format = "resourcexml"
|
||||
file = "/tmp/terraform-acc-tests.xml"
|
||||
}
|
||||
}
|
||||
|
||||
extra_config = {
|
||||
"foo/bar" = "baz"
|
||||
}
|
||||
}
|
||||
`
|
Loading…
Reference in New Issue