github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/transferinstall/datatransferinstall.go (about) 1 package transferinstall 2 3 import ( 4 "fmt" 5 biutils "github.com/jfrog/build-info-go/utils" 6 "github.com/jfrog/gofrog/version" 7 "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" 8 "github.com/jfrog/jfrog-cli-core/v2/utils/config" 9 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 10 "github.com/jfrog/jfrog-client-go/utils/errorutils" 11 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 12 "github.com/jfrog/jfrog-client-go/utils/log" 13 "net/http" 14 "net/url" 15 "os" 16 "path" 17 "path/filepath" 18 "strings" 19 ) 20 21 const ( 22 pluginName = "data-transfer" 23 groovyFileName = "dataTransfer.groovy" 24 jarFileName = "data-transfer.jar" 25 dataTransferUrl = "https://releases.jfrog.io/artifactory/jfrog-releases/data-transfer" 26 libDir = "lib" 27 artifactory = "artifactory" 28 pluginReloadRestApi = "api/plugins/reload" 29 jfrogHomeEnvVar = "JFROG_HOME" 30 latest = "[RELEASE]" 31 ) 32 33 var ( 34 defaultSearchPath = filepath.Join("opt", "jfrog") 35 // Plugin directory locations 36 originalDirPath = FileItem{"etc", "plugins"} 37 v7DirPath = FileItem{"var", "etc", "artifactory", "plugins"} 38 // Error types 39 notValidDestinationErr = fmt.Errorf("can't find the directory in which to install the data-transfer plugin. Please ensure you're running this command on the machine on which Artifactory is installed. You can also use the --home-dir option to specify the directory.") 40 downloadConnectionErr = func(baseUrl, fileName, err string) error { 41 return fmt.Errorf("Could not download the plugin file - '%s' from '%s' due to the following error: '%s'. If this machine has no network access to the download URL, you can download these files from another machine and place them in a directory on this machine. You can then run this command again with the --dir command option, with the directory containing the files as the value.", fileName, baseUrl, err) 42 } 43 // Plugin files 44 transferPluginFiles = PluginFiles{ 45 FileItem{groovyFileName}, 46 FileItem{libDir, jarFileName}, 47 } 48 ) 49 50 type InstallDataTransferPluginCommand struct { 51 // The server that the plugin will be installed on 52 targetServer *config.ServerDetails 53 // install manager manages the list of all the plugin files and the optional target destinations for plugin directory 54 transferManger *PluginInstallManager 55 // The plugin version to download 56 installVersion *version.Version 57 // The local directory the plugin files will be copied from 58 localPluginFilesDir string 59 // The JFrog home directory path provided from cliutils.InstallPluginHomeDir flag 60 localJFrogHomePath string 61 } 62 63 func (idtp *InstallDataTransferPluginCommand) CommandName() string { 64 return "rt_transfer_install" 65 } 66 67 func (idtp *InstallDataTransferPluginCommand) ServerDetails() (*config.ServerDetails, error) { 68 return idtp.targetServer, nil 69 } 70 71 // Set the local file system directory path that the plugin files will be copied from 72 func (idtp *InstallDataTransferPluginCommand) SetLocalPluginFiles(localDir string) *InstallDataTransferPluginCommand { 73 idtp.localPluginFilesDir = localDir 74 return idtp 75 } 76 77 // Set the plugin version we want to download 78 func (idtp *InstallDataTransferPluginCommand) SetInstallVersion(installVersion *version.Version) *InstallDataTransferPluginCommand { 79 idtp.installVersion = installVersion 80 return idtp 81 } 82 83 // Set the Jfrog home directory path 84 func (idtp *InstallDataTransferPluginCommand) SetJFrogHomePath(path string) *InstallDataTransferPluginCommand { 85 idtp.localJFrogHomePath = path 86 return idtp 87 } 88 89 // Create InstallDataTransferCommand 90 func NewInstallDataTransferCommand(server *config.ServerDetails) *InstallDataTransferPluginCommand { 91 manager := NewArtifactoryPluginInstallManager(transferPluginFiles) 92 cmd := &InstallDataTransferPluginCommand{ 93 targetServer: server, 94 transferManger: manager, 95 // Latest 96 installVersion: nil, 97 localPluginFilesDir: "", 98 localJFrogHomePath: "", 99 } 100 return cmd 101 } 102 103 // PluginInstallManager holds all the plugin files and optional plugin directory destinations. 104 // We construct the destination path by searching for the existing plugin directory destination from the options 105 // and joining the local path of the plugin file with the plugin directory 106 type PluginInstallManager struct { 107 // All the plugin files, represented as FileItem with path starting from inside the plugin directory 108 // i.e. for file plugins/lib/file we represent them as FileItem{"lib","file"} 109 files PluginFiles 110 // All the optional destinations of the plugin directory starting from the JFrog home directory represented as FileItem 111 destinations []FileItem 112 } 113 114 // Creates a new Artifactory plugin installation manager 115 func NewArtifactoryPluginInstallManager(bundle PluginFiles) *PluginInstallManager { 116 manager := &PluginInstallManager{ 117 files: bundle, 118 destinations: []FileItem{}, 119 } 120 // Add all the optional destinations for the plugin dir 121 manager.addDestination(originalDirPath) 122 manager.addDestination(v7DirPath) 123 return manager 124 } 125 126 // Add optional plugin directory location as destination 127 func (ftm *PluginInstallManager) addDestination(directory FileItem) { 128 ftm.destinations = append(ftm.destinations, directory) 129 } 130 131 // Search all the local target directories that the plugin directory can exist in base on a given root JFrog Artifactory home directory 132 // We are searching by Joining rootDir/optionalDestination to find the right structure of the JFrog home directory. 133 // The first option that matched and the directory exists is returned as target. 134 func (ftm *PluginInstallManager) findDestination(rootDir string) (exists bool, destination FileItem, err error) { 135 if exists, err = fileutils.IsDirExists(rootDir, false); err != nil || !exists { 136 return 137 } 138 exists = false 139 // Search for the product Artifactory folder 140 folderItems, err := fileutils.ListFiles(rootDir, true) 141 if err != nil { 142 return 143 } 144 for _, folder := range folderItems { 145 if strings.Contains(folder, artifactory) { 146 // Search for the destination inside the folder 147 for _, optionalPluginDirDst := range ftm.destinations { 148 if exists, err = fileutils.IsDirExists(optionalPluginDirDst.toPath(folder), false); err != nil { 149 return 150 } 151 if exists { 152 destination = append([]string{folder}, optionalPluginDirDst...) 153 return 154 } 155 } 156 } 157 } 158 return 159 } 160 161 // Represents a path to file/directory 162 // We represent file 'dir/dir2/file' as FileItem{"dir", "dir2", "file"} 163 // We represent directory 'dir/dir2/' as FileItem{"dir", "dir2"} 164 type FileItem []string 165 166 // List of files represented as FileItem 167 type PluginFiles []FileItem 168 169 // Get the name (last) componenet of the item 170 // i.e. for FileItem{"root", "", "dir", "file.ext"} -> "file.ext" 171 func (f *FileItem) Name() string { 172 size := len(*f) 173 if size == 0 { 174 return "" 175 } 176 return (*f)[size-1] 177 } 178 179 // Get the directory FileItem representation of the item, removing the last componenet and ignoring empty entries. 180 // i.e. for FileItem{"root", "", "dir", "file.ext"} -> FileItem{"root", "dir"} 181 func (f *FileItem) Dirs() *FileItem { 182 dirs := FileItem{} 183 for i := 0; i < len(*f)-1; i++ { 184 dir := (*f)[i] 185 if dir != "" { 186 dirs = append(dirs, dir) 187 } 188 } 189 return &dirs 190 } 191 192 // Split and get the name and directory componenets of the item 193 func (f *FileItem) SplitNameAndDirs() (string, *FileItem) { 194 return f.Name(), f.Dirs() 195 } 196 197 // Convert the item to URL representation, adding prefix tokens as provided 198 func (f *FileItem) toURL(prefixUrl string) (string, error) { 199 myUrl, err := url.Parse(prefixUrl) 200 if err != nil { 201 return "", errorutils.CheckError(err) 202 } 203 myUrl.Path = path.Join(myUrl.Path, path.Join(*f...)) 204 return myUrl.String(), nil 205 } 206 207 // Convert the item to file path representation, ignoring empty entries, adding prefix tokens as provided 208 func (f *FileItem) toPath(previousTokens ...string) string { 209 return filepath.Join(filepath.Join(previousTokens...), filepath.Join(*f.Dirs()...), f.Name()) 210 } 211 212 // Get the plugin directory destination to install the plugin in it. The search is starting from JFrog home directory 213 func (idtp *InstallDataTransferPluginCommand) getPluginDirDestination() (target FileItem, err error) { 214 var exists bool 215 var envVal string 216 217 // Flag override 218 if idtp.localJFrogHomePath != "" { 219 jfrogHomeDir := strings.TrimSpace(idtp.localJFrogHomePath) 220 log.Debug(fmt.Sprintf("Searching for the 'plugins' directory in the JFrog home directory '%s'.", jfrogHomeDir)) 221 if exists, target, err = idtp.transferManger.findDestination(jfrogHomeDir); err != nil || exists { 222 return 223 } 224 if !exists { 225 err = notValidDestinationErr 226 return 227 } 228 } 229 // Environment variable override 230 if envVal, exists = os.LookupEnv(jfrogHomeEnvVar); exists { 231 jfrogHomeDir := strings.TrimSpace(envVal) 232 log.Debug(fmt.Sprintf("Searching for the 'plugins' directory in the JFrog home directory '%s' retrieved from the '%s' environment variable.", jfrogHomeDir, jfrogHomeEnvVar)) 233 if exists, target, err = idtp.transferManger.findDestination(jfrogHomeDir); err != nil || exists { 234 return 235 } 236 } 237 // Default value 238 if !coreutils.IsWindows() { 239 log.Debug(fmt.Sprintf("Searching for the 'plugins' directory in the default path '%s'.", defaultSearchPath)) 240 if exists, target, err = idtp.transferManger.findDestination(defaultSearchPath); err != nil || exists { 241 return 242 } 243 } 244 245 err = notValidDestinationErr 246 return 247 } 248 249 // Transfers the file bundle from src to dst 250 type TransferAction func(src string, dst string, bundle PluginFiles) error 251 252 // Returns the src path and a transfer action 253 func (idtp *InstallDataTransferPluginCommand) getTransferSourceAndAction() (src string, transferAction TransferAction, err error) { 254 // Check if local directory was provided 255 if idtp.localPluginFilesDir != "" { 256 src = idtp.localPluginFilesDir 257 transferAction = CopyFiles 258 log.Debug("Local plugin files provided, copying from file system.") 259 return 260 } 261 // Download files from web 262 var baseSrc *url.URL 263 if baseSrc, err = url.Parse(dataTransferUrl); err != nil { 264 return 265 } 266 if idtp.installVersion == nil { 267 // Latest 268 baseSrc.Path = path.Join(baseSrc.Path, latest) 269 log.Debug("Fetching latest version to the target.") 270 } else { 271 baseSrc.Path = path.Join(baseSrc.Path, idtp.installVersion.GetVersion()) 272 log.Debug(fmt.Sprintf("Fetching plugin version '%s' to the target.", idtp.installVersion.GetVersion())) 273 } 274 src = baseSrc.String() 275 transferAction = DownloadFiles 276 return 277 } 278 279 // Download the plugin files from the given url to the target directory (create path if needed or override existing files) 280 func DownloadFiles(src string, pluginDir string, bundle PluginFiles) (err error) { 281 for _, file := range bundle { 282 fileName, fileDirs := file.SplitNameAndDirs() 283 srcURL, e := file.toURL(src) 284 if e != nil { 285 return e 286 } 287 dstDirPath := fileDirs.toPath(pluginDir) 288 dirURL, e := fileDirs.toURL(src) 289 if e != nil { 290 return e 291 } 292 log.Debug(fmt.Sprintf("Downloading '%s' from '%s' to '%s'", fileName, dirURL, dstDirPath)) 293 if err = fileutils.CreateDirIfNotExist(dstDirPath); err != nil { 294 return 295 } 296 if err = biutils.DownloadFile(filepath.Join(dstDirPath, fileName), srcURL); err != nil { 297 err = downloadConnectionErr(src, fileName, err.Error()) 298 return 299 } 300 } 301 return 302 } 303 304 // Copy the plugin files from the given source to the target directory (create path if needed or override existing files) 305 func CopyFiles(src string, pluginDir string, bundle PluginFiles) (err error) { 306 for _, file := range bundle { 307 fileName, fileDirs := file.SplitNameAndDirs() 308 srcPath := filepath.Join(src, fileName) 309 dstDirPath := fileDirs.toPath(pluginDir) 310 log.Debug(fmt.Sprintf("Copying '%s' from '%s' to '%s'", fileName, src, dstDirPath)) 311 if err = fileutils.CreateDirIfNotExist(dstDirPath); err != nil { 312 return 313 } 314 if err = biutils.CopyFile(dstDirPath, srcPath); err != nil { 315 return 316 } 317 } 318 return 319 } 320 321 // Send reload request to the Artifactory in order to reload the plugin files. 322 func (idtp *InstallDataTransferPluginCommand) sendReloadRequest() error { 323 serviceManager, err := utils.CreateServiceManager(idtp.targetServer, -1, 0, false) 324 if err != nil { 325 return err 326 } 327 serviceDetails := serviceManager.GetConfig().GetServiceDetails() 328 httpDetails := serviceDetails.CreateHttpClientDetails() 329 330 resp, body, err := serviceManager.Client().SendPost(serviceDetails.GetUrl()+pluginReloadRestApi, []byte{}, &httpDetails) 331 if err != nil { 332 return err 333 } 334 if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { 335 return err 336 } 337 return nil 338 } 339 340 func (idtp *InstallDataTransferPluginCommand) Run() (err error) { 341 log.Info(coreutils.PrintBoldTitle(fmt.Sprintf("Installing '%s' plugin...", pluginName))) 342 343 // Get source, destination and transfer action 344 dst, err := idtp.getPluginDirDestination() 345 if err != nil { 346 return 347 } 348 src, transferAction, err := idtp.getTransferSourceAndAction() 349 if err != nil { 350 return 351 } 352 // Execute transferring action 353 if err = transferAction(src, dst.toPath(), idtp.transferManger.files); err != nil { 354 return 355 } 356 // Reload plugins 357 if err = idtp.sendReloadRequest(); err != nil { 358 return 359 } 360 361 log.Info(coreutils.PrintBoldTitle(fmt.Sprintf("The %s plugin installed successfully.", pluginName))) 362 return 363 }