github.com/windmeup/goreleaser@v1.21.95/internal/client/gitea.go (about)

     1  package client
     2  
     3  import (
     4  	"crypto/tls"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"strconv"
    11  
    12  	"code.gitea.io/sdk/gitea"
    13  	"github.com/caarlos0/log"
    14  	"github.com/windmeup/goreleaser/internal/artifact"
    15  	"github.com/windmeup/goreleaser/internal/tmpl"
    16  	"github.com/windmeup/goreleaser/pkg/config"
    17  	"github.com/windmeup/goreleaser/pkg/context"
    18  )
    19  
    20  type giteaClient struct {
    21  	client *gitea.Client
    22  }
    23  
    24  var _ Client = &giteaClient{}
    25  
    26  func getInstanceURL(ctx *context.Context) (string, error) {
    27  	apiURL, err := tmpl.New(ctx).Apply(ctx.Config.GiteaURLs.API)
    28  	if err != nil {
    29  		return "", fmt.Errorf("templating Gitea API URL: %w", err)
    30  	}
    31  
    32  	u, err := url.Parse(apiURL)
    33  	if err != nil {
    34  		return "", err
    35  	}
    36  	u.Path = ""
    37  	rawurl := u.String()
    38  	if rawurl == "" {
    39  		return "", fmt.Errorf("invalid URL: %q", ctx.Config.GiteaURLs.API)
    40  	}
    41  	return rawurl, nil
    42  }
    43  
    44  // newGitea returns a gitea client implementation.
    45  func newGitea(ctx *context.Context, token string) (*giteaClient, error) {
    46  	instanceURL, err := getInstanceURL(ctx)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	transport := &http.Transport{
    51  		Proxy: http.ProxyFromEnvironment,
    52  		TLSClientConfig: &tls.Config{
    53  			// nolint: gosec
    54  			InsecureSkipVerify: ctx.Config.GiteaURLs.SkipTLSVerify,
    55  		},
    56  	}
    57  	httpClient := &http.Client{Transport: transport}
    58  	options := []gitea.ClientOption{
    59  		gitea.SetHTTPClient(httpClient),
    60  	}
    61  	if token != "giteatoken" { // token used in tests
    62  		options = append(options, gitea.SetToken(token))
    63  	}
    64  	client, err := gitea.NewClient(instanceURL, options...)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	if ctx != nil {
    69  		if err := gitea.SetContext(ctx)(client); err != nil {
    70  			return nil, err
    71  		}
    72  	}
    73  	return &giteaClient{client: client}, nil
    74  }
    75  
    76  func (c *giteaClient) Changelog(_ *context.Context, _ Repo, _, _ string) (string, error) {
    77  	return "", ErrNotImplemented
    78  }
    79  
    80  // CloseMilestone closes a given milestone.
    81  func (c *giteaClient) CloseMilestone(_ *context.Context, repo Repo, title string) error {
    82  	closedState := gitea.StateClosed
    83  	opts := gitea.EditMilestoneOption{
    84  		State: &closedState,
    85  		Title: title,
    86  	}
    87  
    88  	_, resp, err := c.client.EditMilestoneByName(repo.Owner, repo.Name, title, opts)
    89  	if resp != nil && resp.StatusCode == http.StatusNotFound {
    90  		return ErrNoMilestoneFound{Title: title}
    91  	}
    92  	return err
    93  }
    94  
    95  func (c *giteaClient) getDefaultBranch(_ *context.Context, repo Repo) (string, error) {
    96  	projectID := repo.String()
    97  	p, res, err := c.client.GetRepo(repo.Owner, repo.Name)
    98  	if err != nil {
    99  		log.WithField("projectID", projectID).
   100  			WithField("statusCode", res.StatusCode).
   101  			WithError(err).
   102  			Warn("error checking for default branch")
   103  		return "", err
   104  	}
   105  	return p.DefaultBranch, nil
   106  }
   107  
   108  // CreateFile creates a file in the repository at a given path
   109  // or updates the file if it exists.
   110  func (c *giteaClient) CreateFile(
   111  	ctx *context.Context,
   112  	commitAuthor config.CommitAuthor,
   113  	repo Repo,
   114  	content []byte,
   115  	path,
   116  	message string,
   117  ) error {
   118  	// use default branch
   119  	var branch string
   120  	var err error
   121  	if repo.Branch != "" {
   122  		branch = repo.Branch
   123  	} else {
   124  		branch, err = c.getDefaultBranch(ctx, repo)
   125  		if err != nil {
   126  			// Fall back to 'master' 😭
   127  			log.WithField("fileName", path).
   128  				WithField("projectID", repo.String()).
   129  				WithField("requestedBranch", branch).
   130  				WithError(err).
   131  				Warn("error checking for default branch, using master")
   132  		}
   133  
   134  	}
   135  
   136  	fileOptions := gitea.FileOptions{
   137  		Message:    message,
   138  		BranchName: branch,
   139  		Author: gitea.Identity{
   140  			Name:  commitAuthor.Name,
   141  			Email: commitAuthor.Email,
   142  		},
   143  		Committer: gitea.Identity{
   144  			Name:  commitAuthor.Name,
   145  			Email: commitAuthor.Email,
   146  		},
   147  	}
   148  
   149  	log.
   150  		WithField("repository", repo.String()).
   151  		WithField("name", repo.Name).
   152  		WithField("name", repo.Name).
   153  		Info("pushing")
   154  
   155  	currentFile, resp, err := c.client.GetContents(repo.Owner, repo.Name, branch, path)
   156  	// file not exist, create it
   157  	if err != nil {
   158  		if resp == nil || resp.StatusCode != http.StatusNotFound {
   159  			return err
   160  		}
   161  		_, _, err = c.client.CreateFile(repo.Owner, repo.Name, path, gitea.CreateFileOptions{
   162  			FileOptions: fileOptions,
   163  			Content:     base64.StdEncoding.EncodeToString(content),
   164  		})
   165  		return err
   166  	}
   167  
   168  	// update file
   169  	_, _, err = c.client.UpdateFile(repo.Owner, repo.Name, path, gitea.UpdateFileOptions{
   170  		FileOptions: fileOptions,
   171  		SHA:         currentFile.SHA,
   172  		Content:     base64.StdEncoding.EncodeToString(content),
   173  	})
   174  	return err
   175  }
   176  
   177  func (c *giteaClient) createRelease(ctx *context.Context, title, body string) (*gitea.Release, error) {
   178  	releaseConfig := ctx.Config.Release
   179  	owner := releaseConfig.Gitea.Owner
   180  	repoName := releaseConfig.Gitea.Name
   181  	tag := ctx.Git.CurrentTag
   182  
   183  	opts := gitea.CreateReleaseOption{
   184  		TagName:      tag,
   185  		Target:       ctx.Git.Commit,
   186  		Title:        title,
   187  		Note:         body,
   188  		IsDraft:      releaseConfig.Draft,
   189  		IsPrerelease: ctx.PreRelease,
   190  	}
   191  	release, _, err := c.client.CreateRelease(owner, repoName, opts)
   192  	if err != nil {
   193  		log.WithError(err).Debug("error creating Gitea release")
   194  		return nil, err
   195  	}
   196  	log.WithField("id", release.ID).Info("Gitea release created")
   197  	return release, nil
   198  }
   199  
   200  func (c *giteaClient) getExistingRelease(owner, repoName, tagName string) (*gitea.Release, error) {
   201  	releases, _, err := c.client.ListReleases(owner, repoName, gitea.ListReleasesOptions{})
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	for _, release := range releases {
   207  		if release.TagName == tagName {
   208  			return release, nil
   209  		}
   210  	}
   211  
   212  	return nil, nil
   213  }
   214  
   215  func (c *giteaClient) updateRelease(ctx *context.Context, title, body string, id int64) (*gitea.Release, error) {
   216  	releaseConfig := ctx.Config.Release
   217  	owner := releaseConfig.Gitea.Owner
   218  	repoName := releaseConfig.Gitea.Name
   219  	tag := ctx.Git.CurrentTag
   220  
   221  	opts := gitea.EditReleaseOption{
   222  		TagName:      tag,
   223  		Target:       ctx.Git.Commit,
   224  		Title:        title,
   225  		Note:         body,
   226  		IsDraft:      &releaseConfig.Draft,
   227  		IsPrerelease: &ctx.PreRelease,
   228  	}
   229  
   230  	release, _, err := c.client.EditRelease(owner, repoName, id, opts)
   231  	if err != nil {
   232  		log.WithError(err).Debug("error updating Gitea release")
   233  		return nil, err
   234  	}
   235  	log.WithField("id", release.ID).Info("Gitea release updated")
   236  	return release, nil
   237  }
   238  
   239  // CreateRelease creates a new release or updates it by keeping
   240  // the release notes if it exists.
   241  func (c *giteaClient) CreateRelease(ctx *context.Context, body string) (string, error) {
   242  	var release *gitea.Release
   243  	var err error
   244  
   245  	releaseConfig := ctx.Config.Release
   246  
   247  	title, err := tmpl.New(ctx).Apply(releaseConfig.NameTemplate)
   248  	if err != nil {
   249  		return "", err
   250  	}
   251  
   252  	release, err = c.getExistingRelease(
   253  		releaseConfig.Gitea.Owner,
   254  		releaseConfig.Gitea.Name,
   255  		ctx.Git.CurrentTag,
   256  	)
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  
   261  	if release != nil {
   262  		body = getReleaseNotes(release.Note, body, ctx.Config.Release.ReleaseNotesMode)
   263  		release, err = c.updateRelease(ctx, title, body, release.ID)
   264  		if err != nil {
   265  			return "", err
   266  		}
   267  	} else {
   268  		release, err = c.createRelease(ctx, title, body)
   269  		if err != nil {
   270  			return "", err
   271  		}
   272  	}
   273  
   274  	return strconv.FormatInt(release.ID, 10), nil
   275  }
   276  
   277  func (c *giteaClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
   278  	downloadURL, err := tmpl.New(ctx).Apply(ctx.Config.GiteaURLs.Download)
   279  	if err != nil {
   280  		return "", fmt.Errorf("templating Gitea download URL: %w", err)
   281  	}
   282  
   283  	return fmt.Sprintf(
   284  		"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
   285  		downloadURL,
   286  		ctx.Config.Release.Gitea.Owner,
   287  		ctx.Config.Release.Gitea.Name,
   288  	), nil
   289  }
   290  
   291  // Upload uploads a file into a release repository.
   292  func (c *giteaClient) Upload(
   293  	ctx *context.Context,
   294  	releaseID string,
   295  	artifact *artifact.Artifact,
   296  	file *os.File,
   297  ) error {
   298  	giteaReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	releaseConfig := ctx.Config.Release
   303  	owner := releaseConfig.Gitea.Owner
   304  	repoName := releaseConfig.Gitea.Name
   305  
   306  	_, _, err = c.client.CreateReleaseAttachment(owner, repoName, giteaReleaseID, file, artifact.Name)
   307  	if err != nil {
   308  		return RetriableError{err}
   309  	}
   310  	return nil
   311  }