github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/client/gitlab.go (about)

     1  package client
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  
     9  	"github.com/apex/log"
    10  	"github.com/goreleaser/goreleaser/internal/artifact"
    11  	"github.com/goreleaser/goreleaser/internal/tmpl"
    12  	"github.com/goreleaser/goreleaser/pkg/config"
    13  	"github.com/goreleaser/goreleaser/pkg/context"
    14  	"github.com/xanzy/go-gitlab"
    15  )
    16  
    17  const DefaultGitLabDownloadURL = "https://gitlab.com"
    18  
    19  type gitlabClient struct {
    20  	client *gitlab.Client
    21  }
    22  
    23  // NewGitLab returns a gitlab client implementation.
    24  func NewGitLab(ctx *context.Context, token string) (Client, error) {
    25  	transport := &http.Transport{
    26  		Proxy: http.ProxyFromEnvironment,
    27  		TLSClientConfig: &tls.Config{
    28  			// nolint: gosec
    29  			InsecureSkipVerify: ctx.Config.GitLabURLs.SkipTLSVerify,
    30  		},
    31  	}
    32  	options := []gitlab.ClientOptionFunc{
    33  		gitlab.WithHTTPClient(&http.Client{
    34  			Transport: transport,
    35  		}),
    36  	}
    37  	if ctx.Config.GitLabURLs.API != "" {
    38  		apiURL, err := tmpl.New(ctx).Apply(ctx.Config.GitLabURLs.API)
    39  		if err != nil {
    40  			return nil, fmt.Errorf("templating GitLab API URL: %w", err)
    41  		}
    42  
    43  		options = append(options, gitlab.WithBaseURL(apiURL))
    44  	}
    45  	client, err := gitlab.NewClient(token, options...)
    46  	if err != nil {
    47  		return &gitlabClient{}, err
    48  	}
    49  	return &gitlabClient{client: client}, nil
    50  }
    51  
    52  // CloseMilestone closes a given milestone.
    53  func (c *gitlabClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
    54  	milestone, err := c.getMilestoneByTitle(repo, title)
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	if milestone == nil {
    60  		return ErrNoMilestoneFound{Title: title}
    61  	}
    62  
    63  	closeStateEvent := "close"
    64  
    65  	opts := &gitlab.UpdateMilestoneOptions{
    66  		Description: &milestone.Description,
    67  		DueDate:     milestone.DueDate,
    68  		StartDate:   milestone.StartDate,
    69  		StateEvent:  &closeStateEvent,
    70  		Title:       &milestone.Title,
    71  	}
    72  
    73  	_, _, err = c.client.Milestones.UpdateMilestone(
    74  		repo.String(),
    75  		milestone.ID,
    76  		opts,
    77  	)
    78  
    79  	return err
    80  }
    81  
    82  // CreateFile gets a file in the repository at a given path
    83  // and updates if it exists or creates it for later pipes in the pipeline.
    84  func (c *gitlabClient) CreateFile(
    85  	ctx *context.Context,
    86  	commitAuthor config.CommitAuthor,
    87  	repo Repo,
    88  	content []byte, // the content of the formula.rb
    89  	path, // the path to the formula.rb
    90  	message string, // the commit msg
    91  ) error {
    92  	fileName := path
    93  	// we assume having the formula in the master branch only
    94  	ref := "master"
    95  	branch := "master"
    96  	opts := &gitlab.GetFileOptions{Ref: &ref}
    97  	castedContent := string(content)
    98  	projectID := repo.Owner + "/" + repo.Name
    99  
   100  	log.WithFields(log.Fields{
   101  		"owner": repo.Owner,
   102  		"name":  repo.Name,
   103  	}).Debug("projectID at brew")
   104  
   105  	_, res, err := c.client.RepositoryFiles.GetFile(projectID, fileName, opts)
   106  	if err != nil && (res == nil || res.StatusCode != 404) {
   107  		log.WithFields(log.Fields{
   108  			"fileName":   fileName,
   109  			"ref":        ref,
   110  			"projectID":  projectID,
   111  			"statusCode": res.StatusCode,
   112  			"err":        err.Error(),
   113  		}).Error("error getting file for brew formula")
   114  		return err
   115  	}
   116  
   117  	log.WithFields(log.Fields{
   118  		"fileName":  fileName,
   119  		"branch":    branch,
   120  		"projectID": projectID,
   121  	}).Debug("found already existing brew formula file")
   122  
   123  	if res.StatusCode == 404 {
   124  		log.WithFields(log.Fields{
   125  			"fileName":  fileName,
   126  			"ref":       ref,
   127  			"projectID": projectID,
   128  		}).Debug("creating brew formula")
   129  		createOpts := &gitlab.CreateFileOptions{
   130  			AuthorName:    &commitAuthor.Name,
   131  			AuthorEmail:   &commitAuthor.Email,
   132  			Content:       &castedContent,
   133  			Branch:        &branch,
   134  			CommitMessage: &message,
   135  		}
   136  		fileInfo, res, err := c.client.RepositoryFiles.CreateFile(projectID, fileName, createOpts)
   137  		if err != nil {
   138  			log.WithFields(log.Fields{
   139  				"fileName":   fileName,
   140  				"branch":     branch,
   141  				"projectID":  projectID,
   142  				"statusCode": res.StatusCode,
   143  				"err":        err.Error(),
   144  			}).Error("error creating brew formula file")
   145  			return err
   146  		}
   147  
   148  		log.WithFields(log.Fields{
   149  			"fileName":  fileName,
   150  			"branch":    branch,
   151  			"projectID": projectID,
   152  			"filePath":  fileInfo.FilePath,
   153  		}).Debug("created brew formula file")
   154  		return nil
   155  	}
   156  
   157  	log.WithFields(log.Fields{
   158  		"fileName":  fileName,
   159  		"ref":       ref,
   160  		"projectID": projectID,
   161  	}).Debug("updating brew formula")
   162  	updateOpts := &gitlab.UpdateFileOptions{
   163  		AuthorName:    &commitAuthor.Name,
   164  		AuthorEmail:   &commitAuthor.Email,
   165  		Content:       &castedContent,
   166  		Branch:        &branch,
   167  		CommitMessage: &message,
   168  	}
   169  
   170  	updateFileInfo, res, err := c.client.RepositoryFiles.UpdateFile(projectID, fileName, updateOpts)
   171  	if err != nil {
   172  		log.WithFields(log.Fields{
   173  			"fileName":   fileName,
   174  			"branch":     branch,
   175  			"projectID":  projectID,
   176  			"statusCode": res.StatusCode,
   177  			"err":        err.Error(),
   178  		}).Error("error updating brew formula file")
   179  		return err
   180  	}
   181  
   182  	log.WithFields(log.Fields{
   183  		"fileName":   fileName,
   184  		"branch":     branch,
   185  		"projectID":  projectID,
   186  		"filePath":   updateFileInfo.FilePath,
   187  		"statusCode": res.StatusCode,
   188  	}).Debug("updated brew formula file")
   189  	return nil
   190  }
   191  
   192  // CreateRelease creates a new release or updates it by keeping
   193  // the release notes if it exists.
   194  func (c *gitlabClient) CreateRelease(ctx *context.Context, body string) (releaseID string, err error) {
   195  	title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
   196  	if err != nil {
   197  		return "", err
   198  	}
   199  	gitlabName, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitLab.Name)
   200  	if err != nil {
   201  		return "", err
   202  	}
   203  	projectID := gitlabName
   204  	if ctx.Config.Release.GitLab.Owner != "" {
   205  		projectID = ctx.Config.Release.GitLab.Owner + "/" + projectID
   206  	}
   207  	log.WithFields(log.Fields{
   208  		"owner":     ctx.Config.Release.GitLab.Owner,
   209  		"name":      gitlabName,
   210  		"projectID": projectID,
   211  	}).Debug("projectID")
   212  
   213  	name := title
   214  	tagName := ctx.Git.CurrentTag
   215  	release, resp, err := c.client.Releases.GetRelease(projectID, tagName)
   216  	if err != nil && (resp == nil || (resp.StatusCode != 403 && resp.StatusCode != 404)) {
   217  		return "", err
   218  	}
   219  
   220  	if resp.StatusCode == 403 || resp.StatusCode == 404 {
   221  		log.WithFields(log.Fields{
   222  			"err": err.Error(),
   223  		}).Debug("get release")
   224  
   225  		description := body
   226  		ref := ctx.Git.Commit
   227  		gitURL := ctx.Git.URL
   228  
   229  		log.WithFields(log.Fields{
   230  			"name":        name,
   231  			"description": description,
   232  			"ref":         ref,
   233  			"url":         gitURL,
   234  		}).Debug("creating release")
   235  		release, _, err = c.client.Releases.CreateRelease(projectID, &gitlab.CreateReleaseOptions{
   236  			Name:        &name,
   237  			Description: &description,
   238  			Ref:         &ref,
   239  			TagName:     &tagName,
   240  		})
   241  
   242  		if err != nil {
   243  			log.WithFields(log.Fields{
   244  				"err": err.Error(),
   245  			}).Debug("error create release")
   246  			return "", err
   247  		}
   248  		log.WithField("name", release.Name).Info("release created")
   249  	} else {
   250  		desc := body
   251  		if release != nil && release.DescriptionHTML != "" {
   252  			desc = release.DescriptionHTML
   253  		}
   254  
   255  		release, _, err = c.client.Releases.UpdateRelease(projectID, tagName, &gitlab.UpdateReleaseOptions{
   256  			Name:        &name,
   257  			Description: &desc,
   258  		})
   259  		if err != nil {
   260  			log.WithFields(log.Fields{
   261  				"err": err.Error(),
   262  			}).Debug("error update release")
   263  			return "", err
   264  		}
   265  
   266  		log.WithField("name", release.Name).Info("release updated")
   267  	}
   268  
   269  	return tagName, err // gitlab references a tag in a repo by its name
   270  }
   271  
   272  func (c *gitlabClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
   273  	var urlTemplate string
   274  	gitlabName, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitLab.Name)
   275  	if err != nil {
   276  		return "", err
   277  	}
   278  	downloadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitLabURLs.Download)
   279  	if err != nil {
   280  		return "", err
   281  	}
   282  
   283  	if ctx.Config.Release.GitLab.Owner != "" {
   284  		urlTemplate = fmt.Sprintf(
   285  			"%s/%s/%s/-/releases/{{ .Tag }}/downloads/{{ .ArtifactName }}",
   286  			downloadURL,
   287  			ctx.Config.Release.GitLab.Owner,
   288  			gitlabName,
   289  		)
   290  	} else {
   291  		urlTemplate = fmt.Sprintf(
   292  			"%s/%s/-/releases/{{ .Tag }}/downloads/{{ .ArtifactName }}",
   293  			downloadURL,
   294  			gitlabName,
   295  		)
   296  	}
   297  	return urlTemplate, nil
   298  }
   299  
   300  // Upload uploads a file into a release repository.
   301  func (c *gitlabClient) Upload(
   302  	ctx *context.Context,
   303  	releaseID string,
   304  	artifact *artifact.Artifact,
   305  	file *os.File,
   306  ) error {
   307  	// create new template and apply name field
   308  	gitlabName, err := tmpl.New(ctx).Apply(ctx.Config.Release.GitLab.Name)
   309  	if err != nil {
   310  		return err
   311  	}
   312  	projectID := gitlabName
   313  	// check if owner is empty
   314  	if ctx.Config.Release.GitLab.Owner != "" {
   315  		projectID = ctx.Config.Release.GitLab.Owner + "/" + projectID
   316  	}
   317  
   318  	log.WithField("file", file.Name()).Debug("uploading file")
   319  	projectFile, _, err := c.client.Projects.UploadFile(
   320  		projectID,
   321  		file.Name(),
   322  		nil,
   323  	)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	log.WithFields(log.Fields{
   329  		"file": file.Name(),
   330  		"url":  projectFile.URL,
   331  	}).Debug("uploaded file")
   332  
   333  	// search for project details based on projectID
   334  	projectDetails, _, err := c.client.Projects.GetProject(projectID, nil)
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	gitlabBaseURL, err := tmpl.New(ctx).Apply(ctx.Config.GitLabURLs.Download)
   340  	if err != nil {
   341  		return fmt.Errorf("templating GitLab Download URL: %w", err)
   342  	}
   343  
   344  	linkURL := gitlabBaseURL + "/" + projectDetails.PathWithNamespace + projectFile.URL
   345  	name := artifact.Name
   346  	filename := "/" + name
   347  	releaseLink, _, err := c.client.ReleaseLinks.CreateReleaseLink(
   348  		projectID,
   349  		releaseID,
   350  		&gitlab.CreateReleaseLinkOptions{
   351  			Name:     &name,
   352  			URL:      &linkURL,
   353  			FilePath: &filename,
   354  		})
   355  	if err != nil {
   356  		return RetriableError{err}
   357  	}
   358  
   359  	log.WithFields(log.Fields{
   360  		"id":  releaseLink.ID,
   361  		"url": releaseLink.DirectAssetURL,
   362  	}).Debug("created release link")
   363  
   364  	// for checksums.txt the field is nil, so we initialize it
   365  	if artifact.Extra == nil {
   366  		artifact.Extra = make(map[string]interface{})
   367  	}
   368  
   369  	return nil
   370  }
   371  
   372  // getMilestoneByTitle returns a milestone by title.
   373  func (c *gitlabClient) getMilestoneByTitle(repo Repo, title string) (*gitlab.Milestone, error) {
   374  	opts := &gitlab.ListMilestonesOptions{
   375  		Title: &title,
   376  	}
   377  
   378  	for {
   379  		milestones, resp, err := c.client.Milestones.ListMilestones(repo.String(), opts)
   380  		if err != nil {
   381  			return nil, err
   382  		}
   383  
   384  		for _, milestone := range milestones {
   385  			if milestone != nil && milestone.Title == title {
   386  				return milestone, nil
   387  			}
   388  		}
   389  
   390  		if resp.NextPage == 0 {
   391  			break
   392  		}
   393  
   394  		opts.Page = resp.NextPage
   395  	}
   396  
   397  	return nil, nil
   398  }