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 }