-
Notifications
You must be signed in to change notification settings - Fork 339
/
gitutil.go
254 lines (220 loc) · 8.23 KB
/
gitutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package util
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/apache/trafficcontrol/v8/cache-config/t3c-apply/config"
)
// EnsureConfigDirIsGitRepo ensures the ATS config directory is a git repo.
// Returns whether it tried to create a git repo, and any error.
// Note the return will be (false, nil) if a git repo already exists.
// Note true and a non-nil error may be returned, if creating a git repo is necessary and attempted and fails.
func EnsureConfigDirIsGitRepo(cfg config.Cfg) (bool, error) {
cmd := exec.Command("git", "status")
cmd.Dir = cfg.TsConfigDir
errPipe, err := cmd.StderrPipe()
if err != nil {
return false, errors.New("getting stderr pipe for command: " + err.Error())
}
if err := cmd.Start(); err != nil {
if _, ok := err.(*exec.ExitError); !ok {
// this means Go failed to run the command, not that the command returned an error.
return false, errors.New("git status returned: " + err.Error())
}
}
errOutput, err := ioutil.ReadAll(errPipe)
if err != nil {
return false, errors.New("reading stderr: " + err.Error())
}
if err := cmd.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); !ok {
// this means Go failed to run the command, not that the command returned an error.
return false, errors.New("waiting for git command: " + err.Error())
}
}
const GitNotARepoMsgPrefix = `fatal: not a git repository`
errOutput = bytes.ToLower(errOutput)
if !bytes.Contains(errOutput, []byte(GitNotARepoMsgPrefix)) {
return false, nil // it's already a git repo
}
if err := makeConfigDirGitRepo(cfg); err != nil {
return true, errors.New("making config dir '" + cfg.TsConfigDir + "' a git repo: " + err.Error())
}
return true, nil
}
const GitSafeDir = "safe.directory"
// GetGitConfigSafeDir checks that TsConfigDir has been configured as
// a safe directory. if not it will be added to the git config
// this will prevent the fatal: detected dubious ownership error
func GetGitConfigSafeDir(cfg config.Cfg) error {
safeDir := GitSafeDir + "=" + cfg.TsConfigDir
cmd := exec.Command("/usr/bin/git", "config", "-l")
cmd.Dir = cfg.TsConfigDir
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("git config returned err %v", string(output))
}
if !bytes.Contains(output, []byte(safeDir)) {
if err := addGitSafeDir(GitSafeDir, cfg.TsConfigDir); err != nil {
return err
}
}
return nil
}
func addGitSafeDir(safeDir string, path string) error {
cmd := exec.Command("/usr/bin/git", "config", "--global", "--add", GitSafeDir, path)
cmd.Dir = path
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("git config add '%v' returned err %v", safeDir, string(output))
}
return nil
}
func makeConfigDirGitRepo(cfg config.Cfg) error {
cmd := exec.Command("git", "init")
cmd.Dir = cfg.TsConfigDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("git init in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
if err := makeConfigDirGitUser(cfg); err != nil {
return fmt.Errorf("git creating user in config dir '%v': %v", cfg.TsConfigDir, err)
}
if err := makeInitialGitCommit(cfg); err != nil {
return errors.New("creating initial git commit: " + err.Error())
}
if err := MakeGitCommitAll(cfg, GitChangeIsSelf, true); err != nil {
return errors.New("creating first files git commit: " + err.Error())
}
return nil
}
const gitEmail = "traffic-control-cache-config@apache-traffic-control.invalid"
const gitUser = "t3c"
func makeConfigDirGitUser(cfg config.Cfg) error {
{
cmd := exec.Command("git", "config", "user.email", gitEmail)
cmd.Dir = cfg.TsConfigDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("git config user.email in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
}
{
cmd := exec.Command("git", "config", "user.name", gitUser)
cmd.Dir = cfg.TsConfigDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("git config user.email in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
}
return nil
}
// makeInitialGitCommit makes the initial commit for a new git repo.
// An initial empty commit is desirable, because the first commit is difficult to manipulate.
func makeInitialGitCommit(cfg config.Cfg) error {
// TODO git config author?
cmd := exec.Command("git", "commit", "--allow-empty", "-m", "Initial commit")
cmd.Dir = cfg.TsConfigDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("git initial commit error: in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
return nil
}
const GitChangeIsSelf = true
const GitChangeNotSelf = false
// makeGitCommitAll makes a git commit of all changes in cfg.TsConfigDir, including untracked files.
func MakeGitCommitAll(cfg config.Cfg, self bool, success bool) error {
{
// if there are no changes, don't do anything
cmd := exec.Command("git", "status", "--porcelain")
cmd.Dir = cfg.TsConfigDir
output, err := cmd.CombinedOutput()
output = bytes.TrimSpace(output)
if err != nil {
return fmt.Errorf("git status error: in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
if len(output) == 0 {
// no error and no output means there were zero changes, so just return
return nil
}
}
{
cmd := exec.Command("git", "add", "-A")
cmd.Dir = cfg.TsConfigDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("git add error: in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
}
now := time.Now() // TODO get a single consistent time when ORT starts?
msg := makeGitCommitMsg(cfg, now, self, success)
{
cmd := exec.Command("git", "commit", "--message", msg)
cmd.Dir = cfg.TsConfigDir
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("git commit error: in config dir '%v' returned err %v msg '%v'", cfg.TsConfigDir, err, string(output))
}
}
return nil
}
func makeGitCommitMsg(cfg config.Cfg, now time.Time, self bool, success bool) string {
const appStr = "t3c"
selfStr := "other"
if self {
selfStr = "self"
}
timeStr := now.UTC().Format(time.RFC3339)
// TODO use full args string literal instead?
modeStr := "report-only=" + strconv.FormatBool(cfg.ReportOnly) +
" files=" + cfg.Files.String() +
" install-packages=" + strconv.FormatBool(cfg.InstallPackages) +
" service-action=" + cfg.ServiceAction.String() +
" ignore-update-flag=" + strconv.FormatBool(cfg.IgnoreUpdateFlag) +
" update-ipallow=" + strconv.FormatBool(cfg.UpdateIPAllow) +
" wait-for-parents=" + strconv.FormatBool(cfg.WaitForParents) +
" no-unset-update-flag=" + strconv.FormatBool(cfg.NoUnsetUpdateFlag) +
" report-only=" + strconv.FormatBool(cfg.ReportOnly)
successStr := "fail"
if success {
successStr = "success"
}
const sep = " "
return strings.Join([]string{appStr, selfStr, modeStr, successStr, timeStr}, sep)
}
func IsGitLockFileOld(lockFile string, now time.Time, maxAge time.Duration) (bool, error) {
lockFileInfo, err := os.Stat(lockFile)
if err != nil {
return false, fmt.Errorf("stat returned error: %v on file %v", err, lockFile)
}
if diff := now.Sub(lockFileInfo.ModTime()); diff > maxAge {
return true, nil
}
return false, nil
}
func RemoveGitLock(lockFile string) error {
err := os.Remove(lockFile)
if err != nil {
return fmt.Errorf("error removing file: %v, %v", lockFile, err.Error())
}
return nil
}