Skip to content

Commit

Permalink
Initial check in for Icinga2 Provider/Resource (#8306)
Browse files Browse the repository at this point in the history
* Initial checkin for PR request

* Added an argument to provider to allow control over whether or not TLS Certs will skip verification. Controllable via provider or env variable being set

* Initial check-in to use refactored module

* Checkin of very MVP for creating/deleting host test which works and validates basic host creation and deletion

* Check in with support for creating hosts with variables working

* Checking in work to date

* Remove code that causes travis CI to fail while I debug

* Adjust create to accept multivale

* Back on track. Working basic tests. go-icinga2-api needs more test too

* Squashing

* Back on track. Working basic tests. go-icinga2-api needs more test too

* Check in refactored hostgroup support

* Check in refactored check_command, hosts, and hsotgroup with a few test

* Checking in service code

* Add in dependency for icinga2 provider

* Add documentation. Refactor, fix and extend based on feedback from Hashicorp

* Added checking and validation around invalid URL and unavailable server
  • Loading branch information
lrsmith authored and stack72 committed Dec 12, 2016
1 parent 399614c commit 015e96d
Show file tree
Hide file tree
Showing 25 changed files with 2,114 additions and 1 deletion.
108 changes: 108 additions & 0 deletions builtin/providers/icinga2/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package icinga2

import (
"fmt"
"net/url"
"os"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/lrsmith/go-icinga2-api/iapi"
)

func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"api_url": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ICINGA2_API_URL", nil),
Description: descriptions["api_url"],
},
"api_user": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ICINGA2_API_USER", nil),
Description: descriptions["api_user"],
},
"api_password": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ICINGA2_API_PASSWORD", nil),
Description: descriptions["api_password"],
},
"insecure_skip_tls_verify": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
DefaultFunc: EnvBoolDefaultFunc("ICINGA2_INSECURE_SKIP_TLS_VERIFY", false),
Description: descriptions["insecure_skip_tls_verify"],
},
},
ResourcesMap: map[string]*schema.Resource{
"icinga2_host": resourceIcinga2Host(),
"icinga2_hostgroup": resourceIcinga2Hostgroup(),
"icinga2_checkcommand": resourceIcinga2Checkcommand(),
"icinga2_service": resourceIcinga2Service(),
},
ConfigureFunc: configureProvider,
}
}

func configureProvider(d *schema.ResourceData) (interface{}, error) {

config, _ := iapi.New(
d.Get("api_user").(string),
d.Get("api_password").(string),
d.Get("api_url").(string),
d.Get("insecure_skip_tls_verify").(bool),
)

err := validateURL(d.Get("api_url").(string))

if err := config.Connect(); err != nil {
return nil, err
}

return config, err
}

var descriptions map[string]string

func init() {
descriptions = map[string]string{
"api_url": "The address of the Icinga2 server.\n",
"api_user": "The user to authenticate to the Icinga2 Server as.\n",
"api_password": "The password for authenticating to the Icinga2 server.\n",
"insecure_skip_tls_verify": "Disable TLS verify when connecting to Icinga2 Server\n",
}
}

func validateURL(urlString string) error {

//ICINGA2_API_URL=https://127.0.0.1:4665/v1
tokens, err := url.Parse(urlString)
if err != nil {
return err
}

if tokens.Scheme != "https" {
return fmt.Errorf("Error : Requests are only allowed to use the HTTPS protocol so that traffic remains encrypted.")
}

if tokens.Path != "/v1" {
return fmt.Errorf("Error : Invalid API version %s specified. Only v1 is currently supported.", tokens.Path)
}

return nil
}

// EnvBoolDefaultFunc is a helper function that returns
func EnvBoolDefaultFunc(k string, dv interface{}) schema.SchemaDefaultFunc {
return func() (interface{}, error) {
if v := os.Getenv(k); v == "true" {
return true, nil
}

return false, nil
}
}
48 changes: 48 additions & 0 deletions builtin/providers/icinga2/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package icinga2

import (
"os"
"testing"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)

var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider

func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"icinga2": testAccProvider,
}
}

func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}

func TestProviderImpl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}

func testAccPreCheck(t *testing.T) {

v := os.Getenv("ICINGA2_API_URL")
if v == "" {
t.Fatal("ICINGA2_API_URL must be set for acceptance tests")
}

v = os.Getenv("ICINGA2_API_USER")
if v == "" {
t.Fatal("ICINGA2_API_USER must be set for acceptance tests")
}

v = os.Getenv("ICINGA2_API_PASSWORD")
if v == "" {
t.Fatal("ICINGA2_API_PASSWORD must be set for acceptance tests")
}

}
118 changes: 118 additions & 0 deletions builtin/providers/icinga2/resource_icinga2_checkcommand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package icinga2

import (
"fmt"

"github.com/hashicorp/terraform/helper/schema"
"github.com/lrsmith/go-icinga2-api/iapi"
)

func resourceIcinga2Checkcommand() *schema.Resource {

return &schema.Resource{
Create: resourceIcinga2CheckcommandCreate,
Read: resourceIcinga2CheckcommandRead,
Delete: resourceIcinga2CheckcommandDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "Name",
ForceNew: true,
},
"command": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"templates": &schema.Schema{
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"arguments": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
},
}
}

func resourceIcinga2CheckcommandCreate(d *schema.ResourceData, meta interface{}) error {

client := meta.(*iapi.Server)

name := d.Get("name").(string)
command := d.Get("command").(string)

arguments := make(map[string]string)
iterator := d.Get("arguments").(map[string]interface{})

for key, value := range iterator {
arguments[key] = value.(string)
}

checkcommands, err := client.CreateCheckcommand(name, command, arguments)
if err != nil {
return err
}

found := false
for _, checkcommand := range checkcommands {
if checkcommand.Name == name {
d.SetId(name)
found = true
}
}

if !found {
return fmt.Errorf("Failed to create Checkcommand %s : %s", name, err)
}

return nil
}

func resourceIcinga2CheckcommandRead(d *schema.ResourceData, meta interface{}) error {

client := meta.(*iapi.Server)

name := d.Get("name").(string)

checkcommands, err := client.GetCheckcommand(name)
if err != nil {
return err
}

found := false
for _, checkcommand := range checkcommands {
if checkcommand.Name == name {
d.SetId(name)
d.Set("command", checkcommand.Attrs.Command[0])
d.Set("Templates", checkcommand.Attrs.Templates)
d.Set("arguments", checkcommand.Attrs.Arguments)
found = true
}
}

if !found {
return fmt.Errorf("Failed to Read Checkcommand %s : %s", name, err)
}

return nil
}

func resourceIcinga2CheckcommandDelete(d *schema.ResourceData, meta interface{}) error {

client := meta.(*iapi.Server)

name := d.Get("name").(string)

err := client.DeleteCheckcommand(name)
if err != nil {
return fmt.Errorf("Failed to Delete Checkcommand %s : %s", name, err)
}

return nil
}
62 changes: 62 additions & 0 deletions builtin/providers/icinga2/resource_icinga2_checkcommand_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package icinga2

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/lrsmith/go-icinga2-api/iapi"
)

func TestAccCreateCheckcommand(t *testing.T) {

var testAccCreateCheckcommand = fmt.Sprintf(`
resource "icinga2_checkcommand" "checkcommand" {
name = "terraform-test-checkcommand-1"
templates = [ "plugin-check-command" ]
command = "/usr/local/bin/check_command"
arguments = {
"-I" = "$IARG$"
"-J" = "$JARG$" }
}`)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCreateCheckcommand,
Check: resource.ComposeTestCheckFunc(
testAccCheckCheckcommandExists("icinga2_checkcommand.checkcommand"),
testAccCheckResourceState("icinga2_checkcommand.checkcommand", "name", "terraform-test-checkcommand-1"),
testAccCheckResourceState("icinga2_checkcommand.checkcommand", "command", "/usr/local/bin/check_command"),
testAccCheckResourceState("icinga2_checkcommand.checkcommand", "arguments.%", "2"),
testAccCheckResourceState("icinga2_checkcommand.checkcommand", "arguments.-I", "$IARG$"),
testAccCheckResourceState("icinga2_checkcommand.checkcommand", "arguments.-J", "$JARG$"),
),
},
},
})
}

func testAccCheckCheckcommandExists(rn string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[rn]
if !ok {
return fmt.Errorf("Checkcommand resource not found: %s", rn)
}

if resource.Primary.ID == "" {
return fmt.Errorf("Checkcommand resource id not set")
}

client := testAccProvider.Meta().(*iapi.Server)
_, err := client.GetCheckcommand(resource.Primary.ID)
if err != nil {
return fmt.Errorf("Error getting getting Checkcommand: %s", err)
}

return nil
}
}
Loading

0 comments on commit 015e96d

Please sign in to comment.