github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/migration/base.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 migration 21 22 import ( 23 "context" 24 "fmt" 25 "os" 26 "strings" 27 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/client-go/dynamic" 33 cmdutil "k8s.io/kubectl/pkg/cmd/util" 34 35 "github.com/1aal/kubeblocks/pkg/cli/types" 36 migrationv1 "github.com/1aal/kubeblocks/pkg/cli/types/migrationapi" 37 "github.com/1aal/kubeblocks/pkg/cli/util" 38 ) 39 40 const ( 41 MigrationTaskLabel = "datamigration.apecloud.io/migrationtask" 42 MigrationTaskStepAnnotation = "datamigration.apecloud.io/step" 43 SerialJobOrderAnnotation = "common.apecloud.io/serial_job_order" 44 ) 45 46 const ( 47 invalidMigrationCrdAdvice = "to use migration-related functions, please ensure that the addon of migration is enabled, use: 'kbcli addon enable migration' to enable the addon" 48 ) 49 50 // Endpoint 51 // Todo: For the source or target is cluster in KubeBlocks. A better way is to get secret from {$clustername}-conn-credential, so the username, password, addresses can be omitted 52 53 type EndpointModel struct { 54 UserName string `json:"userName"` 55 Password string `json:"password"` 56 Address string `json:"address"` 57 // +optional 58 Database string `json:"databaseName,omitempty"` 59 } 60 61 func (e *EndpointModel) BuildFromStr(msgArr *[]string, endpointStr string) error { 62 if endpointStr == "" { 63 BuildErrorMsg(msgArr, "endpoint string cannot be empty") 64 return nil 65 } 66 e.clear() 67 endpointStr = strings.TrimSpace(endpointStr) 68 accountURLPair := strings.Split(endpointStr, "@") 69 if len(accountURLPair) != 2 { 70 BuildErrorMsg(msgArr, "endpoint may not contain account info") 71 return nil 72 } 73 accountPair := strings.Split(accountURLPair[0], ":") 74 if len(accountPair) != 2 { 75 BuildErrorMsg(msgArr, "the account info in endpoint is invalid, should be like \"user:123456\"") 76 return nil 77 } 78 e.UserName = accountPair[0] 79 e.Password = accountPair[1] 80 if strings.LastIndex(accountURLPair[1], "/") != -1 { 81 addressDatabasePair := strings.Split(accountURLPair[1], "/") 82 e.Address = strings.Join(addressDatabasePair[:len(addressDatabasePair)-1], "/") 83 e.Database = addressDatabasePair[len(addressDatabasePair)-1] 84 } else { 85 e.Address = accountURLPair[1] 86 } 87 return nil 88 } 89 90 func (e *EndpointModel) clear() { 91 e.Address = "" 92 e.Password = "" 93 e.UserName = "" 94 e.Database = "" 95 } 96 97 // Migration Object 98 99 type MigrationObjectModel struct { 100 WhiteList []DBObjectExpress `json:"whiteList"` 101 } 102 103 type DBObjectExpress struct { 104 SchemaName string `json:"schemaName"` 105 // +optional 106 IsAll bool `json:"isAll"` 107 // +optional 108 TableList []TableObjectExpress `json:"tableList"` 109 } 110 111 type TableObjectExpress struct { 112 TableName string `json:"tableName"` 113 // +optional 114 IsAll bool `json:"isAll"` 115 } 116 117 func (m *MigrationObjectModel) BuildFromStrs(errMsgArr *[]string, objStrs []string) error { 118 if len(objStrs) == 0 { 119 BuildErrorMsg(errMsgArr, "migration object cannot be empty") 120 return nil 121 } 122 for _, str := range objStrs { 123 msg := "" 124 if str == "" { 125 msg = "the database or database.table in migration object cannot be empty" 126 } 127 dbTablePair := strings.Split(str, ".") 128 if len(dbTablePair) > 2 { 129 msg = fmt.Sprintf("[%s] is not a valid database or database.table", str) 130 } 131 if msg != "" { 132 BuildErrorMsg(errMsgArr, msg) 133 return nil 134 } 135 if len(dbTablePair) == 1 { 136 m.WhiteList = append(m.WhiteList, DBObjectExpress{ 137 SchemaName: str, 138 IsAll: true, 139 }) 140 } else { 141 dbObjPoint, err := m.ContainSchema(dbTablePair[0]) 142 if err != nil { 143 return err 144 } 145 if dbObjPoint != nil { 146 dbObjPoint.TableList = append(dbObjPoint.TableList, TableObjectExpress{ 147 TableName: dbTablePair[1], 148 IsAll: true, 149 }) 150 } else { 151 m.WhiteList = append(m.WhiteList, DBObjectExpress{ 152 SchemaName: dbTablePair[0], 153 TableList: []TableObjectExpress{{ 154 TableName: dbTablePair[1], 155 IsAll: true, 156 }}, 157 }) 158 } 159 } 160 } 161 return nil 162 } 163 164 func (m *MigrationObjectModel) ContainSchema(schemaName string) (*DBObjectExpress, error) { 165 for i := 0; i < len(m.WhiteList); i++ { 166 if m.WhiteList[i].SchemaName == schemaName { 167 return &m.WhiteList[i], nil 168 } 169 } 170 return nil, nil 171 } 172 173 func CliStepChangeToStructure() (map[string]string, []string) { 174 validStepMap := map[string]string{ 175 migrationv1.CliStepPreCheck.String(): migrationv1.CliStepPreCheck.String(), 176 migrationv1.CliStepInitStruct.String(): migrationv1.CliStepInitStruct.String(), 177 migrationv1.CliStepInitData.String(): migrationv1.CliStepInitData.String(), 178 migrationv1.CliStepCdc.String(): migrationv1.CliStepCdc.String(), 179 } 180 validStepKey := make([]string, 0) 181 for k := range validStepMap { 182 validStepKey = append(validStepKey, k) 183 } 184 return validStepMap, validStepKey 185 } 186 187 type TaskTypeEnum string 188 189 const ( 190 Initialization TaskTypeEnum = "initialization" 191 InitializationAndCdc TaskTypeEnum = "initialization-and-cdc" // default value 192 ) 193 194 func (s TaskTypeEnum) String() string { 195 return string(s) 196 } 197 198 func IsMigrationCrdValidWithDynamic(dynamic *dynamic.Interface) (bool, error) { 199 resource := types.CustomResourceDefinitionGVR() 200 if err := APIResource(dynamic, &resource, "migrationtasks.datamigration.apecloud.io", "", nil); err != nil { 201 return false, err 202 } 203 if err := APIResource(dynamic, &resource, "migrationtemplates.datamigration.apecloud.io", "", nil); err != nil { 204 return false, err 205 } 206 if err := APIResource(dynamic, &resource, "serialjobs.common.apecloud.io", "", nil); err != nil { 207 return false, err 208 } 209 return true, nil 210 } 211 212 func PrintCrdInvalidError(err error) { 213 if err == nil { 214 return 215 } 216 if !errors.IsNotFound(err) { 217 util.CheckErr(err) 218 } 219 fmt.Fprintf(os.Stderr, "hint: %s\n", invalidMigrationCrdAdvice) 220 os.Exit(cmdutil.DefaultErrorExitCode) 221 } 222 223 func IsMigrationCrdValidWithFactory(factory cmdutil.Factory) (bool, error) { 224 dynamic, err := factory.DynamicClient() 225 if err != nil { 226 return false, err 227 } 228 return IsMigrationCrdValidWithDynamic(&dynamic) 229 } 230 231 func APIResource(dynamic *dynamic.Interface, resource *schema.GroupVersionResource, name string, namespace string, res interface{}) error { 232 obj, err := (*dynamic).Resource(*resource).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{}, "") 233 if err != nil { 234 return err 235 } 236 if res != nil { 237 return runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, res) 238 } 239 return nil 240 } 241 242 func BuildErrorMsg(msgArr *[]string, msg string) { 243 if *msgArr == nil { 244 *msgArr = make([]string, 1) 245 } 246 *msgArr = append(*msgArr, msg) 247 } 248 249 func BuildInitializationStepsOrder(task *migrationv1.MigrationTask, template *migrationv1.MigrationTemplate) []string { 250 stepMap := make(map[string]string) 251 for _, taskStep := range task.Spec.Initialization.Steps { 252 stepMap[taskStep.String()] = taskStep.String() 253 } 254 resultArr := make([]string, 0) 255 for _, stepModel := range template.Spec.Initialization.Steps { 256 if stepMap[stepModel.Step.String()] != "" { 257 resultArr = append(resultArr, stepModel.Step.CliString()) 258 } 259 } 260 return resultArr 261 }