github.com/cobalt77/jfrog-client-go@v0.14.5/artifactory/services/delete.go (about)

     1  package services
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"strconv"
     7  
     8  	rthttpclient "github.com/cobalt77/jfrog-client-go/artifactory/httpclient"
     9  	"github.com/cobalt77/jfrog-client-go/artifactory/services/utils"
    10  	"github.com/cobalt77/jfrog-client-go/auth"
    11  	clientutils "github.com/cobalt77/jfrog-client-go/utils"
    12  	"github.com/cobalt77/jfrog-client-go/utils/errorutils"
    13  	"github.com/cobalt77/jfrog-client-go/utils/io/content"
    14  	"github.com/cobalt77/jfrog-client-go/utils/log"
    15  	"github.com/jfrog/gofrog/parallel"
    16  )
    17  
    18  type DeleteService struct {
    19  	client     *rthttpclient.ArtifactoryHttpClient
    20  	ArtDetails auth.ServiceDetails
    21  	DryRun     bool
    22  	Threads    int
    23  }
    24  
    25  func NewDeleteService(client *rthttpclient.ArtifactoryHttpClient) *DeleteService {
    26  	return &DeleteService{client: client}
    27  }
    28  
    29  func (ds *DeleteService) GetArtifactoryDetails() auth.ServiceDetails {
    30  	return ds.ArtDetails
    31  }
    32  
    33  func (ds *DeleteService) SetArtifactoryDetails(rt auth.ServiceDetails) {
    34  	ds.ArtDetails = rt
    35  }
    36  
    37  func (ds *DeleteService) IsDryRun() bool {
    38  	return ds.DryRun
    39  }
    40  
    41  func (ds *DeleteService) GetThreads() int {
    42  	return ds.Threads
    43  }
    44  
    45  func (ds *DeleteService) SetThreads(threads int) {
    46  	ds.Threads = threads
    47  }
    48  
    49  func (ds *DeleteService) GetJfrogHttpClient() (*rthttpclient.ArtifactoryHttpClient, error) {
    50  	return ds.client, nil
    51  }
    52  
    53  func (ds *DeleteService) GetPathsToDelete(deleteParams DeleteParams) (resultItems *content.ContentReader, err error) {
    54  	log.Info("Searching artifacts...")
    55  	var tempResultItems, toBeDeletedDirs *content.ContentReader
    56  	switch deleteParams.GetSpecType() {
    57  	case utils.AQL:
    58  		resultItems, err = utils.SearchBySpecWithAql(deleteParams.GetFile(), ds, utils.NONE)
    59  		if err != nil {
    60  			return
    61  		}
    62  	case utils.WILDCARD:
    63  		deleteParams.SetIncludeDirs(true)
    64  		tempResultItems, err = utils.SearchBySpecWithPattern(deleteParams.GetFile(), ds, utils.NONE)
    65  		if err != nil {
    66  			return
    67  		}
    68  		defer tempResultItems.Close()
    69  		toBeDeletedDirs, err = removeNotToBeDeletedDirs(deleteParams.GetFile(), ds, tempResultItems)
    70  		if err != nil {
    71  			return
    72  		}
    73  		// The 'removeNotToBeDeletedDirs' should filter out any folders that should not be deleted, if no action is needed, nil will be return.
    74  		// As a result, we should keep the flow with tempResultItems reader instead.
    75  		if toBeDeletedDirs == nil {
    76  			toBeDeletedDirs = tempResultItems
    77  		}
    78  		defer toBeDeletedDirs.Close()
    79  		resultItems, err = utils.ReduceTopChainDirResult(toBeDeletedDirs)
    80  		if err != nil {
    81  			return
    82  		}
    83  	case utils.BUILD:
    84  		resultItems, err = utils.SearchBySpecWithBuild(deleteParams.GetFile(), ds)
    85  	}
    86  	length, err := resultItems.Length()
    87  	if err != nil {
    88  		return
    89  	}
    90  	utils.LogSearchResults(length)
    91  	return
    92  }
    93  
    94  type fileDeleteHandlerFunc func(utils.ResultItem) parallel.TaskFunc
    95  
    96  func (ds *DeleteService) createFileHandlerFunc(result *utils.Result) fileDeleteHandlerFunc {
    97  	return func(resultItem utils.ResultItem) parallel.TaskFunc {
    98  		return func(threadId int) error {
    99  			result.TotalCount[threadId]++
   100  			logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, ds.DryRun)
   101  			deletePath, e := utils.BuildArtifactoryUrl(ds.GetArtifactoryDetails().GetUrl(), resultItem.GetItemRelativePath(), make(map[string]string))
   102  			if e != nil {
   103  				return e
   104  			}
   105  			log.Info(logMsgPrefix+"Deleting", resultItem.GetItemRelativePath())
   106  			if ds.DryRun {
   107  				return nil
   108  			}
   109  			httpClientsDetails := ds.GetArtifactoryDetails().CreateHttpClientDetails()
   110  			resp, body, err := ds.client.SendDelete(deletePath, nil, &httpClientsDetails)
   111  			if err != nil {
   112  				log.Error(err)
   113  				return err
   114  			}
   115  			if resp.StatusCode != http.StatusNoContent {
   116  				err = errors.New("Artifactory response: " + resp.Status + "\n" + clientutils.IndentJson(body))
   117  				log.Error(errorutils.CheckError(err))
   118  				return err
   119  			}
   120  
   121  			result.SuccessCount[threadId]++
   122  			return nil
   123  		}
   124  	}
   125  }
   126  
   127  func (ds *DeleteService) DeleteFiles(deleteItems *content.ContentReader) (int, error) {
   128  	producerConsumer := parallel.NewBounedRunner(ds.GetThreads(), false)
   129  	errorsQueue := clientutils.NewErrorsQueue(1)
   130  	result := *utils.NewResult(ds.Threads)
   131  	go func() {
   132  		defer producerConsumer.Done()
   133  		for deleteItem := new(utils.ResultItem); deleteItems.NextRecord(deleteItem) == nil; deleteItem = new(utils.ResultItem) {
   134  			fileDeleteHandlerFunc := ds.createFileHandlerFunc(&result)
   135  			producerConsumer.AddTaskWithError(fileDeleteHandlerFunc(*deleteItem), errorsQueue.AddError)
   136  		}
   137  		if err := deleteItems.GetError(); err != nil {
   138  			errorsQueue.AddError(err)
   139  		}
   140  		deleteItems.Reset()
   141  	}()
   142  	return ds.performTasks(producerConsumer, errorsQueue, result)
   143  }
   144  
   145  func (ds *DeleteService) performTasks(consumer parallel.Runner, errorsQueue *clientutils.ErrorsQueue, result utils.Result) (totalDeleted int, err error) {
   146  	consumer.Run()
   147  	err = errorsQueue.GetError()
   148  
   149  	totalDeleted = utils.SumIntArray(result.SuccessCount)
   150  	log.Debug("Deleted", strconv.Itoa(totalDeleted), "artifacts.")
   151  	return
   152  }
   153  
   154  type DeleteConfiguration struct {
   155  	ArtDetails auth.ServiceDetails
   156  	DryRun     bool
   157  }
   158  
   159  func (conf *DeleteConfiguration) GetArtifactoryDetails() auth.ServiceDetails {
   160  	return conf.ArtDetails
   161  }
   162  
   163  func (conf *DeleteConfiguration) SetArtifactoryDetails(art auth.ServiceDetails) {
   164  	conf.ArtDetails = art
   165  }
   166  
   167  func (conf *DeleteConfiguration) IsDryRun() bool {
   168  	return conf.DryRun
   169  }
   170  
   171  type DeleteParams struct {
   172  	*utils.ArtifactoryCommonParams
   173  }
   174  
   175  func (ds *DeleteParams) GetFile() *utils.ArtifactoryCommonParams {
   176  	return ds.ArtifactoryCommonParams
   177  }
   178  
   179  func (ds *DeleteParams) SetIncludeDirs(includeDirs bool) {
   180  	ds.IncludeDirs = includeDirs
   181  }
   182  
   183  func NewDeleteParams() DeleteParams {
   184  	return DeleteParams{ArtifactoryCommonParams: &utils.ArtifactoryCommonParams{}}
   185  }
   186  
   187  // This function receives as an argument a reader within the list of files and dirs to be deleted from Artifactory.
   188  // In case the search params used to create this list included excludeProps, we might need to remove some directories from this list.
   189  // These directories must be removed, because they include files, which should not be deleted, because of the excludeProps params.
   190  // These directories must not be deleted from Artifactory.
   191  // In case of no excludeProps filed in the file spec, nil will be return so all deleteCandidates will get deleted.
   192  func removeNotToBeDeletedDirs(specFile *utils.ArtifactoryCommonParams, ds *DeleteService, deleteCandidates *content.ContentReader) (*content.ContentReader, error) {
   193  	length, err := deleteCandidates.Length()
   194  	if err != nil || specFile.ExcludeProps == "" || length == 0 {
   195  		return nil, err
   196  	}
   197  	// Send AQL to get all artifacts that includes the exclude props.
   198  	resultWriter, err := content.NewContentWriter(content.DefaultKey, true, false)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	bufferFiles, err := utils.FilterCandidateToBeDeleted(deleteCandidates, resultWriter)
   203  	if len(bufferFiles) > 0 {
   204  		defer func() {
   205  			for _, file := range bufferFiles {
   206  				file.Close()
   207  			}
   208  		}()
   209  		artifactNotToBeDeleteReader, err := getSortedArtifactsToNotDelete(specFile, ds)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		defer artifactNotToBeDeleteReader.Close()
   214  		if err = utils.WriteCandidateDirsToBeDeleted(bufferFiles, artifactNotToBeDeleteReader, resultWriter); err != nil {
   215  			return nil, err
   216  		}
   217  	}
   218  	if err = resultWriter.Close(); err != nil {
   219  		return nil, err
   220  	}
   221  	return content.NewContentReader(resultWriter.GetFilePath(), content.DefaultKey), err
   222  }
   223  
   224  func getSortedArtifactsToNotDelete(specFile *utils.ArtifactoryCommonParams, ds *DeleteService) (*content.ContentReader, error) {
   225  	specFile.Props = specFile.ExcludeProps
   226  	specFile.SortOrder = "asc"
   227  	specFile.SortBy = []string{"repo", "path", "name"}
   228  	specFile.ExcludeProps = ""
   229  	return utils.SearchBySpecWithPattern(specFile, ds, utils.NONE)
   230  }