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 }