blob: 8b802860b987b9901d0e99951c61f71adc5bf3d6 [file] [log] [blame]
Adrià Vilanova Martínez25e12112021-08-25 13:48:06 +02001package db
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7
8 "github.com/Masterminds/semver/v3"
9
10 pb "gomodules.avm99963.com/twpt-server/api_proto"
11)
12
13func isValidSemVersion(v string) bool {
14 _, err := semver.NewVersion(v)
15 return err == nil
16}
17
18func fillRawKillSwitch(db *sql.DB, ctx context.Context, k *pb.KillSwitch, featureId int32) error {
19 f, err := GetFeatureById(db, ctx, featureId)
20 if err != nil {
21 return fmt.Errorf("fillRawKillSwitch: %v", err)
22 }
23 if f == nil {
24 return fmt.Errorf("fillRawKillSwitch: linked feature doesn't exist.")
25 }
26 k.Feature = f
27
28 rows, err := db.QueryContext(ctx, "SELECT browser FROM KillSwitch2Browser WHERE kswitch_id = ?", k.Id)
29 if err != nil {
30 return fmt.Errorf("fillRawKillSwitch: %v", err)
31 }
32 defer rows.Close()
33
34 var browsers []pb.Environment_Browser
35 for rows.Next() {
36 var b pb.Environment_Browser
37 if err := rows.Scan(&b); err != nil {
38 return fmt.Errorf("fillRawKillSwitch: %v", err)
39 }
40 browsers = append(browsers, b)
41 }
42 k.Browsers = browsers
43
44 return nil
45}
46
47func GetKillSwitchById(db *sql.DB, ctx context.Context, id int32) (*pb.KillSwitch, error) {
48 query := db.QueryRowContext(ctx, "SELECT kswitch_id, feat_id, min_version, max_version, active FROM KillSwitch WHERE kswitch_id = ?", id)
49 var featureId int32
50 var k pb.KillSwitch
51 if err := query.Scan(&k.Id, &featureId, &k.MinVersion, &k.MaxVersion, &k.Active); err != nil {
52 if err == sql.ErrNoRows {
53 return nil, nil
54 }
55 return nil, fmt.Errorf("GetKillSwitchById: %v.", err)
56 }
57
58 err := fillRawKillSwitch(db, ctx, &k, featureId)
59 if err != nil {
60 return nil, fmt.Errorf("GetKillSwitchById: $v.", err)
61 }
62
63 return &k, nil
64}
65
66func ListKillSwitches(db *sql.DB, ctx context.Context) ([]*pb.KillSwitch, error) {
67 var rows *sql.Rows
68 var err error
69 rows, err = db.QueryContext(ctx, "SELECT kswitch_id, feat_id, min_version, max_version, active FROM KillSwitch")
70 if err != nil {
71 return nil, fmt.Errorf("ListKillSwitches: ", err)
72 }
73 defer rows.Close()
74
75 var killSwitches []*pb.KillSwitch
76 for rows.Next() {
77 var featureId int32
78 var k pb.KillSwitch
79 if err := rows.Scan(&k.Id, &featureId, &k.MinVersion, &k.MaxVersion, &k.Active); err != nil {
80 return nil, fmt.Errorf("ListKillSwitches: %v.", err)
81 }
82 err := fillRawKillSwitch(db, ctx, &k, featureId)
83 if err != nil {
84 return nil, fmt.Errorf("ListKillSwitches: $v.", err)
85 }
86 killSwitches = append(killSwitches, &k)
87 }
88 if err := rows.Err(); err != nil {
89 return nil, fmt.Errorf("ListKillSwitches: ", err)
90 }
91 return killSwitches, nil
92}
93
94func EnableKillSwitch(db *sql.DB, ctx context.Context, k *pb.KillSwitch) error {
95 if k.MinVersion != "" && !isValidSemVersion(k.MinVersion) {
96 return fmt.Errorf("min_version is not a valid semantic version.")
97 }
98 if k.MaxVersion != "" && !isValidSemVersion(k.MaxVersion) {
99 return fmt.Errorf("max_version is not a valid semantic version.")
100 }
101 if k.MinVersion != "" && k.MaxVersion != "" {
102 minVersion, _ := semver.NewVersion(k.MinVersion)
103 maxVersion, _ := semver.NewVersion(k.MaxVersion)
104 if minVersion.GreaterThan(maxVersion) {
105 return fmt.Errorf("min_version must be less than max_version.")
106 }
107 }
108 for _, b := range k.GetBrowsers() {
109 if b == pb.Environment_BROWSER_UNKNOWN {
110 return fmt.Errorf("browsers cannot contain BROWSER_UNKNOWN.")
111 }
112 }
113
114 k.Active = true
115
116 f, err := GetFeatureById(db, ctx, k.Feature.Id)
117 if err != nil {
118 return fmt.Errorf("EnableKillSwitch: %v", err)
119 }
120 if f == nil {
121 return fmt.Errorf("EnableKillSwitch: this feature doesn't exist.")
122 }
123 k.Feature = f
124
125 tx, err := db.BeginTx(ctx, nil)
126 if err != nil {
127 return err
128 }
129
130 result, err := tx.ExecContext(ctx, "INSERT INTO KillSwitch (feat_id, min_version, max_version, active) VALUES (?, ?, ?, ?)", k.Feature.Id, k.MinVersion, k.MaxVersion, k.Active)
131 if err != nil {
132 tx.Rollback()
133 return err
134 }
135
136 id, err := result.LastInsertId()
137 if err != nil {
138 tx.Rollback()
139 return err
140 }
141 k.Id = int32(id)
142
143 for _, b := range k.GetBrowsers() {
144 if _, err := tx.ExecContext(ctx, "INSERT INTO KillSwitch2Browser (kswitch_id, browser) VALUES (?, ?)", k.Id, b); err != nil {
145 tx.Rollback()
146 return err
147 }
148 }
149
150 logEntry := &pb.KillSwitchAuditLogEntry{
151 Description: &pb.KillSwitchAuditLogEntry_KillSwitchEnabled_{
152 &pb.KillSwitchAuditLogEntry_KillSwitchEnabled{
153 KillSwitch: k,
154 },
155 },
156 }
157 if err := AddKillSwitchAuditLogEntry(tx, ctx, logEntry); err != nil {
158 tx.Rollback()
159 return err
160 }
161
162 return tx.Commit()
163}
164
165func DisableKillSwitch(db *sql.DB, ctx context.Context, id int32) error {
166 oldKillSwitch, err := GetKillSwitchById(db, ctx, id)
167 if err != nil {
168 return err
169 }
170 if oldKillSwitch == nil {
171 return fmt.Errorf("Such kill switch doesn't exist")
172 }
173 if oldKillSwitch.GetActive() != true {
174 return fmt.Errorf("The kill switch is not active")
175 }
176
177 tx, err := db.BeginTx(ctx, nil)
178 if err != nil {
179 return err
180 }
181
182 newKillSwitch := *oldKillSwitch
183 newKillSwitch.Active = false
184
185 if _, err := tx.ExecContext(ctx, "UPDATE KillSwitch SET active = ? WHERE kswitch_id = ?", newKillSwitch.Active, id); err != nil {
186 tx.Rollback()
187 return err
188 }
189
190 logEntry := &pb.KillSwitchAuditLogEntry{
191 Description: &pb.KillSwitchAuditLogEntry_KillSwitchDisabled_{
192 &pb.KillSwitchAuditLogEntry_KillSwitchDisabled{
193 Transformation: &pb.KillSwitchTransformation{
194 Old: oldKillSwitch,
195 New: &newKillSwitch,
196 },
197 },
198 },
199 }
200 if err := AddKillSwitchAuditLogEntry(tx, ctx, logEntry); err != nil {
201 tx.Rollback()
202 return err
203 }
204
205 return tx.Commit()
206}