github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/plugins/utils.go (about) 1 package plugins 2 3 import ( 4 "encoding/json" 5 "errors" 6 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 7 "github.com/jfrog/jfrog-cli-core/v2/utils/lock" 8 cliLog "github.com/jfrog/jfrog-cli-core/v2/utils/log" 9 "github.com/jfrog/jfrog-client-go/utils/errorutils" 10 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 11 "github.com/jfrog/jfrog-client-go/utils/log" 12 "os" 13 "path/filepath" 14 "strings" 15 "sync" 16 ) 17 18 // Internal golang locking 19 // In case the 2 threads in the same needs to check plugins.yml file or migrate the fileSystem files to the latest version. 20 var mutex sync.Mutex 21 22 func init() { 23 cliLog.SetDefaultLogger() 24 } 25 26 type PluginsV1 struct { 27 Version int `json:"version,omitempty"` 28 } 29 30 // CheckPluginsVersionAndConvertIfNeeded In case the latest plugin's layout version isn't match to the local plugins hierarchy at '.jfrog/plugins' - 31 // Migrate to the latest version. 32 func CheckPluginsVersionAndConvertIfNeeded() (err error) { 33 // Check if 'plugins' directory exists in .jfrog 34 jfrogHomeDir, err := coreutils.GetJfrogHomeDir() 35 if err != nil { 36 return 37 } 38 exists, err := fileutils.IsDirExists(filepath.Join(jfrogHomeDir, coreutils.JfrogPluginsDirName), false) 39 if err != nil || !exists { 40 return 41 } 42 43 return readPluginsConfigAndConvertV0tToV1IfNeeded() 44 } 45 46 func readPluginsConfigAndConvertV0tToV1IfNeeded() (err error) { 47 content, err := getPluginsConfigFileContent() 48 // Return without converting in case of an error, or if the plugins.yml file already exists, which indicates a conversion has already been made. 49 if err != nil || len(content) != 0 { 50 return 51 } 52 // Locking mechanism - two threads in the same process. 53 mutex.Lock() 54 defer mutex.Unlock() 55 // Locking mechanism - in case two process would read/migrate local files at '.jfrog/plugins'. 56 var lockDirPath string 57 lockDirPath, err = coreutils.GetJfrogPluginsLockDir() 58 if err != nil { 59 return 60 } 61 var unlockFunc func() error 62 unlockFunc, err = lock.CreateLock(lockDirPath) 63 // Defer the lockFile.Unlock() function before throwing a possible error to avoid deadlock situations. 64 defer func() { 65 err = errors.Join(err, unlockFunc()) 66 }() 67 if err != nil { 68 return 69 } 70 // The reason behind reading the config again is that it's possible that another thread or process already changed the plugins file, 71 // So we read again inside that locked section to indicate that we indeed need to convert the plugins' layout. 72 content, err = getPluginsConfigFileContent() 73 if err != nil { 74 return 75 } 76 if len(content) == 0 { 77 // No plugins.yaml file was found. This means that we are in v0. 78 // Convert plugins layout to the latest version. 79 _, err = convertPluginsV0ToV1() 80 } 81 return 82 } 83 84 func getPluginsConfigFileContent() (content []byte, err error) { 85 pluginsFilePath, err := getPluginsFilePath() 86 if err != nil { 87 return 88 } 89 exists, err := fileutils.IsFileExists(pluginsFilePath, false) 90 if err != nil { 91 return 92 } 93 if exists { 94 content, err = fileutils.ReadFile(pluginsFilePath) 95 } 96 return 97 } 98 99 // V0: in the plugins directory there was no 'plugins.yaml' file. This means that all executable files are in the same directory. 100 // V1: We should create a 'plugins.yml' file inside the 'plugins' directory, and also modify the files' hierarchy inside 'plugins' directory. 101 func convertPluginsV0ToV1() (*PluginsV1, error) { 102 err := convertFileSystemLayoutV0ToV1() 103 if err != nil { 104 return nil, err 105 } 106 return CreatePluginsConfigFile() 107 } 108 109 // Change the file's hierarchy inside 'plugins' directory to: 110 // 111 // plugins (dir) 112 // plugin-name (dir) 113 // bin (dir) 114 // plugin-executable (file) 115 // resources:(optional dir) 116 // ... (directories/files) 117 func convertFileSystemLayoutV0ToV1() error { 118 plugins, err := coreutils.GetPluginsDirContent() 119 if err != nil { 120 return err 121 } 122 pluginsDir, err := coreutils.GetJfrogPluginsDir() 123 if err != nil { 124 return err 125 } 126 for _, p := range plugins { 127 // Skip 'plugins.yaml' 128 if p.Name() == coreutils.JfrogPluginsFileName { 129 continue 130 } 131 if p.IsDir() { 132 log.Error("unexpected directory in plugins directory: " + p.Name()) 133 continue 134 } 135 136 pluginsName := removeFileExtension(p.Name()) 137 // For example case of ".DS_Store" files 138 if pluginsName == "" { 139 continue 140 } 141 // Move plugins exec files inside a directory, which has the plugin's name. 142 // Create a directory with the plugin's name + "_dir" extension, move the file inside and change directory's name back to plugin's name only. 143 pluginDirPathWithExtension := filepath.Join(pluginsDir, pluginsName+"_dir") 144 err = os.MkdirAll(filepath.Join(pluginDirPathWithExtension, coreutils.PluginsExecDirName), 0777) 145 if err != nil { 146 return errorutils.CheckError(err) 147 } 148 err = fileutils.MoveFile(filepath.Join(pluginsDir, p.Name()), filepath.Join(pluginDirPathWithExtension, coreutils.PluginsExecDirName, p.Name())) 149 if err != nil { 150 return err 151 } 152 err = fileutils.MoveDir(pluginDirPathWithExtension, filepath.Join(pluginsDir, pluginsName)) 153 if err != nil { 154 return err 155 } 156 err = os.RemoveAll(pluginDirPathWithExtension) 157 if err != nil { 158 return errorutils.CheckError(err) 159 } 160 err = coreutils.ChmodPluginsDirectoryContent() 161 if err != nil { 162 return err 163 } 164 } 165 return nil 166 } 167 168 func removeFileExtension(fileName string) string { 169 return strings.Split(fileName, ".")[0] 170 } 171 172 func CreatePluginsConfigFile() (*PluginsV1, error) { 173 pluginsFilePath, err := getPluginsFilePath() 174 if err != nil { 175 return nil, err 176 } 177 plugins := new(PluginsV1) 178 plugins.Version = 1 179 content, err := json.Marshal(plugins) 180 if err != nil { 181 return nil, errorutils.CheckError(err) 182 } 183 err = os.WriteFile(pluginsFilePath, content, 0600) 184 if err != nil { 185 return nil, errorutils.CheckError(err) 186 } 187 return plugins, nil 188 } 189 190 func getPluginsFilePath() (string, error) { 191 pluginsFilePath, err := coreutils.GetJfrogHomeDir() 192 if err != nil { 193 return "", err 194 } 195 err = os.MkdirAll(pluginsFilePath, 0777) 196 if err != nil { 197 return "", err 198 } 199 pluginsFilePath = filepath.Join(pluginsFilePath, coreutils.JfrogPluginsDirName, coreutils.JfrogPluginsFileName) 200 return pluginsFilePath, nil 201 } 202 203 func GetLocalPluginExecutableName(pluginName string) string { 204 if coreutils.IsWindows() { 205 return pluginName + ".exe" 206 } 207 return pluginName 208 }