add subject artifact repo (#18394)

add suject_artifact_repo column in the table artifact_accessory

Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2023-03-22 21:48:09 +08:00 committed by GitHub
parent 395ae77d64
commit deaecf2de5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 47 additions and 20 deletions

View File

@ -9385,11 +9385,15 @@ definitions:
subject_artifact_id: subject_artifact_id:
type: integer type: integer
format: int64 format: int64
description: The subject artifact id of the accessory description: Going to be deprecated, use repo and digest for insteand. The subject artifact id of the accessory.
subject_artifact_digest: subject_artifact_digest:
type: string type: string
description: The subject artifact digest of the accessory description: The subject artifact digest of the accessory
x-omitempty: false x-omitempty: false
subject_artifact_repo:
type: string
description: The subject artifact repository name of the accessory
x-omitempty: false
size: size:
type: integer type: integer
format: int64 format: int64

View File

@ -1,8 +1,9 @@
/* remove the redundant data from table artifact_blob */ /* remove the redundant data from table artifact_blob */
delete from artifact_blob afb where not exists (select digest from blob b where b.digest = afb.digest_af); delete from artifact_blob afb where not exists (select digest from blob b where b.digest = afb.digest_af);
/* add subject_artifact_digest*/ /* add subject_artifact_digest and subject_artifact_repo */
alter table artifact_accessory add column IF NOT EXISTS subject_artifact_digest varchar(1024); alter table artifact_accessory add column IF NOT EXISTS subject_artifact_digest varchar(1024);
alter table artifact_accessory add column IF NOT EXISTS subject_artifact_repo varchar(1024);
DO $$ DO $$
DECLARE DECLARE
@ -12,7 +13,7 @@ BEGIN
FOR acc IN SELECT * FROM artifact_accessory FOR acc IN SELECT * FROM artifact_accessory
LOOP LOOP
SELECT * INTO art from artifact where id = acc.subject_artifact_id; SELECT * INTO art from artifact where id = acc.subject_artifact_id;
UPDATE artifact_accessory SET subject_artifact_digest=art.digest WHERE subject_artifact_id = art.id; UPDATE artifact_accessory SET subject_artifact_digest=art.digest, subject_artifact_repo=art.repository_name WHERE subject_artifact_id = art.id;
END LOOP; END LOOP;
END $$; END $$;

View File

@ -161,7 +161,7 @@ func (c *controller) Ensure(ctx context.Context, repository, digest string, opti
} }
} }
for _, acc := range option.Accs { for _, acc := range option.Accs {
if err = c.accessoryMgr.Ensure(ctx, artifact.Digest, artifact.ID, acc.ArtifactID, acc.Size, acc.Digest, acc.Type); err != nil { if err = c.accessoryMgr.Ensure(ctx, artifact.Digest, artifact.RepositoryName, artifact.ID, acc.ArtifactID, acc.Size, acc.Digest, acc.Type); err != nil {
return false, 0, err return false, 0, err
} }
} }

View File

@ -564,7 +564,7 @@ func (c *controllerTestSuite) TestCopy() {
c.artMgr.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil) c.artMgr.On("Create", mock.Anything, mock.Anything).Return(int64(1), nil)
c.regCli.On("Copy", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) c.regCli.On("Copy", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
c.tagCtl.On("Ensure").Return(nil) c.tagCtl.On("Ensure").Return(nil)
c.accMgr.On("Ensure", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) c.accMgr.On("Ensure", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
_, err := c.ctl.Copy(orm.NewContext(nil, &ormtesting.FakeOrmer{}), "library/hello-world", "latest", "library/hello-world2") _, err := c.ctl.Copy(orm.NewContext(nil, &ormtesting.FakeOrmer{}), "library/hello-world", "latest", "library/hello-world2")
c.Require().Nil(err) c.Require().Nil(err)
} }

View File

@ -36,6 +36,7 @@ type daoTestSuite struct {
artDAO artdao.DAO artDAO artdao.DAO
artifactID int64 artifactID int64
subArtifactID int64 subArtifactID int64
subArtifactRepo string
subArtifactDigest string subArtifactDigest string
accID int64 accID int64
ctx context.Context ctx context.Context
@ -53,6 +54,7 @@ func (d *daoTestSuite) SetupSuite() {
MediaType: "application/vnd.oci.image.config.v1+json", MediaType: "application/vnd.oci.image.config.v1+json",
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1, ProjectID: 1,
RepositoryName: "goharbor",
RepositoryID: 1000, RepositoryID: 1000,
Digest: d.DigestString(), Digest: d.DigestString(),
} }
@ -60,6 +62,7 @@ func (d *daoTestSuite) SetupSuite() {
d.subArtifactID = artifactID d.subArtifactID = artifactID
d.Require().Nil(err) d.Require().Nil(err)
d.subArtifactDigest = art.Digest d.subArtifactDigest = art.Digest
d.subArtifactRepo = art.RepositoryName
d.artDAO = artdao.New() d.artDAO = artdao.New()
artifactID, err = d.artDAO.Create(d.ctx, &artdao.Artifact{ artifactID, err = d.artDAO.Create(d.ctx, &artdao.Artifact{
@ -68,6 +71,7 @@ func (d *daoTestSuite) SetupSuite() {
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1, ProjectID: 1,
RepositoryID: 1000, RepositoryID: 1000,
RepositoryName: "goharbor",
Digest: d.DigestString(), Digest: d.DigestString(),
}) })
d.Require().Nil(err) d.Require().Nil(err)
@ -76,6 +80,7 @@ func (d *daoTestSuite) SetupSuite() {
accID, err := d.dao.Create(d.ctx, &Accessory{ accID, err := d.dao.Create(d.ctx, &Accessory{
ArtifactID: d.artifactID, ArtifactID: d.artifactID,
SubjectArtifactDigest: d.subArtifactDigest, SubjectArtifactDigest: d.subArtifactDigest,
SubjectArtifactRepo: d.subArtifactRepo,
Digest: d.DigestString(), Digest: d.DigestString(),
Size: 1234, Size: 1234,
Type: "cosign.signature", Type: "cosign.signature",
@ -153,6 +158,7 @@ func (d *daoTestSuite) TestCreate() {
// conflict // conflict
acc := &Accessory{ acc := &Accessory{
ArtifactID: d.artifactID, ArtifactID: d.artifactID,
SubjectArtifactRepo: d.subArtifactRepo,
SubjectArtifactDigest: d.subArtifactDigest, SubjectArtifactDigest: d.subArtifactDigest,
Digest: d.DigestString(), Digest: d.DigestString(),
Size: 1234, Size: 1234,
@ -181,6 +187,7 @@ func (d *daoTestSuite) TestDeleteOfArtifact() {
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1, ProjectID: 1,
RepositoryID: 1000, RepositoryID: 1000,
RepositoryName: "goharbor",
Digest: d.DigestString(), Digest: d.DigestString(),
} }
subArtID, err := d.artDAO.Create(d.ctx, art) subArtID, err := d.artDAO.Create(d.ctx, art)
@ -193,6 +200,7 @@ func (d *daoTestSuite) TestDeleteOfArtifact() {
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1, ProjectID: 1,
RepositoryID: 1000, RepositoryID: 1000,
RepositoryName: "goharbor",
Digest: d.DigestString(), Digest: d.DigestString(),
}) })
d.Require().Nil(err) d.Require().Nil(err)
@ -204,6 +212,7 @@ func (d *daoTestSuite) TestDeleteOfArtifact() {
ManifestMediaType: "application/vnd.oci.image.manifest.v1+json", ManifestMediaType: "application/vnd.oci.image.manifest.v1+json",
ProjectID: 1, ProjectID: 1,
RepositoryID: 1000, RepositoryID: 1000,
RepositoryName: "goharbor",
Digest: d.DigestString(), Digest: d.DigestString(),
}) })
d.Require().Nil(err) d.Require().Nil(err)
@ -212,6 +221,7 @@ func (d *daoTestSuite) TestDeleteOfArtifact() {
acc1 := &Accessory{ acc1 := &Accessory{
ArtifactID: artID1, ArtifactID: artID1,
SubjectArtifactDigest: art.Digest, SubjectArtifactDigest: art.Digest,
SubjectArtifactRepo: art.RepositoryName,
Digest: d.DigestString(), Digest: d.DigestString(),
Size: 1234, Size: 1234,
Type: "cosign.signature", Type: "cosign.signature",
@ -222,6 +232,7 @@ func (d *daoTestSuite) TestDeleteOfArtifact() {
acc2 := &Accessory{ acc2 := &Accessory{
ArtifactID: artID2, ArtifactID: artID2,
SubjectArtifactDigest: art.Digest, SubjectArtifactDigest: art.Digest,
SubjectArtifactRepo: art.RepositoryName,
Digest: d.DigestString(), Digest: d.DigestString(),
Size: 1234, Size: 1234,
Type: "cosign.signature", Type: "cosign.signature",
@ -242,14 +253,14 @@ func (d *daoTestSuite) TestDeleteOfArtifact() {
_, err = d.dao.DeleteAccessories(d.ctx, &q.Query{ _, err = d.dao.DeleteAccessories(d.ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"SubjectArtifactDigest": art.Digest, "SubjectArtifactDigest": art.Digest, "SubjectArtifactRepo": art.RepositoryName,
}, },
}) })
d.Require().Nil(err) d.Require().Nil(err)
accs, err = d.dao.List(d.ctx, &q.Query{ accs, err = d.dao.List(d.ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"SubjectArtifactDigest": art.Digest, "SubjectArtifactDigest": art.Digest, "SubjectArtifactRepo": art.RepositoryName,
}, },
}) })
d.Require().Nil(err) d.Require().Nil(err)

View File

@ -29,6 +29,7 @@ type Accessory struct {
ID int64 `orm:"pk;auto;column(id)" json:"id"` ID int64 `orm:"pk;auto;column(id)" json:"id"`
ArtifactID int64 `orm:"column(artifact_id)" json:"artifact_id"` ArtifactID int64 `orm:"column(artifact_id)" json:"artifact_id"`
SubjectArtifactID int64 `orm:"column(subject_artifact_id)" json:"subject_artifact_id"` SubjectArtifactID int64 `orm:"column(subject_artifact_id)" json:"subject_artifact_id"`
SubjectArtifactRepo string `orm:"column(subject_artifact_repo)" json:"subject_artifact_repo"`
SubjectArtifactDigest string `orm:"column(subject_artifact_digest)" json:"subject_artifact_digest"` SubjectArtifactDigest string `orm:"column(subject_artifact_digest)" json:"subject_artifact_digest"`
Type string `orm:"column(type)" json:"type"` Type string `orm:"column(type)" json:"type"`
Size int64 `orm:"column(size)" json:"size"` Size int64 `orm:"column(size)" json:"size"`

View File

@ -38,7 +38,7 @@ var (
// Manager is the only interface of artifact module to provide the management functions for artifacts // Manager is the only interface of artifact module to provide the management functions for artifacts
type Manager interface { type Manager interface {
// Ensure ... // Ensure ...
Ensure(ctx context.Context, subArtDigest string, subArtID, artifactID, size int64, digest, accType string) error Ensure(ctx context.Context, subArtDigest, subArtRepo string, subArtID, artifactID, size int64, digest, accType string) error
// Get the artifact specified by the ID // Get the artifact specified by the ID
Get(ctx context.Context, id int64) (accessory model.Accessory, err error) Get(ctx context.Context, id int64) (accessory model.Accessory, err error)
// Count returns the total count of accessory according to the query. // Count returns the total count of accessory according to the query.
@ -66,7 +66,7 @@ type manager struct {
dao dao.DAO dao dao.DAO
} }
func (m *manager) Ensure(ctx context.Context, subArtDigest string, subArtID, artifactID, size int64, digest, accType string) error { func (m *manager) Ensure(ctx context.Context, subArtDigest, subArtRepo string, subArtID, artifactID, size int64, digest, accType string) error {
accs, err := m.dao.List(ctx, q.New(q.KeyWords{"ArtifactID": artifactID, "Digest": digest})) accs, err := m.dao.List(ctx, q.New(q.KeyWords{"ArtifactID": artifactID, "Digest": digest}))
if err != nil { if err != nil {
return err return err
@ -78,6 +78,7 @@ func (m *manager) Ensure(ctx context.Context, subArtDigest string, subArtID, art
acc := model.AccessoryData{ acc := model.AccessoryData{
ArtifactID: artifactID, ArtifactID: artifactID,
SubArtifactID: subArtID, SubArtifactID: subArtID,
SubArtifactRepo: subArtRepo,
SubArtifactDigest: subArtDigest, SubArtifactDigest: subArtDigest,
Digest: digest, Digest: digest,
Size: size, Size: size,
@ -96,6 +97,7 @@ func (m *manager) Get(ctx context.Context, id int64) (model.Accessory, error) {
ID: acc.ID, ID: acc.ID,
ArtifactID: acc.ArtifactID, ArtifactID: acc.ArtifactID,
SubArtifactID: acc.SubjectArtifactID, SubArtifactID: acc.SubjectArtifactID,
SubArtifactRepo: acc.SubjectArtifactRepo,
SubArtifactDigest: acc.SubjectArtifactDigest, SubArtifactDigest: acc.SubjectArtifactDigest,
Size: acc.Size, Size: acc.Size,
Digest: acc.Digest, Digest: acc.Digest,
@ -119,6 +121,7 @@ func (m *manager) List(ctx context.Context, query *q.Query) ([]model.Accessory,
ID: accD.ID, ID: accD.ID,
ArtifactID: accD.ArtifactID, ArtifactID: accD.ArtifactID,
SubArtifactID: accD.SubjectArtifactID, SubArtifactID: accD.SubjectArtifactID,
SubArtifactRepo: accD.SubjectArtifactRepo,
SubArtifactDigest: accD.SubjectArtifactDigest, SubArtifactDigest: accD.SubjectArtifactDigest,
Size: accD.Size, Size: accD.Size,
Digest: accD.Digest, Digest: accD.Digest,
@ -137,6 +140,7 @@ func (m *manager) Create(ctx context.Context, accessory model.AccessoryData) (in
acc := &dao.Accessory{ acc := &dao.Accessory{
ArtifactID: accessory.ArtifactID, ArtifactID: accessory.ArtifactID,
SubjectArtifactID: accessory.SubArtifactID, SubjectArtifactID: accessory.SubArtifactID,
SubjectArtifactRepo: accessory.SubArtifactRepo,
SubjectArtifactDigest: accessory.SubArtifactDigest, SubjectArtifactDigest: accessory.SubArtifactDigest,
Size: accessory.Size, Size: accessory.Size,
Digest: accessory.Digest, Digest: accessory.Digest,

View File

@ -45,7 +45,7 @@ func (m *managerTestSuite) SetupTest() {
func (m *managerTestSuite) TestEnsure() { func (m *managerTestSuite) TestEnsure() {
mock.OnAnything(m.dao, "List").Return([]*dao.Accessory{}, nil) mock.OnAnything(m.dao, "List").Return([]*dao.Accessory{}, nil)
mock.OnAnything(m.dao, "Create").Return(int64(1), nil) mock.OnAnything(m.dao, "Create").Return(int64(1), nil)
err := m.mgr.Ensure(nil, string(""), int64(1), int64(2), int64(1), "sha256:1234", model.TypeCosignSignature) err := m.mgr.Ensure(nil, string(""), string(""), int64(1), int64(2), int64(1), "sha256:1234", model.TypeCosignSignature)
m.Require().Nil(err) m.Require().Nil(err)
} }

View File

@ -80,6 +80,7 @@ type AccessoryData struct {
ID int64 `json:"id"` ID int64 `json:"id"`
ArtifactID int64 `json:"artifact_id"` ArtifactID int64 `json:"artifact_id"`
SubArtifactID int64 `json:"subject_artifact_id"` SubArtifactID int64 `json:"subject_artifact_id"`
SubArtifactRepo string `json:"subject_artifact_repo"`
SubArtifactDigest string `json:"subject_artifact_digest"` SubArtifactDigest string `json:"subject_artifact_digest"`
Type string `json:"type"` Type string `json:"type"`
Size int64 `json:"size"` Size int64 `json:"size"`

View File

@ -112,6 +112,7 @@ func SignatureMiddleware() func(http.Handler) http.Handler {
} }
accData := model.AccessoryData{ accData := model.AccessoryData{
ArtifactID: art.ID, ArtifactID: art.ID,
SubArtifactRepo: info.Repository,
SubArtifactDigest: fmt.Sprintf("%s:%s", digest.SHA256, subjectArtDigest), SubArtifactDigest: fmt.Sprintf("%s:%s", digest.SHA256, subjectArtDigest),
Size: art.Size, Size: art.Size,
Digest: art.Digest, Digest: art.Digest,

View File

@ -151,6 +151,7 @@ func (suite *MiddlewareTestSuite) TestCosignSignature() {
suite.Equal(1, len(accs)) suite.Equal(1, len(accs))
suite.Equal(subArtDigest, accs[0].GetData().SubArtifactDigest) suite.Equal(subArtDigest, accs[0].GetData().SubArtifactDigest)
suite.Equal(artID, accs[0].GetData().ArtifactID) suite.Equal(artID, accs[0].GetData().ArtifactID)
suite.Equal(name, accs[0].GetData().SubArtifactRepo)
suite.True(accs[0].IsHard()) suite.True(accs[0].IsHard())
suite.Equal(model.TypeCosignSignature, accs[0].GetData().Type) suite.Equal(model.TypeCosignSignature, accs[0].GetData().Type)
}) })

View File

@ -97,6 +97,7 @@ func Middleware() func(http.Handler) http.Handler {
} }
accData := model.AccessoryData{ accData := model.AccessoryData{
ArtifactID: art.ID, ArtifactID: art.ID,
SubArtifactRepo: info.Repository,
SubArtifactDigest: mf.Subject.Digest.String(), SubArtifactDigest: mf.Subject.Digest.String(),
Size: art.Size, Size: art.Size,
Digest: art.Digest, Digest: art.Digest,

View File

@ -157,6 +157,7 @@ func (suite *MiddlewareTestSuite) TestSubject() {
suite.Equal(1, len(accs)) suite.Equal(1, len(accs))
suite.Equal(subArtDigest, accs[0].GetData().SubArtifactDigest) suite.Equal(subArtDigest, accs[0].GetData().SubArtifactDigest)
suite.Equal(artID, accs[0].GetData().ArtifactID) suite.Equal(artID, accs[0].GetData().ArtifactID)
suite.Equal(name, accs[0].GetData().SubArtifactRepo)
suite.True(accs[0].IsHard()) suite.True(accs[0].IsHard())
suite.Equal(model.TypeSubject, accs[0].GetData().Type) suite.Equal(model.TypeSubject, accs[0].GetData().Type)
}) })

View File

@ -60,9 +60,9 @@ func (r *referrersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
// Query accessories with matching subject artifact digest and artifactType // Query accessories with matching subject artifact digest and artifactType
query := q.New(q.KeyWords{"SubjectArtifactDigest": art.Digest}) query := q.New(q.KeyWords{"SubjectArtifactDigest": art.Digest, "SubjectArtifactRepo": art.RepositoryName})
if at != "" { if at != "" {
query = q.New(q.KeyWords{"SubjectArtifactDigest": art.Digest, "Type": at}) query = q.New(q.KeyWords{"SubjectArtifactDigest": art.Digest, "SubjectArtifactRepo": art.RepositoryName, "Type": at})
} }
total, err := r.accessoryManager.Count(ctx, query) total, err := r.accessoryManager.Count(ctx, query)
if err != nil { if err != nil {

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
beegocontext "github.com/beego/beego/v2/server/web/context" beegocontext "github.com/beego/beego/v2/server/web/context"
"github.com/goharbor/harbor/src/lib/q"
accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model" accessorymodel "github.com/goharbor/harbor/src/pkg/accessory/model"
basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base" basemodel "github.com/goharbor/harbor/src/pkg/accessory/model/base"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
@ -43,15 +42,16 @@ func TestReferrersHandlerOK(t *testing.T) {
}, },
}, nil) }, nil)
accessoryMock.On("Count", mock.Anything, q.New(q.KeyWords{"SubjectArtifactDigest": digestVal})). accessoryMock.On("Count", mock.Anything, mock.Anything).
Return(int64(1), nil) Return(int64(1), nil)
accessoryMock.On("List", mock.Anything, q.New(q.KeyWords{"SubjectArtifactDigest": digestVal})). accessoryMock.On("List", mock.Anything, mock.Anything).
Return([]accessorymodel.Accessory{ Return([]accessorymodel.Accessory{
&basemodel.Default{ &basemodel.Default{
Data: accessorymodel.AccessoryData{ Data: accessorymodel.AccessoryData{
ID: 1, ID: 1,
ArtifactID: 2, ArtifactID: 2,
SubArtifactDigest: digestVal, SubArtifactDigest: digestVal,
SubArtifactRepo: "goharbor",
Type: accessorymodel.TypeCosignSignature, Type: accessorymodel.TypeCosignSignature,
}, },
}, },

View File

@ -18,6 +18,7 @@ func (a *Accessory) ToSwagger() *models.Accessory {
ID: a.ID, ID: a.ID,
ArtifactID: a.ArtifactID, ArtifactID: a.ArtifactID,
SubjectArtifactID: a.SubArtifactID, SubjectArtifactID: a.SubArtifactID,
SubjectArtifactRepo: a.SubArtifactRepo,
SubjectArtifactDigest: a.SubArtifactDigest, SubjectArtifactDigest: a.SubArtifactDigest,
Size: a.Size, Size: a.Size,
Digest: a.Digest, Digest: a.Digest,

View File

@ -92,13 +92,13 @@ func (_m *Manager) DeleteAccessories(ctx context.Context, _a1 *q.Query) error {
return r0 return r0
} }
// Ensure provides a mock function with given fields: ctx, subArtDigest, subArtID, artifactID, size, digest, accType // Ensure provides a mock function with given fields: ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType
func (_m *Manager) Ensure(ctx context.Context, subArtDigest string, subArtID int64, artifactID int64, size int64, digest string, accType string) error { func (_m *Manager) Ensure(ctx context.Context, subArtDigest string, subArtRepo string, subArtID int64, artifactID int64, size int64, digest string, accType string) error {
ret := _m.Called(ctx, subArtDigest, subArtID, artifactID, size, digest, accType) ret := _m.Called(ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, int64, int64, int64, string, string) error); ok { if rf, ok := ret.Get(0).(func(context.Context, string, string, int64, int64, int64, string, string) error); ok {
r0 = rf(ctx, subArtDigest, subArtID, artifactID, size, digest, accType) r0 = rf(ctx, subArtDigest, subArtRepo, subArtID, artifactID, size, digest, accType)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
} }