-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
Changes from 3 commits
4ba89a1
06084cd
87f5ffd
638f132
e35caae
bd51cbd
e448a56
8ccf7fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package main |
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 | ||
} |
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 | ||
} |
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") | ||
} | ||
} |
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, | ||
}, | ||
}, | ||
} | ||
} |
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) | ||
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 != "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a database is initially created with an explicit I think making the field |
||
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) | ||
} |
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}" | ||
} | ||
` |
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, | ||
}, | ||
}, | ||
} | ||
} |
There was a problem hiding this comment.
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 theWITH 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 bothOptional
andComputed
. This tells Terraform that the user can optionally override it (theOptional
part) but if they don't then the value will be computed based on something the server knows.