github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/utils/transferconfigbase.go (about) 1 package utils 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strings" 8 9 "github.com/jfrog/gofrog/datastructures" 10 "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" 11 "github.com/jfrog/jfrog-cli-core/v2/utils/config" 12 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 13 "github.com/jfrog/jfrog-client-go/access" 14 "github.com/jfrog/jfrog-client-go/artifactory" 15 "github.com/jfrog/jfrog-client-go/artifactory/services" 16 clientUtils "github.com/jfrog/jfrog-client-go/utils" 17 "github.com/jfrog/jfrog-client-go/utils/errorutils" 18 "github.com/jfrog/jfrog-client-go/utils/io/httputils" 19 "github.com/jfrog/jfrog-client-go/utils/log" 20 "golang.org/x/exp/slices" 21 ) 22 23 const ( 24 MinJFrogProjectsArtifactoryVersion = "7.0.0" 25 defaultAdminUsername = "admin" 26 defaultAdminPassword = "password" 27 ) 28 29 type TransferConfigBase struct { 30 SourceServerDetails *config.ServerDetails 31 TargetServerDetails *config.ServerDetails 32 SourceArtifactoryManager artifactory.ArtifactoryServicesManager 33 TargetArtifactoryManager artifactory.ArtifactoryServicesManager 34 SourceAccessManager *access.AccessServicesManager 35 TargetAccessManager *access.AccessServicesManager 36 IncludeReposPatterns []string 37 ExcludeReposPatterns []string 38 FederatedMembersRemoved bool 39 } 40 41 func NewTransferConfigBase(sourceServer, targetServer *config.ServerDetails) *TransferConfigBase { 42 return &TransferConfigBase{ 43 SourceServerDetails: sourceServer, 44 TargetServerDetails: targetServer, 45 } 46 } 47 48 func (tcb *TransferConfigBase) SetIncludeReposPatterns(includeReposPatterns []string) *TransferConfigBase { 49 tcb.IncludeReposPatterns = includeReposPatterns 50 return tcb 51 } 52 53 func (tcb *TransferConfigBase) SetExcludeReposPatterns(excludeReposPatterns []string) *TransferConfigBase { 54 tcb.ExcludeReposPatterns = excludeReposPatterns 55 return tcb 56 } 57 58 func (tcb *TransferConfigBase) GetRepoFilter() *utils.IncludeExcludeFilter { 59 return &utils.IncludeExcludeFilter{ 60 IncludePatterns: tcb.IncludeReposPatterns, 61 ExcludePatterns: tcb.ExcludeReposPatterns, 62 } 63 } 64 65 func (tcb *TransferConfigBase) CreateServiceManagers(dryRun bool) (err error) { 66 if tcb.SourceArtifactoryManager, err = utils.CreateServiceManager(tcb.SourceServerDetails, -1, 0, dryRun); err != nil { 67 return 68 } 69 if tcb.TargetArtifactoryManager, err = utils.CreateServiceManager(tcb.TargetServerDetails, -1, 0, dryRun); err != nil { 70 return 71 } 72 if tcb.SourceAccessManager, err = utils.CreateAccessServiceManager(tcb.SourceServerDetails, false); err != nil { 73 return 74 } 75 if tcb.TargetAccessManager, err = utils.CreateAccessServiceManager(tcb.TargetServerDetails, false); err != nil { 76 return 77 } 78 return 79 } 80 81 // Make sure that the server is configured with a valid admin Access Token. 82 // serverDetails - The server to check 83 // accessManager - Access Manager to run ping 84 func (tcb *TransferConfigBase) ValidateAccessServerConnection(serverDetails *config.ServerDetails, accessManager *access.AccessServicesManager) error { 85 if serverDetails.Password != "" { 86 return errorutils.CheckErrorf("it looks like you configured the '%[1]s' instance with username and password.\n"+ 87 "This command can be used with admin Access Token only.\n"+ 88 "Please use the 'jf c edit %[1]s' command to configure the Access Token, and then re-run the command", serverDetails.ServerId) 89 } 90 91 if _, err := accessManager.Ping(); err != nil { 92 return errors.Join(err, fmt.Errorf("the '%[1]s' instance Access Token is not valid. Please provide a valid access token by running the 'jf c edit %[1]s'", serverDetails.ServerId)) 93 } 94 return nil 95 } 96 97 // Make sure source and target Artifactory URLs are different. 98 func (tcb *TransferConfigBase) ValidateDifferentServers() error { 99 // Avoid exporting and importing to the same server 100 log.Info("Verifying source and target servers are different...") 101 if tcb.SourceServerDetails.GetArtifactoryUrl() == tcb.TargetServerDetails.GetArtifactoryUrl() { 102 return errorutils.CheckErrorf("The source and target Artifactory servers are identical, but should be different.") 103 } 104 105 return nil 106 } 107 108 // Create a map between the repository types to the list of repositories to transfer. 109 func (tcb *TransferConfigBase) GetSelectedRepositories() (map[utils.RepoType][]services.RepositoryDetails, error) { 110 allTargetRepos, err := tcb.getAllTargetRepositories() 111 if err != nil { 112 return nil, err 113 } 114 115 result := make(map[utils.RepoType][]services.RepositoryDetails, len(utils.RepoTypes)+1) 116 sourceRepos, err := tcb.SourceArtifactoryManager.GetAllRepositories() 117 if err != nil { 118 return nil, err 119 } 120 121 includeExcludeFilter := tcb.GetRepoFilter() 122 for _, sourceRepo := range *sourceRepos { 123 if shouldIncludeRepo, err := includeExcludeFilter.ShouldIncludeRepository(sourceRepo.Key); err != nil { 124 return nil, err 125 } else if shouldIncludeRepo { 126 if allTargetRepos.Exists(sourceRepo.Key) { 127 log.Info("Repository '" + sourceRepo.Key + "' already exists in the target Artifactory server. Skipping.") 128 continue 129 } 130 repoType := utils.RepoTypeFromString(sourceRepo.Type) 131 result[repoType] = append(result[repoType], sourceRepo) 132 } 133 } 134 return result, nil 135 } 136 137 // Deactivate key encryption in Artifactory, to allow retrieving raw text values in the artifactory-config.xml or in a remote repository. 138 func (tcb *TransferConfigBase) DeactivateKeyEncryption() (reactivateKeyEncryption func() error, err error) { 139 var wasEncrypted bool 140 if wasEncrypted, err = tcb.SourceArtifactoryManager.DeactivateKeyEncryption(); err != nil { 141 return func() error { return nil }, err 142 } 143 if !wasEncrypted { 144 return func() error { return nil }, nil 145 } 146 return tcb.SourceArtifactoryManager.ActivateKeyEncryption, nil 147 } 148 149 // Transfer all repositories to the target Artifactory server 150 // reposToTransfer - Map between a repository type to the list of repository names 151 // remoteRepositories - Remote repositories params, we get the remote repository params in an earlier stage after decryption 152 func (tcb *TransferConfigBase) TransferRepositoriesToTarget(reposToTransfer map[utils.RepoType][]services.RepositoryDetails, remoteRepositories []interface{}) (err error) { 153 // Transfer remote repositories 154 for i, remoteRepositoryName := range reposToTransfer[utils.Remote] { 155 if err = tcb.createRepositoryAndAssignToProject(remoteRepositories[i], remoteRepositoryName); err != nil { 156 return 157 } 158 } 159 // Transfer local, federated and unknown repositories. 160 for _, repoType := range []utils.RepoType{utils.Local, utils.Federated, utils.Unknown} { 161 if len(reposToTransfer[repoType]) == 0 { 162 continue 163 } 164 165 if err = tcb.transferSpecificRepositoriesToTarget(reposToTransfer[repoType], repoType); err != nil { 166 return 167 } 168 } 169 if len(reposToTransfer[utils.Virtual]) == 0 { 170 return 171 } 172 return tcb.transferVirtualRepositoriesToTarget(reposToTransfer[utils.Virtual]) 173 } 174 175 // Get a set of all repositories in the target Artifactory server. 176 func (tcb *TransferConfigBase) getAllTargetRepositories() (*datastructures.Set[string], error) { 177 targetRepos, err := tcb.TargetArtifactoryManager.GetAllRepositories() 178 if err != nil { 179 return nil, err 180 } 181 allTargetRepos := datastructures.MakeSet[string]() 182 for _, targetRepo := range *targetRepos { 183 allTargetRepos.Add(targetRepo.Key) 184 } 185 return allTargetRepos, nil 186 } 187 188 // Transfer local, federated, unknown, or virtual repositories 189 // reposToTransfer - Repositories names to transfer 190 // repoType - Repository type 191 func (tcb *TransferConfigBase) transferSpecificRepositoriesToTarget(reposToTransfer []services.RepositoryDetails, repoType utils.RepoType) (err error) { 192 for _, repo := range reposToTransfer { 193 var params interface{} 194 if err = tcb.SourceArtifactoryManager.GetRepository(repo.Key, ¶ms); err != nil { 195 return 196 } 197 if repoType == utils.Federated { 198 if params, err = tcb.removeFederatedMembers(params); err != nil { 199 return 200 } 201 } 202 if err = tcb.createRepositoryAndAssignToProject(params, repo); err != nil { 203 return 204 } 205 } 206 return 207 } 208 209 // Transfer virtual repositories 210 // reposToTransfer - Repositories names to transfer 211 func (tcb *TransferConfigBase) transferVirtualRepositoriesToTarget(reposToTransfer []services.RepositoryDetails) (err error) { 212 allReposParams := make(map[string]interface{}) 213 var singleRepoParamsMap map[string]interface{} 214 var singleRepoParams interface{} 215 // Step 1 - Get and create all virtual repositories with the included repositories removed 216 for _, repoToTransfer := range reposToTransfer { 217 // Get repository params 218 if err = tcb.SourceArtifactoryManager.GetRepository(repoToTransfer.Key, &singleRepoParams); err != nil { 219 return 220 } 221 allReposParams[repoToTransfer.Key] = singleRepoParams 222 singleRepoParamsMap, err = InterfaceToMap(singleRepoParams) 223 if err != nil { 224 return 225 } 226 227 // Create virtual repository without included repositories 228 repositories := singleRepoParamsMap["repositories"] 229 delete(singleRepoParamsMap, "repositories") 230 if err = tcb.createRepositoryAndAssignToProject(singleRepoParamsMap, repoToTransfer); err != nil { 231 return 232 } 233 234 // Restore included repositories to set them later on 235 if repositories != nil { 236 singleRepoParamsMap["repositories"] = repositories 237 } 238 } 239 240 // Step 2 - Update all virtual repositories with the included repositories 241 for repoKey, repoParams := range allReposParams { 242 if err = tcb.TargetArtifactoryManager.UpdateRepositoryWithParams(repoParams, repoKey); err != nil { 243 return 244 } 245 } 246 return 247 } 248 249 // Get all remote repositories. This method must run after disabling Artifactory key encryption. 250 // remoteRepositoryNames - Remote repository names to transfer 251 func (tcb *TransferConfigBase) GetAllRemoteRepositories(remoteRepositoryNames []string) ([]interface{}, error) { 252 remoteRepositories := make([]interface{}, 0, len(remoteRepositoryNames)) 253 for _, repoKey := range remoteRepositoryNames { 254 var repoDetails interface{} 255 if err := tcb.SourceArtifactoryManager.GetRepository(repoKey, &repoDetails); err != nil { 256 return nil, err 257 } 258 remoteRepositories = append(remoteRepositories, repoDetails) 259 } 260 return remoteRepositories, nil 261 } 262 263 func CreateArtifactoryClientDetails(serviceManager artifactory.ArtifactoryServicesManager) (*httputils.HttpClientDetails, error) { 264 config := serviceManager.GetConfig() 265 if config == nil { 266 return nil, errorutils.CheckErrorf("expected full config, but no configuration exists") 267 } 268 rtDetails := config.GetServiceDetails() 269 if rtDetails == nil { 270 return nil, errorutils.CheckErrorf("artifactory details not configured") 271 } 272 clientDetails := rtDetails.CreateHttpClientDetails() 273 return &clientDetails, nil 274 } 275 276 // Check if there is a configured user using default credentials 'admin:password' by pinging Artifactory. 277 func (tcb *TransferConfigBase) IsDefaultCredentials() (bool, error) { 278 // Check if admin is locked 279 lockedUsers, err := tcb.SourceArtifactoryManager.GetLockedUsers() 280 if err != nil { 281 return false, err 282 } 283 if slices.Contains(lockedUsers, defaultAdminUsername) { 284 return false, nil 285 } 286 287 // Ping Artifactory with the default admin:password credentials 288 artDetails := config.ServerDetails{ArtifactoryUrl: clientUtils.AddTrailingSlashIfNeeded(tcb.SourceServerDetails.ArtifactoryUrl), User: defaultAdminUsername, Password: defaultAdminPassword} 289 servicesManager, err := utils.CreateServiceManager(&artDetails, -1, 0, false) 290 if err != nil { 291 return false, err 292 } 293 294 // This cannot be executed with commands.Exec()! Doing so will cause usage report being sent with admin:password as well. 295 if _, err = servicesManager.Ping(); err == nil { 296 log.Output() 297 log.Warn("The default 'admin:password' credentials are used by a configured user in your source platform.\n" + 298 "Those credentials will be transferred to your SaaS target platform.") 299 return true, nil 300 } 301 302 // If the password of the admin user is not the default one, reset the failed login attempts counter in Artifactory 303 // by unlocking the user. We do that to avoid login suspension to the admin user. 304 return false, tcb.SourceArtifactoryManager.UnlockUser(defaultAdminUsername) 305 } 306 307 func (tcb *TransferConfigBase) LogTitle(title string) { 308 log.Info(coreutils.PrintBoldTitle(fmt.Sprintf("========== %s ==========", title))) 309 } 310 311 // Remove federated members from the input federated repository. 312 // federatedRepoParams - Federated repository parameters 313 func (tcb *TransferConfigBase) removeFederatedMembers(federatedRepoParams interface{}) (interface{}, error) { 314 repoMap, err := InterfaceToMap(federatedRepoParams) 315 if err != nil { 316 return nil, err 317 } 318 if _, exist := repoMap["members"]; exist { 319 delete(repoMap, "members") 320 tcb.FederatedMembersRemoved = true 321 } else { 322 return federatedRepoParams, nil 323 } 324 return MapToInterface(repoMap) 325 } 326 327 // Create a repository in the target server and assign the repository to the required project, if any. 328 // repoParams - Repository parameters 329 // repoKey - Repository key 330 func (tcb *TransferConfigBase) createRepositoryAndAssignToProject(repoParams interface{}, repoDetails services.RepositoryDetails) (err error) { 331 var projectKey string 332 if repoParams, projectKey, err = removeProjectKeyIfNeeded(repoParams, repoDetails.Key); err != nil { 333 return 334 } 335 if projectKey != "" { 336 // Workaround - It's possible that the repository could be assigned to a project in the access.bootstrap.json. 337 // This is why we make sure to detach it before actually creating the repository. 338 // If the project isn't linked to the repository, an error might come up, but we ignore it because we can't 339 // be certain whether the repository was actually assigned to the project or not. 340 _ = tcb.TargetAccessManager.UnassignRepoFromProject(repoDetails.Key) 341 } 342 if err = tcb.TargetArtifactoryManager.CreateRepositoryWithParams(repoParams, repoDetails.Key); err != nil { 343 return 344 } 345 if projectKey != "" { 346 return tcb.TargetAccessManager.AssignRepoToProject(repoDetails.Key, projectKey, true) 347 } 348 return 349 } 350 351 // Remove non-default project key from the repository parameters if existed and the repository key does not start with it. 352 // This is needed to allow creating repository assigned to projects so that the repository name is not starting with the project key prefix. 353 // Returns the updated repository params, the project key if removed and an error if any. 354 func removeProjectKeyIfNeeded(repoParams interface{}, repoKey string) (interface{}, string, error) { 355 var projectKey string 356 repoMap, err := InterfaceToMap(repoParams) 357 if err != nil { 358 return nil, "", err 359 } 360 if value, exist := repoMap["projectKey"]; exist { 361 var ok bool 362 if projectKey, ok = value.(string); !ok { 363 return nil, "", errorutils.CheckErrorf("couldn't parse the 'projectKey' value '%v' of repository '%s'", value, repoKey) 364 } 365 if projectKey == "default" || strings.HasPrefix(repoKey, projectKey+"-") { 366 // The repository key is starting with the project key prefix: 367 // <project-key>-<repo-name> 368 return repoParams, "", nil 369 } 370 delete(repoMap, "projectKey") 371 } else { 372 return repoParams, "", nil 373 } 374 response, err := MapToInterface(repoMap) 375 if err != nil { 376 return nil, "", err 377 } 378 return response, projectKey, errorutils.CheckError(err) 379 } 380 381 // During the transfer-config commands we remove federated members, if existed. 382 // This method log an info that the federated members should be reconfigured in the target server. 383 func (tcb *TransferConfigBase) LogIfFederatedMemberRemoved() { 384 if tcb.FederatedMembersRemoved { 385 log.Info("☝️ Your Federated repositories have been transferred to your target instance, but their members have been removed on the target.\n", 386 "You should add members to your Federated repositories on your target instance as described here:", 387 coreutils.JFrogHelpUrl+"jfrog-artifactory-documentation/federated-repositories") 388 } 389 } 390 391 // Convert the input JSON interface to a map. 392 // jsonInterface - JSON interface, such as repository params 393 func InterfaceToMap(jsonInterface interface{}) (map[string]interface{}, error) { 394 b, err := json.Marshal(jsonInterface) 395 if err != nil { 396 return nil, errorutils.CheckError(err) 397 } 398 newMap := make(map[string]interface{}) 399 err = errorutils.CheckError(json.Unmarshal(b, &newMap)) 400 return newMap, err 401 } 402 403 // Convert the input map to JSON interface. 404 // mapToTransfer - Map of string to interface, such as repository name 405 func MapToInterface(mapToTransfer map[string]interface{}) (interface{}, error) { 406 repoBytes, err := json.Marshal(mapToTransfer) 407 if err != nil { 408 return nil, errorutils.CheckError(err) 409 } 410 var response interface{} 411 err = json.Unmarshal(repoBytes, &response) 412 return response, errorutils.CheckError(err) 413 }