peridot/initdb/main.go

195 lines
5.7 KiB
Go

// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package main
import (
"context"
"fmt"
"log"
"net/url"
"os"
"strings"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/spf13/viper"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"peridot.resf.org/utils"
)
var root = &cobra.Command{
Use: "initdb",
Run: mn,
}
var cnf = utils.NewFlagConfig()
func init() {
cnf.DefaultPort = 9999
dname := "initdb"
cnf.DatabaseName = &dname
cnf.Name = "initdb"
pf := root.PersistentFlags()
pf.String("target.db", "", "target db to initialize")
pf.Bool("skip", false, "Whether to skip InitDB without removing it as an init container")
utils.AddFlags(pf, cnf)
}
func mn(_ *cobra.Command, _ []string) {
if viper.GetBool("skip") {
os.Exit(0)
}
ctx := context.TODO()
targetDB := viper.GetString("target.db")
if targetDB == "" {
log.Fatal("no target db")
}
env := os.Getenv("RESF_ENV")
namespace := os.Getenv("RESF_NS")
roleName := fmt.Sprintf("%s-%s", namespace, targetDB)
secretName := fmt.Sprintf("%s-database-password", targetDB)
// creates the in-cluster config
config, err := rest.InClusterConfig()
if err != nil {
log.Fatal(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatal(err)
}
_, err = clientset.CoreV1().Secrets(namespace).Get(ctx, "env", metav1.GetOptions{})
if err != nil {
_, err = clientset.CoreV1().Secrets(namespace).Create(ctx, &v1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "env",
Namespace: namespace,
},
StringData: map[string]string{
"hydra": uuid.New().String(),
},
}, metav1.CreateOptions{})
if err != nil {
log.Fatal(err)
}
}
check, err := clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
if err == nil || check.Data["password"] != nil || len(check.Data["password"]) > 0 {
os.Exit(0)
}
secret, err := clientset.CoreV1().Secrets(fmt.Sprintf("initdb-%s", env)).Get(ctx, "initdb-password", metav1.GetOptions{})
if err != nil {
log.Fatal(err)
}
newUrl := strings.Replace(viper.GetString("database.url"), "REPLACEME", url.QueryEscape(string(secret.Data["password"])[:]), 1)
if secret.Data["username"] != nil {
newUrl = strings.Replace(newUrl, "postgres:", fmt.Sprintf("%s:", url.QueryEscape(string(secret.Data["username"]))), 1)
}
viper.Set("database.url", newUrl)
pg := utils.PgInit()
err = func() error {
pw := uuid.New().String()
_, err := pg.Exec(fmt.Sprintf("create database \"%s\"", targetDB))
if err != nil && !strings.Contains(err.Error(), "already exists") {
return err
}
_, err = pg.Exec(fmt.Sprintf("create role \"%s\";", roleName))
if err != nil && !strings.Contains(err.Error(), "already exists") {
return err
}
_, err = pg.Exec(fmt.Sprintf("alter role \"%s\" with password '%s'; alter role \"%s\" with login; grant all on database \"%s\" to \"%s\"", roleName, pw, roleName, targetDB, roleName))
if err != nil {
return err
}
dbUrl := viper.GetString("database.url")
dbUrl = strings.Replace(dbUrl, "/postgres?", "/REPLACEDB?", 1)
dbUrl = strings.Replace(dbUrl, "/initdb?", "/REPLACEDB?", 1)
dbUrl = strings.Replace(dbUrl, "/REPLACEDB?", fmt.Sprintf("/%s?", targetDB), 1)
viper.Set("database.url", dbUrl)
pgInDb := utils.PgInit()
_, err = pgInDb.Exec(fmt.Sprintf("grant all privileges on all tables in schema public to \"%s\"; grant all privileges on all sequences in schema public to \"%s\";", roleName, roleName))
if err != nil {
return err
}
_, err = clientset.CoreV1().Secrets(namespace).Create(ctx, &v1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{
"password": pw,
},
}, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}()
if err != nil {
log.Fatal(err)
}
}
func main() {
if err := root.Execute(); err != nil {
log.Fatal(err)
}
}