github.com/goreleaser/goreleaser@v1.25.1/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/goreleaser/goreleaser/internal/artifact"
    15  	"github.com/goreleaser/goreleaser/internal/tmpl"
    16  	"github.com/goreleaser/goreleaser/pkg/config"
    17  	"github.com/goreleaser/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 := log.WithField("projectID", projectID)
   100  		if res != nil {
   101  			log = log.WithField("statusCode", res.StatusCode)
   102  		}
   103  		log.WithError(err).
   104  			Warn("error checking for default branch")
   105  		return "", err
   106  	}
   107  	return p.DefaultBranch, nil
   108  }
   109  
   110  // CreateFile creates a file in the repository at a given path
   111  // or updates the file if it exists.
   112  func (c *giteaClient) CreateFile(
   113  	ctx *context.Context,
   114  	commitAuthor config.CommitAuthor,
   115  	repo Repo,
   116  	content []byte,
   117  	path,
   118  	message string,
   119  ) error {
   120  	// use default branch
   121  	var branch string
   122  	var err error
   123  	if repo.Branch != "" {
   124  		branch = repo.Branch
   125  	} else {
   126  		branch, err = c.getDefaultBranch(ctx, repo)
   127  		if err != nil {
   128  			// Fall back to 'master' 😭
   129  			log.WithField("fileName", path).
   130  				WithField("projectID", repo.String()).
   131  				WithField("requestedBranch", branch).
   132  				WithError(err).
   133  				Warn("error checking for default branch, using master")
   134  		}
   135  
   136  	}
   137  
   138  	fileOptions := gitea.FileOptions{
   139  		Message:    message,
   140  		BranchName: branch,
   141  		Author: gitea.Identity{
   142  			Name:  commitAuthor.Name,
   143  			Email: commitAuthor.Email,
   144  		},
   145  		Committer: gitea.Identity{
   146  			Name:  commitAuthor.Name,
   147  			Email: commitAuthor.Email,
   148  		},
   149  	}
   150  
   151  	log.
   152  		WithField("repository", repo.String()).
   153  		WithField("name", repo.Name).
   154  		WithField("name", repo.Name).
   155  		Info("pushing")
   156  
   157  	currentFile, resp, err := c.client.GetContents(repo.Owner, repo.Name, branch, path)
   158  	// file not exist, create it
   159  	if err != nil {
   160  		if resp == nil || resp.StatusCode != http.StatusNotFound {
   161  			return err
   162  		}
   163  		_, _, err = c.client.CreateFile(repo.Owner, repo.Name, path, gitea.CreateFileOptions{
   164  			FileOptions: fileOptions,
   165  			Content:     base64.StdEncoding.EncodeToString(content),
   166  		})
   167  		return err
   168  	}
   169  
   170  	// update file
   171  	_, _, err = c.client.UpdateFile(repo.Owner, repo.Name, path, gitea.UpdateFileOptions{
   172  		FileOptions: fileOptions,
   173  		SHA:         currentFile.SHA,
   174  		Content:     base64.StdEncoding.EncodeToString(content),
   175  	})
   176  	return err
   177  }
   178  
   179  func (c *giteaClient) createRelease(ctx *context.Context, title, body string) (*gitea.Release, error) {
   180  	releaseConfig := ctx.Config.Release
   181  	owner := releaseConfig.Gitea.Owner
   182  	repoName := releaseConfig.Gitea.Name
   183  	tag := ctx.Git.CurrentTag
   184  
   185  	opts := gitea.CreateReleaseOption{
   186  		TagName:      tag,
   187  		Target:       ctx.Git.Commit,
   188  		Title:        title,
   189  		Note:         body,
   190  		IsDraft:      releaseConfig.Draft,
   191  		IsPrerelease: ctx.PreRelease,
   192  	}
   193  	release, _, err := c.client.CreateRelease(owner, repoName, opts)
   194  	if err != nil {
   195  		log.WithError(err).Debug("error creating Gitea release")
   196  		return nil, err
   197  	}
   198  	log.WithField("id", release.ID).Info("Gitea release created")
   199  	return release, nil
   200  }
   201  
   202  func (c *giteaClient) getExistingRelease(owner, repoName, tagName string) (*gitea.Release, error) {
   203  	releases, _, err := c.client.ListReleases(owner, repoName, gitea.ListReleasesOptions{})
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	for _, release := range releases {
   209  		if release.TagName == tagName {
   210  			return release, nil
   211  		}
   212  	}
   213  
   214  	return nil, nil
   215  }
   216  
   217  func (c *giteaClient) updateRelease(ctx *context.Context, title, body string, id int64) (*gitea.Release, error) {
   218  	releaseConfig := ctx.Config.Release
   219  	owner := releaseConfig.Gitea.Owner
   220  	repoName := releaseConfig.Gitea.Name
   221  	tag := ctx.Git.CurrentTag
   222  
   223  	opts := gitea.EditReleaseOption{
   224  		TagName:      tag,
   225  		Target:       ctx.Git.Commit,
   226  		Title:        title,
   227  		Note:         body,
   228  		IsDraft:      &releaseConfig.Draft,
   229  		IsPrerelease: &ctx.PreRelease,
   230  	}
   231  
   232  	release, _, err := c.client.EditRelease(owner, repoName, id, opts)
   233  	if err != nil {
   234  		log.WithError(err).Debug("error updating Gitea release")
   235  		return nil, err
   236  	}
   237  	log.WithField("id", release.ID).Info("Gitea release updated")
   238  	return release, nil
   239  }
   240  
   241  // CreateRelease creates a new release or updates it by keeping
   242  // the release notes if it exists.
   243  func (c *giteaClient) CreateRelease(ctx *context.Context, body string) (string, error) {
   244  	var release *gitea.Release
   245  	var err error
   246  
   247  	releaseConfig := ctx.Config.Release
   248  
   249  	title, err := tmpl.New(ctx).Apply(releaseConfig.NameTemplate)
   250  	if err != nil {
   251  		return "", err
   252  	}
   253  
   254  	release, err = c.getExistingRelease(
   255  		releaseConfig.Gitea.Owner,
   256  		releaseConfig.Gitea.Name,
   257  		ctx.Git.CurrentTag,
   258  	)
   259  	if err != nil {
   260  		return "", err
   261  	}
   262  
   263  	if release != nil {
   264  		body = getReleaseNotes(release.Note, body, ctx.Config.Release.ReleaseNotesMode)
   265  		release, err = c.updateRelease(ctx, title, body, release.ID)
   266  		if err != nil {
   267  			return "", err
   268  		}
   269  	} else {
   270  		release, err = c.createRelease(ctx, title, body)
   271  		if err != nil {
   272  			return "", err
   273  		}
   274  	}
   275  
   276  	return strconv.FormatInt(release.ID, 10), nil
   277  }
   278  
   279  func (c *giteaClient) PublishRelease(_ *context.Context, _ string /* releaseID */) (err error) {
   280  	// TODO: Create release as draft while uploading artifacts and only publish it here.
   281  	return nil
   282  }
   283  
   284  func (c *giteaClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
   285  	downloadURL, err := tmpl.New(ctx).Apply(ctx.Config.GiteaURLs.Download)
   286  	if err != nil {
   287  		return "", fmt.Errorf("templating Gitea download URL: %w", err)
   288  	}
   289  
   290  	return fmt.Sprintf(
   291  		"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
   292  		downloadURL,
   293  		ctx.Config.Release.Gitea.Owner,
   294  		ctx.Config.Release.Gitea.Name,
   295  	), nil
   296  }
   297  
   298  // Upload uploads a file into a release repository.
   299  func (c *giteaClient) Upload(
   300  	ctx *context.Context,
   301  	releaseID string,
   302  	artifact *artifact.Artifact,
   303  	file *os.File,
   304  ) error {
   305  	giteaReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
   306  	if err != nil {
   307  		return err
   308  	}
   309  	releaseConfig := ctx.Config.Release
   310  	owner := releaseConfig.Gitea.Owner
   311  	repoName := releaseConfig.Gitea.Name
   312  
   313  	_, _, err = c.client.CreateReleaseAttachment(owner, repoName, giteaReleaseID, file, artifact.Name)
   314  	if err != nil {
   315  		return RetriableError{err}
   316  	}
   317  	return nil
   318  }