mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Merge pull request #10745 from reasonerjt/artifact-signature-populate-v2
Artifact signature populate
This commit is contained in:
commit
560dd8ce7b
@ -563,6 +563,10 @@ definitions:
|
||||
type: boolean
|
||||
x-omitempty: false
|
||||
description: The immutable status of the tag
|
||||
signed:
|
||||
type: boolean
|
||||
x-omitempty: false
|
||||
description: The attribute indicates whether the tag is signed or not
|
||||
ExtraAttrs:
|
||||
type: object
|
||||
additionalProperties:
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match"
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/label"
|
||||
"github.com/goharbor/harbor/src/pkg/signature"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"strings"
|
||||
|
||||
@ -94,6 +95,7 @@ func NewController() Controller {
|
||||
repoMgr: repository.Mgr,
|
||||
artMgr: artifact.Mgr,
|
||||
tagMgr: tag.Mgr,
|
||||
sigMgr: signature.GetManager(),
|
||||
labelMgr: label.Mgr,
|
||||
abstractor: abstractor.NewAbstractor(),
|
||||
immutableMtr: rule.NewRuleMatcher(),
|
||||
@ -106,6 +108,7 @@ type controller struct {
|
||||
repoMgr repository.Manager
|
||||
artMgr artifact.Manager
|
||||
tagMgr tag.Manager
|
||||
sigMgr signature.Manager
|
||||
labelMgr label.Manager
|
||||
abstractor abstractor.Abstractor
|
||||
immutableMtr match.ImmutableTagMatcher
|
||||
@ -382,9 +385,6 @@ func (c *controller) assembleArtifact(ctx context.Context, art *artifact.Artifac
|
||||
if option.WithScanOverview {
|
||||
c.populateScanOverview(ctx, artifact)
|
||||
}
|
||||
if option.WithSignature {
|
||||
c.populateSignature(ctx, artifact)
|
||||
}
|
||||
// populate addition links
|
||||
c.populateAdditionLinks(ctx, artifact)
|
||||
return artifact
|
||||
@ -413,12 +413,36 @@ func (c *controller) assembleTag(ctx context.Context, tag *tm.Tag, option *TagOp
|
||||
if option == nil {
|
||||
return t
|
||||
}
|
||||
repo, err := c.repoMgr.Get(ctx, tag.RepositoryID)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo for tag: %s, error: %v", tag.Name, err)
|
||||
return t
|
||||
}
|
||||
if option.WithImmutableStatus {
|
||||
c.populateImmutableStatus(ctx, t)
|
||||
}
|
||||
if option.WithSignature {
|
||||
if a, err := c.artMgr.Get(ctx, t.ArtifactID); err != nil {
|
||||
log.Errorf("Failed to get artifact for tag: %s, error: %v, skip populating signature", t.Name, err)
|
||||
} else {
|
||||
c.populateTagSignature(ctx, repo.Name, t, a.Digest, option)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (c *controller) populateTagSignature(ctx context.Context, repo string, tag *Tag, digest string, option *TagOption) {
|
||||
if option.SignatureChecker == nil {
|
||||
chk, err := signature.GetManager().GetCheckerByRepo(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
option.SignatureChecker = chk
|
||||
}
|
||||
tag.Signed = option.SignatureChecker.IsTagSigned(tag.Name, digest)
|
||||
}
|
||||
|
||||
func (c *controller) populateLabels(ctx context.Context, art *Artifact) {
|
||||
labels, err := c.labelMgr.ListByArtifact(ctx, art.ID)
|
||||
if err != nil {
|
||||
|
@ -131,7 +131,6 @@ func (c *controllerTestSuite) TestAssembleArtifact() {
|
||||
},
|
||||
WithLabel: true,
|
||||
WithScanOverview: true,
|
||||
WithSignature: true,
|
||||
}
|
||||
tg := &tag.Tag{
|
||||
ID: 1,
|
||||
@ -262,7 +261,6 @@ func (c *controllerTestSuite) TestList() {
|
||||
option := &Option{
|
||||
WithTag: true,
|
||||
WithScanOverview: true,
|
||||
WithSignature: true,
|
||||
}
|
||||
c.artMgr.On("List").Return(1, []*artifact.Artifact{
|
||||
{
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
cmodels "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/signature"
|
||||
"github.com/goharbor/harbor/src/pkg/tag/model/tag"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
@ -73,6 +74,7 @@ func (a *Artifact) ToSwagger() *models.Artifact {
|
||||
PushTime: strfmt.DateTime(tag.PushTime),
|
||||
RepositoryID: tag.RepositoryID,
|
||||
Immutable: tag.Immutable,
|
||||
Signed: tag.Signed,
|
||||
})
|
||||
}
|
||||
for addition, link := range a.AdditionLinks {
|
||||
@ -104,7 +106,8 @@ func (a *Artifact) ToSwagger() *models.Artifact {
|
||||
type Tag struct {
|
||||
tag.Tag
|
||||
Immutable bool
|
||||
// TODO add other attrs: signature, etc
|
||||
Signed bool
|
||||
// TODO add other attrs: label, etc
|
||||
}
|
||||
|
||||
// AdditionLink is a link via that the addition can be fetched
|
||||
@ -119,13 +122,13 @@ type Option struct {
|
||||
TagOption *TagOption // only works when WithTag is set to true
|
||||
WithLabel bool
|
||||
WithScanOverview bool
|
||||
// TODO move it to TagOption?
|
||||
WithSignature bool
|
||||
}
|
||||
|
||||
// TagOption is used to specify the properties returned when listing/getting tags
|
||||
type TagOption struct {
|
||||
WithImmutableStatus bool
|
||||
WithSignature bool
|
||||
SignatureChecker *signature.Checker
|
||||
}
|
||||
|
||||
// TODO move this to GC controller?
|
||||
|
@ -17,7 +17,7 @@ package models
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/notary/model"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary/model"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
)
|
||||
|
||||
|
@ -34,8 +34,6 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/notary"
|
||||
notarymodel "github.com/goharbor/harbor/src/common/utils/notary/model"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
notifierEvt "github.com/goharbor/harbor/src/core/notifier/event"
|
||||
@ -45,6 +43,8 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/immutabletag/match/rule"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/api/scan"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary"
|
||||
notarymodel "github.com/goharbor/harbor/src/pkg/signature/notary/model"
|
||||
"github.com/goharbor/harbor/src/replication"
|
||||
"github.com/goharbor/harbor/src/replication/event"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
|
@ -17,9 +17,9 @@ package contenttrust
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/notary"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
@ -20,9 +20,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
notarytest "github.com/goharbor/harbor/src/common/utils/notary/test"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
notarytest "github.com/goharbor/harbor/src/pkg/signature/notary/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -29,10 +29,10 @@ import (
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
notarytest "github.com/goharbor/harbor/src/common/utils/notary/test"
|
||||
testutils "github.com/goharbor/harbor/src/common/utils/test"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
notarytest "github.com/goharbor/harbor/src/pkg/signature/notary/test"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
94
src/pkg/signature/manager.go
Normal file
94
src/pkg/signature/manager.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright Project Harbor 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 signature
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary/model"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Checker checks the signature status of artifact
|
||||
type Checker struct {
|
||||
signatures map[string]string
|
||||
}
|
||||
|
||||
// IsTagSigned checks if the tag of the artifact is signed, it also checks the signed artifact has the same digest as parm.
|
||||
func (sc Checker) IsTagSigned(tag, digest string) bool {
|
||||
d, ok := sc.signatures[tag]
|
||||
if len(digest) == 0 {
|
||||
return ok
|
||||
}
|
||||
return digest == d
|
||||
}
|
||||
|
||||
// IsArtifactSigned checks if the artifact with given digest is signed.
|
||||
func (sc Checker) IsArtifactSigned(digest string) bool {
|
||||
for _, v := range sc.signatures {
|
||||
if v == digest {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Manager interface for handling signatures of artifacts
|
||||
type Manager interface {
|
||||
// GetCheckerByRepo returns a Checker for checking signature
|
||||
GetCheckerByRepo(ctx context.Context, repo string) (*Checker, error)
|
||||
}
|
||||
|
||||
type mgr struct {
|
||||
}
|
||||
|
||||
// GetCheckerByRepo ...
|
||||
func (m *mgr) GetCheckerByRepo(ctx context.Context, repo string) (*Checker, error) {
|
||||
if !config.WithNotary() { // return a checker that always return false
|
||||
return &Checker{}, nil
|
||||
}
|
||||
s := make(map[string]string)
|
||||
targets, err := m.getTargetsByRepo(ctx, repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, t := range targets {
|
||||
if d, err := notary.DigestFromTarget(t); err != nil {
|
||||
log.Warningf("Failed to get signed digest for tag %s, error: %v, skip", t.Tag, err)
|
||||
} else {
|
||||
s[t.Tag] = d
|
||||
}
|
||||
}
|
||||
return &Checker{s}, nil
|
||||
}
|
||||
|
||||
func (m *mgr) getTargetsByRepo(ctx context.Context, repo string) ([]model.Target, error) {
|
||||
name := "unknown"
|
||||
if sc, ok := security.FromContext(ctx); !ok || sc == nil {
|
||||
log.Warningf("Unable to get security context")
|
||||
} else {
|
||||
name = sc.GetUsername()
|
||||
}
|
||||
return notary.GetInternalTargets(config.InternalNotaryEndpoint(), name, repo)
|
||||
}
|
||||
|
||||
var instance = &mgr{}
|
||||
|
||||
// GetManager ...
|
||||
func GetManager() Manager {
|
||||
return instance
|
||||
}
|
100
src/pkg/signature/manager_test.go
Normal file
100
src/pkg/signature/manager_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
package signature
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// B/C the notary requires private key for signing token, b
|
||||
// before running locally, please make sure the env var is set as follow:
|
||||
// export TOKEN_PRIVATE_KEY_PATH="/harbor/tests/private_key.pem"
|
||||
endpoint := "10.117.4.142"
|
||||
// notary-demo/busybox:1.0 is signed, more details in the notary/test pkg
|
||||
notaryServer := test.NewNotaryServer(endpoint)
|
||||
defer notaryServer.Close()
|
||||
conf := map[string]interface{}{
|
||||
common.WithNotary: "true",
|
||||
common.NotaryURL: notaryServer.URL,
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
}
|
||||
config.InitWithSettings(conf)
|
||||
result := m.Run()
|
||||
if result != 0 {
|
||||
os.Exit(result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCheckerByRepo(t *testing.T) {
|
||||
type in struct {
|
||||
repo string
|
||||
tag string
|
||||
digest string
|
||||
}
|
||||
type res struct {
|
||||
tagSigned bool
|
||||
artSigned bool
|
||||
}
|
||||
m := GetManager()
|
||||
cases := []struct {
|
||||
input in
|
||||
expect res
|
||||
}{
|
||||
{
|
||||
input: in{
|
||||
repo: "notary-demo/busybox",
|
||||
tag: "1.0",
|
||||
digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7",
|
||||
},
|
||||
expect: res{
|
||||
tagSigned: true,
|
||||
artSigned: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: in{
|
||||
repo: "notary-demo/busybox",
|
||||
tag: "1.0",
|
||||
digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a8",
|
||||
},
|
||||
expect: res{
|
||||
tagSigned: false,
|
||||
artSigned: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: in{
|
||||
repo: "notary-demo/busybox",
|
||||
tag: "2.0",
|
||||
digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7",
|
||||
},
|
||||
expect: res{
|
||||
tagSigned: false,
|
||||
artSigned: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: in{
|
||||
repo: "non-exist",
|
||||
tag: "1.0",
|
||||
digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7",
|
||||
},
|
||||
expect: res{
|
||||
tagSigned: false,
|
||||
artSigned: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
checker, err := m.GetCheckerByRepo(context.Background(), c.input.repo)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, c.expect.tagSigned, checker.IsTagSigned(c.input.tag, c.input.digest),
|
||||
"Unexpected tagSigned value for input: %#v", c.input)
|
||||
assert.Equal(t, c.expect.artSigned, checker.IsArtifactSigned(c.input.digest), "Unexpected artSigned value for input: %#v", c.input)
|
||||
}
|
||||
}
|
@ -17,13 +17,12 @@ package notary
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
model2 "github.com/goharbor/harbor/src/pkg/signature/notary/model"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/notary/model"
|
||||
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/registry"
|
||||
@ -54,7 +53,7 @@ func init() {
|
||||
}
|
||||
|
||||
// GetInternalTargets wraps GetTargets to read config values for getting full-qualified repo from internal notary instance.
|
||||
func GetInternalTargets(notaryEndpoint string, username string, repo string) ([]model.Target, error) {
|
||||
func GetInternalTargets(notaryEndpoint string, username string, repo string) ([]model2.Target, error) {
|
||||
ext, err := config.ExtEndpoint()
|
||||
if err != nil {
|
||||
log.Errorf("Error while reading external endpoint: %v", err)
|
||||
@ -68,8 +67,8 @@ func GetInternalTargets(notaryEndpoint string, username string, repo string) ([]
|
||||
// GetTargets is a help function called by API to fetch signature information of a given repository.
|
||||
// Per docker's convention the repository should contain the information of endpoint, i.e. it should look
|
||||
// like "192.168.0.1/library/ubuntu", instead of "library/ubuntu" (fqRepo for fully-qualified repo)
|
||||
func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]model.Target, error) {
|
||||
res := []model.Target{}
|
||||
func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]model2.Target, error) {
|
||||
res := []model2.Target{}
|
||||
t, err := tokenutil.MakeToken(username, tokenutil.Notary,
|
||||
[]*token.ResourceActions{
|
||||
{
|
||||
@ -103,7 +102,7 @@ func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]model.
|
||||
log.Warningf("Failed to clear cached root.json: %s, error: %v, when repo is removed from notary the signature status maybe incorrect", rootJSON, rmErr)
|
||||
}
|
||||
for _, t := range targets {
|
||||
res = append(res, model.Target{
|
||||
res = append(res, model2.Target{
|
||||
Tag: t.Name,
|
||||
Hashes: t.Hashes,
|
||||
})
|
||||
@ -112,7 +111,7 @@ func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]model.
|
||||
}
|
||||
|
||||
// DigestFromTarget get a target and return the value of digest, in accordance to Docker-Content-Digest
|
||||
func DigestFromTarget(t model.Target) (string, error) {
|
||||
func DigestFromTarget(t model2.Target) (string, error) {
|
||||
sha, ok := t.Hashes["sha256"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no valid hash, expecting sha256")
|
@ -16,10 +16,9 @@ package notary
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
model2 "github.com/goharbor/harbor/src/pkg/signature/notary/model"
|
||||
test2 "github.com/goharbor/harbor/src/pkg/signature/notary/test"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/notary/model"
|
||||
|
||||
notarytest "github.com/goharbor/harbor/src/common/utils/notary/test"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -36,7 +35,7 @@ var endpoint = "10.117.4.142"
|
||||
var notaryServer *httptest.Server
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
notaryServer = notarytest.NewNotaryServer(endpoint)
|
||||
notaryServer = test2.NewNotaryServer(endpoint)
|
||||
defer notaryServer.Close()
|
||||
var defaultConfig = map[string]interface{}{
|
||||
common.ExtEndpoint: "https://" + endpoint,
|
||||
@ -80,13 +79,13 @@ func TestGetDigestFromTarget(t *testing.T) {
|
||||
}
|
||||
}`
|
||||
|
||||
var t1 model.Target
|
||||
var t1 model2.Target
|
||||
err := json.Unmarshal([]byte(str), &t1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hash2 := make(map[string][]byte)
|
||||
t2 := model.Target{
|
||||
t2 := model2.Target{
|
||||
Tag: "2.0",
|
||||
Hashes: hash2,
|
||||
}
|
@ -2,10 +2,10 @@ package contenttrust
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/common/utils/notary"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
internal_errors "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/pkg/signature/notary"
|
||||
serror "github.com/goharbor/harbor/src/server/error"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"net/http"
|
||||
|
@ -229,10 +229,12 @@ func option(withTag, withImmutableStatus, withLabel, withScanOverview, withSigna
|
||||
option.WithTag = *(withTag)
|
||||
}
|
||||
if option.WithTag {
|
||||
option.TagOption = &artifact.TagOption{}
|
||||
if withImmutableStatus != nil {
|
||||
option.TagOption = &artifact.TagOption{
|
||||
WithImmutableStatus: *(withImmutableStatus),
|
||||
}
|
||||
option.TagOption.WithImmutableStatus = *(withImmutableStatus)
|
||||
}
|
||||
if withSignature != nil {
|
||||
option.TagOption.WithSignature = *withSignature
|
||||
}
|
||||
}
|
||||
if withLabel != nil {
|
||||
@ -241,8 +243,5 @@ func option(withTag, withImmutableStatus, withLabel, withScanOverview, withSigna
|
||||
if withScanOverview != nil {
|
||||
option.WithScanOverview = *(withScanOverview)
|
||||
}
|
||||
if withSignature != nil {
|
||||
option.WithSignature = *(withSignature)
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user