Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit for postgresql provider #3653

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions builtin/bins/provider-postgresql/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"github.com/hashicorp/terraform/builtin/providers/postgresql"
"github.com/hashicorp/terraform/plugin"
)

func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: postgresql.Provider,
})
}
1 change: 1 addition & 0 deletions builtin/bins/provider-postgresql/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package main
26 changes: 26 additions & 0 deletions builtin/providers/postgresql/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package postgresql

import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)

type Config struct {
Host string
Port int
Username string
Password string
}

// NewClient() return new db conn
func (c *Config) NewClient() (*sql.DB, error) {
connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s", c.Host, c.Port, c.Username, c.Password)

db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, fmt.Errorf("Error connecting to postgresql server: %s", err)
}

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

import (
"fmt"

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

func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"host": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("POSTGRESQL_HOST", nil),
Description: "The postgresql server address",
},
"port": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 5432,
Description: "The postgresql server port",
},
"username": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("POSTGRESQL_USERNAME", nil),
Description: "Username for postgresql server connection",
},
"password": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("POSTGRESQL_PASSWORD", nil),
Description: "Password for postgresql server connection",
},
},

ResourcesMap: map[string]*schema.Resource{
"postgresql_database": resourcePostgresqlDatabase(),
"postgresql_role": resourcePostgresqlRole(),
},

ConfigureFunc: providerConfigure,
}
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
Host: d.Get("host").(string),
Port: d.Get("port").(int),
Username: d.Get("username").(string),
Password: d.Get("password").(string),
}

client, err := config.NewClient()
if err != nil {
return nil, fmt.Errorf("Error initializing Postgresql client: %s", err)
}

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

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{
"postgresql": testAccProvider,
}
}

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

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

func testAccPreCheck(t *testing.T) {
if v := os.Getenv("POSTGRESQL_HOST"); v == "" {
t.Fatal("POSTGRESQL_HOST must be set for acceptance tests")
}
if v := os.Getenv("POSTGRESQL_USERNAME"); v == "" {
t.Fatal("POSTGRESQL_USERNAME must be set for acceptance tests")
}
if v := os.Getenv("POSTGRESQL_PASSWORD"); v == "" {
t.Fatal("POSTGRESQL_PASSWORD must be set for acceptance tests")
}
}
27 changes: 27 additions & 0 deletions builtin/providers/postgresql/resource_postgresql_database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package postgresql

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

func resourcePostgresqlDatabase() *schema.Resource {
return &schema.Resource{
Create: resourcePostgresqlDatabaseCreate,
Read: resourcePostgresqlDatabaseRead,
Update: resourcePostgresqlDatabaseUpdate,
Delete: resourcePostgresqlDatabaseDelete,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"owner": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},
},
}
}
82 changes: 82 additions & 0 deletions builtin/providers/postgresql/resource_postgresql_database_funcs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package postgresql

import (
"database/sql"
"fmt"

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

func resourcePostgresqlDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*sql.DB)
dbName := d.Get("name").(string)
dbOwner := d.Get("owner").(string)
var dbOwnerCfg string
if dbOwner != "" {
dbOwnerCfg = fmt.Sprintf("WITH OWNER=%s", pq.QuoteIdentifier(dbOwner))
} else {
dbOwnerCfg = ""
}

query := fmt.Sprintf("CREATE DATABASE %s %s", pq.QuoteIdentifier(dbName), dbOwnerCfg)
_, err := conn.Query(query)
if err != nil {
return fmt.Errorf("Error creating postgresql database: %s", err)
}

d.SetId(dbName)

return nil
}

func resourcePostgresqlDatabaseDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*sql.DB)
dbName := d.Get("name").(string)

query := fmt.Sprintf("DROP DATABASE %s", pq.QuoteIdentifier(dbName))
_, err := conn.Query(query)
if err != nil {
return err
}

d.SetId("")

return nil
}

func resourcePostgresqlDatabaseRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*sql.DB)
dbName := d.Get("name").(string)

var owner string
err := conn.QueryRow("SELECT pg_catalog.pg_get_userbyid(d.datdba) from pg_database d WHERE datname=$1", dbName).Scan(&owner)
switch {
case err == sql.ErrNoRows:
d.SetId("")
return nil
case err != nil:
return fmt.Errorf("Error reading info about database: %s", err)
default:
d.Set("owner", owner)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do CREATE DATABASE without the WITH OWNER clause above, and then we read here, will this get populated with a default owner?

If so, it's important to mark the owner argument as both Optional and Computed. This tells Terraform that the user can optionally override it (the Optional part) but if they don't then the value will be computed based on something the server knows.

return nil
}
}

func resourcePostgresqlDatabaseUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*sql.DB)
dbName := d.Get("name").(string)

if d.HasChange("owner") {
owner := d.Get("owner").(string)
if owner != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a database is initially created with an explicit owner, and then that argument is removed from the configuration, I think this will cause a problem because Terraform's planning/diffing code will think it needs to set the owner to the empty string but terraform apply (which will run this code) won't actually make that change.

I think making the field Computed is the right answer for this, too. That will mean that when the owner argument is removed from the config Terraform should show the diff as being something like "foo" -> (computed), and then it will populate the state with whatever existing owner is obtained by the call to Read at the end of this function.

query := fmt.Sprintf("ALTER DATABASE %s OWNER TO %s", pq.QuoteIdentifier(dbName), pq.QuoteIdentifier(owner))
_, err := conn.Query(query)
if err != nil {
return fmt.Errorf("Error updating owner for database: %s", err)
}
}
}

return resourcePostgresqlDatabaseRead(d, meta)
}
109 changes: 109 additions & 0 deletions builtin/providers/postgresql/resource_postgresql_database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package postgresql

import (
"database/sql"
"fmt"
"testing"

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

func TestAccPostgresqlDatabase_Basic(t *testing.T) {

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckPostgresqlDatabaseDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccPostgresqlDatabaseConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckPostgresqlDatabaseExists("postgresql_database.mydb", "myrole"),
resource.TestCheckResourceAttr(
"postgresql_database.mydb", "name", "mydb"),
resource.TestCheckResourceAttr(
"postgresql_database.mydb", "owner", "myrole"),
),
},
},
})
}

func testAccCheckPostgresqlDatabaseDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*sql.DB)

for _, rs := range s.RootModule().Resources {
if rs.Type != "postgresql_database" {
continue
}

exists, err := checkDatabaseExists(client, rs.Primary.ID)

if err != nil {
return fmt.Errorf("Error checking db %s", err)
}

if exists {
return fmt.Errorf("Db still exists after destroy")
}
}

return nil
}

func testAccCheckPostgresqlDatabaseExists(n string, owner string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Resource not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

actualOwner := rs.Primary.Attributes["owner"]
if actualOwner != owner {
return fmt.Errorf("Wrong owner for db expected %s got %s", owner, actualOwner)
}

client := testAccProvider.Meta().(*sql.DB)
exists, err := checkDatabaseExists(client, rs.Primary.ID)

if err != nil {
return fmt.Errorf("Error checking db %s", err)
}

if !exists {
return fmt.Errorf("Db not found")
}

return nil
}
}

func checkDatabaseExists(conn *sql.DB, dbName string) (bool, error) {
var _rez int
err := conn.QueryRow("SELECT 1 from pg_database d WHERE datname=$1", dbName).Scan(&_rez)
switch {
case err == sql.ErrNoRows:
return false, nil
case err != nil:
return false, fmt.Errorf("Error reading info about database: %s", err)
default:
return true, nil
}
}

var testAccPostgresqlDatabaseConfig = `
resource "postgresql_role" "myrole" {
name = "myrole"
login = true
}

resource "postgresql_database" "mydb" {
name = "mydb"
owner = "${postgresql_role.myrole.name}"
}
`
28 changes: 28 additions & 0 deletions builtin/providers/postgresql/resource_postgresql_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package postgresql

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

func resourcePostgresqlRole() *schema.Resource {
return &schema.Resource{
Create: resourcePostgresqlRoleCreate,
Read: resourcePostgresqlRoleRead,
Update: resourcePostgresqlRoleUpdate,
Delete: resourcePostgresqlRoleDelete,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"login": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Default: false,
},
},
}
}
Loading