mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-09 16:55:38 +02:00
[GSoC 2019] kubectl plugin for the Postgres operator (#579)
* Add a prototype of a kubectl plugin for the Postgres operator Work done by Vineeth Pothulapati <vineethpothulapati@outlook.com> during Google Summer of Code 2019
This commit is contained in:
committed by
Sergey Dudoladov
parent
056b2222a5
commit
fd6a2454e8
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"log"
|
||||
)
|
||||
|
||||
// addDbCmd represents the addDb command
|
||||
var addDbCmd = &cobra.Command{
|
||||
Use: "add-db",
|
||||
Short: "Adds a DB and its owner to a Postgres cluster. The owner role is created if it does not exist",
|
||||
Long: `Adds a new DB to the Postgres cluster. Owner needs to be specified by the -o flag, cluster with -c flag.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
dbName := args[0]
|
||||
dbOwner, _ := cmd.Flags().GetString("owner")
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
addDb(dbName, dbOwner, clusterName)
|
||||
} else {
|
||||
fmt.Println("database name can't be empty. Use kubectl pg add-db [-h | --help] for more info")
|
||||
}
|
||||
|
||||
},
|
||||
Example: `
|
||||
kubectl pg add-db db01 -o owner01 -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
// add db and it's owner to the cluster
|
||||
func addDb(dbName string, dbOwner string, clusterName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
namespace := getCurrentNamespace()
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var dbOwnerExists bool
|
||||
dbUsers := postgresql.Spec.Users
|
||||
for key, _ := range dbUsers {
|
||||
if key == dbOwner {
|
||||
dbOwnerExists = true
|
||||
}
|
||||
}
|
||||
var patch []byte
|
||||
// validating reserved DB names
|
||||
if dbOwnerExists && dbName != "postgres" && dbName != "template0" && dbName != "template1" {
|
||||
patch = dbPatch(dbName, dbOwner)
|
||||
} else if !dbOwnerExists {
|
||||
log.Fatal("The provided db-owner doesn't exist")
|
||||
} else {
|
||||
log.Fatal("The provided db-name is reserved by postgres")
|
||||
}
|
||||
|
||||
updatedPostgres, err := postgresConfig.Postgresqls(namespace).Patch(postgresql.Name, types.MergePatchType, patch, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if updatedPostgres.ResourceVersion != postgresql.ResourceVersion {
|
||||
fmt.Printf("Created new database %s with owner %s in PostgreSQL cluster %s.\n", dbName, dbOwner, updatedPostgres.Name)
|
||||
} else {
|
||||
fmt.Printf("postgresql %s is unchanged.\n", updatedPostgres.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func dbPatch(dbname string, owner string) []byte {
|
||||
ins := map[string]map[string]map[string]string{"spec": {"databases": {dbname: owner}}}
|
||||
patchInstances, err := json.Marshal(ins)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse patch for add-db")
|
||||
}
|
||||
return patchInstances
|
||||
}
|
||||
|
||||
func init() {
|
||||
addDbCmd.Flags().StringP("owner", "o", "", "provide owner of the database.")
|
||||
addDbCmd.Flags().StringP("cluster", "c", "", "provide a postgres cluster name.")
|
||||
rootCmd.AddCommand(addDbCmd)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var allowedPrivileges = []string{"SUPERUSER", "REPLICATION", "INHERIT", "LOGIN", "NOLOGIN", "CREATEROLE", "CREATEDB", "BYPASSURL"}
|
||||
|
||||
// addUserCmd represents the addUser command
|
||||
var addUserCmd = &cobra.Command{
|
||||
Use: "add-user",
|
||||
Short: "Adds a user to the postgres cluster with given privileges",
|
||||
Long: `Adds a user to the postgres cluster. You can add privileges as well with -p flag.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
privileges, _ := cmd.Flags().GetString("privileges")
|
||||
|
||||
if len(args) > 0 {
|
||||
user := args[0]
|
||||
var permissions []string
|
||||
var perms []string
|
||||
|
||||
if privileges != "" {
|
||||
parsedRoles := strings.Replace(privileges, ",", " ", -1)
|
||||
parsedRoles = strings.ToUpper(parsedRoles)
|
||||
permissions = strings.Fields(parsedRoles)
|
||||
var invalidPerms []string
|
||||
|
||||
for _, userPrivilege := range permissions {
|
||||
validPerm := false
|
||||
for _, privilege := range allowedPrivileges {
|
||||
if privilege == userPrivilege {
|
||||
perms = append(perms, userPrivilege)
|
||||
validPerm = true
|
||||
}
|
||||
}
|
||||
if !validPerm {
|
||||
invalidPerms = append(invalidPerms, userPrivilege)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidPerms) > 0 {
|
||||
fmt.Printf("Invalid privileges %s\n", invalidPerms)
|
||||
return
|
||||
}
|
||||
}
|
||||
addUser(user, clusterName, perms)
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
kubectl pg add-user user01 -p login,createdb -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
// add user to the cluster with provided permissions
|
||||
func addUser(user string, clusterName string, permissions []string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
namespace := getCurrentNamespace()
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
setUsers := make(map[string]bool)
|
||||
for _, k := range permissions {
|
||||
setUsers[k] = true
|
||||
}
|
||||
|
||||
if existingRoles, key := postgresql.Spec.Users[user]; key {
|
||||
for _, k := range existingRoles {
|
||||
setUsers[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
Privileges := []string{}
|
||||
for keys, values := range setUsers {
|
||||
if values {
|
||||
Privileges = append(Privileges, keys)
|
||||
}
|
||||
}
|
||||
|
||||
patch := applyUserPatch(user, Privileges)
|
||||
updatedPostgresql, err := postgresConfig.Postgresqls(namespace).Patch(postgresql.Name, types.MergePatchType, patch, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if updatedPostgresql.ResourceVersion != postgresql.ResourceVersion {
|
||||
fmt.Printf("postgresql %s is updated with new user %s and with privileges %s.\n", updatedPostgresql.Name, user, permissions)
|
||||
} else {
|
||||
fmt.Printf("postgresql %s is unchanged.\n", updatedPostgresql.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func applyUserPatch(user string, value []string) []byte {
|
||||
ins := map[string]map[string]map[string][]string{"spec": {"users": {user: value}}}
|
||||
patchInstances, err := json.Marshal(ins)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse number of instances json")
|
||||
}
|
||||
return patchInstances
|
||||
}
|
||||
|
||||
func init() {
|
||||
addUserCmd.Flags().StringP("cluster", "c", "", "add user to the provided cluster.")
|
||||
addUserCmd.Flags().StringP("privileges", "p", "", "add privileges separated by commas without spaces")
|
||||
rootCmd.AddCommand(addUserCmd)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
postgresConstants "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextbeta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"log"
|
||||
)
|
||||
|
||||
// checkCmd represent kubectl pg check.
|
||||
var checkCmd = &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Checks the Postgres operator is installed in the k8s cluster",
|
||||
Long: `Checks that the Postgres CRD is registered in a k8s cluster.
|
||||
This means that the operator pod was able to start normally.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
check()
|
||||
},
|
||||
Example: `
|
||||
kubectl pg check
|
||||
`,
|
||||
}
|
||||
|
||||
// check validates postgresql CRD registered or not.
|
||||
func check() *v1beta1.CustomResourceDefinition {
|
||||
config := getConfig()
|
||||
apiExtClient, err := apiextbeta1.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
crdInfo, err := apiExtClient.CustomResourceDefinitions().Get(postgresConstants.PostgresCRDResouceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if crdInfo.Name == postgresConstants.PostgresCRDResouceName {
|
||||
fmt.Printf("Postgres Operator is installed in the k8s cluster.\n")
|
||||
} else {
|
||||
fmt.Printf("Postgres Operator is not installed in the k8s cluster.\n")
|
||||
}
|
||||
return crdInfo
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(checkCmd)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"log"
|
||||
"os"
|
||||
user "os/user"
|
||||
)
|
||||
|
||||
// connectCmd represents the kubectl pg connect command
|
||||
var connectCmd = &cobra.Command{
|
||||
Use: "connect",
|
||||
Short: "Connects to the shell prompt, psql prompt of postgres cluster",
|
||||
Long: `Connects to the shell prompt, psql prompt of postgres cluster and also to specified replica or master.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
master, _ := cmd.Flags().GetBool("master")
|
||||
replica, _ := cmd.Flags().GetString("replica")
|
||||
psql, _ := cmd.Flags().GetBool("psql")
|
||||
userName, _ := cmd.Flags().GetString("user")
|
||||
dbName, _ := cmd.Flags().GetString("database")
|
||||
|
||||
if psql {
|
||||
if userName == "" {
|
||||
userInfo, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
userName = userInfo.Username
|
||||
}
|
||||
}
|
||||
if dbName == "" {
|
||||
dbName = userName
|
||||
}
|
||||
|
||||
connect(clusterName, master, replica, psql, userName, dbName)
|
||||
},
|
||||
Example: `
|
||||
#connects to the master of postgres cluster
|
||||
kubectl pg connect -c cluster -m
|
||||
|
||||
#connects to the random replica of postgres cluster
|
||||
kubectl pg connect -c cluster
|
||||
|
||||
#connects to the provided replica number of postgres cluster
|
||||
kubectl pg connect -c cluster -r 2
|
||||
|
||||
#connects to psql prompt of master for provided postgres cluster with current shell user
|
||||
kubectl pg connect -c cluster -p -m
|
||||
|
||||
#connects to psql prompt of random replica for provided postgres cluster with provided user and db
|
||||
kubectl pg connect -c cluster -p -u user01 -d db01
|
||||
`,
|
||||
}
|
||||
|
||||
func connect(clusterName string, master bool, replica string, psql bool, user string, dbName string) {
|
||||
config := getConfig()
|
||||
client, er := kubernetes.NewForConfig(config)
|
||||
if er != nil {
|
||||
log.Fatal(er)
|
||||
}
|
||||
|
||||
podName := getPodName(clusterName, master, replica)
|
||||
execRequest := &rest.Request{}
|
||||
|
||||
if psql {
|
||||
execRequest = client.CoreV1().RESTClient().Post().Resource("pods").
|
||||
Name(podName).
|
||||
Namespace(getCurrentNamespace()).
|
||||
SubResource("exec").
|
||||
Param("container", "postgres").
|
||||
Param("command", "psql").
|
||||
Param("command", dbName).
|
||||
Param("command", user).
|
||||
Param("stdin", "true").
|
||||
Param("stdout", "true").
|
||||
Param("stderr", "true").
|
||||
Param("tty", "true")
|
||||
} else {
|
||||
execRequest = client.CoreV1().RESTClient().Post().Resource("pods").
|
||||
Name(podName).
|
||||
Namespace(getCurrentNamespace()).
|
||||
SubResource("exec").
|
||||
Param("container", "postgres").
|
||||
Param("command", "su").
|
||||
Param("command", "postgres").
|
||||
Param("stdin", "true").
|
||||
Param("stdout", "true").
|
||||
Param("stderr", "true").
|
||||
Param("tty", "true")
|
||||
}
|
||||
|
||||
exec, err := remotecommand.NewSPDYExecutor(config, "POST", execRequest.URL())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = exec.Stream(remotecommand.StreamOptions{
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Tty: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
connectCmd.Flags().StringP("cluster", "c", "", "provide the cluster name.")
|
||||
connectCmd.Flags().BoolP("master", "m", false, "connect to master.")
|
||||
connectCmd.Flags().StringP("replica", "r", "", "connect to replica. Specify replica number.")
|
||||
connectCmd.Flags().BoolP("psql", "p", false, "connect to psql prompt.")
|
||||
connectCmd.Flags().StringP("user", "u", "", "provide user.")
|
||||
connectCmd.Flags().StringP("database", "d", "", "provide database name.")
|
||||
rootCmd.AddCommand(connectCmd)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"io/ioutil"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
"log"
|
||||
)
|
||||
|
||||
// createCmd kubectl pg create.
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Creates postgres object using manifest file",
|
||||
Long: `Creates postgres custom resource objects from a manifest file.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fileName, _ := cmd.Flags().GetString("file")
|
||||
create(fileName)
|
||||
},
|
||||
Example: `
|
||||
kubectl pg create -f cluster-manifest.yaml
|
||||
`,
|
||||
}
|
||||
|
||||
// Create postgresql resources.
|
||||
func create(fileName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
ymlFile, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresSql := obj.(*v1.Postgresql)
|
||||
_, err = postgresConfig.Postgresqls(postgresSql.Namespace).Create(postgresSql)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("postgresql %s created.\n", postgresSql.Name)
|
||||
}
|
||||
|
||||
func init() {
|
||||
createCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.")
|
||||
rootCmd.AddCommand(createCmd)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"io/ioutil"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"log"
|
||||
)
|
||||
|
||||
// deleteCmd represents kubectl pg delete.
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Deletes postgresql object by cluster-name/manifest file",
|
||||
Long: `Deletes the postgres objects identified by a manifest file or cluster-name.
|
||||
Deleting the manifest is sufficient to delete the cluster.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
namespace, _ := cmd.Flags().GetString("namespace")
|
||||
file, _ := cmd.Flags().GetString("file")
|
||||
|
||||
if file != "" {
|
||||
deleteByFile(file)
|
||||
} else if namespace != "" {
|
||||
if len(args) != 0 {
|
||||
clusterName := args[0]
|
||||
deleteByName(clusterName, namespace)
|
||||
} else {
|
||||
fmt.Println("cluster name can't be empty")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("use the flag either -n or -f to delete a resource.")
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
#Deleting the postgres cluster using manifest file
|
||||
kubectl pg delete -f cluster-manifest.yaml
|
||||
|
||||
#Deleting the postgres cluster using cluster name in current namespace.
|
||||
kubectl pg delete cluster01
|
||||
|
||||
#Deleting the postgres cluster using cluster name in provided namespace
|
||||
kubectl pg delete cluster01 -n namespace01
|
||||
`,
|
||||
}
|
||||
|
||||
func deleteByFile(file string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ymlFile, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresSql := obj.(*v1.Postgresql)
|
||||
_, err = postgresConfig.Postgresqls(postgresSql.Namespace).Get(postgresSql.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Postgresql %s not found with the provided namespace %s : %s \n", postgresSql.Name, postgresSql.Namespace, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Are you sure you want to remove this PostgreSQL cluster? If so, please type (%s/%s) and hit Enter\n", postgresSql.Namespace, postgresSql.Name)
|
||||
|
||||
confirmAction(postgresSql.Name, postgresSql.Namespace)
|
||||
err = postgresConfig.Postgresqls(postgresSql.Namespace).Delete(postgresSql.Name, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Postgresql %s deleted from %s.\n", postgresSql.Name, postgresSql.Namespace)
|
||||
}
|
||||
|
||||
func deleteByName(clusterName string, namespace string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = postgresConfig.Postgresqls(namespace).Get(clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Postgresql %s not found with the provided namespace %s : %s \n", clusterName, namespace, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Are you sure you want to remove this PostgreSQL cluster? If so, please type (%s/%s) and hit Enter\n", namespace, clusterName)
|
||||
|
||||
confirmAction(clusterName, namespace)
|
||||
err = postgresConfig.Postgresqls(namespace).Delete(clusterName, &metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Postgresql %s deleted from %s.\n", clusterName, namespace)
|
||||
}
|
||||
|
||||
func init() {
|
||||
namespace := getCurrentNamespace()
|
||||
deleteCmd.Flags().StringP("namespace", "n", namespace, "namespace of the cluster to be deleted.")
|
||||
deleteCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.")
|
||||
rootCmd.AddCommand(deleteCmd)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// extVolumeCmd represents the extVolume command
|
||||
var extVolumeCmd = &cobra.Command{
|
||||
Use: "ext-volume",
|
||||
Short: "Increases the volume size of a given Postgres cluster",
|
||||
Long: `Extends the volume of the postgres cluster. But volume cannot be shrinked.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
if len(args) > 0 {
|
||||
volume := args[0]
|
||||
extVolume(volume, clusterName)
|
||||
} else {
|
||||
fmt.Println("please enter the cluster name with -c flag & volume in desired units")
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
#Extending the volume size of provided cluster
|
||||
kubectl pg ext-volume 2Gi -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
// extend volume with provided size & cluster name
|
||||
func extVolume(increasedVolumeSize string, clusterName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
namespace := getCurrentNamespace()
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatalf("hii %v", err)
|
||||
}
|
||||
|
||||
oldSize, err := resource.ParseQuantity(postgresql.Spec.Volume.Size)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
newSize, err := resource.ParseQuantity(increasedVolumeSize)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = strconv.Atoi(newSize.String())
|
||||
if err == nil {
|
||||
fmt.Println("provide the valid volume size with respective units i.e Ki, Mi, Gi")
|
||||
return
|
||||
}
|
||||
|
||||
if newSize.Value() > oldSize.Value() {
|
||||
patchInstances := volumePatch(newSize)
|
||||
response, err := postgresConfig.Postgresqls(namespace).Patch(postgresql.Name, types.MergePatchType, patchInstances, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if postgresql.ResourceVersion != response.ResourceVersion {
|
||||
fmt.Printf("%s volume is extended to %s.\n", response.Name, increasedVolumeSize)
|
||||
} else {
|
||||
fmt.Printf("%s volume %s is unchanged.\n", response.Name, postgresql.Spec.Volume.Size)
|
||||
}
|
||||
} else if newSize.Value() == oldSize.Value() {
|
||||
fmt.Println("volume already has the desired size.")
|
||||
} else {
|
||||
fmt.Printf("volume %s size cannot be shrinked.\n", postgresql.Spec.Volume.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func volumePatch(volume resource.Quantity) []byte {
|
||||
patchData := map[string]map[string]map[string]resource.Quantity{"spec": {"volume": {"size": volume}}}
|
||||
patch, err := json.Marshal(patchData)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse patch to extend volume")
|
||||
}
|
||||
return patch
|
||||
}
|
||||
|
||||
func init() {
|
||||
extVolumeCmd.Flags().StringP("cluster", "c", "", "provide cluster name.")
|
||||
rootCmd.AddCommand(extVolumeCmd)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
TrimCreateTimestamp = 6000000000
|
||||
)
|
||||
|
||||
// listCmd represents kubectl pg list.
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Lists all the resources of kind postgresql",
|
||||
Long: `Lists all the info specific to postgresql objects.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
allNamespaces, _ := cmd.Flags().GetBool("all-namespaces")
|
||||
namespace, _ := cmd.Flags().GetString("namespace")
|
||||
if allNamespaces {
|
||||
list(allNamespaces, "")
|
||||
} else {
|
||||
list(allNamespaces, namespace)
|
||||
}
|
||||
|
||||
},
|
||||
Example: `
|
||||
#Lists postgres cluster in current namespace
|
||||
kubectl pg list
|
||||
|
||||
#Lists postgres clusters in all namespaces
|
||||
kubectl pg list -A
|
||||
`,
|
||||
}
|
||||
|
||||
// list command to list postgres.
|
||||
func list(allNamespaces bool, namespace string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var listPostgres *v1.PostgresqlList
|
||||
listPostgres, err = postgresConfig.Postgresqls(namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(listPostgres.Items) == 0 {
|
||||
if namespace != "" {
|
||||
fmt.Printf("No Postgresql clusters found in namespace: %v\n", namespace)
|
||||
} else {
|
||||
fmt.Println("No Postgresql clusters found in all namespaces")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if allNamespaces {
|
||||
listAll(listPostgres)
|
||||
} else {
|
||||
listWithNamespace(listPostgres)
|
||||
}
|
||||
}
|
||||
|
||||
func listAll(listPostgres *v1.PostgresqlList) {
|
||||
template := "%-32s%-16s%-12s%-12s%-12s%-12s%-12s\n"
|
||||
fmt.Printf(template, "NAME", "STATUS", "INSTANCES", "VERSION", "AGE", "VOLUME", "NAMESPACE")
|
||||
for _, pgObjs := range listPostgres.Items {
|
||||
fmt.Printf(template, pgObjs.Name, pgObjs.Status.PostgresClusterStatus, strconv.Itoa(int(pgObjs.Spec.NumberOfInstances)),
|
||||
pgObjs.Spec.PgVersion, time.Since(pgObjs.CreationTimestamp.Time).Truncate(TrimCreateTimestamp), pgObjs.Spec.Size, pgObjs.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
func listWithNamespace(listPostgres *v1.PostgresqlList) {
|
||||
template := "%-32s%-16s%-12s%-12s%-12s%-12s\n"
|
||||
fmt.Printf(template, "NAME", "STATUS", "INSTANCES", "VERSION", "AGE", "VOLUME")
|
||||
for _, pgObjs := range listPostgres.Items {
|
||||
fmt.Printf(template, pgObjs.Name, pgObjs.Status.PostgresClusterStatus, strconv.Itoa(int(pgObjs.Spec.NumberOfInstances)),
|
||||
pgObjs.Spec.PgVersion, time.Since(pgObjs.CreationTimestamp.Time).Truncate(TrimCreateTimestamp), pgObjs.Spec.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
listCmd.Flags().BoolP("all-namespaces", "A", false, "list pg resources across all namespaces.")
|
||||
listCmd.Flags().StringP("namespace", "n", getCurrentNamespace(), "provide the namespace")
|
||||
rootCmd.AddCommand(listCmd)
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// logsCmd represents the logs command
|
||||
var logsCmd = &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "This will fetch the logs of the specified postgres cluster & postgres operator",
|
||||
Long: `Fetches the logs of the postgres cluster (i.e master( with -m flag) & replica with (-r 1 pod number) and without -m or -r connects to random replica`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
opLogs, _ := cmd.Flags().GetBool("operator")
|
||||
clusterName, _ := cmd.Flags().GetString("cluster")
|
||||
master, _ := cmd.Flags().GetBool("master")
|
||||
replica, _ := cmd.Flags().GetString("replica")
|
||||
|
||||
if opLogs {
|
||||
operatorLogs()
|
||||
} else {
|
||||
clusterLogs(clusterName, master, replica)
|
||||
}
|
||||
},
|
||||
Example: `
|
||||
#Fetch the logs of the postgres operator
|
||||
kubectl pg logs -o
|
||||
|
||||
#Fetch the logs of the master for provided cluster
|
||||
kubectl pg logs -c cluster01 -m
|
||||
|
||||
#Fetch the logs of the random replica for provided cluster
|
||||
kubectl pg logs -c cluster01
|
||||
|
||||
#Fetch the logs of the provided replica number of the cluster
|
||||
kubectl pg logs -c cluster01 -r 3
|
||||
`,
|
||||
}
|
||||
|
||||
func operatorLogs() {
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operator := getPostgresOperator(client)
|
||||
allPods, err := client.CoreV1().Pods(operator.Namespace).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var operatorPodName string
|
||||
for _, pod := range allPods.Items {
|
||||
for key, value := range pod.Labels {
|
||||
if (key == "name" && value == OperatorName) || (key == "app.kubernetes.io/name" && value == OperatorName) {
|
||||
operatorPodName = pod.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execRequest := client.CoreV1().RESTClient().Get().Namespace(operator.Namespace).
|
||||
Name(operatorPodName).
|
||||
Resource("pods").
|
||||
SubResource("log").
|
||||
Param("follow", "--follow").
|
||||
Param("container", OperatorName)
|
||||
|
||||
readCloser, err := execRequest.Stream()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer readCloser.Close()
|
||||
_, err = io.Copy(os.Stdout, readCloser)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func clusterLogs(clusterName string, master bool, replica string) {
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
podName := getPodName(clusterName, master, replica)
|
||||
execRequest := client.CoreV1().RESTClient().Get().Namespace(getCurrentNamespace()).
|
||||
Name(podName).
|
||||
Resource("pods").
|
||||
SubResource("log").
|
||||
Param("follow", "--follow").
|
||||
Param("container", "postgres")
|
||||
|
||||
readCloser, err := execRequest.Stream()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer readCloser.Close()
|
||||
_, err = io.Copy(os.Stdout, readCloser)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(logsCmd)
|
||||
logsCmd.Flags().BoolP("operator", "o", false, "logs of operator")
|
||||
logsCmd.Flags().StringP("cluster", "c", "", "logs for the provided cluster")
|
||||
logsCmd.Flags().BoolP("master", "m", false, "Patroni logs of master")
|
||||
logsCmd.Flags().StringP("replica", "r", "", "Patroni logs of replica. Specify replica number.")
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "kubectl-pg",
|
||||
Short: "kubectl plugin for the Zalando Postgres operator.",
|
||||
Long: `kubectl pg plugin for interaction with Zalando postgres operator.`,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
viper.SetDefault("author", "Vineeth Pothulapati <vineethpothulapati@outlook.com>")
|
||||
viper.SetDefault("license", "mit")
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// scaleCmd represents the scale command
|
||||
var scaleCmd = &cobra.Command{
|
||||
Use: "scale",
|
||||
Short: "Add/remove pods to a Postgres cluster",
|
||||
Long: `Scales the postgres objects using cluster-name.
|
||||
Scaling to 0 leads to down time.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clusterName, err := cmd.Flags().GetString("cluster")
|
||||
namespace, err := cmd.Flags().GetString("namespace")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
numberOfInstances, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
scale(int32(numberOfInstances), clusterName, namespace)
|
||||
} else {
|
||||
fmt.Println("Please enter number of instances to scale.")
|
||||
}
|
||||
|
||||
},
|
||||
Example: `
|
||||
#Usage
|
||||
kubectl pg scale [NUMBER-OF-INSTANCES] -c [CLUSTER-NAME] -n [NAMESPACE]
|
||||
|
||||
#Scales the number of instances of the provided cluster
|
||||
kubectl pg scale 5 -c cluster01
|
||||
`,
|
||||
}
|
||||
|
||||
func scale(numberOfInstances int32, clusterName string, namespace string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresql, err := postgresConfig.Postgresqls(namespace).Get(clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
minInstances, maxInstances := allowedMinMaxInstances(config)
|
||||
|
||||
if minInstances == -1 && maxInstances == -1 {
|
||||
postgresql.Spec.NumberOfInstances = numberOfInstances
|
||||
} else if numberOfInstances <= maxInstances && numberOfInstances >= minInstances {
|
||||
postgresql.Spec.NumberOfInstances = numberOfInstances
|
||||
} else if minInstances == -1 && numberOfInstances < postgresql.Spec.NumberOfInstances ||
|
||||
maxInstances == -1 && numberOfInstances > postgresql.Spec.NumberOfInstances {
|
||||
postgresql.Spec.NumberOfInstances = numberOfInstances
|
||||
} else {
|
||||
log.Fatalf("cannot scale to the provided instances as they don't adhere to MIN_INSTANCES: %v and MAX_INSTANCES: %v provided in configmap or operatorconfiguration", maxInstances, minInstances)
|
||||
}
|
||||
|
||||
if numberOfInstances == 0 {
|
||||
fmt.Printf("Scaling to zero leads to down time. please type %s/%s and hit Enter this serves to confirm the action\n", namespace, clusterName)
|
||||
confirmAction(clusterName, namespace)
|
||||
}
|
||||
|
||||
patchInstances := scalePatch(numberOfInstances)
|
||||
UpdatedPostgres, err := postgresConfig.Postgresqls(namespace).Patch(postgresql.Name, types.MergePatchType, patchInstances, "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if UpdatedPostgres.ResourceVersion != postgresql.ResourceVersion {
|
||||
fmt.Printf("scaled postgresql %s/%s to %d instances\n", UpdatedPostgres.Namespace, UpdatedPostgres.Name, UpdatedPostgres.Spec.NumberOfInstances)
|
||||
return
|
||||
}
|
||||
fmt.Printf("postgresql %s is unchanged.\n", postgresql.Name)
|
||||
}
|
||||
|
||||
func scalePatch(value int32) []byte {
|
||||
instances := map[string]map[string]int32{"spec": {"numberOfInstances": value}}
|
||||
patchInstances, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
log.Fatal(err, "unable to parse patch for scale")
|
||||
}
|
||||
return patchInstances
|
||||
}
|
||||
|
||||
func allowedMinMaxInstances(config *rest.Config) (int32, int32) {
|
||||
k8sClient, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var operator *v1.Deployment
|
||||
operator = getPostgresOperator(k8sClient)
|
||||
|
||||
operatorContainer := operator.Spec.Template.Spec.Containers
|
||||
var configMapName, operatorConfigName string
|
||||
// -1 indicates no limitations for min/max instances
|
||||
minInstances := -1
|
||||
maxInstances := -1
|
||||
for _, envData := range operatorContainer[0].Env {
|
||||
if envData.Name == "CONFIG_MAP_NAME" {
|
||||
configMapName = envData.Value
|
||||
}
|
||||
if envData.Name == "POSTGRES_OPERATOR_CONFIGURATION_OBJECT" {
|
||||
operatorConfigName = envData.Value
|
||||
}
|
||||
}
|
||||
|
||||
if operatorConfigName == "" {
|
||||
configMap, err := k8sClient.CoreV1().ConfigMaps(operator.Namespace).Get(configMapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
configMapData := configMap.Data
|
||||
for key, value := range configMapData {
|
||||
if key == "min_instances" {
|
||||
minInstances, err = strconv.Atoi(value)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid min instances in configmap %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if key == "max_instances" {
|
||||
maxInstances, err = strconv.Atoi(value)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid max instances in configmap %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if configMapName == "" {
|
||||
pgClient, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operatorConfig, err := pgClient.OperatorConfigurations(operator.Namespace).Get(operatorConfigName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatalf("unable to read operator configuration %v", err)
|
||||
}
|
||||
|
||||
minInstances = int(operatorConfig.Configuration.MinInstances)
|
||||
maxInstances = int(operatorConfig.Configuration.MaxInstances)
|
||||
}
|
||||
return int32(minInstances), int32(maxInstances)
|
||||
}
|
||||
|
||||
func init() {
|
||||
namespace := getCurrentNamespace()
|
||||
scaleCmd.Flags().StringP("namespace", "n", namespace, "namespace of the cluster to be scaled")
|
||||
scaleCmd.Flags().StringP("cluster", "c", "", "provide the cluster name.")
|
||||
rootCmd.AddCommand(scaleCmd)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
"io/ioutil"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"log"
|
||||
)
|
||||
|
||||
// updateCmd represents kubectl pg update
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Updates postgresql object using manifest file",
|
||||
Long: `Updates the state of cluster using manifest file to reflect the changes on the cluster.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fileName, _ := cmd.Flags().GetString("file")
|
||||
updatePgResources(fileName)
|
||||
},
|
||||
Example: `
|
||||
#usage
|
||||
kubectl pg update -f [File-NAME]
|
||||
|
||||
#update the postgres cluster with updated manifest file
|
||||
kubectl pg update -f cluster-manifest.yaml
|
||||
`,
|
||||
}
|
||||
|
||||
// Update postgresql resources.
|
||||
func updatePgResources(fileName string) {
|
||||
config := getConfig()
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
ymlFile, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
newPostgresObj := obj.(*v1.Postgresql)
|
||||
oldPostgresObj, err := postgresConfig.Postgresqls(newPostgresObj.Namespace).Get(newPostgresObj.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
newPostgresObj.ResourceVersion = oldPostgresObj.ResourceVersion
|
||||
response, err := postgresConfig.Postgresqls(newPostgresObj.Namespace).Update(newPostgresObj)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if newPostgresObj.ResourceVersion != response.ResourceVersion {
|
||||
fmt.Printf("postgresql %s updated.\n", response.Name)
|
||||
} else {
|
||||
fmt.Printf("postgresql %s is unchanged.\n", response.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
updateCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.")
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
OperatorName = "postgres-operator"
|
||||
DefaultNamespace = "default"
|
||||
)
|
||||
|
||||
func getConfig() *restclient.Config {
|
||||
var kubeconfig *string
|
||||
var config *restclient.Config
|
||||
envKube := os.Getenv("KUBECONFIG")
|
||||
if envKube != "" {
|
||||
kubeconfig = &envKube
|
||||
} else {
|
||||
if home := homedir.HomeDir(); home != "" {
|
||||
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
|
||||
} else {
|
||||
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
|
||||
}
|
||||
}
|
||||
flag.Parse()
|
||||
var err error
|
||||
config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func getCurrentNamespace() string {
|
||||
namespace, err := exec.Command("kubectl", "config", "view", "--minify", "--output", "jsonpath={..namespace}").CombinedOutput()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
currentNamespace := string(namespace)
|
||||
if currentNamespace == "" {
|
||||
currentNamespace = DefaultNamespace
|
||||
}
|
||||
return currentNamespace
|
||||
}
|
||||
|
||||
func confirmAction(clusterName string, namespace string) {
|
||||
for {
|
||||
confirmClusterDetails := ""
|
||||
_, err := fmt.Scan(&confirmClusterDetails)
|
||||
if err != nil {
|
||||
log.Fatalf("couldn't get confirmation from the user %v", err)
|
||||
}
|
||||
clusterDetails := strings.Split(confirmClusterDetails, "/")
|
||||
if clusterDetails[0] != namespace || clusterDetails[1] != clusterName {
|
||||
fmt.Printf("cluster name or namespace doesn't match. Please re-enter %s/%s\nHint: Press (ctrl+c) to exit\n", namespace, clusterName)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPodName(clusterName string, master bool, replicaNumber string) string {
|
||||
config := getConfig()
|
||||
client, er := kubernetes.NewForConfig(config)
|
||||
if er != nil {
|
||||
log.Fatal(er)
|
||||
}
|
||||
|
||||
postgresConfig, err := PostgresqlLister.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
postgresCluster, err := postgresConfig.Postgresqls(getCurrentNamespace()).Get(clusterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
numOfInstances := postgresCluster.Spec.NumberOfInstances
|
||||
var podName string
|
||||
var podRole string
|
||||
replica := clusterName + "-" + replicaNumber
|
||||
|
||||
for ins := 0; ins < int(numOfInstances); ins++ {
|
||||
pod, err := client.CoreV1().Pods(getCurrentNamespace()).Get(clusterName+"-"+strconv.Itoa(ins), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
podRole = pod.Labels["spilo-role"]
|
||||
if podRole == "master" && master {
|
||||
podName = pod.Name
|
||||
fmt.Printf("connected to %s with pod name as %s\n", podRole, podName)
|
||||
break
|
||||
} else if podRole == "replica" && !master && (pod.Name == replica || replicaNumber == "") {
|
||||
podName = pod.Name
|
||||
fmt.Printf("connected to %s with pod name as %s\n", podRole, podName)
|
||||
break
|
||||
}
|
||||
}
|
||||
if podName == "" {
|
||||
log.Fatal("Provided replica doesn't exist")
|
||||
}
|
||||
return podName
|
||||
}
|
||||
|
||||
func getPostgresOperator(k8sClient *kubernetes.Clientset) *v1.Deployment {
|
||||
var operator *v1.Deployment
|
||||
operator, err := k8sClient.AppsV1().Deployments(getCurrentNamespace()).Get(OperatorName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return operator
|
||||
}
|
||||
|
||||
allDeployments := k8sClient.AppsV1().Deployments("")
|
||||
listDeployments, err := allDeployments.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, deployment := range listDeployments.Items {
|
||||
if deployment.Name == OperatorName {
|
||||
operator = deployment.DeepCopy()
|
||||
break
|
||||
} else {
|
||||
for key, value := range deployment.Labels {
|
||||
if key == "app.kubernetes.io/name" && value == OperatorName {
|
||||
operator = deployment.DeepCopy()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return operator
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright © 2019 Vineeth Pothulapati <vineethpothulapati@outlook.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var KubectlPgVersion string = "1.0"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "version of kubectl-pg & postgres-operator",
|
||||
Long: `version of kubectl-pg and current running postgres-operator`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
namespace, err := cmd.Flags().GetString("namespace")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
version(namespace)
|
||||
},
|
||||
Example: `
|
||||
#Lists the version of kubectl pg plugin and postgres operator in current namespace
|
||||
kubectl pg version
|
||||
|
||||
#Lists the version of kubectl pg plugin and postgres operator in provided namespace
|
||||
kubectl pg version -n namespace01
|
||||
`,
|
||||
}
|
||||
|
||||
func version(namespace string) {
|
||||
fmt.Printf("kubectl-pg: %s\n", KubectlPgVersion)
|
||||
|
||||
config := getConfig()
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
operatorDeployment := getPostgresOperator(client)
|
||||
if operatorDeployment.Name == "" {
|
||||
log.Fatal("make sure zalando's postgres operator is running")
|
||||
}
|
||||
operatorImage := operatorDeployment.Spec.Template.Spec.Containers[0].Image
|
||||
imageDetails := strings.Split(operatorImage, ":")
|
||||
imageSplit := len(imageDetails)
|
||||
imageVersion := imageDetails[imageSplit-1]
|
||||
fmt.Printf("Postgres-Operator: %s\n", imageVersion)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
versionCmd.Flags().StringP("namespace", "n", DefaultNamespace, "provide the namespace.")
|
||||
}
|
||||
Reference in New Issue
Block a user