code.gitea.io/gitea@v1.19.3/modules/private/hook.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package private
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"strconv"
    13  	"time"
    14  
    15  	"code.gitea.io/gitea/modules/json"
    16  	"code.gitea.io/gitea/modules/setting"
    17  )
    18  
    19  // Git environment variables
    20  const (
    21  	GitAlternativeObjectDirectories = "GIT_ALTERNATE_OBJECT_DIRECTORIES"
    22  	GitObjectDirectory              = "GIT_OBJECT_DIRECTORY"
    23  	GitQuarantinePath               = "GIT_QUARANTINE_PATH"
    24  	GitPushOptionCount              = "GIT_PUSH_OPTION_COUNT"
    25  )
    26  
    27  // GitPushOptions is a wrapper around a map[string]string
    28  type GitPushOptions map[string]string
    29  
    30  // GitPushOptions keys
    31  const (
    32  	GitPushOptionRepoPrivate  = "repo.private"
    33  	GitPushOptionRepoTemplate = "repo.template"
    34  )
    35  
    36  // Bool checks for a key in the map and parses as a boolean
    37  func (g GitPushOptions) Bool(key string, def bool) bool {
    38  	if val, ok := g[key]; ok {
    39  		if b, err := strconv.ParseBool(val); err == nil {
    40  			return b
    41  		}
    42  	}
    43  	return def
    44  }
    45  
    46  // HookOptions represents the options for the Hook calls
    47  type HookOptions struct {
    48  	OldCommitIDs                    []string
    49  	NewCommitIDs                    []string
    50  	RefFullNames                    []string
    51  	UserID                          int64
    52  	UserName                        string
    53  	GitObjectDirectory              string
    54  	GitAlternativeObjectDirectories string
    55  	GitQuarantinePath               string
    56  	GitPushOptions                  GitPushOptions
    57  	PullRequestID                   int64
    58  	DeployKeyID                     int64 // if the pusher is a DeployKey, then UserID is the repo's org user.
    59  	IsWiki                          bool
    60  	ActionPerm                      int
    61  }
    62  
    63  // SSHLogOption ssh log options
    64  type SSHLogOption struct {
    65  	IsError bool
    66  	Message string
    67  }
    68  
    69  // HookPostReceiveResult represents an individual result from PostReceive
    70  type HookPostReceiveResult struct {
    71  	Results      []HookPostReceiveBranchResult
    72  	RepoWasEmpty bool
    73  	Err          string
    74  }
    75  
    76  // HookPostReceiveBranchResult represents an individual branch result from PostReceive
    77  type HookPostReceiveBranchResult struct {
    78  	Message bool
    79  	Create  bool
    80  	Branch  string
    81  	URL     string
    82  }
    83  
    84  // HookProcReceiveResult represents an individual result from ProcReceive
    85  type HookProcReceiveResult struct {
    86  	Results []HookProcReceiveRefResult
    87  	Err     string
    88  }
    89  
    90  // HookProcReceiveRefResult represents an individual result from ProcReceive
    91  type HookProcReceiveRefResult struct {
    92  	OldOID       string
    93  	NewOID       string
    94  	Ref          string
    95  	OriginalRef  string
    96  	IsForcePush  bool
    97  	IsNotMatched bool
    98  	Err          string
    99  }
   100  
   101  // HookPreReceive check whether the provided commits are allowed
   102  func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (int, string) {
   103  	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s",
   104  		url.PathEscape(ownerName),
   105  		url.PathEscape(repoName),
   106  	)
   107  	req := newInternalRequest(ctx, reqURL, "POST")
   108  	req = req.Header("Content-Type", "application/json")
   109  	jsonBytes, _ := json.Marshal(opts)
   110  	req.Body(jsonBytes)
   111  	req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
   112  	resp, err := req.Response()
   113  	if err != nil {
   114  		return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
   115  	}
   116  	defer resp.Body.Close()
   117  
   118  	if resp.StatusCode != http.StatusOK {
   119  		return resp.StatusCode, decodeJSONError(resp).Err
   120  	}
   121  
   122  	return http.StatusOK, ""
   123  }
   124  
   125  // HookPostReceive updates services and users
   126  func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, string) {
   127  	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s",
   128  		url.PathEscape(ownerName),
   129  		url.PathEscape(repoName),
   130  	)
   131  
   132  	req := newInternalRequest(ctx, reqURL, "POST")
   133  	req = req.Header("Content-Type", "application/json")
   134  	req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
   135  	jsonBytes, _ := json.Marshal(opts)
   136  	req.Body(jsonBytes)
   137  	resp, err := req.Response()
   138  	if err != nil {
   139  		return nil, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
   140  	}
   141  	defer resp.Body.Close()
   142  
   143  	if resp.StatusCode != http.StatusOK {
   144  		return nil, decodeJSONError(resp).Err
   145  	}
   146  	res := &HookPostReceiveResult{}
   147  	_ = json.NewDecoder(resp.Body).Decode(res)
   148  
   149  	return res, ""
   150  }
   151  
   152  // HookProcReceive proc-receive hook
   153  func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, error) {
   154  	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s",
   155  		url.PathEscape(ownerName),
   156  		url.PathEscape(repoName),
   157  	)
   158  
   159  	req := newInternalRequest(ctx, reqURL, "POST")
   160  	req = req.Header("Content-Type", "application/json")
   161  	req.SetTimeout(60*time.Second, time.Duration(60+len(opts.OldCommitIDs))*time.Second)
   162  	jsonBytes, _ := json.Marshal(opts)
   163  	req.Body(jsonBytes)
   164  	resp, err := req.Response()
   165  	if err != nil {
   166  		return nil, fmt.Errorf("Unable to contact gitea: %w", err)
   167  	}
   168  	defer resp.Body.Close()
   169  
   170  	if resp.StatusCode != http.StatusOK {
   171  		return nil, errors.New(decodeJSONError(resp).Err)
   172  	}
   173  	res := &HookProcReceiveResult{}
   174  	_ = json.NewDecoder(resp.Body).Decode(res)
   175  
   176  	return res, nil
   177  }
   178  
   179  // SetDefaultBranch will set the default branch to the provided branch for the provided repository
   180  func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) error {
   181  	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/set-default-branch/%s/%s/%s",
   182  		url.PathEscape(ownerName),
   183  		url.PathEscape(repoName),
   184  		url.PathEscape(branch),
   185  	)
   186  	req := newInternalRequest(ctx, reqURL, "POST")
   187  	req = req.Header("Content-Type", "application/json")
   188  
   189  	req.SetTimeout(60*time.Second, 60*time.Second)
   190  	resp, err := req.Response()
   191  	if err != nil {
   192  		return fmt.Errorf("Unable to contact gitea: %w", err)
   193  	}
   194  	defer resp.Body.Close()
   195  	if resp.StatusCode != http.StatusOK {
   196  		return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err)
   197  	}
   198  	return nil
   199  }
   200  
   201  // SSHLog sends ssh error log response
   202  func SSHLog(ctx context.Context, isErr bool, msg string) error {
   203  	reqURL := setting.LocalURL + "api/internal/ssh/log"
   204  	req := newInternalRequest(ctx, reqURL, "POST")
   205  	req = req.Header("Content-Type", "application/json")
   206  
   207  	jsonBytes, _ := json.Marshal(&SSHLogOption{
   208  		IsError: isErr,
   209  		Message: msg,
   210  	})
   211  	req.Body(jsonBytes)
   212  
   213  	req.SetTimeout(60*time.Second, 60*time.Second)
   214  	resp, err := req.Response()
   215  	if err != nil {
   216  		return fmt.Errorf("unable to contact gitea: %w", err)
   217  	}
   218  
   219  	defer resp.Body.Close()
   220  	if resp.StatusCode != http.StatusOK {
   221  		return fmt.Errorf("Error returned from gitea: %v", decodeJSONError(resp).Err)
   222  	}
   223  	return nil
   224  }