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 }