Skip to content

Commit 768d98a

Browse files
committedAug 5, 2022
Initial commit
0 parents  commit 768d98a

12 files changed

+570
-0
lines changed
 

‎.github/workflows/build.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
- name: Set up Go
15+
uses: actions/setup-go@v2
16+
with:
17+
go-version: 1.18
18+
- name: Install dependencies
19+
run: |
20+
go mod download
21+
curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh
22+
- name: Test
23+
run: |
24+
make build
25+
./bin/goreleaser check

‎.github/workflows/release.yml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
goreleaser:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v2
17+
with:
18+
fetch-depth: 0
19+
- name: Set up Go
20+
uses: actions/setup-go@v2
21+
with:
22+
go-version: 1.18
23+
- name: Run GoReleaser
24+
uses: goreleaser/goreleaser-action@v2
25+
with:
26+
distribution: goreleaser
27+
version: latest
28+
args: release --rm-dist
29+
env:
30+
GITHUB_TOKEN: ${{ secrets.GA_GORELEASER_TOKEN }}

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea/
2+
.vscode/

‎.goreleaser.yml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
project_name: rdapp
2+
3+
before:
4+
hooks:
5+
- go mod download
6+
7+
release:
8+
github:
9+
owner: kishaningithub
10+
name: rdapp
11+
12+
builds:
13+
- main: ./cmd/main.go
14+
binary: rdapp
15+
goos:
16+
- windows
17+
- darwin
18+
- linux
19+
goarch:
20+
- amd64
21+
- arm64
22+
23+
brews:
24+
- tap:
25+
owner: kishaningithub
26+
name: homebrew-tap
27+
folder: Formula
28+
homepage: https://github.com/kishaningithub/rdapp
29+
description: rdapp - redshift data api postgres proxy
30+
license: MIT
31+
32+
nfpms:
33+
- id: rdapp
34+
package_name: rdapp
35+
homepage: https://github.com/kishaningithub/rdapp
36+
description: rdapp - redshift data api postgres proxy
37+
license: MIT
38+
formats:
39+
- apk
40+
- deb
41+
- rpm

‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Kishan B
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎Makefile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
unit-test:
2+
go test -v ./...
3+
4+
build: download-deps tidy-deps fmt unit-test compile
5+
6+
fmt: ## Run the code formatter
7+
gofmt -l -s -w .
8+
9+
download-deps:
10+
go mod download
11+
12+
tidy-deps:
13+
go mod tidy
14+
15+
update-deps:
16+
go get -u ./...
17+
go mod tidy
18+
19+
compile:
20+
go build -v ./...

‎cmd/main.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"os"
8+
9+
rdapp "github.com/kishaningithub/rdapp/pkg"
10+
)
11+
12+
const examples = `
13+
examples:
14+
rdapp`
15+
16+
func main() {
17+
var options rdapp.Options
18+
flag.Usage = func() {
19+
fmt.Fprintln(os.Stderr, "usage: rdapp [options]")
20+
flag.PrintDefaults()
21+
fmt.Fprintln(os.Stderr, examples)
22+
}
23+
flag.StringVar(&options.ListenAddress, "listen", "127.0.0.1:15432", "Listen address")
24+
flag.Parse()
25+
err := rdapp.RunPostgresRedshiftProxy(options)
26+
if err != nil {
27+
log.Fatal(err)
28+
}
29+
}

‎go.mod

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module github.com/kishaningithub/rdapp
2+
3+
go 1.18
4+
5+
require (
6+
github.com/jackc/pgproto3/v2 v2.3.0
7+
github.com/jackc/pgx/v4 v4.16.1
8+
github.com/stretchr/testify v1.7.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
14+
github.com/jackc/pgconn v1.12.1 // indirect
15+
github.com/jackc/pgio v1.0.0 // indirect
16+
github.com/jackc/pgpassfile v1.0.0 // indirect
17+
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
18+
github.com/jackc/pgtype v1.11.0 // indirect
19+
github.com/pmezard/go-difflib v1.0.0 // indirect
20+
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
21+
golang.org/x/text v0.3.7 // indirect
22+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
23+
)

‎go.sum

+179
Large diffs are not rendered by default.

‎pkg/connection_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package rdapp_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/jackc/pgx/v4"
9+
rdapp "github.com/kishaningithub/rdapp/pkg"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestConnectivity(t *testing.T) {
14+
options := rdapp.Options{
15+
ListenAddress: "localhost:15432",
16+
}
17+
18+
go func() {
19+
err := rdapp.RunPostgresRedshiftProxy(options)
20+
require.NoError(t, err)
21+
}()
22+
databaseUrl := "postgres://postgres:mypassword@localhost:15432/postgres"
23+
conn, err := pgx.Connect(context.Background(), databaseUrl)
24+
25+
require.NoError(t, err)
26+
defer conn.Close(context.Background())
27+
connInfo := conn.Config()
28+
require.Equal(t, "localhost", connInfo.Host)
29+
}
30+
31+
func TestQueryExecution(t *testing.T) {
32+
options := rdapp.Options{
33+
ListenAddress: "localhost:15432",
34+
}
35+
36+
go func() {
37+
err := rdapp.RunPostgresRedshiftProxy(options)
38+
require.NoError(t, err)
39+
}()
40+
41+
databaseUrl := "postgres://postgres:mypassword@localhost:15432/postgres"
42+
conn, err := pgx.Connect(context.Background(), databaseUrl)
43+
require.NoError(t, err)
44+
defer conn.Close(context.Background())
45+
connInfo := conn.Config()
46+
require.Equal(t, "localhost", connInfo.Host)
47+
var name string
48+
var weight int64
49+
err = conn.QueryRow(context.Background(), "select name, weight from widgets where id=$1", 42).Scan(&name, &weight)
50+
require.NoError(t, err)
51+
fmt.Println(name, weight)
52+
}

‎pkg/listener.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package rdapp
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net"
7+
)
8+
9+
type Options struct {
10+
ListenAddress string
11+
}
12+
13+
func RunPostgresRedshiftProxy(options Options) error {
14+
listener, err := net.Listen("tcp", options.ListenAddress)
15+
if err != nil {
16+
return fmt.Errorf("error while listening to %s: %w", options.ListenAddress, err)
17+
}
18+
19+
for {
20+
conn, err := listener.Accept()
21+
if err != nil {
22+
return fmt.Errorf("error while accepting a new connection: %w", err)
23+
}
24+
log.Println("Accepted connection from", conn.RemoteAddr())
25+
26+
backend := NewRedshiftBackend(conn)
27+
go func() {
28+
err := backend.Run()
29+
if err != nil {
30+
log.Println(err)
31+
}
32+
log.Println("Closed connection from", conn.RemoteAddr())
33+
}()
34+
}
35+
}

‎pkg/server.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package rdapp
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"log"
7+
8+
"github.com/jackc/pgproto3/v2"
9+
)
10+
11+
type RedshiftBackend struct {
12+
backend *pgproto3.Backend
13+
conn io.ReadWriteCloser
14+
}
15+
16+
func NewRedshiftBackend(conn io.ReadWriteCloser) *RedshiftBackend {
17+
backend := pgproto3.NewBackend(pgproto3.NewChunkReader(conn), conn)
18+
19+
connHandler := &RedshiftBackend{
20+
backend: backend,
21+
conn: conn,
22+
}
23+
24+
return connHandler
25+
}
26+
27+
func (p *RedshiftBackend) Run() error {
28+
defer p.Close()
29+
30+
err := p.handleStartup()
31+
if err != nil {
32+
return err
33+
}
34+
35+
for {
36+
msg, err := p.backend.Receive()
37+
if err != nil {
38+
return fmt.Errorf("error receiving message: %w", err)
39+
}
40+
log.Printf("received message %#v", msg)
41+
42+
switch msg.(type) {
43+
case *pgproto3.Query, *pgproto3.Parse:
44+
response := "it works :-P"
45+
buf := (&pgproto3.RowDescription{Fields: []pgproto3.FieldDescription{
46+
{
47+
Name: []byte("haha"),
48+
TableOID: 0,
49+
TableAttributeNumber: 0,
50+
DataTypeOID: 25,
51+
DataTypeSize: -1,
52+
TypeModifier: -1,
53+
Format: 0,
54+
},
55+
}}).Encode(nil)
56+
buf = (&pgproto3.DataRow{Values: [][]byte{[]byte(response)}}).Encode(buf)
57+
buf = (&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")}).Encode(buf)
58+
buf = (&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf)
59+
_, err = p.conn.Write(buf)
60+
if err != nil {
61+
return fmt.Errorf("error writing query response: %w", err)
62+
}
63+
case *pgproto3.Terminate:
64+
return nil
65+
default:
66+
return fmt.Errorf("received message other than Query from client: %#v", msg)
67+
}
68+
}
69+
}
70+
71+
func (p *RedshiftBackend) handleStartup() error {
72+
startupMessage, err := p.backend.ReceiveStartupMessage()
73+
if err != nil {
74+
return fmt.Errorf("error receiving startup message: %w", err)
75+
}
76+
log.Printf("startup message received %#v", startupMessage)
77+
78+
switch startupMessage.(type) {
79+
case *pgproto3.StartupMessage:
80+
buf := (&pgproto3.AuthenticationOk{}).Encode(nil)
81+
// buf = (&pgproto3.ParameterStatus{Name: "application_name", Value: "psql"}).Encode(buf)
82+
buf = (&pgproto3.ParameterStatus{Name: "client_encoding", Value: "UTF8"}).Encode(buf)
83+
buf = (&pgproto3.ParameterStatus{Name: "DateStyle", Value: "ISO"}).Encode(buf)
84+
buf = (&pgproto3.ParameterStatus{Name: "integer_datetimes", Value: "on"}).Encode(buf)
85+
buf = (&pgproto3.ParameterStatus{Name: "IntervalStyle", Value: "postgres"}).Encode(buf)
86+
buf = (&pgproto3.ParameterStatus{Name: "is_superuser", Value: "on"}).Encode(buf)
87+
buf = (&pgproto3.ParameterStatus{Name: "server_encoding", Value: "UTF8"}).Encode(buf)
88+
buf = (&pgproto3.ParameterStatus{Name: "server_version", Value: "11.5"}).Encode(buf)
89+
buf = (&pgproto3.ParameterStatus{Name: "session_authorization", Value: "jack"}).Encode(buf)
90+
buf = (&pgproto3.ParameterStatus{Name: "standard_conforming_strings", Value: "on"}).Encode(buf)
91+
buf = (&pgproto3.ParameterStatus{Name: "TimeZone", Value: "US/Central"}).Encode(buf)
92+
buf = (&pgproto3.BackendKeyData{ProcessID: 31007, SecretKey: 1013083042}).Encode(buf)
93+
buf = (&pgproto3.ReadyForQuery{TxStatus: 'I'}).Encode(buf)
94+
_, err = p.conn.Write(buf)
95+
if err != nil {
96+
return fmt.Errorf("error sending ready for query: %w", err)
97+
}
98+
case *pgproto3.SSLRequest:
99+
_, err = p.conn.Write([]byte("N"))
100+
if err != nil {
101+
return fmt.Errorf("error sending deny SSL request: %w", err)
102+
}
103+
return p.handleStartup()
104+
default:
105+
return fmt.Errorf("unknown startup message: %#v", startupMessage)
106+
}
107+
108+
return nil
109+
}
110+
111+
func (p *RedshiftBackend) Close() error {
112+
return p.conn.Close()
113+
}

0 commit comments

Comments
 (0)
Please sign in to comment.