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
10 changes: 4 additions & 6 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

How to start:
1. Change the current directory to the directory with this README file.
2. Create the `env.json` file from the `env.json.template` and place it in the same directory:
2. Create the `../.env` file from the `../.env.template` (see the repository root for file `.env.template`)
```sh
cp env/env.json.template env/env.json
cp ../.env.template ../.env
```
3. Fill the `env/env.json` file
* The fields in the `server` object correspond to the fields of the same object from the [cloudbeaver.conf](https://dbeaver.com/docs/cloudbeaver/Server-configuration/#main-server-configuration).
* `apiToken` is the [API token](https://github.com/dbeaver/cloudbeaver/wiki/Generate-API-access-token).
5. You are ready to Go!
3. Fill the environment variables in the `.env` file.
4. You are ready to Go!
```sh
go build && ./go
```
Expand Down
111 changes: 49 additions & 62 deletions go/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,17 @@ import (
"errors"
"fmt"
"log/slog"
"os"
"time"

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

// Queries and mutations
const (
authQuery = `
query authLogin($token: String!) {
authLogin(provider: "token", credentials: { token: $token }) {
userTokens {
userId
}
authStatus
}
}
`

createTeamQuery = `
query createTeam($teamId: ID!) {
createTeam(teamId: $teamId) {
teamId
}
}
`

deleteTeamQuery = `
query deleteTeam($teamId: ID!, $force: Boolean) {
deleteTeam(teamId: $teamId, force: $force)
}
`

createProjectMutation = `
mutation RmCreateProject($projectName: String!) {
rmCreateProject(projectName: $projectName) {
id
}
}
`

deleteProjectMutation = `
mutation RmDeleteProject($projectId: ID!) {
rmDeleteProject(projectId: $projectId)
}
`
addProjectPermissionsMutation = `
mutation addProjectsPermissions($projectIds: [ID!]!, $subjectIds: [ID!]!, $permissions: [String!]!) {
rmAddProjectsPermissions(
projectIds: $projectIds
subjectIds: $subjectIds
permissions: $permissions
)
}
`
)

type Client struct {
GraphQLClient graphql.Client
Endpoint string
GraphQLClient graphql.Client
Endpoint string
OperationsPath string
}

func (client Client) sendRequest(operationName, query string, variables graphql.Object) (json.RawMessage, error) {
Expand All @@ -90,42 +41,70 @@ func (client Client) sendRequestDiscardingData(operationName, query string, vari
return err
}

func (client Client) readOperationText(operationName string) (string, error) {
path := client.OperationsPath + "/" + operationName + ".gql"
bytes, err := os.ReadFile(path)
if err != nil {
return "", lib.WrapError(fmt.Sprintf("unable to read operation file %s", path), err)
}
return string(bytes), nil
}

func (client Client) Auth(token string) error {
query, err := client.readOperationText("auth")
if err != nil {
return err
}
variables := map[string]any{
"token": token,
}
return client.sendRequestDiscardingData("auth", authQuery, variables)
return client.sendRequestDiscardingData("auth", query, variables)
}

func (client Client) CreateTeam(teamId string) error {
query, err := client.readOperationText("create_team")
if err != nil {
return err
}
variables := map[string]any{
"teamId": teamId,
}
return client.sendRequestDiscardingData(
fmt.Sprintf("create team '%s'", teamId),
createTeamQuery,
variables)
query,
variables,
)
}

func (client Client) DeleteTeam(teamId string, force bool) error {
query, err := client.readOperationText("delete_team")
if err != nil {
return err
}
variables := map[string]any{
"teamId": teamId,
"force": force,
}
return client.sendRequestDiscardingData(
fmt.Sprintf("delete team '%s'", teamId),
deleteTeamQuery,
variables)
query,
variables,
)
}

func (client Client) CreateProject(projectName string) (id string, err error) {
query, err := client.readOperationText("create_project")
if err != nil {
return "", err
}
variables := map[string]any{
"projectName": projectName,
}
rawData, err := client.sendRequest(
fmt.Sprintf("create project with name '%s'", projectName),
createProjectMutation,
variables)
query,
variables,
)
if err != nil {
return id, err
}
Expand All @@ -138,18 +117,26 @@ func (client Client) CreateProject(projectName string) (id string, err error) {
}

func (client Client) DeleteProject(projectId string) error {
query, err := client.readOperationText("delete_project")
if err != nil {
return err
}
variables := map[string]any{
"projectId": projectId,
}
return client.sendRequestDiscardingData(
fmt.Sprintf("delete project with id '%s'", projectId),
deleteProjectMutation,
query,
variables,
)
}

// subjectIds: Ids of teams or individual users
func (client Client) AddProjectAccess(projectId string, subjectIds ...string) error {
query, err := client.readOperationText("add_project_permissions")
if err != nil {
return err
}
variables := map[string]any{
"projectIds": [1]string{projectId},
"subjectIds": subjectIds,
Expand All @@ -160,7 +147,7 @@ func (client Client) AddProjectAccess(projectId string, subjectIds ...string) er
}
return client.sendRequestDiscardingData(
fmt.Sprintf("grant subjects %s access to project with id '%s'", subjectIds, projectId),
addProjectPermissionsMutation,
query,
variables,
)
}
41 changes: 0 additions & 41 deletions go/env/env.go

This file was deleted.

7 changes: 0 additions & 7 deletions go/env/env.json.template

This file was deleted.

9 changes: 1 addition & 8 deletions go/graphql/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"io"
"log/slog"
"net/http"

"github.com/dbeaver/cloudbeaver-graphql-examples/go/lib"
Expand All @@ -23,7 +22,7 @@ func (client Client) Execute(endpoint string, request Request) (Response, error)
if err != nil {
return Response{}, lib.WrapError("unable to execute POST request", err)
}
defer closeOrWarn(httpResponse.Body)
defer lib.CloseOrWarn(httpResponse.Body)
rawResponseBody, err := io.ReadAll(httpResponse.Body)
if err != nil {
return Response{}, lib.WrapError("unable to read GraphQL response body", err)
Expand All @@ -34,9 +33,3 @@ func (client Client) Execute(endpoint string, request Request) (Response, error)
}
return response, err
}

func closeOrWarn(closer io.Closer) {
if err := closer.Close(); err != nil {
slog.Warn("error while closing a Closer: " + err.Error())
}
}
12 changes: 11 additions & 1 deletion go/lib/lib.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package lib

import "fmt"
import (
"fmt"
"io"
"log/slog"
)

func WrapError(errorMessage string, original error) error {
return fmt.Errorf("%s\n\tCaused by: %w", errorMessage, original)
}

func CloseOrWarn(closer io.Closer) {
if err := closer.Close(); err != nil {
slog.Warn("error while closing a Closer: " + err.Error())
}
}
54 changes: 47 additions & 7 deletions go/main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package main

import (
"bufio"
"flag"
"fmt"
"log/slog"
"net/http"
"net/http/cookiejar"
"os"
"strings"

"github.com/dbeaver/cloudbeaver-graphql-examples/go/api"
"github.com/dbeaver/cloudbeaver-graphql-examples/go/env"
"github.com/dbeaver/cloudbeaver-graphql-examples/go/graphql"
"github.com/dbeaver/cloudbeaver-graphql-examples/go/lib"
)
Expand All @@ -27,15 +30,17 @@ func main() {

func main0() error {
// Instantiate a client
env, err := env.Read()
envFlag := flag.String("env", "../.env", "Path to the .env file")
operationsFlag := flag.String("operations", "../operations", "Path to the folder with GraphQL operations")
flag.Parse()
env, err := readEnv(*envFlag)
if err != nil {
return lib.WrapError("error while reading variables", err)
}
apiClient := initClient(env.GraphqlEndpoint())
apiClient := initClient(env.serverURL+"/"+env.serviceURI+"/gql", *operationsFlag)

// Auth
err = apiClient.Auth(env.Token)
env.PurgeToken()
err = apiClient.Auth(env.apiToken)
if err != nil {
return err
}
Expand Down Expand Up @@ -67,14 +72,49 @@ func main0() error {
return nil
}

func initClient(endpoint string) api.Client {
type env struct {
apiToken string
serverURL string
serviceURI string
}

func readEnv(envFilePath string) (env, error) {
env := env{}
file, err := os.Open(envFilePath)
if err != nil {
return env, lib.WrapError("error while opening the env file", err)
}
defer lib.CloseOrWarn(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
before, after, found := strings.Cut(scanner.Text(), "=")
if !found {
continue
}
before = strings.TrimSpace(before)
after = strings.TrimSpace(after)
switch before {
case "api_token":
env.apiToken = after
case "server_url":
env.serverURL = after
case "service_uri":
env.serviceURI = 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}
return api.Client{GraphQLClient: graphQLClient, Endpoint: endpoint, OperationsPath: operationsPath}
}

func cleanup(callDescription string, apiCall func() error) {
Expand Down
Loading