Skip to content

Commit 5808852

Browse files
committed
feat: add --repo flag for repo-scoped CI secrets and variables
Switch secret and variable commands to the deployed depot.ci.v2 API, which separates org-wide and repo-scoped methods. All subcommands (add, list, remove) now accept --repo owner/name to target a specific repository. Without --repo, behavior is unchanged (org-wide). Made-with: Cursor
1 parent 599da25 commit 5808852

File tree

10 files changed

+4450
-103
lines changed

10 files changed

+4450
-103
lines changed

pkg/api/ci.go

Lines changed: 114 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"connectrpc.com/connect"
88
civ1 "github.com/depot/cli/pkg/proto/depot/ci/v1"
99
"github.com/depot/cli/pkg/proto/depot/ci/v1/civ1connect"
10+
civ2 "github.com/depot/cli/pkg/proto/depot/ci/v2"
11+
"github.com/depot/cli/pkg/proto/depot/ci/v2/civ2connect"
1012
)
1113

1214
var baseURLFunc = getBaseURL
@@ -106,59 +108,90 @@ func newCISecretServiceClient() civ1connect.SecretServiceClient {
106108
return civ1connect.NewSecretServiceClient(getHTTPClient(baseURL), baseURL, WithUserAgent())
107109
}
108110

109-
// CIAddSecret adds a single CI secret to an organization
111+
func newCISecretServiceV2Client() civ2connect.SecretServiceClient {
112+
baseURL := baseURLFunc()
113+
return civ2connect.NewSecretServiceClient(getHTTPClient(baseURL), baseURL, WithUserAgent())
114+
}
115+
116+
// CIAddSecret adds a single CI secret to an organization (org-wide).
110117
func CIAddSecret(ctx context.Context, token, orgID, name, value string) error {
111-
return CIAddSecretWithDescription(ctx, token, orgID, name, value, "")
118+
return CIAddSecretWithDescription(ctx, token, orgID, name, value, "", "")
112119
}
113120

114-
// CIAddSecretWithDescription adds a single CI secret to an organization, with an optional description.
115-
func CIAddSecretWithDescription(ctx context.Context, token, orgID, name, value, description string) error {
116-
client := newCISecretServiceClient()
117-
req := &civ1.AddSecretRequest{
118-
Name: name,
119-
Value: value,
121+
// CIAddSecretWithDescription adds a CI secret, optionally scoped to a repo.
122+
func CIAddSecretWithDescription(ctx context.Context, token, orgID, name, value, description, repo string) error {
123+
client := newCISecretServiceV2Client()
124+
if repo != "" {
125+
req := &civ2.AddRepoSecretRequest{Repo: repo, Name: name, Value: value}
126+
if description != "" {
127+
req.Description = &description
128+
}
129+
_, err := client.AddRepoSecret(ctx, WithAuthenticationAndOrg(connect.NewRequest(req), token, orgID))
130+
return err
120131
}
132+
req := &civ2.AddOrgSecretRequest{Name: name, Value: value}
121133
if description != "" {
122134
req.Description = &description
123135
}
124-
_, err := client.AddSecret(ctx, WithAuthenticationAndOrg(connect.NewRequest(req), token, orgID))
136+
_, err := client.AddOrgSecret(ctx, WithAuthenticationAndOrg(connect.NewRequest(req), token, orgID))
125137
return err
126138
}
127139

128-
// CISecret contains metadata about a CI secret
140+
// CISecret contains metadata about a CI secret.
129141
type CISecret struct {
130142
Name string `json:"name"`
131143
Description string `json:"description,omitempty"`
132144
CreatedAt string `json:"createdAt,omitempty"`
145+
Scope string `json:"scope"`
133146
}
134147

135-
// CIListSecrets lists all CI secrets for an organization
136-
func CIListSecrets(ctx context.Context, token, orgID string) ([]CISecret, error) {
137-
client := newCISecretServiceClient()
138-
resp, err := client.ListSecrets(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ1.ListSecretsRequest{}), token, orgID))
148+
func secretFromProto(s *civ2.Secret, scope string) CISecret {
149+
cs := CISecret{Name: s.Name, Scope: scope}
150+
if s.Description != nil {
151+
cs.Description = *s.Description
152+
}
153+
if s.LastModified != nil {
154+
cs.CreatedAt = s.LastModified.AsTime().Format(time.RFC3339)
155+
}
156+
return cs
157+
}
158+
159+
// CIListSecrets lists CI secrets. When repo is non-empty it returns both
160+
// org-wide and repo-specific secrets so the caller can see effective state.
161+
func CIListSecrets(ctx context.Context, token, orgID, repo string) ([]CISecret, error) {
162+
client := newCISecretServiceV2Client()
163+
164+
orgResp, err := client.ListOrgSecrets(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.ListOrgSecretsRequest{}), token, orgID))
139165
if err != nil {
140166
return nil, err
141167
}
142-
secrets := make([]CISecret, 0, len(resp.Msg.Secrets))
143-
for _, s := range resp.Msg.Secrets {
144-
cs := CISecret{
145-
Name: s.Name,
146-
}
147-
if s.Description != nil {
148-
cs.Description = *s.Description
168+
169+
secrets := make([]CISecret, 0, len(orgResp.Msg.Secrets))
170+
for _, s := range orgResp.Msg.Secrets {
171+
secrets = append(secrets, secretFromProto(s, "org"))
172+
}
173+
174+
if repo != "" {
175+
repoResp, err := client.ListRepoSecrets(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.ListRepoSecretsRequest{Repo: repo}), token, orgID))
176+
if err != nil {
177+
return nil, err
149178
}
150-
if s.LastModified != nil {
151-
cs.CreatedAt = s.LastModified.AsTime().Format(time.RFC3339)
179+
for _, s := range repoResp.Msg.Secrets {
180+
secrets = append(secrets, secretFromProto(s, repo))
152181
}
153-
secrets = append(secrets, cs)
154182
}
183+
155184
return secrets, nil
156185
}
157186

158-
// CIDeleteSecret deletes a CI secret from an organization
159-
func CIDeleteSecret(ctx context.Context, token, orgID, name string) error {
160-
client := newCISecretServiceClient()
161-
_, err := client.RemoveSecret(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ1.RemoveSecretRequest{Name: name}), token, orgID))
187+
// CIDeleteSecret deletes a CI secret, optionally scoped to a repo.
188+
func CIDeleteSecret(ctx context.Context, token, orgID, name, repo string) error {
189+
client := newCISecretServiceV2Client()
190+
if repo != "" {
191+
_, err := client.RemoveRepoSecret(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.RemoveRepoSecretRequest{Repo: repo, Name: name}), token, orgID))
192+
return err
193+
}
194+
_, err := client.RemoveOrgSecret(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.RemoveOrgSecretRequest{Name: name}), token, orgID))
162195
return err
163196
}
164197

@@ -167,49 +200,76 @@ func newCIVariableServiceClient() civ1connect.VariableServiceClient {
167200
return civ1connect.NewVariableServiceClient(getHTTPClient(baseURL), baseURL, WithUserAgent())
168201
}
169202

170-
// CIAddVariable adds a single CI variable to an organization
171-
func CIAddVariable(ctx context.Context, token, orgID, name, value string) error {
172-
client := newCIVariableServiceClient()
173-
_, err := client.AddVariable(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ1.AddVariableRequest{
174-
Name: name,
175-
Value: value,
176-
}), token, orgID))
203+
func newCIVariableServiceV2Client() civ2connect.VariableServiceClient {
204+
baseURL := baseURLFunc()
205+
return civ2connect.NewVariableServiceClient(getHTTPClient(baseURL), baseURL, WithUserAgent())
206+
}
207+
208+
// CIAddVariable adds a CI variable, optionally scoped to a repo.
209+
func CIAddVariable(ctx context.Context, token, orgID, name, value, repo string) error {
210+
client := newCIVariableServiceV2Client()
211+
if repo != "" {
212+
_, err := client.AddRepoVariable(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.AddRepoVariableRequest{Repo: repo, Name: name, Value: value}), token, orgID))
213+
return err
214+
}
215+
_, err := client.AddOrgVariable(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.AddOrgVariableRequest{Name: name, Value: value}), token, orgID))
177216
return err
178217
}
179218

180-
// CIVariable contains metadata about a CI variable
219+
// CIVariable contains metadata about a CI variable.
181220
type CIVariable struct {
182221
Name string `json:"name"`
183222
Description string `json:"description,omitempty"`
184223
CreatedAt string `json:"createdAt,omitempty"`
224+
Scope string `json:"scope"`
185225
}
186226

187-
// CIListVariables lists all CI variables for an organization
188-
func CIListVariables(ctx context.Context, token, orgID string) ([]CIVariable, error) {
189-
client := newCIVariableServiceClient()
190-
resp, err := client.ListVariables(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ1.ListVariablesRequest{}), token, orgID))
227+
func variableFromProto(v *civ2.Variable, scope string) CIVariable {
228+
cv := CIVariable{Name: v.Name, Scope: scope}
229+
if v.Description != nil {
230+
cv.Description = *v.Description
231+
}
232+
if v.LastModified != nil {
233+
cv.CreatedAt = v.LastModified.AsTime().Format(time.RFC3339)
234+
}
235+
return cv
236+
}
237+
238+
// CIListVariables lists CI variables. When repo is non-empty it returns both
239+
// org-wide and repo-specific variables.
240+
func CIListVariables(ctx context.Context, token, orgID, repo string) ([]CIVariable, error) {
241+
client := newCIVariableServiceV2Client()
242+
243+
orgResp, err := client.ListOrgVariables(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.ListOrgVariablesRequest{}), token, orgID))
191244
if err != nil {
192245
return nil, err
193246
}
194-
variables := make([]CIVariable, 0, len(resp.Msg.Variables))
195-
for _, v := range resp.Msg.Variables {
196-
cv := CIVariable{
197-
Name: v.Name,
198-
}
199-
if v.Description != nil {
200-
cv.Description = *v.Description
247+
248+
variables := make([]CIVariable, 0, len(orgResp.Msg.Variables))
249+
for _, v := range orgResp.Msg.Variables {
250+
variables = append(variables, variableFromProto(v, "org"))
251+
}
252+
253+
if repo != "" {
254+
repoResp, err := client.ListRepoVariables(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.ListRepoVariablesRequest{Repo: repo}), token, orgID))
255+
if err != nil {
256+
return nil, err
201257
}
202-
if v.LastModified != nil {
203-
cv.CreatedAt = v.LastModified.AsTime().Format(time.RFC3339)
258+
for _, v := range repoResp.Msg.Variables {
259+
variables = append(variables, variableFromProto(v, repo))
204260
}
205-
variables = append(variables, cv)
206261
}
262+
207263
return variables, nil
208264
}
209265

210-
// CIDeleteVariable deletes a CI variable from an organization
211-
func CIDeleteVariable(ctx context.Context, token, orgID, name string) error {
212-
client := newCIVariableServiceClient()
213-
_, err := client.RemoveVariable(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ1.RemoveVariableRequest{Name: name}), token, orgID))
266+
// CIDeleteVariable deletes a CI variable, optionally scoped to a repo.
267+
func CIDeleteVariable(ctx context.Context, token, orgID, name, repo string) error {
268+
client := newCIVariableServiceV2Client()
269+
if repo != "" {
270+
_, err := client.RemoveRepoVariable(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.RemoveRepoVariableRequest{Repo: repo, Name: name}), token, orgID))
271+
return err
272+
}
273+
_, err := client.RemoveOrgVariable(ctx, WithAuthenticationAndOrg(connect.NewRequest(&civ2.RemoveOrgVariableRequest{Name: name}), token, orgID))
214274
return err
215275
}

pkg/cmd/ci/migrate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func runMigrate(ctx context.Context, opts migrateOptions) error {
320320
return err
321321
}
322322

323-
if err := api.CIAddVariable(ctx, token, orgID, name, value); err != nil {
323+
if err := api.CIAddVariable(ctx, token, orgID, name, value, ""); err != nil {
324324
return fmt.Errorf("failed to configure variable %s: %w", name, err)
325325
}
326326
configuredVariables = append(configuredVariables, name)

0 commit comments

Comments
 (0)