Skip to content

Commit 4cf4606

Browse files
committed
[usage] Define db.Workspace model
1 parent 9202ddd commit 4cf4606

File tree

6 files changed

+353
-1
lines changed

6 files changed

+353
-1
lines changed

components/usage/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ replace k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.5 /
5757
require (
5858
github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000
5959
github.com/go-sql-driver/mysql v1.6.0
60+
github.com/relvacode/iso8601 v1.1.0
6061
github.com/spf13/cobra v1.4.0
6162
github.com/stretchr/testify v1.7.0
63+
gorm.io/datatypes v1.0.6
6264
gorm.io/driver/mysql v1.3.3
6365
gorm.io/gorm v1.23.5
6466
)

components/usage/go.sum

Lines changed: 143 additions & 0 deletions
Large diffs are not rendered by default.

components/usage/pkg/db/conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,5 @@ func ConnectForTests(t *testing.T) *gorm.DB {
6161
require.NoError(t, rawConn.Close(), "must close database connection")
6262
})
6363

64-
return conn
64+
return conn.Debug()
6565
}

components/usage/pkg/db/types.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package db
6+
7+
import (
8+
"database/sql/driver"
9+
"fmt"
10+
"github.com/relvacode/iso8601"
11+
"time"
12+
)
13+
14+
func NewVarcharTime(t time.Time) VarcharTime {
15+
return VarcharTime(t.UTC())
16+
}
17+
18+
func NewVarcharTimeFromStr(s string) (VarcharTime, error) {
19+
parsed, err := iso8601.ParseString(string(s))
20+
if err != nil {
21+
return VarcharTime{}, fmt.Errorf("failed to parse as ISO 8601: %w", err)
22+
}
23+
return VarcharTime(parsed), nil
24+
}
25+
26+
// VarcharTime exists for cases where records are inserted into the DB as VARCHAR but actually contain a timestamp which is time.RFC3339
27+
type VarcharTime time.Time
28+
29+
// Scan implements the Scanner interface.
30+
func (n *VarcharTime) Scan(value interface{}) error {
31+
if value == nil {
32+
return fmt.Errorf("nil value")
33+
}
34+
35+
switch s := value.(type) {
36+
case []uint8:
37+
parsed, err := iso8601.ParseString(string(s))
38+
if err != nil {
39+
return fmt.Errorf("failed to parse %v into ISO8601: %w", string(s), err)
40+
}
41+
*n = VarcharTime(parsed.UTC())
42+
return nil
43+
}
44+
return fmt.Errorf("unknown scan value for VarcharTime with value: %v", value)
45+
}
46+
47+
// Value implements the driver Valuer interface.
48+
func (n VarcharTime) Value() (driver.Value, error) {
49+
return time.Time(n).UTC().Format(time.RFC3339Nano), nil
50+
}
51+
52+
func (n VarcharTime) String() string {
53+
return time.Time(n).Format(time.RFC3339Nano)
54+
}

components/usage/pkg/db/workspace.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package db
6+
7+
import (
8+
"database/sql"
9+
"gorm.io/datatypes"
10+
"time"
11+
)
12+
13+
// Workspace represents the underlying DB object
14+
type Workspace struct {
15+
ID string `gorm:"primary_key;column:id;type:char;size:36;" json:"id"`
16+
OwnerID string `gorm:"column:ownerId;type:char;size:36;" json:"owner_id"`
17+
ProjectID sql.NullString `gorm:"column:projectId;type:char;size:36;" json:"project_id"`
18+
Description string `gorm:"column:description;type:varchar;size:255;" json:"description"`
19+
Type string `gorm:"column:type;type:char;size:16;default:regular;" json:"type"`
20+
CloneURL string `gorm:"column:cloneURL;type:varchar;size:255;" json:"clone_url"`
21+
22+
ContextURL string `gorm:"column:contextURL;type:text;size:65535;" json:"context_url"`
23+
Context datatypes.JSON `gorm:"column:context;type:text;size:65535;" json:"context"`
24+
Config datatypes.JSON `gorm:"column:config;type:text;size:65535;" json:"config"`
25+
BasedOnPrebuildID sql.NullString `gorm:"column:basedOnPrebuildId;type:char;size:36;" json:"based_on_prebuild_id"`
26+
BasedOnSnapshotID sql.NullString `gorm:"column:basedOnSnapshotId;type:char;size:36;" json:"based_on_snapshot_id"`
27+
ImageSource datatypes.JSON `gorm:"column:imageSource;type:text;size:65535;" json:"image_source"`
28+
ImageNameResolved string `gorm:"column:imageNameResolved;type:varchar;size:255;" json:"image_name_resolved"`
29+
BaseImageNameResolved string `gorm:"column:baseImageNameResolved;type:varchar;size:255;" json:"base_image_name_resolved"`
30+
31+
CreationTime VarcharTime `gorm:"column:creationTime;type:varchar;size:255;" json:"creation_time"`
32+
LastModified time.Time `gorm:"column:_lastModified;type:timestamp;default:CURRENT_TIMESTAMP(6);" json:"_last_modified"`
33+
SoftDeletedTime VarcharTime `gorm:"column:softDeletedTime;type:varchar;size:255;" json:"soft_deleted_time"`
34+
ContentDeletedTime VarcharTime `gorm:"column:contentDeletedTime;type:varchar;size:255;" json:"content_deleted_time"`
35+
36+
Archived int32 `gorm:"column:archived;type:tinyint;default:0;" json:"archived"`
37+
Shareable int32 `gorm:"column:shareable;type:tinyint;default:0;" json:"shareable"`
38+
39+
SoftDeleted sql.NullString `gorm:"column:softDeleted;type:char;size:4;" json:"soft_deleted"`
40+
Pinned int32 `gorm:"column:pinned;type:tinyint;default:0;" json:"pinned"`
41+
42+
// deleted is reserved for use by db-sync
43+
_ int32 `gorm:"column:deleted;type:tinyint;default:0;" json:"deleted"`
44+
}
45+
46+
func (d *Workspace) TableName() string {
47+
return "d_b_workspace"
48+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package db_test
6+
7+
import (
8+
"fmt"
9+
"github.com/gitpod-io/gitpod/usage/pkg/db"
10+
"github.com/stretchr/testify/require"
11+
"gorm.io/gorm"
12+
"strings"
13+
"testing"
14+
)
15+
16+
var testObject = map[string]interface{}{
17+
"id": "a0089911-5efc-4e2d-8714-0d3f67d82769",
18+
"creationTime": "2019-05-10T09:54:28.185Z",
19+
"ownerId": "39051644-7a8f-4ddb-8bd3-fbede229ad4e",
20+
"contextURL": "https://github.com/gitpod-io/gitpod",
21+
"description": "gitpod-io/gitpod - master",
22+
"context": "{\"isFile\":false,\"path\":\"\",\"" +
23+
"title\":\"gitpod-io/gitpod - master\",\"" +
24+
"ref\":\"master\",\"" +
25+
"revision\":\"6a463917e0718d36ee764a898db2801bc4da580f\",\"" +
26+
"repository\":{\"cloneUrl\":\"https://github.com/gitpod-io/gitpod.git\",\"" +
27+
"host\":\"github.com\",\"" +
28+
"name\":\"gitpod\",\"" +
29+
"owner\":\"gitpod-io\",\"" +
30+
"private\":false}}",
31+
"config": "{\"ports\":[{\"port\":3000}],\"tasks\":[],\"image\":\"eu.gcr.io/gitpod-dev/workspace-full:master.1458\"}",
32+
"archived": "0",
33+
"shareable": "0",
34+
"imageSource": "{\"baseImageResolved\":\"eu.gcr.io/gitpod-dev/workspace-full@sha256:9c0892d1901210f3999c9bd64e371038bfc0505f04858e959e1bb3778dcf19c1\"}",
35+
"imageNameResolved": "eu.gcr.io/gitpod-dev/workspace-full@sha256:9c0892d1901210f3999c9bd64e371038bfc0505f04858e959e1bb3778dcf19c1",
36+
"_lastModified": "2020-04-14T00:31:26.101Z",
37+
"deleted": "0",
38+
"type": "regular",
39+
"baseImageNameResolved": "eu.gcr.io/gitpod-dev/workspace-full@sha256:9c0892d1901210f3999c9bd64e371038bfc0505f04858e959e1bb3778dcf19c1",
40+
"softDeleted": "gc",
41+
"pinned": "0",
42+
"softDeletedTime": "2020-03-24T00:03:03.777Z",
43+
"contentDeletedTime": "2020-04-14T00:31:22.979Z",
44+
//"basedOnPrebuildId": null,
45+
//"basedOnSnapshotId": null,
46+
//"projectId": null,
47+
"cloneURL": "",
48+
}
49+
50+
func TestRead(t *testing.T) {
51+
conn := db.ConnectForTests(t)
52+
53+
insertRawWorkspace(t, conn, testObject)
54+
55+
var ws db.Workspace
56+
tx := conn.First(&ws)
57+
require.NoError(t, tx.Error)
58+
59+
require.Equal(t, stringToVarchar(t, testObject["creationTime"].(string)), ws.CreationTime)
60+
require.Equal(t, stringToVarchar(t, testObject["softDeletedTime"].(string)), ws.SoftDeletedTime)
61+
require.Equal(t, testObject["context"].(string), ws.Context.String())
62+
}
63+
64+
func insertRawWorkspace(t *testing.T, conn *gorm.DB, rawWorkspace map[string]interface{}) string {
65+
columns := []string{
66+
"id", "creationTime",
67+
"ownerId", "contextURL", "description", "context", "config", "imageSource", "imageNameResolved", "archived", "shareable",
68+
"deleted", "type", "baseImageNameResolved", "softDeleted", "pinned", "softDeletedTime", "contentDeletedTime", "basedOnPrebuildId", "basedOnSnapshotId", "projectId", "cloneURL",
69+
}
70+
statement := fmt.Sprintf(`
71+
INSERT INTO d_b_workspace (
72+
%s
73+
) VALUES ?;`, strings.Join(columns, ", "))
74+
75+
id := rawWorkspace["id"].(string)
76+
77+
var values []interface{}
78+
for _, col := range columns {
79+
val, ok := rawWorkspace[col]
80+
if !ok {
81+
values = append(values, "null")
82+
} else {
83+
values = append(values, val)
84+
}
85+
86+
}
87+
88+
tx := conn.Debug().Exec(statement, values)
89+
require.NoError(t, tx.Error)
90+
91+
t.Cleanup(func() {
92+
tx := conn.Delete(&db.Workspace{ID: id})
93+
require.NoError(t, tx.Error)
94+
})
95+
96+
return id
97+
}
98+
99+
func stringToVarchar(t *testing.T, s string) db.VarcharTime {
100+
t.Helper()
101+
102+
converted, err := db.NewVarcharTimeFromStr(s)
103+
require.NoError(t, err)
104+
return converted
105+
}

0 commit comments

Comments
 (0)