Skip to content

Commit 113df79

Browse files
authored
Add optional 'resources' directory to plugins (#1514)
*Adding an option to create and publish plugins to Artifactory with a directory named "resources", which contains all resources that the developer wants to add to the plugin. *When publishing a plugin make sure that you have a "resources" directory in root. *Resources are being uploaded to Artifactory in a zip file. *There are changes in the plugins directory hierarchy at the local '.jfrog' directory.
1 parent 4246a70 commit 113df79

File tree

12 files changed

+373
-105
lines changed

12 files changed

+373
-105
lines changed

artifactory_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5308,7 +5308,7 @@ func TestTerraformPublish(t *testing.T) {
53085308
assert.NoError(t, os.MkdirAll(tests.Out+"/results/", 0777))
53095309
runRt(t, "download", tests.TerraformRepo+"/namespace/provider/*", tests.Out+"/results/", "--explode=true")
53105310
// Validate
5311-
paths, err := fileutils.ListFilesRecursiveWalkIntoDirSymlink(tests.Out+"/results", false)
5311+
paths, err := fileutils.ListFilesRecursiveWalkIntoDirSymlink(filepath.Join(tests.Out, "results"), false)
53125312
assert.NoError(t, err)
53135313
assert.NoError(t, tests.ValidateListsIdentical(tests.GetTerraformModulesFilesDownload(), paths))
53145314
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ require (
9393

9494
replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.2.3
9595

96-
replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.12.1-0.20220412133016-7e9342af1eea
96+
replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.12.1-0.20220413150046-30f13ed79855
9797

98-
replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.12.3
98+
replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.12.4-0.20220414170856-d1e29190993e
9999

100100
replace github.com/jfrog/gocmd => github.com/jfrog/gocmd v0.6.0

go.sum

Lines changed: 74 additions & 4 deletions
Large diffs are not rendered by default.

plugins/commands/install.go

Lines changed: 103 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package commands
22

33
import (
4+
ioutils "github.com/jfrog/jfrog-client-go/utils/io"
5+
"github.com/mholt/archiver/v3"
46
"net/http"
57
"os"
68
"path/filepath"
79
"strings"
810

911
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
12+
"github.com/jfrog/jfrog-cli-core/v2/utils/plugins"
1013
commandsUtils "github.com/jfrog/jfrog-cli/plugins/commands/utils"
1114
clientUtils "github.com/jfrog/jfrog-client-go/utils"
1215

@@ -30,6 +33,10 @@ func InstallCmd(c *cli.Context) error {
3033
if err != nil {
3134
return err
3235
}
36+
err = plugins.CheckPluginsVersionAndConvertIfNeeded()
37+
if err != nil {
38+
return err
39+
}
3340
return runInstallCmd(c.Args().Get(0))
3441
}
3542

@@ -44,26 +51,26 @@ func runInstallCmd(requestedPlugin string) error {
4451
return err
4552
}
4653

47-
url, httpDetails, err := getServerDetails()
54+
url, serverDetails, err := getServerDetails()
4855
if err != nil {
4956
return err
5057
}
5158

52-
pluginRtPath, err := getRequiredPluginRtPath(pluginName, version)
59+
pluginRtDirPath, err := getRequiredPluginRtDirPath(pluginName, version)
5360
if err != nil {
5461
return err
5562
}
56-
downloadUrl := clientUtils.AddTrailingSlashIfNeeded(url) + pluginRtPath
63+
execDownloadUrl := clientUtils.AddTrailingSlashIfNeeded(url) + pluginRtDirPath + "/"
5764

58-
should, err := shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl, httpDetails)
65+
should, err := shouldDownloadPlugin(pluginsDir, pluginName, execDownloadUrl, commandsUtils.CreatePluginsHttpDetails(&serverDetails))
5966
if err != nil {
6067
return err
6168
}
6269
if !should {
6370
return errorutils.CheckErrorf("the plugin with the requested version already exists locally")
6471
}
6572

66-
return downloadPlugin(pluginsDir, pluginName, downloadUrl, httpDetails)
73+
return downloadPlugin(pluginsDir, pluginName, execDownloadUrl, commandsUtils.CreatePluginsHttpDetails(&serverDetails))
6774
}
6875

6976
// Assert repo env is not passed without server env.
@@ -99,22 +106,22 @@ func createPluginsDirIfNeeded() (string, error) {
99106
}
100107

101108
// Use the server ID if provided, else use the official registry.
102-
func getServerDetails() (string, httputils.HttpClientDetails, error) {
109+
func getServerDetails() (string, config.ServerDetails, error) {
103110
serverId := os.Getenv(commandsUtils.PluginsServerEnv)
104111
if serverId == "" {
105-
return commandsUtils.PluginsOfficialRegistryUrl, httputils.HttpClientDetails{}, nil
112+
return commandsUtils.PluginsOfficialRegistryUrl, config.ServerDetails{ArtifactoryUrl: commandsUtils.PluginsOfficialRegistryUrl}, nil
106113
}
107114

108115
rtDetails, err := config.GetSpecificConfig(serverId, false, true)
109116
if err != nil {
110-
return "", httputils.HttpClientDetails{}, err
117+
return "", config.ServerDetails{}, err
111118
}
112-
return rtDetails.ArtifactoryUrl, commandsUtils.CreatePluginsHttpDetails(rtDetails), nil
119+
return rtDetails.ArtifactoryUrl, *rtDetails, nil
113120
}
114121

115122
// Checks if the requested plugin exists in registry and does not exists locally.
116123
func shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetails httputils.HttpClientDetails) (bool, error) {
117-
exists, err := fileutils.IsFileExists(filepath.Join(pluginsDir, commandsUtils.GetLocalPluginExecutableName(pluginName)), false)
124+
exists, err := fileutils.IsDirExists(filepath.Join(pluginsDir, pluginName), false)
118125
if err != nil {
119126
return false, err
120127
}
@@ -137,38 +144,30 @@ func shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetail
137144
if err != nil {
138145
return false, err
139146
}
140-
equal, err := fileutils.IsEqualToLocalFile(filepath.Join(pluginsDir, pluginName), details.Checksum.Md5, details.Checksum.Sha1)
147+
equal, err := fileutils.IsEqualToLocalFile(filepath.Join(pluginsDir, pluginName, coreutils.PluginsExecDirName, plugins.GetLocalPluginExecutableName(pluginName)), details.Checksum.Md5, details.Checksum.Sha1)
141148
return !equal, err
142149
}
143150

144-
// Returns the path of the plugin's executable in registry, corresponding to the local architecture.
145-
func getRequiredPluginRtPath(pluginName, version string) (string, error) {
151+
// Returns the path of the JFrog CLI plugin's directory in registry, corresponding to the local architecture.
152+
func getRequiredPluginRtDirPath(pluginName, version string) (pluginDirRtPath string, err error) {
146153
arc, err := commandsUtils.GetLocalArchitecture()
147154
if err != nil {
148-
return "", err
155+
return
149156
}
150-
return commandsUtils.GetPluginPathInArtifactory(pluginName, version, arc), nil
157+
pluginDirRtPath = commandsUtils.GetPluginDirPath(pluginName, version, arc)
158+
return
151159
}
152160

153161
func createPluginsDir(pluginsDir string) error {
154-
return os.MkdirAll(pluginsDir, 0777)
162+
err := os.MkdirAll(pluginsDir, 0777)
163+
if err != nil {
164+
return errorutils.CheckError(err)
165+
}
166+
_, err = plugins.CreatePluginsConfigFile()
167+
return err
155168
}
156169

157170
func downloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetails httputils.HttpClientDetails) (err error) {
158-
exeName := commandsUtils.GetLocalPluginExecutableName(pluginName)
159-
log.Debug("Downloading plugin from: ", downloadUrl)
160-
downloadDetails := &httpclient.DownloadFileDetails{
161-
FileName: pluginName,
162-
DownloadPath: downloadUrl,
163-
LocalPath: pluginsDir,
164-
LocalFileName: exeName,
165-
RelativePath: exeName,
166-
}
167-
168-
client, err := httpclient.ClientBuilder().Build()
169-
if err != nil {
170-
return
171-
}
172171
// Init progress bar.
173172
progressMgr, logFile, err := progressbar.InitProgressBarIfPossible(false)
174173
if err != nil {
@@ -185,19 +184,16 @@ func downloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetails http
185184
}
186185
}()
187186
}
188-
log.Info("Downloading plugin: " + pluginName)
189187

190-
resp, err := client.DownloadFileWithProgress(downloadDetails, "", httpDetails, false, progressMgr)
188+
err = downloadPluginExec(downloadUrl, pluginName, pluginsDir, httpDetails, progressMgr)
191189
if err != nil {
192190
return
193191
}
194-
log.Debug("Artifactory response: ", resp.Status)
195-
err = errorutils.CheckResponseStatus(resp, http.StatusOK)
192+
err = downloadPluginsResources(downloadUrl, pluginName, pluginsDir, httpDetails, progressMgr)
196193
if err != nil {
197194
return
198195
}
199-
log.Debug("Plugin downloaded successfully.")
200-
err = os.Chmod(filepath.Join(pluginsDir, exeName), 0777)
196+
log.Info("Plugin downloaded successfully.")
201197
return
202198
}
203199

@@ -211,3 +207,74 @@ func getNameAndVersion(requested string) (name, version string, err error) {
211207
}
212208
return split[0], split[1], nil
213209
}
210+
211+
func downloadPluginExec(downloadUrl, pluginName, pluginsDir string, httpDetails httputils.HttpClientDetails, progressMgr ioutils.ProgressMgr) (err error) {
212+
exeName := plugins.GetLocalPluginExecutableName(pluginName)
213+
downloadDetails := &httpclient.DownloadFileDetails{
214+
FileName: pluginName,
215+
DownloadPath: clientUtils.AddTrailingSlashIfNeeded(downloadUrl) + exeName,
216+
LocalPath: filepath.Join(pluginsDir, pluginName, coreutils.PluginsExecDirName),
217+
LocalFileName: exeName,
218+
RelativePath: exeName,
219+
}
220+
log.Debug("Downloading plugin's executable from: ", downloadDetails.DownloadPath)
221+
_, err = downloadFromArtifactory(downloadDetails, httpDetails, progressMgr)
222+
if err != nil {
223+
return
224+
}
225+
err = os.Chmod(filepath.Join(downloadDetails.LocalPath, downloadDetails.LocalFileName), 0777)
226+
if errorutils.CheckError(err) != nil {
227+
return
228+
}
229+
log.Debug("Plugin's executable downloaded successfully.")
230+
return
231+
}
232+
233+
func downloadPluginsResources(downloadUrl, pluginName, pluginsDir string, httpDetails httputils.HttpClientDetails, progressMgr ioutils.ProgressMgr) (err error) {
234+
downloadDetails := &httpclient.DownloadFileDetails{
235+
FileName: pluginName,
236+
DownloadPath: clientUtils.AddTrailingSlashIfNeeded(downloadUrl) + coreutils.PluginsResourcesDirName + ".zip",
237+
LocalPath: filepath.Join(pluginsDir, pluginName),
238+
LocalFileName: coreutils.PluginsResourcesDirName + ".zip",
239+
RelativePath: coreutils.PluginsResourcesDirName + ".zip",
240+
}
241+
log.Debug("Downloading plugin's resources from: ", downloadDetails.DownloadPath)
242+
statusCode, err := downloadFromArtifactory(downloadDetails, httpDetails, progressMgr)
243+
if err != nil {
244+
return
245+
}
246+
if statusCode == http.StatusNotFound {
247+
log.Debug("No resources were downloaded.")
248+
return nil
249+
}
250+
err = archiver.Unarchive(filepath.Join(downloadDetails.LocalPath, downloadDetails.LocalFileName), filepath.Join(downloadDetails.LocalPath, coreutils.PluginsResourcesDirName)+string(os.PathSeparator))
251+
if errorutils.CheckError(err) != nil {
252+
return
253+
}
254+
err = os.Remove(filepath.Join(downloadDetails.LocalPath, downloadDetails.LocalFileName))
255+
if err != nil {
256+
return
257+
}
258+
err = coreutils.ChmodPluginsDirectoryContent()
259+
if errorutils.CheckError(err) != nil {
260+
return
261+
}
262+
log.Debug("Plugin's resources downloaded successfully.")
263+
return
264+
}
265+
266+
func downloadFromArtifactory(downloadDetails *httpclient.DownloadFileDetails, httpDetails httputils.HttpClientDetails, progressMgr ioutils.ProgressMgr) (statusCode int, err error) {
267+
client, err := httpclient.ClientBuilder().Build()
268+
if err != nil {
269+
return
270+
}
271+
log.Info("Downloading: " + downloadDetails.FileName)
272+
resp, err := client.DownloadFileWithProgress(downloadDetails, "", httpDetails, false, progressMgr)
273+
if err != nil {
274+
return
275+
}
276+
statusCode = resp.StatusCode
277+
log.Debug("Artifactory response: ", statusCode)
278+
err = errorutils.CheckResponseStatus(resp, http.StatusOK, http.StatusNotFound)
279+
return
280+
}

plugins/commands/publish.go

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package commands
22

33
import (
4+
buildinfoutils "github.com/jfrog/build-info-go/utils"
45
"github.com/jfrog/gofrog/io"
56
"github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/generic"
7+
commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils"
68
rtutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
79
"github.com/jfrog/jfrog-cli-core/v2/common/spec"
810
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
11+
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
912
"github.com/jfrog/jfrog-cli/plugins/commands/utils"
1013
pluginsutils "github.com/jfrog/jfrog-cli/plugins/utils"
1114
"github.com/jfrog/jfrog-cli/utils/cliutils"
@@ -175,28 +178,91 @@ func verifyUniqueVersion(pluginName, pluginVersion string, rtDetails *config.Ser
175178
}
176179

177180
func uploadPlugin(pluginLocalPath, pluginName, pluginVersion, arc string, rtDetails *config.ServerDetails) error {
178-
targetPath := utils.GetPluginPathInArtifactory(pluginName, pluginVersion, arc)
179-
log.Info("Upload plugin to: " + targetPath + "...")
181+
pluginDirRtPath := utils.GetPluginDirPath(pluginName, pluginVersion, arc)
182+
log.Info("Upload plugin to: " + pluginDirRtPath + "...")
183+
// First uploading resources directory (this is the complex part). If the upload is successful, upload the executable file.
184+
// Upload plugin's resources directory if exists
185+
exists, err := buildinfoutils.IsDirExists(coreutils.PluginsResourcesDirName, true)
186+
if err != nil {
187+
return errorutils.CheckError(err)
188+
}
189+
if exists {
190+
empty, err := fileutils.IsDirEmpty(coreutils.PluginsResourcesDirName)
191+
if err != nil {
192+
return err
193+
}
194+
if !empty {
195+
resourcesPattern := filepath.Join(coreutils.PluginsResourcesDirName, "(*)")
196+
resourcesTargetPath := path.Join(pluginDirRtPath, coreutils.PluginsResourcesDirName+".zip")
197+
err = uploadPluginsResources(resourcesPattern, resourcesTargetPath, rtDetails)
198+
if err != nil {
199+
return err
200+
}
201+
}
202+
}
203+
// Upload plugin's executable
204+
execTargetPath := path.Join(pluginDirRtPath, utils.GetPluginExecutableName(pluginName, arc))
205+
err = uploadPluginsExec(pluginLocalPath, execTargetPath, rtDetails)
206+
if err != nil {
207+
return err
208+
}
209+
return nil
210+
}
180211

181-
uploadCmd := generic.NewUploadCommand()
182-
uploadCmd.SetUploadConfiguration(createUploadConfiguration()).
183-
SetServerDetails(rtDetails).
184-
SetSpec(createUploadSpec(pluginLocalPath, targetPath))
212+
func uploadPluginsExec(pattern, target string, rtDetails *config.ServerDetails) error {
213+
log.Debug("Upload plugin's executable to: " + target + "...")
214+
result, err := createAndRunPluginsExecUploadCommand(pattern, target, rtDetails)
215+
if err != nil {
216+
return err
217+
}
218+
if result.SuccessCount() == 0 {
219+
return errorutils.CheckErrorf("plugin's executable upload failed as no files were affected. Verify source path is valid")
220+
}
221+
if result.SuccessCount() > 1 {
222+
return errorutils.CheckErrorf("while uploading plugin's executable more than one file was uploaded. Unexpected behaviour, aborting")
223+
}
224+
return nil
225+
}
185226

186-
err := uploadCmd.Run()
227+
func uploadPluginsResources(pattern, target string, rtDetails *config.ServerDetails) error {
228+
log.Debug("Upload plugin's resources to: " + target + "...")
229+
result, err := createAndRunPluginsResourcesUploadCommand(pattern, target, rtDetails)
187230
if err != nil {
188231
return err
189232
}
190-
result := uploadCmd.Result()
191233
if result.SuccessCount() == 0 {
192-
return errorutils.CheckErrorf("plugin upload failed as no files were affected. Verify source path is valid")
234+
return errorutils.CheckErrorf("plugin's resources upload failed as no files were affected. Verify source path is valid")
193235
}
194236
if result.SuccessCount() > 1 {
195-
return errorutils.CheckErrorf("more than one file was uploaded. Unexpected behaviour, aborting")
237+
return errorutils.CheckErrorf("while zipping and uploading plugin's resources directory more than one file was uploaded. Unexpected behaviour, aborting")
196238
}
197239
return nil
198240
}
199241

242+
func createAndRunPluginsExecUploadCommand(pattern, target string, rtDetails *config.ServerDetails) (*commandsutils.Result, error) {
243+
uploadCmd := generic.NewUploadCommand()
244+
uploadCmd.SetUploadConfiguration(createUploadConfiguration()).
245+
SetServerDetails(rtDetails).
246+
SetSpec(createExecUploadSpec(pattern, target))
247+
err := uploadCmd.Run()
248+
if err != nil {
249+
return nil, err
250+
}
251+
return uploadCmd.Result(), nil
252+
}
253+
254+
func createAndRunPluginsResourcesUploadCommand(pattern, target string, rtDetails *config.ServerDetails) (*commandsutils.Result, error) {
255+
uploadCmd := generic.NewUploadCommand()
256+
uploadCmd.SetUploadConfiguration(createUploadConfiguration()).
257+
SetServerDetails(rtDetails).
258+
SetSpec(createResourcesUploadSpec(pattern, target))
259+
err := uploadCmd.Run()
260+
if err != nil {
261+
return nil, err
262+
}
263+
return uploadCmd.Result(), nil
264+
}
265+
200266
// Copy the uploaded version to override latest dir.
201267
func copyToLatestDir(pluginName, pluginVersion string, rtDetails *config.ServerDetails) error {
202268
log.Info("Copying version to latest dir...")
@@ -217,13 +283,24 @@ func createCopySpec(pluginName, pluginVersion string) *spec.SpecFiles {
217283
BuildSpec()
218284
}
219285

220-
func createUploadSpec(source, target string) *spec.SpecFiles {
286+
func createExecUploadSpec(source, target string) *spec.SpecFiles {
221287
return spec.NewBuilder().
222288
Pattern(source).
223289
Target(target).
224290
BuildSpec()
225291
}
226292

293+
// Resources directory is being uploaded to Artifactory in a zip file.
294+
func createResourcesUploadSpec(source, target string) *spec.SpecFiles {
295+
return spec.NewBuilder().
296+
Pattern(source).
297+
Target(target).
298+
Archive("zip").
299+
Recursive(true).
300+
TargetPathInArchive("{1}").
301+
BuildSpec()
302+
}
303+
227304
func createUploadConfiguration() *rtutils.UploadConfiguration {
228305
uploadConfiguration := new(rtutils.UploadConfiguration)
229306
uploadConfiguration.Threads = cliutils.Threads

0 commit comments

Comments
 (0)