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