github.com/ffrizzo/terraform@v0.8.2-0.20161219200057-992e12335f3d/builtin/providers/postgresql/resource_postgresql_role.go (about)

     1  package postgresql
     2  
     3  import (
     4  	"database/sql"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/errwrap"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"github.com/lib/pq"
    13  )
    14  
    15  const (
    16  	roleBypassRLSAttr     = "bypass_row_level_security"
    17  	roleConnLimitAttr     = "connection_limit"
    18  	roleCreateDBAttr      = "create_database"
    19  	roleCreateRoleAttr    = "create_role"
    20  	roleEncryptedPassAttr = "encrypted_password"
    21  	roleInheritAttr       = "inherit"
    22  	roleLoginAttr         = "login"
    23  	roleNameAttr          = "name"
    24  	rolePasswordAttr      = "password"
    25  	roleReplicationAttr   = "replication"
    26  	roleSuperuserAttr     = "superuser"
    27  	roleValidUntilAttr    = "valid_until"
    28  
    29  	// Deprecated options
    30  	roleDepEncryptedAttr = "encrypted"
    31  )
    32  
    33  func resourcePostgreSQLRole() *schema.Resource {
    34  	return &schema.Resource{
    35  		Create: resourcePostgreSQLRoleCreate,
    36  		Read:   resourcePostgreSQLRoleRead,
    37  		Update: resourcePostgreSQLRoleUpdate,
    38  		Delete: resourcePostgreSQLRoleDelete,
    39  		Importer: &schema.ResourceImporter{
    40  			State: schema.ImportStatePassthrough,
    41  		},
    42  
    43  		Schema: map[string]*schema.Schema{
    44  			roleNameAttr: {
    45  				Type:        schema.TypeString,
    46  				Required:    true,
    47  				Description: "The name of the role",
    48  			},
    49  			rolePasswordAttr: {
    50  				Type:        schema.TypeString,
    51  				Optional:    true,
    52  				Computed:    true,
    53  				Sensitive:   true,
    54  				DefaultFunc: schema.EnvDefaultFunc("PGPASSWORD", nil),
    55  				Description: "Sets the role's password",
    56  			},
    57  			roleDepEncryptedAttr: {
    58  				Type:       schema.TypeString,
    59  				Optional:   true,
    60  				Deprecated: fmt.Sprintf("Rename PostgreSQL role resource attribute %q to %q", roleDepEncryptedAttr, roleEncryptedPassAttr),
    61  			},
    62  			roleEncryptedPassAttr: {
    63  				Type:        schema.TypeBool,
    64  				Optional:    true,
    65  				Default:     true,
    66  				Description: "Control whether the password is stored encrypted in the system catalogs",
    67  			},
    68  			roleValidUntilAttr: {
    69  				Type:        schema.TypeString,
    70  				Optional:    true,
    71  				Default:     "infinity",
    72  				Description: "Sets a date and time after which the role's password is no longer valid",
    73  			},
    74  			roleConnLimitAttr: {
    75  				Type:         schema.TypeInt,
    76  				Optional:     true,
    77  				Computed:     true,
    78  				Description:  "How many concurrent connections can be made with this role",
    79  				ValidateFunc: validateConnLimit,
    80  			},
    81  			roleSuperuserAttr: {
    82  				Type:        schema.TypeBool,
    83  				Optional:    true,
    84  				Default:     false,
    85  				Description: `Determine whether the new role is a "superuser"`,
    86  			},
    87  			roleCreateDBAttr: {
    88  				Type:        schema.TypeBool,
    89  				Optional:    true,
    90  				Default:     false,
    91  				Description: "Define a role's ability to create databases",
    92  			},
    93  			roleCreateRoleAttr: {
    94  				Type:        schema.TypeBool,
    95  				Optional:    true,
    96  				Default:     false,
    97  				Description: "Determine whether this role will be permitted to create new roles",
    98  			},
    99  			roleInheritAttr: {
   100  				Type:        schema.TypeBool,
   101  				Optional:    true,
   102  				Default:     true,
   103  				Description: `Determine whether a role "inherits" the privileges of roles it is a member of`,
   104  			},
   105  			roleLoginAttr: {
   106  				Type:        schema.TypeBool,
   107  				Optional:    true,
   108  				Default:     false,
   109  				Description: "Determine whether a role is allowed to log in",
   110  			},
   111  			roleReplicationAttr: {
   112  				Type:        schema.TypeBool,
   113  				Optional:    true,
   114  				Default:     false,
   115  				Description: "Determine whether a role is allowed to initiate streaming replication or put the system in and out of backup mode",
   116  			},
   117  			roleBypassRLSAttr: {
   118  				Type:        schema.TypeBool,
   119  				Optional:    true,
   120  				Default:     false,
   121  				Description: "Determine whether a role bypasses every row-level security (RLS) policy",
   122  			},
   123  		},
   124  	}
   125  }
   126  
   127  func resourcePostgreSQLRoleCreate(d *schema.ResourceData, meta interface{}) error {
   128  	c := meta.(*Client)
   129  	conn, err := c.Connect()
   130  	if err != nil {
   131  		return errwrap.Wrapf("Error connecting to PostgreSQL: {{err}}", err)
   132  	}
   133  	defer conn.Close()
   134  
   135  	stringOpts := []struct {
   136  		hclKey string
   137  		sqlKey string
   138  	}{
   139  		{rolePasswordAttr, "PASSWORD"},
   140  		{roleValidUntilAttr, "VALID UNTIL"},
   141  	}
   142  	intOpts := []struct {
   143  		hclKey string
   144  		sqlKey string
   145  	}{
   146  		{roleConnLimitAttr, "CONNECTION LIMIT"},
   147  	}
   148  	boolOpts := []struct {
   149  		hclKey        string
   150  		sqlKeyEnable  string
   151  		sqlKeyDisable string
   152  	}{
   153  		{roleSuperuserAttr, "CREATEDB", "NOCREATEDB"},
   154  		{roleCreateRoleAttr, "CREATEROLE", "NOCREATEROLE"},
   155  		{roleInheritAttr, "INHERIT", "NOINHERIT"},
   156  		{roleLoginAttr, "LOGIN", "NOLOGIN"},
   157  		{roleReplicationAttr, "REPLICATION", "NOREPLICATION"},
   158  		{roleBypassRLSAttr, "BYPASSRLS", "NOBYPASSRLS"},
   159  
   160  		// roleEncryptedPassAttr is used only when rolePasswordAttr is set.
   161  		// {roleEncryptedPassAttr, "ENCRYPTED", "UNENCRYPTED"},
   162  	}
   163  
   164  	createOpts := make([]string, 0, len(stringOpts)+len(intOpts)+len(boolOpts))
   165  
   166  	for _, opt := range stringOpts {
   167  		v, ok := d.GetOk(opt.hclKey)
   168  		if !ok {
   169  			continue
   170  		}
   171  
   172  		val := v.(string)
   173  		if val != "" {
   174  			switch {
   175  			case opt.hclKey == rolePasswordAttr:
   176  				if strings.ToUpper(v.(string)) == "NULL" {
   177  					createOpts = append(createOpts, "PASSWORD NULL")
   178  				} else {
   179  					if d.Get(roleEncryptedPassAttr).(bool) {
   180  						createOpts = append(createOpts, "ENCRYPTED")
   181  					} else {
   182  						createOpts = append(createOpts, "UNENCRYPTED")
   183  					}
   184  					createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, pqQuoteLiteral(val)))
   185  				}
   186  			case opt.hclKey == roleValidUntilAttr:
   187  				switch {
   188  				case v.(string) == "", strings.ToLower(v.(string)) == "infinity":
   189  					createOpts = append(createOpts, fmt.Sprintf("%s '%s'", opt.sqlKey, "infinity"))
   190  				default:
   191  					createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
   192  				}
   193  			default:
   194  				createOpts = append(createOpts, fmt.Sprintf("%s %s", opt.sqlKey, pq.QuoteIdentifier(val)))
   195  			}
   196  		}
   197  	}
   198  
   199  	for _, opt := range intOpts {
   200  		val := d.Get(opt.hclKey).(int)
   201  		createOpts = append(createOpts, fmt.Sprintf("%s %d", opt.sqlKey, val))
   202  	}
   203  
   204  	for _, opt := range boolOpts {
   205  		if opt.hclKey == roleEncryptedPassAttr {
   206  			// This attribute is handled above in the stringOpts
   207  			// loop.
   208  			continue
   209  		}
   210  		val := d.Get(opt.hclKey).(bool)
   211  		valStr := opt.sqlKeyDisable
   212  		if val {
   213  			valStr = opt.sqlKeyEnable
   214  		}
   215  		createOpts = append(createOpts, valStr)
   216  	}
   217  
   218  	roleName := d.Get(roleNameAttr).(string)
   219  	createStr := strings.Join(createOpts, " ")
   220  	if len(createOpts) > 0 {
   221  		createStr = " WITH " + createStr
   222  	}
   223  
   224  	query := fmt.Sprintf("CREATE ROLE %s%s", pq.QuoteIdentifier(roleName), createStr)
   225  	_, err = conn.Query(query)
   226  	if err != nil {
   227  		return errwrap.Wrapf(fmt.Sprintf("Error creating role %s: {{err}}", roleName), err)
   228  	}
   229  
   230  	d.SetId(roleName)
   231  
   232  	return resourcePostgreSQLRoleRead(d, meta)
   233  }
   234  
   235  func resourcePostgreSQLRoleDelete(d *schema.ResourceData, meta interface{}) error {
   236  	client := meta.(*Client)
   237  	conn, err := client.Connect()
   238  	if err != nil {
   239  		return err
   240  	}
   241  	defer conn.Close()
   242  
   243  	roleName := d.Get(roleNameAttr).(string)
   244  	query := fmt.Sprintf("DROP ROLE %s", pq.QuoteIdentifier(roleName))
   245  	_, err = conn.Query(query)
   246  	if err != nil {
   247  		return errwrap.Wrapf("Error deleting role: {{err}}", err)
   248  	}
   249  
   250  	d.SetId("")
   251  
   252  	return nil
   253  }
   254  
   255  func resourcePostgreSQLRoleRead(d *schema.ResourceData, meta interface{}) error {
   256  	c := meta.(*Client)
   257  	conn, err := c.Connect()
   258  	if err != nil {
   259  		return err
   260  	}
   261  	defer conn.Close()
   262  
   263  	roleId := d.Id()
   264  	var roleSuperuser, roleInherit, roleCreateRole, roleCreateDB, roleCanLogin, roleReplication, roleBypassRLS bool
   265  	var roleConnLimit int
   266  	var roleName, roleValidUntil string
   267  	err = conn.QueryRow("SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolconnlimit, COALESCE(rolvaliduntil::TEXT, 'infinity'), rolbypassrls FROM pg_catalog.pg_roles WHERE rolname=$1", roleId).Scan(&roleName, &roleSuperuser, &roleInherit, &roleCreateRole, &roleCreateDB, &roleCanLogin, &roleReplication, &roleConnLimit, &roleValidUntil, &roleBypassRLS)
   268  	switch {
   269  	case err == sql.ErrNoRows:
   270  		log.Printf("[WARN] PostgreSQL role (%s) not found", roleId)
   271  		d.SetId("")
   272  		return nil
   273  	case err != nil:
   274  		return errwrap.Wrapf("Error reading role: {{err}}", err)
   275  	default:
   276  		d.Set(roleNameAttr, roleName)
   277  		d.Set(roleBypassRLSAttr, roleBypassRLS)
   278  		d.Set(roleConnLimitAttr, roleConnLimit)
   279  		d.Set(roleCreateDBAttr, roleCreateDB)
   280  		d.Set(roleCreateRoleAttr, roleCreateRole)
   281  		d.Set(roleEncryptedPassAttr, true)
   282  		d.Set(roleInheritAttr, roleInherit)
   283  		d.Set(roleLoginAttr, roleCanLogin)
   284  		d.Set(roleReplicationAttr, roleReplication)
   285  		d.Set(roleSuperuserAttr, roleSuperuser)
   286  		d.Set(roleValidUntilAttr, roleValidUntil)
   287  		d.SetId(roleName)
   288  	}
   289  
   290  	if !roleSuperuser {
   291  		// Return early if not superuser user
   292  		return nil
   293  	}
   294  
   295  	var rolePassword string
   296  	err = conn.QueryRow("SELECT COALESCE(passwd, '') FROM pg_catalog.pg_shadow AS s WHERE s.usename = $1", roleId).Scan(&rolePassword)
   297  	switch {
   298  	case err == sql.ErrNoRows:
   299  		return fmt.Errorf("PostgreSQL role (%s) not found in shadow database: {{err}}", roleId)
   300  	case err != nil:
   301  		return errwrap.Wrapf("Error reading role: {{err}}", err)
   302  	default:
   303  		d.Set(rolePasswordAttr, rolePassword)
   304  		return nil
   305  	}
   306  }
   307  
   308  func resourcePostgreSQLRoleUpdate(d *schema.ResourceData, meta interface{}) error {
   309  	c := meta.(*Client)
   310  	conn, err := c.Connect()
   311  	if err != nil {
   312  		return err
   313  	}
   314  	defer conn.Close()
   315  
   316  	if err := setRoleName(conn, d); err != nil {
   317  		return err
   318  	}
   319  
   320  	if err := setRoleBypassRLS(conn, d); err != nil {
   321  		return err
   322  	}
   323  
   324  	if err := setRoleConnLimit(conn, d); err != nil {
   325  		return err
   326  	}
   327  
   328  	if err := setRoleCreateDB(conn, d); err != nil {
   329  		return err
   330  	}
   331  
   332  	if err := setRoleCreateRole(conn, d); err != nil {
   333  		return err
   334  	}
   335  
   336  	if err := setRoleInherit(conn, d); err != nil {
   337  		return err
   338  	}
   339  
   340  	if err := setRoleLogin(conn, d); err != nil {
   341  		return err
   342  	}
   343  
   344  	if err := setRoleReplication(conn, d); err != nil {
   345  		return err
   346  	}
   347  
   348  	if err := setRoleSuperuser(conn, d); err != nil {
   349  		return err
   350  	}
   351  
   352  	if err := setRoleValidUntil(conn, d); err != nil {
   353  		return err
   354  	}
   355  
   356  	return resourcePostgreSQLRoleRead(d, meta)
   357  }
   358  
   359  func setRoleName(conn *sql.DB, d *schema.ResourceData) error {
   360  	if !d.HasChange(roleNameAttr) {
   361  		return nil
   362  	}
   363  
   364  	oraw, nraw := d.GetChange(roleNameAttr)
   365  	o := oraw.(string)
   366  	n := nraw.(string)
   367  	if n == "" {
   368  		return errors.New("Error setting role name to an empty string")
   369  	}
   370  
   371  	query := fmt.Sprintf("ALTER ROLE %s RENAME TO %s", pq.QuoteIdentifier(o), pq.QuoteIdentifier(n))
   372  	if _, err := conn.Query(query); err != nil {
   373  		return errwrap.Wrapf("Error updating role NAME: {{err}}", err)
   374  	}
   375  	d.SetId(n)
   376  
   377  	return nil
   378  }
   379  
   380  func setRoleBypassRLS(conn *sql.DB, d *schema.ResourceData) error {
   381  	if !d.HasChange(roleBypassRLSAttr) {
   382  		return nil
   383  	}
   384  
   385  	bypassRLS := d.Get(roleBypassRLSAttr).(bool)
   386  	tok := "NOBYPASSRLS"
   387  	if bypassRLS {
   388  		tok = "BYPASSRLS"
   389  	}
   390  	roleName := d.Get(roleNameAttr).(string)
   391  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   392  	if _, err := conn.Query(query); err != nil {
   393  		return errwrap.Wrapf("Error updating role BYPASSRLS: {{err}}", err)
   394  	}
   395  
   396  	return nil
   397  }
   398  
   399  func setRoleConnLimit(conn *sql.DB, d *schema.ResourceData) error {
   400  	if !d.HasChange(roleConnLimitAttr) {
   401  		return nil
   402  	}
   403  
   404  	connLimit := d.Get(roleConnLimitAttr).(int)
   405  	roleName := d.Get(roleNameAttr).(string)
   406  	query := fmt.Sprintf("ALTER ROLE %s CONNECTION LIMIT = %d", pq.QuoteIdentifier(roleName), connLimit)
   407  	if _, err := conn.Query(query); err != nil {
   408  		return errwrap.Wrapf("Error updating role CONNECTION LIMIT: {{err}}", err)
   409  	}
   410  
   411  	return nil
   412  }
   413  
   414  func setRoleCreateDB(conn *sql.DB, d *schema.ResourceData) error {
   415  	if !d.HasChange(roleCreateDBAttr) {
   416  		return nil
   417  	}
   418  
   419  	createDB := d.Get(roleCreateDBAttr).(bool)
   420  	tok := "NOCREATEDB"
   421  	if createDB {
   422  		tok = "CREATEDB"
   423  	}
   424  	roleName := d.Get(roleNameAttr).(string)
   425  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   426  	if _, err := conn.Query(query); err != nil {
   427  		return errwrap.Wrapf("Error updating role CREATEDB: {{err}}", err)
   428  	}
   429  
   430  	return nil
   431  }
   432  
   433  func setRoleCreateRole(conn *sql.DB, d *schema.ResourceData) error {
   434  	if !d.HasChange(roleCreateRoleAttr) {
   435  		return nil
   436  	}
   437  
   438  	createRole := d.Get(roleCreateRoleAttr).(bool)
   439  	tok := "NOCREATEROLE"
   440  	if createRole {
   441  		tok = "CREATEROLE"
   442  	}
   443  	roleName := d.Get(roleNameAttr).(string)
   444  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   445  	if _, err := conn.Query(query); err != nil {
   446  		return errwrap.Wrapf("Error updating role CREATEROLE: {{err}}", err)
   447  	}
   448  
   449  	return nil
   450  }
   451  
   452  func setRoleInherit(conn *sql.DB, d *schema.ResourceData) error {
   453  	if !d.HasChange(roleInheritAttr) {
   454  		return nil
   455  	}
   456  
   457  	inherit := d.Get(roleInheritAttr).(bool)
   458  	tok := "NOINHERIT"
   459  	if inherit {
   460  		tok = "INHERIT"
   461  	}
   462  	roleName := d.Get(roleNameAttr).(string)
   463  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   464  	if _, err := conn.Query(query); err != nil {
   465  		return errwrap.Wrapf("Error updating role INHERIT: {{err}}", err)
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  func setRoleLogin(conn *sql.DB, d *schema.ResourceData) error {
   472  	if !d.HasChange(roleLoginAttr) {
   473  		return nil
   474  	}
   475  
   476  	login := d.Get(roleLoginAttr).(bool)
   477  	tok := "NOLOGIN"
   478  	if login {
   479  		tok = "LOGIN"
   480  	}
   481  	roleName := d.Get(roleNameAttr).(string)
   482  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   483  	if _, err := conn.Query(query); err != nil {
   484  		return errwrap.Wrapf("Error updating role LOGIN: {{err}}", err)
   485  	}
   486  
   487  	return nil
   488  }
   489  
   490  func setRoleReplication(conn *sql.DB, d *schema.ResourceData) error {
   491  	if !d.HasChange(roleReplicationAttr) {
   492  		return nil
   493  	}
   494  
   495  	replication := d.Get(roleReplicationAttr).(bool)
   496  	tok := "NOREPLICATION"
   497  	if replication {
   498  		tok = "REPLICATION"
   499  	}
   500  	roleName := d.Get(roleNameAttr).(string)
   501  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   502  	if _, err := conn.Query(query); err != nil {
   503  		return errwrap.Wrapf("Error updating role REPLICATION: {{err}}", err)
   504  	}
   505  
   506  	return nil
   507  }
   508  
   509  func setRoleSuperuser(conn *sql.DB, d *schema.ResourceData) error {
   510  	if !d.HasChange(roleSuperuserAttr) {
   511  		return nil
   512  	}
   513  
   514  	superuser := d.Get(roleSuperuserAttr).(bool)
   515  	tok := "NOSUPERUSER"
   516  	if superuser {
   517  		tok = "SUPERUSER"
   518  	}
   519  	roleName := d.Get(roleNameAttr).(string)
   520  	query := fmt.Sprintf("ALTER ROLE %s WITH %s", pq.QuoteIdentifier(roleName), tok)
   521  	if _, err := conn.Query(query); err != nil {
   522  		return errwrap.Wrapf("Error updating role SUPERUSER: {{err}}", err)
   523  	}
   524  
   525  	return nil
   526  }
   527  
   528  func setRoleValidUntil(conn *sql.DB, d *schema.ResourceData) error {
   529  	if !d.HasChange(roleValidUntilAttr) {
   530  		return nil
   531  	}
   532  
   533  	validUntil := d.Get(roleValidUntilAttr).(string)
   534  	if validUntil == "" {
   535  		return nil
   536  	} else if strings.ToLower(validUntil) == "infinity" {
   537  		validUntil = "infinity"
   538  	}
   539  
   540  	roleName := d.Get(roleNameAttr).(string)
   541  	query := fmt.Sprintf("ALTER ROLE %s VALID UNTIL '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(validUntil))
   542  
   543  	if _, err := conn.Query(query); err != nil {
   544  		return errwrap.Wrapf("Error updating role VALID UNTIL: {{err}}", err)
   545  	}
   546  
   547  	return nil
   548  }