/
repo_client.go
146 lines (120 loc) · 4.5 KB
/
repo_client.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
package gh
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"github.com/google/go-github/v58/github"
"github.com/snapcrafters/tokenator/internal/config"
"golang.org/x/crypto/nacl/box"
)
// RepoClient is used for manipulating secrets and environments in Github repositories.
type RepoClient struct {
client *github.Client
org string
}
// NewRepoClient constructs a new RepoClient with the specified credentials.
func NewRepoClient(token string, org string) *RepoClient {
return &RepoClient{
client: github.NewClient(nil).WithAuthToken(token),
org: org,
}
}
// SetEnvSecret sets a secret in the specified environment for the specified repo. If the
// environment does not exist, it is created.
func (rc *RepoClient) SetEnvSecret(ctx context.Context, repo string, track config.Track, secretName, secretValue string) error {
r, _, err := rc.client.Repositories.Get(ctx, rc.org, repo)
if err != nil {
return fmt.Errorf("failed to get repository: %w", err)
}
err = rc.ensureEnvironment(ctx, repo, track)
if err != nil {
return fmt.Errorf("failed to get environment: %w", err)
}
secret, err := rc.encryptSecret(ctx, r, track.Environment, secretName, secretValue)
if err != nil {
return fmt.Errorf("failed to encrypt secret: %w", err)
}
_, err = rc.client.Actions.CreateOrUpdateEnvSecret(ctx, int(*r.ID), track.Environment, secret)
if err != nil {
return fmt.Errorf("failed to set secret in environment: %w", err)
}
return nil
}
// encryptSecret fetches the public key from the specified Environment, and uses it to encrypt
// the specified secretValue such that it can be uploaded securely.
func (rc *RepoClient) encryptSecret(ctx context.Context, repo *github.Repository, envName, secretName, secretValue string) (*github.EncryptedSecret, error) {
key, _, err := rc.client.Actions.GetEnvPublicKey(ctx, int(*repo.ID), envName)
if err != nil {
return nil, fmt.Errorf("failed to get environment public key: %w", err)
}
// Decode the public key from base64
keyBytes, err := base64.StdEncoding.DecodeString(*key.Key)
if err != nil {
return nil, fmt.Errorf("failed to decode key: %w", err)
}
// Encrypt the secret value
encrypted, err := box.SealAnonymous(nil, []byte(secretValue), (*[32]byte)(keyBytes), rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to encrypt secret: %w", err)
}
return &github.EncryptedSecret{
Name: secretName,
KeyID: *key.KeyID,
EncryptedValue: base64.StdEncoding.EncodeToString(encrypted),
}, nil
}
// ensureEnvironment attempts to fetch the specified Environment for the specified repo, and
// creates it if it doesn't exist.
func (rc *RepoClient) ensureEnvironment(ctx context.Context, repo string, track config.Track) error {
_, resp, err := rc.client.Repositories.GetEnvironment(ctx, rc.org, repo, track.Environment)
if resp.StatusCode == http.StatusNotFound {
err = rc.createEnvironment(ctx, repo, track)
if err != nil {
return fmt.Errorf("failed to create environment: %w", err)
}
return nil
}
if err != nil {
return fmt.Errorf("failed to get environment: %w", err)
}
return nil
}
// createEnvironment creates an environment for the specified repository
func (rc *RepoClient) createEnvironment(ctx context.Context, repo string, track config.Track) error {
t := true
f := false
createArgs := &github.CreateUpdateEnvironment{
CanAdminsBypass: &t,
DeploymentBranchPolicy: &github.BranchPolicy{
CustomBranchPolicies: &t,
ProtectedBranches: &f,
},
PreventSelfReview: &t,
}
_, _, err := rc.client.Repositories.CreateUpdateEnvironment(ctx, rc.org, repo, track.Environment, createArgs)
if err != nil {
return fmt.Errorf("failed to create branch policy: %w", err)
}
err = rc.createDeploymentBranchPolicy(ctx, repo, track.Environment, track.Branch)
if err != nil {
return fmt.Errorf("failed to create branch policy: %w", err)
}
if track.Branch != "candidate" {
err = rc.createDeploymentBranchPolicy(ctx, repo, track.Environment, "candidate")
if err != nil {
return fmt.Errorf("failed to create branch policy: %w", err)
}
}
return nil
}
func (rc *RepoClient) createDeploymentBranchPolicy(ctx context.Context, repo string, env string, branch string) error {
b := branch
branchPolicyRequest := &github.DeploymentBranchPolicyRequest{Name: &b}
_, _, err := rc.client.Repositories.CreateDeploymentBranchPolicy(ctx, rc.org, repo, env, branchPolicyRequest)
if err != nil {
return fmt.Errorf("failed to create branch policy for environment '%s', branch '%s': %w", env, branch, err)
}
return nil
}