GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 51a090...7d2a3e )
by
unknown
05:22
created

viderBuilder.Build   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
package providers
2
3
import (
4
	"bytes"
5
	"context"
6
	"encoding/json"
7
	"errors"
8
	"fmt"
9
	"os"
10
	"os/exec"
11
	"strings"
12
	"sync"
13
	"time"
14
)
15
16
type ExternalOptions struct {
17
	// Timeout, in milliseconds.
18
	Timeout int
19
}
20
21
// ExternalCredentialUpdateCallback 定义External凭证更新回调函数类型
22
type ExternalCredentialUpdateCallback func(accessKeyId, accessKeySecret, securityToken string, expiration int64) error
23
24
type externalCredentialResponse struct {
25
	Mode            string `json:"mode"`
26
	AccessKeyId     string `json:"access_key_id"`
27
	AccessKeySecret string `json:"access_key_secret"`
28
	SecurityToken   string `json:"sts_token"`
29
	Expiration      string `json:"expiration,omitempty"`
30
}
31
32
type ExternalCredentialsProvider struct {
33
	processCommand string
34
	options        *ExternalOptions
35
36
	lastUpdateTimestamp int64
37
	expirationTimestamp int64
38
	sessionCredentials  *sessionCredentials
39
	// External credential call back
40
	credentialUpdateCallback ExternalCredentialUpdateCallback
41
	// 互斥锁,用于并发安全
42
	mu sync.RWMutex
43
}
44
45
type ExternalCredentialsProviderBuilder struct {
46
	provider *ExternalCredentialsProvider
47
}
48
49
func NewExternalCredentialsProviderBuilder() *ExternalCredentialsProviderBuilder {
50
	return &ExternalCredentialsProviderBuilder{
51
		provider: &ExternalCredentialsProvider{},
52
	}
53
}
54
55
func (b *ExternalCredentialsProviderBuilder) WithProcessCommand(processCommand string) *ExternalCredentialsProviderBuilder {
56
	b.provider.processCommand = processCommand
57
	return b
58
}
59
60
func (b *ExternalCredentialsProviderBuilder) WithExternalOptions(options *ExternalOptions) *ExternalCredentialsProviderBuilder {
61
	b.provider.options = options
62
	return b
63
}
64
65
func (b *ExternalCredentialsProviderBuilder) WithCredentialUpdateCallback(callback ExternalCredentialUpdateCallback) *ExternalCredentialsProviderBuilder {
66
	b.provider.credentialUpdateCallback = callback
67
	return b
68
}
69
70
func (b *ExternalCredentialsProviderBuilder) Build() (provider *ExternalCredentialsProvider, err error) {
71
	if b.provider.processCommand == "" {
72
		err = errors.New("process_command is empty")
73
		return
74
	}
75
76
	provider = b.provider
77
	return
78
}
79
80
func (provider *ExternalCredentialsProvider) getCredentials() (session *sessionCredentials, err error) {
81
	args := strings.Fields(provider.processCommand)
82
	if len(args) == 0 {
83
		err = errors.New("process_command is empty")
84
		return
85
	}
86
87
	// 确保 options 不为 nil,并设置默认超时时间
88
	timeout := 60 * 1000 // 默认 60 秒
89
	if provider.options != nil && provider.options.Timeout > 0 {
90
		timeout = provider.options.Timeout
91
	}
92
93
	var cancelFunc func()
94
	ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Millisecond)
95
	defer cancelFunc()
96
97
	cmd := exec.CommandContext(ctx, args[0], args[1:]...)
98
	cmd.Env = os.Environ()
99
100
	// 创建一个buffer来捕获标准输出
101
	var stdoutBuf bytes.Buffer
102
	cmd.Stdout = &stdoutBuf
103
104
	// 创建一个buffer来捕获标准错误输出
105
	var stderrBuf bytes.Buffer
106
	cmd.Stderr = &stderrBuf
107
108
	// Start the command
109
	if err := cmd.Start(); err != nil {
110
		return nil, fmt.Errorf("failed to execute external command: %w\nstderr: %s", err, stderrBuf.String())
111
	}
112
113
	done := make(chan error, 1)
114
	go func() {
115
		done <- cmd.Wait()
116
	}()
117
118
	select {
119
	case <-ctx.Done():
120
		// 超时了,context 会自动终止命令
121
		<-done
122
		return nil, fmt.Errorf("command process timed out after %d milliseconds", timeout)
123
	case execError := <-done:
124
		if execError != nil {
125
			// 检查是否是超时导致的错误
126
			if errors.Is(execError, context.DeadlineExceeded) {
127
				return nil, fmt.Errorf("command process timed out after %d milliseconds", timeout)
128
			}
129
			return nil, fmt.Errorf("failed to execute external command: %w\nstderr: %s", execError, stderrBuf.String())
130
		}
131
	}
132
133
	// 只解析标准输出
134
	buf := stdoutBuf.Bytes()
135
136
	// 解析得到凭证响应
137
	var resp externalCredentialResponse
138
	err = json.Unmarshal(buf, &resp)
139
	if err != nil {
140
		fmt.Println(provider.processCommand)
141
		fmt.Println(string(buf))
142
		return nil, fmt.Errorf("failed to parse external command output: %w", err)
143
	}
144
145
	// 验证返回的凭证数据
146
	if resp.AccessKeyId == "" || resp.AccessKeySecret == "" {
147
		return nil, fmt.Errorf("invalid credential response: access_key_id or access_key_secret is empty")
148
	}
149
150
	// 根据 mode 验证 SecurityToken
151
	if resp.Mode == "StsToken" && resp.SecurityToken == "" {
152
		return nil, fmt.Errorf("invalid StsToken credential response: sts_token is empty")
153
	}
154
155
	session = &sessionCredentials{
156
		AccessKeyId:     resp.AccessKeyId,
157
		AccessKeySecret: resp.AccessKeySecret,
158
		SecurityToken:   resp.SecurityToken,
159
		Expiration:      resp.Expiration,
160
	}
161
162
	return
163
}
164
165
func (provider *ExternalCredentialsProvider) needUpdateCredential() (result bool) {
166
	provider.mu.RLock()
167
	defer provider.mu.RUnlock()
168
169
	// 如果没有缓存凭证,需要更新
170
	if provider.sessionCredentials == nil {
171
		return true
172
	}
173
174
	// 如果没有过期时间,每次都更新(因为外部命令可能返回动态凭证)
175
	if provider.expirationTimestamp == 0 {
176
		return true
177
	}
178
179
	// 如果凭证即将过期(提前180秒),需要更新
180
	return provider.expirationTimestamp-time.Now().Unix() <= 180
181
}
182
183
func (provider *ExternalCredentialsProvider) GetCredentials() (cc *Credentials, err error) {
184
	// 先检查是否需要更新(使用读锁)
185
	provider.mu.RLock()
186
	needUpdate := provider.sessionCredentials == nil ||
187
		provider.expirationTimestamp == 0 ||
188
		provider.expirationTimestamp-time.Now().Unix() <= 180
189
	provider.mu.RUnlock()
190
191
	if needUpdate {
192
		// 获取新凭证(在锁外执行,避免阻塞其他 goroutine)
193
		sessionCredentials, err1 := provider.getCredentials()
194
		if err1 != nil {
195
			return nil, err1
196
		}
197
198
		// 使用写锁更新共享状态
199
		provider.mu.Lock()
200
		// 双重检查,避免多个 goroutine 同时更新
201
		if provider.sessionCredentials == nil ||
202
			provider.expirationTimestamp == 0 ||
203
			provider.expirationTimestamp-time.Now().Unix() <= 180 {
204
			provider.sessionCredentials = sessionCredentials
205
206
			// 如果返回了过期时间,解析并缓存
207
			if sessionCredentials.Expiration != "" {
208
				expirationTime, err2 := time.Parse("2006-01-02T15:04:05Z", sessionCredentials.Expiration)
209
				if err2 != nil {
210
					// 如果解析失败,不设置过期时间,下次调用时重新获取
211
					provider.expirationTimestamp = 0
212
				} else {
213
					provider.lastUpdateTimestamp = time.Now().Unix()
214
					provider.expirationTimestamp = expirationTime.Unix()
215
				}
216
			} else {
217
				// 没有过期时间,下次调用时重新获取
218
				provider.expirationTimestamp = 0
219
			}
220
		}
221
		expirationTimestamp := provider.expirationTimestamp
222
		sessionCredentials = provider.sessionCredentials
223
		provider.mu.Unlock()
224
225
		// 如果设置了回调函数,则调用回调函数写回配置文件(在锁外执行)
226
		if provider.credentialUpdateCallback != nil {
227
			err1 := provider.credentialUpdateCallback(
228
				sessionCredentials.AccessKeyId,
229
				sessionCredentials.AccessKeySecret,
230
				sessionCredentials.SecurityToken,
231
				expirationTimestamp,
232
			)
233
			if err1 != nil {
234
				fmt.Printf("Warning: failed to update external credentials in config file: %v\n", err1)
235
			}
236
		}
237
	}
238
239
	// 使用读锁读取凭证
240
	provider.mu.RLock()
241
	cc = &Credentials{
242
		AccessKeyId:     provider.sessionCredentials.AccessKeyId,
243
		AccessKeySecret: provider.sessionCredentials.AccessKeySecret,
244
		SecurityToken:   provider.sessionCredentials.SecurityToken,
245
		ProviderName:    provider.GetProviderName(),
246
	}
247
	provider.mu.RUnlock()
248
	return
249
}
250
251
func (provider *ExternalCredentialsProvider) GetProviderName() string {
252
	return "external"
253
}
254