mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-06-02 21:49:25 +00:00
parent
c986c7f5ee
commit
0b2b776ed8
|
@ -164,11 +164,6 @@ var flags = []cli.Flag{
|
||||||
Name: "registry-service",
|
Name: "registry-service",
|
||||||
Usage: "registry plugin endpoint",
|
Usage: "registry plugin endpoint",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
|
||||||
EnvVars: []string{"WOODPECKER_GATEKEEPER_ENDPOINT"},
|
|
||||||
Name: "gating-service",
|
|
||||||
Usage: "gated build endpoint",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
EnvVars: []string{"WOODPECKER_CONFIG_SERVICE_ENDPOINT"},
|
EnvVars: []string{"WOODPECKER_CONFIG_SERVICE_ENDPOINT"},
|
||||||
Name: "config-service-endpoint",
|
Name: "config-service-endpoint",
|
||||||
|
|
|
@ -43,7 +43,6 @@ import (
|
||||||
"github.com/woodpecker-ci/woodpecker/server/logging"
|
"github.com/woodpecker-ci/woodpecker/server/logging"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/plugins/configuration"
|
"github.com/woodpecker-ci/woodpecker/server/plugins/configuration"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/plugins/sender"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/pubsub"
|
"github.com/woodpecker-ci/woodpecker/server/pubsub"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/remote"
|
"github.com/woodpecker-ci/woodpecker/server/remote"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/router"
|
"github.com/woodpecker-ci/woodpecker/server/router"
|
||||||
|
@ -266,13 +265,8 @@ func setupEvilGlobals(c *cli.Context, v store.Store, r remote.Remote) {
|
||||||
}
|
}
|
||||||
server.Config.Services.Registries = setupRegistryService(c, v)
|
server.Config.Services.Registries = setupRegistryService(c, v)
|
||||||
server.Config.Services.Secrets = setupSecretService(c, v)
|
server.Config.Services.Secrets = setupSecretService(c, v)
|
||||||
server.Config.Services.Senders = sender.New(v, v)
|
|
||||||
server.Config.Services.Environ = setupEnvironService(c, v)
|
server.Config.Services.Environ = setupEnvironService(c, v)
|
||||||
|
|
||||||
if endpoint := c.String("gating-service"); endpoint != "" {
|
|
||||||
server.Config.Services.Senders = sender.NewRemote(endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endpoint := c.String("config-service-endpoint"); endpoint != "" {
|
if endpoint := c.String("config-service-endpoint"); endpoint != "" {
|
||||||
secret := c.String("config-service-secret")
|
secret := c.String("config-service-secret")
|
||||||
if secret == "" {
|
if secret == "" {
|
||||||
|
|
|
@ -33,7 +33,6 @@ var Config = struct {
|
||||||
Pubsub pubsub.Publisher
|
Pubsub pubsub.Publisher
|
||||||
Queue queue.Queue
|
Queue queue.Queue
|
||||||
Logs logging.Log
|
Logs logging.Log
|
||||||
Senders model.SenderService
|
|
||||||
Secrets model.SecretService
|
Secrets model.SecretService
|
||||||
Registries model.RegistryService
|
Registries model.RegistryService
|
||||||
Environ model.EnvironService
|
Environ model.EnvironService
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright 2021 Woodpecker Authors
|
|
||||||
// Copyright 2018 Drone.IO Inc.
|
|
||||||
//
|
|
||||||
// Licensed 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.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
type SenderService interface {
|
|
||||||
SenderAllowed(*User, *Repo, *Build, *Config) (bool, error)
|
|
||||||
SenderCreate(*Repo, *Sender) error
|
|
||||||
SenderUpdate(*Repo, *Sender) error
|
|
||||||
SenderDelete(*Repo, string) error
|
|
||||||
SenderList(*Repo) ([]*Sender, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SenderStore interface {
|
|
||||||
SenderFind(*Repo, string) (*Sender, error)
|
|
||||||
SenderList(*Repo) ([]*Sender, error)
|
|
||||||
SenderCreate(*Sender) error
|
|
||||||
SenderUpdate(*Sender) error
|
|
||||||
SenderDelete(*Sender) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Sender struct {
|
|
||||||
ID int64 `json:"-" xorm:"pk autoincr 'sender_id'"`
|
|
||||||
RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'sender_repo_id'"`
|
|
||||||
Login string `json:"login" xorm:"UNIQUE(s) 'sender_login'"`
|
|
||||||
Allow bool `json:"allow" xorm:"sender_allow"`
|
|
||||||
Block bool `json:"block" xorm:"sender_block"`
|
|
||||||
Branch []string `json:"branch" xorm:"-"`
|
|
||||||
Deploy []string `json:"deploy" xorm:"-"`
|
|
||||||
Event []string `json:"event" xorm:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName return database table name for xorm
|
|
||||||
func (Sender) TableName() string {
|
|
||||||
return "senders"
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package sender
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type builtin struct {
|
|
||||||
store model.SenderStore
|
|
||||||
conf model.ConfigStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new local gating service.
|
|
||||||
func New(store model.SenderStore, conf model.ConfigStore) model.SenderService {
|
|
||||||
return &builtin{store, conf}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builtin) SenderAllowed(user *model.User, repo *model.Repo, build *model.Build, conf *model.Config) (bool, error) {
|
|
||||||
if build.Event == model.EventPull && build.Sender != user.Login {
|
|
||||||
// check to see if the configuration has already been used in an
|
|
||||||
// existing build. If yes it is considered approved.
|
|
||||||
if ok, _ := b.conf.ConfigFindApproved(conf); ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
// else check to see if the configuration is sent from a user
|
|
||||||
// account that is a repositroy approver themselves.
|
|
||||||
sender, err := b.store.SenderFind(repo, build.Sender)
|
|
||||||
if err != nil || sender.Block {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builtin) SenderCreate(repo *model.Repo, sender *model.Sender) error {
|
|
||||||
return b.store.SenderCreate(sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builtin) SenderUpdate(repo *model.Repo, sender *model.Sender) error {
|
|
||||||
return b.store.SenderUpdate(sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builtin) SenderDelete(repo *model.Repo, login string) error {
|
|
||||||
sender, err := b.store.SenderFind(repo, login)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return b.store.SenderDelete(sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builtin) SenderList(repo *model.Repo) ([]*model.Sender, error) {
|
|
||||||
return b.store.SenderList(repo)
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package sender
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/plugins/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
type plugin struct {
|
|
||||||
endpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRemote returns a new remote gating service.
|
|
||||||
func NewRemote(endpoint string) model.SenderService {
|
|
||||||
return &plugin{endpoint}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) SenderAllowed(user *model.User, repo *model.Repo, build *model.Build, conf *model.Config) (bool, error) {
|
|
||||||
path := fmt.Sprintf("%s/senders/%s/%s/%s/verify", p.endpoint, repo.Owner, repo.Name, build.Sender)
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"build": build,
|
|
||||||
"config": conf,
|
|
||||||
}
|
|
||||||
err := internal.Send(context.TODO(), "POST", path, &data, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) SenderCreate(repo *model.Repo, sender *model.Sender) error {
|
|
||||||
path := fmt.Sprintf("%s/senders/%s/%s", p.endpoint, repo.Owner, repo.Name)
|
|
||||||
return internal.Send(context.TODO(), "POST", path, sender, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) SenderUpdate(repo *model.Repo, sender *model.Sender) error {
|
|
||||||
path := fmt.Sprintf("%s/senders/%s/%s", p.endpoint, repo.Owner, repo.Name)
|
|
||||||
return internal.Send(context.TODO(), "PUT", path, sender, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) SenderDelete(repo *model.Repo, login string) error {
|
|
||||||
path := fmt.Sprintf("%s/senders/%s/%s/%s", p.endpoint, repo.Owner, repo.Name, login)
|
|
||||||
return internal.Send(context.TODO(), "DELETE", path, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) SenderList(repo *model.Repo) (out []*model.Sender, err error) {
|
|
||||||
path := fmt.Sprintf("%s/senders/%s/%s", p.endpoint, repo.Owner, repo.Name)
|
|
||||||
err = internal.Send(context.TODO(), "GET", path, nil, out)
|
|
||||||
return out, err
|
|
||||||
}
|
|
26
server/store/datastore/migration/005_drop_senders.go
Normal file
26
server/store/datastore/migration/005_drop_senders.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2022 Woodpecker Authors
|
||||||
|
//
|
||||||
|
// Licensed 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.
|
||||||
|
|
||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dropSenders = task{
|
||||||
|
name: "drop-senders",
|
||||||
|
fn: func(sess *xorm.Session) error {
|
||||||
|
return sess.DropTable("senders")
|
||||||
|
},
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ var migrationTasks = []*task{
|
||||||
&alterTableReposDropAllowDeploysAllowTags,
|
&alterTableReposDropAllowDeploysAllowTags,
|
||||||
&fixPRSecretEventName,
|
&fixPRSecretEventName,
|
||||||
&alterTableReposDropCounter,
|
&alterTableReposDropCounter,
|
||||||
|
&dropSenders,
|
||||||
}
|
}
|
||||||
|
|
||||||
var allBeans = []interface{}{
|
var allBeans = []interface{}{
|
||||||
|
@ -46,7 +47,6 @@ var allBeans = []interface{}{
|
||||||
new(model.Registry),
|
new(model.Registry),
|
||||||
new(model.Repo),
|
new(model.Repo),
|
||||||
new(model.Secret),
|
new(model.Secret),
|
||||||
new(model.Sender),
|
|
||||||
new(model.Task),
|
new(model.Task),
|
||||||
new(model.User),
|
new(model.User),
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,6 @@ func (s storage) DeleteRepo(repo *model.Repo) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Where("sender_repo_id = ?", repo.ID).Delete(new(model.Sender)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := sess.Where("config_repo_id = ?", repo.ID).Delete(new(model.Config)); err != nil {
|
if _, err := sess.Where("config_repo_id = ?", repo.ID).Delete(new(model.Config)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,7 +372,6 @@ func TestRepoCrud(t *testing.T) {
|
||||||
new(model.Proc),
|
new(model.Proc),
|
||||||
new(model.File),
|
new(model.File),
|
||||||
new(model.Secret),
|
new(model.Secret),
|
||||||
new(model.Sender),
|
|
||||||
new(model.Registry),
|
new(model.Registry),
|
||||||
new(model.Config))
|
new(model.Config))
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright 2021 Woodpecker Authors
|
|
||||||
//
|
|
||||||
// Licensed 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.
|
|
||||||
|
|
||||||
package datastore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s storage) SenderFind(repo *model.Repo, login string) (*model.Sender, error) {
|
|
||||||
sender := &model.Sender{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Login: login,
|
|
||||||
}
|
|
||||||
return sender, wrapGet(s.engine.Get(sender))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s storage) SenderList(repo *model.Repo) ([]*model.Sender, error) {
|
|
||||||
senders := make([]*model.Sender, 0, perPage)
|
|
||||||
return senders, s.engine.Where("sender_repo_id = ?", repo.ID).Find(&senders)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s storage) SenderCreate(sender *model.Sender) error {
|
|
||||||
// only Insert set auto created ID back to object
|
|
||||||
_, err := s.engine.Insert(sender)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s storage) SenderUpdate(sender *model.Sender) error {
|
|
||||||
_, err := s.engine.ID(sender.ID).AllCols().Update(sender)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s storage) SenderDelete(sender *model.Sender) error {
|
|
||||||
_, err := s.engine.ID(sender.ID).Delete(new(model.Sender))
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
// Copyright 2018 Drone.IO Inc.
|
|
||||||
//
|
|
||||||
// Licensed 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.
|
|
||||||
|
|
||||||
package datastore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSenderFind(t *testing.T) {
|
|
||||||
store, closer := newTestStore(t, new(model.Sender))
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
err := store.SenderCreate(&model.Sender{
|
|
||||||
RepoID: 1,
|
|
||||||
Login: "octocat",
|
|
||||||
Allow: true,
|
|
||||||
Block: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error: insert secret: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sender, err := store.SenderFind(&model.Repo{ID: 1}, "octocat")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got, want := sender.RepoID, int64(1); got != want {
|
|
||||||
t.Errorf("Want repo id %d, got %d", want, got)
|
|
||||||
}
|
|
||||||
if got, want := sender.Login, "octocat"; got != want {
|
|
||||||
t.Errorf("Want sender login %s, got %s", want, got)
|
|
||||||
}
|
|
||||||
if got, want := sender.Allow, true; got != want {
|
|
||||||
t.Errorf("Want sender allow %v, got %v", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSenderList(t *testing.T) {
|
|
||||||
store, closer := newTestStore(t, new(model.Sender))
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
assert.NoError(t, store.SenderCreate(&model.Sender{
|
|
||||||
RepoID: 1,
|
|
||||||
Login: "octocat",
|
|
||||||
Allow: true,
|
|
||||||
Block: false,
|
|
||||||
}))
|
|
||||||
assert.NoError(t, store.SenderCreate(&model.Sender{
|
|
||||||
RepoID: 1,
|
|
||||||
Login: "defunkt",
|
|
||||||
Allow: true,
|
|
||||||
Block: false,
|
|
||||||
}))
|
|
||||||
|
|
||||||
list, err := store.SenderList(&model.Repo{ID: 1})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got, want := len(list), 2; got != want {
|
|
||||||
t.Errorf("Want %d senders, got %d", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSenderUpdate(t *testing.T) {
|
|
||||||
store, closer := newTestStore(t, new(model.Sender))
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
sender := &model.Sender{
|
|
||||||
RepoID: 1,
|
|
||||||
Login: "octocat",
|
|
||||||
Allow: true,
|
|
||||||
Block: false,
|
|
||||||
}
|
|
||||||
if err := store.SenderCreate(sender); err != nil {
|
|
||||||
t.Errorf("Unexpected error: insert sender: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.EqualValues(t, 1, sender.ID)
|
|
||||||
sender.Allow = false
|
|
||||||
if err := store.SenderUpdate(sender); err != nil {
|
|
||||||
t.Errorf("Unexpected error: update sender: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updated, err := store.SenderFind(&model.Repo{ID: 1}, "octocat")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, updated.Allow)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSenderIndexes(t *testing.T) {
|
|
||||||
store, closer := newTestStore(t, new(model.Sender))
|
|
||||||
defer closer()
|
|
||||||
|
|
||||||
if err := store.SenderCreate(&model.Sender{
|
|
||||||
RepoID: 1,
|
|
||||||
Login: "octocat",
|
|
||||||
Allow: true,
|
|
||||||
Block: false,
|
|
||||||
}); err != nil {
|
|
||||||
t.Errorf("Unexpected error: insert sender: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// fail due to duplicate name
|
|
||||||
if err := store.SenderCreate(&model.Sender{
|
|
||||||
RepoID: 1,
|
|
||||||
Login: "octocat",
|
|
||||||
Allow: true,
|
|
||||||
Block: false,
|
|
||||||
}); err == nil {
|
|
||||||
t.Errorf("Unexpected error: duplicate login")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -104,14 +104,6 @@ type Store interface {
|
||||||
ConfigCreate(*model.Config) error
|
ConfigCreate(*model.Config) error
|
||||||
BuildConfigCreate(*model.BuildConfig) error
|
BuildConfigCreate(*model.BuildConfig) error
|
||||||
|
|
||||||
// Sender
|
|
||||||
SenderFind(*model.Repo, string) (*model.Sender, error)
|
|
||||||
// SenderList TODO: paginate
|
|
||||||
SenderList(*model.Repo) ([]*model.Sender, error)
|
|
||||||
SenderCreate(*model.Sender) error
|
|
||||||
SenderUpdate(*model.Sender) error
|
|
||||||
SenderDelete(*model.Sender) error
|
|
||||||
|
|
||||||
// Secrets
|
// Secrets
|
||||||
SecretFind(*model.Repo, string) (*model.Secret, error)
|
SecretFind(*model.Repo, string) (*model.Secret, error)
|
||||||
SecretList(*model.Repo) ([]*model.Secret, error)
|
SecretList(*model.Repo) ([]*model.Secret, error)
|
||||||
|
|
Loading…
Reference in a new issue