Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ server_url=
# `api` by default.
# See `serviceURI` field here: https://github.com/dbeaver/cloudbeaver/wiki/Server-configuration
service_uri=api

license_text=
77 changes: 77 additions & 0 deletions go/api/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package api

import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log/slog"
"os"
"strings"
"time"

"github.com/dbeaver/cloudbeaver-graphql-examples/go/graphql"
Expand All @@ -14,6 +17,7 @@ import (

type Client struct {
GraphQLClient graphql.Client
ServerURL string
Endpoint string
OperationsPath string
}
Expand Down Expand Up @@ -61,6 +65,23 @@ func (client Client) Auth(token string) error {
return client.sendRequestDiscardingData("auth", query, variables)
}

func (client Client) LocalAuth(username, password string) error {
query, err := client.readOperationText("generic_auth")
if err != nil {
return err
}
rawMD5Sum := md5.Sum([]byte(password))
passwordHash := strings.ToUpper(hex.EncodeToString(rawMD5Sum[:]))
variables := map[string]any{
"provider": "local",
"credentials": map[string]any{
"user": username,
"password": passwordHash,
},
}
return client.sendRequestDiscardingData("local auth", query, variables)
}

func (client Client) CreateTeam(teamId string) error {
query, err := client.readOperationText("create_team")
if err != nil {
Expand Down Expand Up @@ -151,3 +172,59 @@ func (client Client) AddProjectAccess(projectId string, subjectIds ...string) er
variables,
)
}

func (client Client) ImportLicense(licenseText string) ([]byte, error) {
query, err := client.readOperationText("import_license")
if err != nil {
return []byte{}, err
}
variables := map[string]any{
"licenseText": licenseText,
}
return client.sendRequest(
"importing a license",
query,
variables,
)
}

func (client Client) InitialConfiguration(username, password, serverName string) ([]byte, error) {
query, err := client.readOperationText("initial_configuration")
if err != nil {
return []byte{}, err
}
variables := map[string]any{
"configuration": map[string]any{
"adminName": username,
"adminPassword": password,
"serverName": serverName,
"sessionExpireTime": 694201337,
"adminCredentialsSaveEnabled": false,
"publicCredentialsSaveEnabled": false,
"customConnectionsEnabled": false,
"disabledDrivers": [6]string{
"sqlite-ee:sqlite_ee",
"sqlite-crypt:sqlite_crypt",
"h2:h2_embedded",
"h2:h2_embedded_v2",
"generic:duckdb_jdbc",
"h2gis:h2gis_embedded",
},
"enabledAuthProviders": [4]string{
"local",
"token",
"openid",
"okta-openid",
},
"anonymousAccessEnabled": false,
"enabledFeatures": [0]string{},
"resourceManagerEnabled": true,
"secretManagerEnabled": false,
},
}
return client.sendRequest(
"doing initial configuration",
query,
variables,
)
}
76 changes: 25 additions & 51 deletions go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ const (
)

func main() {
if err := main0(); err != nil {
if err := run(); err != nil {
slog.Error(err.Error())
os.Exit(1)
}
}

func main0() error {
func run() error {
// Instantiate a client
envFlag := flag.String("env", "../.env", "Path to the .env file")
operationsFlag := flag.String("operations", "../operations", "Path to the folder with GraphQL operations")
Expand All @@ -37,45 +37,31 @@ func main0() error {
if err != nil {
return lib.WrapError("error while reading variables", err)
}
apiClient := initClient(env.serverURL+"/"+env.serviceURI+"/gql", *operationsFlag)

// Auth
err = apiClient.Auth(env.apiToken)
if err != nil {
return err
}

// Creation / deletion of a team
err = apiClient.CreateTeam(teamId)
cookieJar, err := cookiejar.New(nil)
if err != nil {
return err
// Invariant: the method that creates cookie jar with no options never returns non-nil err
panic("encountered error while creating a cookie jar! " + err.Error())
}
defer cleanup("delete team "+teamId, func() error {
return apiClient.DeleteTeam(teamId, true)
})

// Creation of a project
projectId, err := apiClient.CreateProject(projectName)
if err != nil {
return err
graphQLClient := graphql.Client{HttpClient: &http.Client{Jar: cookieJar}}
apiClient := api.Client{
GraphQLClient: graphQLClient,
ServerURL: env.serverURL,
Endpoint: env.serverURL + "/" + env.serviceURI + "/gql",
OperationsPath: *operationsFlag,
}
defer cleanup("delete project "+projectId, func() error {
return apiClient.DeleteProject(projectId)
})

// Grant access
err = apiClient.AddProjectAccess(projectId, teamId)
if err != nil {
// Run scenarios
if err := scenario0(&apiClient, env.apiToken); err != nil {
return err
}

return nil
return scenario1(&apiClient, env.licenseText)
}

type env struct {
apiToken string
serverURL string
serviceURI string
apiToken string
serverURL string
serviceURI string
licenseText string
}

func readEnv(envFilePath string) (env, error) {
Expand All @@ -87,7 +73,11 @@ func readEnv(envFilePath string) (env, error) {
defer lib.CloseOrWarn(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
before, after, found := strings.Cut(scanner.Text(), "=")
line := scanner.Text()
if strings.HasPrefix(line, "#") {
continue
}
before, after, found := strings.Cut(line, "=")
if !found {
continue
}
Expand All @@ -100,27 +90,11 @@ func readEnv(envFilePath string) (env, error) {
env.serverURL = after
case "service_uri":
env.serviceURI = after
case "license_text":
env.licenseText = after
default:
slog.Warn(fmt.Sprintf("unknown env variable: %s", before))
}
}
return env, err
}

func initClient(endpoint string, operationsPath string) api.Client {
cookieJar, err := cookiejar.New(nil)
if err != nil {
// Invariant: the method that creates cookie jar with no options never returns non-nil err
panic("encountered error while creating a cookie jar! " + err.Error())
}
graphQLClient := graphql.Client{HttpClient: &http.Client{Jar: cookieJar}}
return api.Client{GraphQLClient: graphQLClient, Endpoint: endpoint, OperationsPath: operationsPath}
}

func cleanup(callDescription string, apiCall func() error) {
err := apiCall()
if err != nil {
slog.Warn("unable to " + callDescription)
slog.Warn(err.Error())
}
}
44 changes: 44 additions & 0 deletions go/scenario0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"log/slog"

"github.com/dbeaver/cloudbeaver-graphql-examples/go/api"
)

func scenario0(apiClient *api.Client, token string) error {
// Auth w/ API token
err := apiClient.Auth(token)
if err != nil {
return err
}

// Creation / deletion of a team
err = apiClient.CreateTeam(teamId)
if err != nil {
return err
}
defer cleanup("delete team "+teamId, func() error {
return apiClient.DeleteTeam(teamId, true)
})

// Creation of a project
projectId, err := apiClient.CreateProject(projectName)
if err != nil {
return err
}
defer cleanup("delete project "+projectId, func() error {
return apiClient.DeleteProject(projectId)
})

// Grant access
return apiClient.AddProjectAccess(projectId, teamId)
}

func cleanup(callDescription string, apiCall func() error) {
err := apiCall()
if err != nil {
slog.Warn("unable to " + callDescription)
slog.Warn(err.Error())
}
}
38 changes: 38 additions & 0 deletions go/scenario1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"crypto/tls"
"fmt"
"net/http"

"github.com/dbeaver/cloudbeaver-graphql-examples/go/api"
)

// This scenario is used to perform initial configuration of the server, such as setting up the license and local admin.
func scenario1(apiClient *api.Client, licenseText string) error {
username := "teadmin"
password := "VeryStr0ngPassw0rd"

// USE WITH CAUTION: this disables TLS certt verification, which is not recommended for production use
if false {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}

rawResponse, err := apiClient.InitialConfiguration(username, password, objectPrefix+"server-name")
if err != nil {
return err
}
fmt.Println(string(rawResponse))

if err := apiClient.LocalAuth(username, password); err != nil {
return err
}

rawResponse, err = apiClient.ImportLicense(licenseText)
if err != nil {
return err
}
fmt.Println(string(rawResponse))

return nil
}
5 changes: 5 additions & 0 deletions operations/generic_auth.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query genericAuth($provider: ID!, $credentials: Object!) {
authLogin(provider: $provider, credentials: $credentials) {
authStatus
}
}
20 changes: 20 additions & 0 deletions operations/import_license.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
mutation importProductLicense($licenseText: String) {
license: importProductLicense(licenseText: $licenseText) {
licenseType
ownerCompany
ownerName
ownerEmail
yearsNumber
subscription
multiInstance
unlimitedServers
licenseIssueTime
licenseStartTime
licenseEndTime
serversNumber
licenseRoles {
role
usersNumber
}
}
}
3 changes: 3 additions & 0 deletions operations/initial_configuration.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query configureServer($configuration: ServerConfigInput!) {
configureServer(configuration: $configuration)
}