github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/engines/postgres/commands.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package postgres
    21  
    22  import (
    23  	"fmt"
    24  	"strconv"
    25  	"strings"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  
    29  	"github.com/1aal/kubeblocks/pkg/lorry/engines"
    30  	"github.com/1aal/kubeblocks/pkg/lorry/engines/models"
    31  )
    32  
    33  var _ engines.ClusterCommands = &Commands{}
    34  
    35  type Commands struct {
    36  	info     engines.EngineInfo
    37  	examples map[models.ClientType]engines.BuildConnectExample
    38  }
    39  
    40  func NewCommands() engines.ClusterCommands {
    41  	return &Commands{
    42  		info: engines.EngineInfo{
    43  			Client:      "psql",
    44  			Container:   "postgresql",
    45  			PasswordEnv: "$PGPASSWORD",
    46  			UserEnv:     "$PGUSER",
    47  			Database:    "postgres",
    48  		},
    49  		examples: map[models.ClientType]engines.BuildConnectExample{
    50  			models.CLI: func(info *engines.ConnectionInfo) string {
    51  				return fmt.Sprintf(`# psql client connection example
    52  psql -h%s -p %s -U %s %s
    53  `, info.Host, info.Port, info.User, info.Database)
    54  			},
    55  
    56  			models.DJANGO: func(info *engines.ConnectionInfo) string {
    57  				return fmt.Sprintf(`# .env
    58  DB_HOST=%s
    59  DB_NAME=%s
    60  DB_USER=%s
    61  DB_PASSWORD=%s
    62  DB_PORT=%s
    63  
    64  # settings.py
    65  DATABASES = {
    66    'default': {
    67      'ENGINE': 'django.db.backends.postgresql',
    68      'NAME': os.environ.get('DB_NAME'),
    69      'HOST': os.environ.get('DB_HOST'),
    70      'PORT': os.environ.get('DB_PORT'),
    71      'USER': os.environ.get('DB_USER'),
    72      'PASSWORD': os.environ.get('DB_PASSWORD'),
    73    }
    74  }
    75  `, info.Host, info.Database, info.User, info.Password, info.Port)
    76  			},
    77  
    78  			models.DOTNET: func(info *engines.ConnectionInfo) string {
    79  				return fmt.Sprintf(`# Startup.cs
    80  var connectionString = "Host=%s;Port=%s;Username=%s;Password=%s;Database=%s";
    81  await using var dataSource = NpgsqlDataSource.Create(connectionString);
    82  `, info.Host, info.Port, info.User, info.Password, info.Database)
    83  			},
    84  
    85  			models.GO: func(info *engines.ConnectionInfo) string {
    86  				const goConnectExample = `# main.go
    87  package main
    88  
    89  import (
    90      "database/sql"
    91      "log"
    92      "os"
    93  
    94       _ "github.com/lib/pq"
    95  )
    96  
    97  func main() {
    98      db, err := sql.Open("postgres", os.Getenv("DSN"))
    99      if err != nil {
   100          log.Fatalf("failed to connect: %v", err)
   101      }
   102      defer db.Close()
   103  
   104      if err := db.Ping(); err != nil {
   105          log.Fatalf("failed to ping: %v", err)
   106      }
   107  
   108      log.Println("Successfully connected!")
   109  }
   110  `
   111  				dsn := fmt.Sprintf(`# .env
   112  DSN=%s:%s@tcp(%s:%s)/%s
   113  `, info.User, info.Password, info.Host, info.Port, info.Database)
   114  				return fmt.Sprintf("%s\n%s", dsn, goConnectExample)
   115  			},
   116  
   117  			models.JAVA: func(info *engines.ConnectionInfo) string {
   118  				return fmt.Sprintf(`Class.forName("org.postgresql.Driver");
   119  Connection conn = DriverManager.getConnection(
   120    "jdbc:postgresql://%s:%s/%s?user=%s&password=%s");
   121  `, info.Host, info.Port, info.Database, info.User, info.Password)
   122  			},
   123  
   124  			models.NODEJS: func(info *engines.ConnectionInfo) string {
   125  				return fmt.Sprintf(`# .env
   126  DATABASE_URL='postgres://%s:%s@%s:%s/%s'
   127  
   128  # app.js
   129  require('dotenv').config();
   130  const postgres = require('postgres');
   131  const connection = postgres(process.env.DATABASE_URL);
   132  connection.end();
   133  `, info.User, info.Password, info.Host, info.Port, info.Database)
   134  			},
   135  
   136  			models.PHP: func(info *engines.ConnectionInfo) string {
   137  				return fmt.Sprintf(`# .env
   138  HOST=%s
   139  PORT=%s
   140  USERNAME=%s
   141  PASSWORD=%s
   142  DATABASE=%s
   143  
   144  # index.php
   145  <?php
   146    $dbconn =pg_connect($_ENV["HOST"], $_ENV["USERNAME"], $_ENV["PASSWORD"], $_ENV["DATABASE"], $_ENV["PORT"]);
   147    pg_close($dbconn)
   148  ?>
   149  `, info.Host, info.Port, info.User, info.Password, info.Database)
   150  			},
   151  
   152  			models.PRISMA: func(info *engines.ConnectionInfo) string {
   153  				return fmt.Sprintf(`# .env
   154  DATABASE_URL='postgres://%s:%s@%s:%s/%s'
   155  
   156  # schema.prisma
   157  generator client {
   158    provider = "prisma-client-js"
   159  }
   160  
   161  datasource db {
   162    provider = "postgresql"
   163    url = env("DATABASE_URL")
   164    relationMode = "prisma"
   165  }
   166  `, info.User, info.Password, info.Host, info.Port, info.Database)
   167  			},
   168  
   169  			models.PYTHON: func(info *engines.ConnectionInfo) string {
   170  				return fmt.Sprintf(`# run the following command in the terminal to install dependencies
   171  pip install python-dotenv psycopg2
   172  
   173  # .env
   174  HOST=%s
   175  PORT=%s
   176  USERNAME=%s
   177  PASSWORD=%s
   178  DATABASE=%s
   179  
   180  # main.py
   181  from dotenv import load_dotenv
   182  load_dotenv()
   183  import os
   184  import MySQLdb
   185  
   186  connection = psycopg2.connect(
   187    host= os.getenv("HOST"),
   188    port=os.getenv("PORT"),
   189    user=os.getenv("USERNAME"),
   190    password=os.getenv("PASSWORD"),
   191    database=os.getenv("DATABASE"),
   192  )
   193  `, info.Host, info.Port, info.User, info.Password, info.Database)
   194  			},
   195  
   196  			models.RAILS: func(info *engines.ConnectionInfo) string {
   197  				return fmt.Sprintf(`# Gemfile
   198  gem 'pg'
   199  
   200  # config/database.yml
   201  development:
   202    <<: *default
   203    adapter: postgresql
   204    database: %s
   205    username: %s
   206    host: %s
   207    password: %s
   208  `, info.Database, info.User, info.Host, info.Password)
   209  			},
   210  
   211  			models.RUST: func(info *engines.ConnectionInfo) string {
   212  				return fmt.Sprintf(`# run the following command in the terminal
   213  export DATABASE_URL="postgresql://%s:%s@%s:%s/%s"
   214  
   215  # src/main.rs
   216  use std::env;
   217  
   218  fn main() {
   219      let url = env::var("DATABASE_URL").expect("DATABASE_URL not found");
   220  	let conn = Connection::connect(url, TlsMode::None).unwrap();
   221      println!("Successfully connected!");
   222  }
   223  
   224  # Cargo.toml
   225  [package]
   226  name = "kubeblocks_hello_world"
   227  version = "0.0.1"
   228  `, info.User, info.Password, info.Host, info.Port, info.Database)
   229  			},
   230  
   231  			models.SYMFONY: func(info *engines.ConnectionInfo) string {
   232  				return fmt.Sprintf(`# .env
   233  DATABASE_URL='postgresql://%s:%s@%s:%s/%s'
   234  `, info.User, info.Password, info.Host, info.Port, info.Database)
   235  			},
   236  		},
   237  	}
   238  }
   239  
   240  func (m *Commands) ConnectCommand(connectInfo *engines.AuthInfo) []string {
   241  	userName := m.info.UserEnv
   242  	userPass := m.info.PasswordEnv
   243  
   244  	if connectInfo != nil {
   245  		userName = connectInfo.UserName
   246  		userPass = connectInfo.UserPasswd
   247  	}
   248  
   249  	// please refer to PostgreSQL documentation for more details
   250  	// https://www.postgresql.org/docs/current/libpq-envars.html
   251  	cmd := []string{fmt.Sprintf("PGUSER=%s PGPASSWORD=%s PGDATABASE=%s %s", userName, userPass, m.info.Database, m.info.Client)}
   252  	return []string{"sh", "-c", strings.Join(cmd, " ")}
   253  }
   254  
   255  func (m *Commands) Container() string {
   256  	return m.info.Container
   257  }
   258  
   259  func (m *Commands) ConnectExample(info *engines.ConnectionInfo, client string) string {
   260  	if len(info.Database) == 0 {
   261  		info.Database = m.info.Database
   262  	}
   263  	return engines.BuildExample(info, client, m.examples)
   264  }
   265  
   266  func (m *Commands) ExecuteCommand(scripts []string) ([]string, []corev1.EnvVar, error) {
   267  	cmd := []string{}
   268  	cmd = append(cmd, "/bin/sh", "-c", "-ex")
   269  	args := []string{}
   270  	for _, script := range scripts {
   271  		// split each script with a new line
   272  		lines := strings.Split(script, "\n")
   273  		for _, line := range lines {
   274  			args = append(args, fmt.Sprintf("-c %s", strconv.Quote(line)))
   275  		}
   276  	}
   277  	cmd = append(cmd, fmt.Sprintf("%s %s", m.info.Client, strings.Join(args, " ")))
   278  	envVars := []corev1.EnvVar{
   279  		{
   280  			Name:  "PGHOST",
   281  			Value: fmt.Sprintf("$(%s)", engines.EnvVarMap[engines.HOST]),
   282  		},
   283  		{
   284  			Name:  "PGUSER",
   285  			Value: fmt.Sprintf("$(%s)", engines.EnvVarMap[engines.USER]),
   286  		},
   287  		{
   288  			Name:  "PGPASSWORD",
   289  			Value: fmt.Sprintf("$(%s)", engines.EnvVarMap[engines.PASSWORD]),
   290  		},
   291  		{
   292  			Name:  "PGDATABASE",
   293  			Value: m.info.Database,
   294  		},
   295  	}
   296  	return cmd, envVars, nil
   297  }