Skip to content

Commit b202448

Browse files
stanleyzjbuntingcyrilgdn
authored
feat: Add postgresql_security_label resource (#482)
This PR is based on the great job @jbunting did in #365, which fixed the quoted identifier for object name and provider in the `pg_seclabels` table. Additional changes: 1. Update the doc to explain the import process --------- Co-authored-by: Jared Bunting <jared.bunting@trillianthealth.com> Co-authored-by: Cyril Gaudin <cyril.gaudin@gmail.com>
1 parent 31fee05 commit b202448

12 files changed

+560
-1
lines changed

postgresql/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
featureFunction
4646
featureServer
4747
featureCreateRoleSelfGrant
48+
featureSecurityLabel
4849
)
4950

5051
var (
@@ -120,6 +121,7 @@ var (
120121
// New privileges rules in version 16
121122
// https://www.postgresql.org/docs/16/release-16.html#RELEASE-16-PRIVILEGES
122123
featureCreateRoleSelfGrant: semver.MustParseRange(">=16.0.0"),
124+
featureSecurityLabel: semver.MustParseRange(">=11.0.0"),
123125
}
124126
)
125127

postgresql/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ func Provider() *schema.Provider {
207207
"postgresql_function": resourcePostgreSQLFunction(),
208208
"postgresql_server": resourcePostgreSQLServer(),
209209
"postgresql_user_mapping": resourcePostgreSQLUserMapping(),
210+
"postgresql_security_label": resourcePostgreSQLSecurityLabel(),
210211
},
211212

212213
DataSourcesMap: map[string]*schema.Resource{
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package postgresql
2+
3+
import (
4+
"bytes"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
"regexp"
9+
"strings"
10+
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/lib/pq"
13+
)
14+
15+
const (
16+
securityLabelObjectNameAttr = "object_name"
17+
securityLabelObjectTypeAttr = "object_type"
18+
securityLabelProviderAttr = "label_provider"
19+
securityLabelLabelAttr = "label"
20+
)
21+
22+
func resourcePostgreSQLSecurityLabel() *schema.Resource {
23+
return &schema.Resource{
24+
Create: PGResourceFunc(resourcePostgreSQLSecurityLabelCreate),
25+
Read: PGResourceFunc(resourcePostgreSQLSecurityLabelRead),
26+
Update: PGResourceFunc(resourcePostgreSQLSecurityLabelUpdate),
27+
Delete: PGResourceFunc(resourcePostgreSQLSecurityLabelDelete),
28+
Importer: &schema.ResourceImporter{
29+
StateContext: schema.ImportStatePassthroughContext,
30+
},
31+
32+
Schema: map[string]*schema.Schema{
33+
securityLabelObjectNameAttr: {
34+
Type: schema.TypeString,
35+
Required: true,
36+
ForceNew: true,
37+
Description: "The name of the existing object to apply the security label to",
38+
},
39+
securityLabelObjectTypeAttr: {
40+
Type: schema.TypeString,
41+
Required: true,
42+
ForceNew: true,
43+
Description: "The type of the existing object to apply the security label to",
44+
},
45+
securityLabelProviderAttr: {
46+
Type: schema.TypeString,
47+
Required: true,
48+
ForceNew: true,
49+
Description: "The provider to apply the security label for",
50+
},
51+
securityLabelLabelAttr: {
52+
Type: schema.TypeString,
53+
Required: true,
54+
ForceNew: false,
55+
Description: "The label to be applied",
56+
},
57+
},
58+
}
59+
}
60+
61+
func resourcePostgreSQLSecurityLabelCreate(db *DBConnection, d *schema.ResourceData) error {
62+
if !db.featureSupported(featureSecurityLabel) {
63+
return fmt.Errorf(
64+
"security Label is not supported for this Postgres version (%s)",
65+
db.version,
66+
)
67+
}
68+
log.Printf("[DEBUG] PostgreSQL security label Create")
69+
label := d.Get(securityLabelLabelAttr).(string)
70+
if err := resourcePostgreSQLSecurityLabelUpdateImpl(db, d, pq.QuoteLiteral(label)); err != nil {
71+
return err
72+
}
73+
74+
d.SetId(generateSecurityLabelID(d))
75+
76+
return resourcePostgreSQLSecurityLabelReadImpl(db, d)
77+
}
78+
79+
func resourcePostgreSQLSecurityLabelUpdateImpl(db *DBConnection, d *schema.ResourceData, label string) error {
80+
b := bytes.NewBufferString("SECURITY LABEL ")
81+
82+
objectType := d.Get(securityLabelObjectTypeAttr).(string)
83+
objectName := d.Get(securityLabelObjectNameAttr).(string)
84+
provider := d.Get(securityLabelProviderAttr).(string)
85+
fmt.Fprint(b, " FOR ", pq.QuoteIdentifier(provider))
86+
fmt.Fprint(b, " ON ", objectType, pq.QuoteIdentifier(objectName))
87+
fmt.Fprint(b, " IS ", label)
88+
89+
if _, err := db.Exec(b.String()); err != nil {
90+
log.Printf("[WARN] PostgreSQL security label Create failed %s", err)
91+
return fmt.Errorf("could not create security label: %w", err)
92+
}
93+
return nil
94+
}
95+
96+
func resourcePostgreSQLSecurityLabelRead(db *DBConnection, d *schema.ResourceData) error {
97+
if !db.featureSupported(featureSecurityLabel) {
98+
return fmt.Errorf(
99+
"Security Label is not supported for this Postgres version (%s)",
100+
db.version,
101+
)
102+
}
103+
log.Printf("[DEBUG] PostgreSQL security label Read")
104+
105+
return resourcePostgreSQLSecurityLabelReadImpl(db, d)
106+
}
107+
108+
func resourcePostgreSQLSecurityLabelReadImpl(db *DBConnection, d *schema.ResourceData) error {
109+
objectType := d.Get(securityLabelObjectTypeAttr).(string)
110+
objectName := d.Get(securityLabelObjectNameAttr).(string)
111+
provider := d.Get(securityLabelProviderAttr).(string)
112+
113+
txn, err := startTransaction(db.client, "")
114+
if err != nil {
115+
return err
116+
}
117+
defer deferredRollback(txn)
118+
119+
query := "SELECT objtype, provider, objname, label FROM pg_seclabels WHERE objtype = $1 and objname = $2 and provider = $3"
120+
row := db.QueryRow(query, objectType, quoteIdentifier(objectName), quoteIdentifier(provider))
121+
122+
var label, newObjectName, newProvider string
123+
err = row.Scan(&objectType, &newProvider, &newObjectName, &label)
124+
switch {
125+
case err == sql.ErrNoRows:
126+
log.Printf("[WARN] PostgreSQL security label for (%s '%s') with provider %s not found", objectType, objectName, provider)
127+
d.SetId("")
128+
return nil
129+
case err != nil:
130+
return fmt.Errorf("Error reading security label: %w", err)
131+
}
132+
133+
if quoteIdentifier(objectName) != newObjectName || quoteIdentifier(provider) != newProvider {
134+
// In reality, this should never happen, but if it does, we want to make sure that the state is in sync with the remote system
135+
// This will trigger a TF error saying that the provider has a bug if it ever happens
136+
objectName = newObjectName
137+
provider = newProvider
138+
}
139+
d.Set(securityLabelObjectTypeAttr, objectType)
140+
d.Set(securityLabelObjectNameAttr, objectName)
141+
d.Set(securityLabelProviderAttr, provider)
142+
d.Set(securityLabelLabelAttr, label)
143+
d.SetId(generateSecurityLabelID(d))
144+
145+
return nil
146+
}
147+
148+
func resourcePostgreSQLSecurityLabelDelete(db *DBConnection, d *schema.ResourceData) error {
149+
if !db.featureSupported(featureSecurityLabel) {
150+
return fmt.Errorf(
151+
"Security Label is not supported for this Postgres version (%s)",
152+
db.version,
153+
)
154+
}
155+
log.Printf("[DEBUG] PostgreSQL security label Delete")
156+
157+
if err := resourcePostgreSQLSecurityLabelUpdateImpl(db, d, "NULL"); err != nil {
158+
return err
159+
}
160+
161+
d.SetId("")
162+
163+
return nil
164+
}
165+
166+
func resourcePostgreSQLSecurityLabelUpdate(db *DBConnection, d *schema.ResourceData) error {
167+
if !db.featureSupported(featureServer) {
168+
return fmt.Errorf(
169+
"Security Label is not supported for this Postgres version (%s)",
170+
db.version,
171+
)
172+
}
173+
log.Printf("[DEBUG] PostgreSQL security label Update")
174+
175+
label := d.Get(securityLabelLabelAttr).(string)
176+
if err := resourcePostgreSQLSecurityLabelUpdateImpl(db, d, pq.QuoteLiteral(label)); err != nil {
177+
return err
178+
}
179+
180+
return resourcePostgreSQLSecurityLabelReadImpl(db, d)
181+
}
182+
183+
func generateSecurityLabelID(d *schema.ResourceData) string {
184+
return strings.Join([]string{
185+
d.Get(securityLabelProviderAttr).(string),
186+
d.Get(securityLabelObjectTypeAttr).(string),
187+
d.Get(securityLabelObjectNameAttr).(string),
188+
}, ".")
189+
}
190+
191+
func quoteIdentifier(s string) string {
192+
var result = s
193+
re := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
194+
if !re.MatchString(s) || s != strings.ToLower(s) {
195+
result = pq.QuoteIdentifier(s)
196+
}
197+
return result
198+
}

0 commit comments

Comments
 (0)