mirror of https://github.com/harness/drone.git
fix: [CDE-413]: Setting branch URL in all gitspace API response (#2881)
* fix: [CDE-413]: Adding spacePath to method. * fix: [CDE-413]: Adding method to get branch url in the scm interface and using it to set the branch url in the gitspace config api responses. Removing the SCM interface as it has only 1 impl. Minor refactoring. * fix: [CDE-413]: Adding method to get branch url in the scm interface and using it to set the branch url in the gitspace config api responses. Removing the SCM interface as it has only 1 impl. Minor refactoring.devcontainer-setup
parent
178af11902
commit
6dbe85fbfd
|
@ -76,6 +76,9 @@ func (c *Controller) Action(
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
gitspaceConfig.BranchURL = c.gitspaceSvc.GetBranchURL(ctx, gitspaceConfig)
|
||||
|
||||
// All the actions should be idempotent.
|
||||
switch in.Action {
|
||||
case enum.GitspaceActionTypeStart:
|
||||
|
|
|
@ -37,7 +37,7 @@ type Controller struct {
|
|||
gitspaceEventStore store.GitspaceEventStore
|
||||
tx dbtx.Transactor
|
||||
statefulLogger *logutil.StatefulLogger
|
||||
scm scm.SCM
|
||||
scm *scm.SCM
|
||||
repoStore store.RepoStore
|
||||
gitspaceSvc *gitspace.Service
|
||||
gitspaceLimiter limiter.Gitspace
|
||||
|
@ -52,7 +52,7 @@ func NewController(
|
|||
spaceStore store.SpaceStore,
|
||||
gitspaceEventStore store.GitspaceEventStore,
|
||||
statefulLogger *logutil.StatefulLogger,
|
||||
scm scm.SCM,
|
||||
scm *scm.SCM,
|
||||
repoStore store.RepoStore,
|
||||
gitspaceSvc *gitspace.Service,
|
||||
gitspaceLimiter limiter.Gitspace,
|
||||
|
|
|
@ -177,6 +177,7 @@ func (c *Controller) Create(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gitspaceConfig.BranchURL = c.gitspaceSvc.GetBranchURL(ctx, gitspaceConfig)
|
||||
return gitspaceConfig, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,10 @@ func (c *Controller) ListAllGitspaces( // nolint:gocognit
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for _, gitspaceConfig := range result {
|
||||
gitspaceConfig.BranchURL = c.gitspaceSvc.GetBranchURL(ctx, gitspaceConfig)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ func ProvideController(
|
|||
spaceStore store.SpaceStore,
|
||||
eventStore store.GitspaceEventStore,
|
||||
statefulLogger *logutil.StatefulLogger,
|
||||
scm scm.SCM,
|
||||
scm *scm.SCM,
|
||||
repoStore store.RepoStore,
|
||||
gitspaceSvc *gitspace.Service,
|
||||
gitspaceLimiter limiter.Gitspace,
|
||||
|
|
|
@ -46,7 +46,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
type orchestrator struct {
|
||||
scm scm.SCM
|
||||
scm *scm.SCM
|
||||
infraProviderResourceStore store.InfraProviderResourceStore
|
||||
infraProvisioner infrastructure.InfraProvisioner
|
||||
containerOrchestrator container.Orchestrator
|
||||
|
@ -60,7 +60,7 @@ type orchestrator struct {
|
|||
var _ Orchestrator = (*orchestrator)(nil)
|
||||
|
||||
func NewOrchestrator(
|
||||
scm scm.SCM,
|
||||
scm *scm.SCM,
|
||||
infraProviderResourceStore store.InfraProviderResourceStore,
|
||||
infraProvisioner infrastructure.InfraProvisioner,
|
||||
containerOrchestrator container.Orchestrator,
|
||||
|
|
|
@ -32,7 +32,7 @@ var WireSet = wire.NewSet(
|
|||
)
|
||||
|
||||
func ProvideOrchestrator(
|
||||
scm scm.SCM,
|
||||
scm *scm.SCM,
|
||||
infraProviderResourceStore store.InfraProviderResourceStore,
|
||||
infraProvisioner infrastructure.InfraProvisioner,
|
||||
containerOrchestrator container.Orchestrator,
|
||||
|
|
|
@ -49,9 +49,11 @@ type GitnessSCM struct {
|
|||
}
|
||||
|
||||
// ListBranches implements Provider.
|
||||
func (s *GitnessSCM) ListBranches(ctx context.Context,
|
||||
func (s *GitnessSCM) ListBranches(
|
||||
ctx context.Context,
|
||||
filter *BranchFilter,
|
||||
_ *ResolvedCredentials) ([]Branch, error) {
|
||||
_ *ResolvedCredentials,
|
||||
) ([]Branch, error) {
|
||||
repo, err := s.repoStore.FindByRef(ctx, filter.Repository)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find repo: %w", err)
|
||||
|
@ -83,10 +85,12 @@ func mapBranch(b git.Branch) Branch {
|
|||
}
|
||||
}
|
||||
|
||||
// ListReporisotries implements Provider.
|
||||
func (s *GitnessSCM) ListReporisotries(ctx context.Context,
|
||||
// ListRepositories implements Provider.
|
||||
func (s *GitnessSCM) ListRepositories(
|
||||
ctx context.Context,
|
||||
filter *RepositoryFilter,
|
||||
_ *ResolvedCredentials) ([]Repository, error) {
|
||||
_ *ResolvedCredentials,
|
||||
) ([]Repository, error) {
|
||||
repos, err := s.repoStore.List(ctx, filter.SpaceID, &types.RepoFilter{
|
||||
Page: filter.Page,
|
||||
Size: filter.Size,
|
||||
|
@ -125,10 +129,12 @@ func mapRepository(repo *types.Repository) (Repository, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func NewGitnessSCM(repoStore store.RepoStore, git git.Interface,
|
||||
func NewGitnessSCM(
|
||||
repoStore store.RepoStore, git git.Interface,
|
||||
tokenStore store.TokenStore,
|
||||
principalStore store.PrincipalStore,
|
||||
urlProvider urlprovider.Provider) *GitnessSCM {
|
||||
urlProvider urlprovider.Provider,
|
||||
) *GitnessSCM {
|
||||
return &GitnessSCM{
|
||||
repoStore: repoStore,
|
||||
git: git,
|
||||
|
@ -138,7 +144,7 @@ func NewGitnessSCM(repoStore store.RepoStore, git git.Interface,
|
|||
}
|
||||
}
|
||||
|
||||
func (s GitnessSCM) ResolveCredentials(
|
||||
func (s *GitnessSCM) ResolveCredentials(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
) (*ResolvedCredentials, error) {
|
||||
|
@ -200,7 +206,8 @@ func (s GitnessSCM) ResolveCredentials(
|
|||
return resolvedCredentails, nil
|
||||
}
|
||||
|
||||
func (s GitnessSCM) GetFileContent(ctx context.Context,
|
||||
func (s *GitnessSCM) GetFileContent(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
filePath string,
|
||||
_ *ResolvedCredentials,
|
||||
|
@ -243,8 +250,13 @@ func (s GitnessSCM) GetFileContent(ctx context.Context,
|
|||
return catFileOutput, nil
|
||||
}
|
||||
|
||||
func findUserFromUID(ctx context.Context,
|
||||
func findUserFromUID(
|
||||
ctx context.Context,
|
||||
principalStore store.PrincipalStore, userUID string,
|
||||
) (*types.User, error) {
|
||||
return principalStore.FindUserByUID(ctx, userUID)
|
||||
}
|
||||
|
||||
func (s *GitnessSCM) GetBranchURL(_ string, repoURL string, branch string) (string, error) {
|
||||
return fmt.Sprintf("%s/files/%s", strings.TrimSuffix(repoURL, ".git"), branch), nil
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var _ Provider = (*GenericSCM)(nil)
|
||||
|
||||
type GenericSCM struct {
|
||||
}
|
||||
|
||||
|
@ -38,21 +40,24 @@ func NewGenericSCM() *GenericSCM {
|
|||
return &GenericSCM{}
|
||||
}
|
||||
|
||||
// ListBranches implements Provider.
|
||||
func (s *GenericSCM) ListBranches(_ context.Context,
|
||||
func (s *GenericSCM) ListBranches(
|
||||
_ context.Context,
|
||||
_ *BranchFilter,
|
||||
_ *ResolvedCredentials) ([]Branch, error) {
|
||||
_ *ResolvedCredentials,
|
||||
) ([]Branch, error) {
|
||||
return []Branch{}, nil
|
||||
}
|
||||
|
||||
// ListReporisotries implements Provider.
|
||||
func (s *GenericSCM) ListReporisotries(_ context.Context,
|
||||
func (s *GenericSCM) ListRepositories(
|
||||
_ context.Context,
|
||||
_ *RepositoryFilter,
|
||||
_ *ResolvedCredentials) ([]Repository, error) {
|
||||
_ *ResolvedCredentials,
|
||||
) ([]Repository, error) {
|
||||
return []Repository{}, nil
|
||||
}
|
||||
|
||||
func (s GenericSCM) GetFileContent(ctx context.Context,
|
||||
func (s *GenericSCM) GetFileContent(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
filePath string,
|
||||
_ *ResolvedCredentials,
|
||||
|
@ -115,7 +120,7 @@ func (s GenericSCM) GetFileContent(ctx context.Context,
|
|||
return catFileOutput.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s GenericSCM) ResolveCredentials(
|
||||
func (s *GenericSCM) ResolveCredentials(
|
||||
_ context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
) (*ResolvedCredentials, error) {
|
||||
|
@ -131,3 +136,7 @@ func (s GenericSCM) ResolveCredentials(
|
|||
resolvedCredentials.RepoName = repoName
|
||||
return resolvedCredentials, err
|
||||
}
|
||||
|
||||
func (s *GenericSCM) GetBranchURL(_ string, repoURL string, _ string) (string, error) {
|
||||
return repoURL, nil
|
||||
}
|
||||
|
|
|
@ -34,29 +34,17 @@ var (
|
|||
|
||||
const devcontainerDefaultPath = ".devcontainer/devcontainer.json"
|
||||
|
||||
var _ SCM = (*scm)(nil)
|
||||
|
||||
type SCM interface {
|
||||
// GetSCMRepoDetails fetches repository name, credentials & devcontainer config file from the given repo and branch.
|
||||
GetSCMRepoDetails(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
) (*ResolvedDetails, error)
|
||||
|
||||
// CheckValidCodeRepo checks if the current URL is a valid and accessible code repo,
|
||||
// input can be connector info, user token etc.
|
||||
CheckValidCodeRepo(ctx context.Context, request CodeRepositoryRequest) (*CodeRepositoryResponse, error)
|
||||
}
|
||||
|
||||
type scm struct {
|
||||
type SCM struct {
|
||||
scmProviderFactory Factory
|
||||
}
|
||||
|
||||
func NewSCM(factory Factory) SCM {
|
||||
return &scm{scmProviderFactory: factory}
|
||||
func NewSCM(factory Factory) *SCM {
|
||||
return &SCM{scmProviderFactory: factory}
|
||||
}
|
||||
|
||||
func (s scm) CheckValidCodeRepo(
|
||||
// CheckValidCodeRepo checks if the current URL is a valid and accessible code repo,
|
||||
// input can be connector info, user token etc.
|
||||
func (s *SCM) CheckValidCodeRepo(
|
||||
ctx context.Context,
|
||||
codeRepositoryRequest CodeRepositoryRequest,
|
||||
) (*CodeRepositoryResponse, error) {
|
||||
|
@ -110,7 +98,8 @@ func (s scm) CheckValidCodeRepo(
|
|||
return codeRepositoryResponse, nil
|
||||
}
|
||||
|
||||
func (s scm) GetSCMRepoDetails(
|
||||
// GetSCMRepoDetails fetches repository name, credentials & devcontainer config file from the given repo and branch.
|
||||
func (s *SCM) GetSCMRepoDetails(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
) (*ResolvedDetails, error) {
|
||||
|
@ -172,3 +161,16 @@ func detectDefaultGitBranch(ctx context.Context, gitRepoDir string) (string, err
|
|||
}
|
||||
return match[1], nil
|
||||
}
|
||||
|
||||
func (s *SCM) GetBranchURL(
|
||||
spacePath string,
|
||||
repoType enum.GitspaceCodeRepoType,
|
||||
repoURL string,
|
||||
branch string,
|
||||
) (string, error) {
|
||||
scmProvider, err := s.scmProviderFactory.GetSCMProvider(repoType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to resolve scm provider while generating branch url: %w", err)
|
||||
}
|
||||
return scmProvider.GetBranchURL(spacePath, repoURL, branch)
|
||||
}
|
||||
|
|
|
@ -15,29 +15,11 @@
|
|||
package scm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
ResolveCredentials(ctx context.Context, gitspaceConfig types.GitspaceConfig) (*ResolvedCredentials, error)
|
||||
GetFileContent(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
filePath string,
|
||||
credentials *ResolvedCredentials,
|
||||
) ([]byte, error)
|
||||
ListReporisotries(ctx context.Context,
|
||||
filter *RepositoryFilter,
|
||||
credentials *ResolvedCredentials) ([]Repository, error)
|
||||
ListBranches(ctx context.Context,
|
||||
filter *BranchFilter,
|
||||
credentials *ResolvedCredentials) ([]Branch, error)
|
||||
}
|
||||
|
||||
type Factory struct {
|
||||
providers map[enum.GitspaceCodeRepoType]Provider
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2023 Harness, 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 scm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
ResolveCredentials(ctx context.Context, gitspaceConfig types.GitspaceConfig) (*ResolvedCredentials, error)
|
||||
|
||||
GetFileContent(
|
||||
ctx context.Context,
|
||||
gitspaceConfig types.GitspaceConfig,
|
||||
filePath string,
|
||||
credentials *ResolvedCredentials,
|
||||
) ([]byte, error)
|
||||
|
||||
ListRepositories(
|
||||
ctx context.Context,
|
||||
filter *RepositoryFilter,
|
||||
credentials *ResolvedCredentials,
|
||||
) ([]Repository, error)
|
||||
|
||||
ListBranches(
|
||||
ctx context.Context,
|
||||
filter *BranchFilter,
|
||||
credentials *ResolvedCredentials,
|
||||
) ([]Branch, error)
|
||||
|
||||
GetBranchURL(spacePath string, repoURL string, branch string) (string, error)
|
||||
}
|
|
@ -44,6 +44,6 @@ func ProvideFactory(gitness *GitnessSCM, genericSCM *GenericSCM) Factory {
|
|||
return NewFactory(gitness, genericSCM)
|
||||
}
|
||||
|
||||
func ProvideSCM(factory Factory) SCM {
|
||||
func ProvideSCM(factory Factory) *SCM {
|
||||
return NewSCM(factory)
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ func (c *Service) Find(
|
|||
if txErr != nil {
|
||||
return nil, txErr
|
||||
}
|
||||
gitspaceConfigResult.BranchURL = c.GetBranchURL(ctx, gitspaceConfigResult)
|
||||
return gitspaceConfigResult, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -20,11 +20,14 @@ import (
|
|||
|
||||
gitspaceevents "github.com/harness/gitness/app/events/gitspace"
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator"
|
||||
"github.com/harness/gitness/app/gitspace/scm"
|
||||
"github.com/harness/gitness/app/services/infraprovider"
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func NewService(
|
||||
|
@ -36,6 +39,7 @@ func NewService(
|
|||
spaceStore store.SpaceStore,
|
||||
infraProviderSvc *infraprovider.Service,
|
||||
orchestrator orchestrator.Orchestrator,
|
||||
scm *scm.SCM,
|
||||
) *Service {
|
||||
return &Service{
|
||||
tx: tx,
|
||||
|
@ -46,6 +50,7 @@ func NewService(
|
|||
spaceStore: spaceStore,
|
||||
infraProviderSvc: infraProviderSvc,
|
||||
orchestrator: orchestrator,
|
||||
scm: scm,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +63,7 @@ type Service struct {
|
|||
tx dbtx.Transactor
|
||||
infraProviderSvc *infraprovider.Service
|
||||
orchestrator orchestrator.Orchestrator
|
||||
scm *scm.SCM
|
||||
}
|
||||
|
||||
func (c *Service) ListGitspacesForSpace(
|
||||
|
@ -100,6 +106,7 @@ func (c *Service) ListGitspacesForSpace(
|
|||
} else {
|
||||
gitspaceConfig.State = enum.GitspaceStateUninitialized
|
||||
}
|
||||
gitspaceConfig.BranchURL = c.GetBranchURL(ctx, gitspaceConfig)
|
||||
}
|
||||
return nil
|
||||
}, dbtx.TxDefaultReadOnly)
|
||||
|
@ -127,3 +134,14 @@ func (c *Service) getLatestInstanceMap(
|
|||
}
|
||||
return gitspaceInstancesMap, nil
|
||||
}
|
||||
|
||||
func (c *Service) GetBranchURL(ctx context.Context, config *types.GitspaceConfig) string {
|
||||
branchURL, err := c.scm.GetBranchURL(config.SpacePath, config.CodeRepo.Type, config.CodeRepo.URL,
|
||||
config.CodeRepo.Branch)
|
||||
if err != nil {
|
||||
log.Warn().Ctx(ctx).Err(err).Msgf("failed to get branch URL for gitspace config %s, returning repo url",
|
||||
config.Identifier)
|
||||
branchURL = config.CodeRepo.URL
|
||||
}
|
||||
return branchURL
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package gitspace
|
|||
import (
|
||||
gitspaceevents "github.com/harness/gitness/app/events/gitspace"
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator"
|
||||
"github.com/harness/gitness/app/gitspace/scm"
|
||||
"github.com/harness/gitness/app/services/infraprovider"
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
@ -37,8 +38,8 @@ func ProvideGitspace(
|
|||
spaceStore store.SpaceStore,
|
||||
infraProviderSvc *infraprovider.Service,
|
||||
orchestrator orchestrator.Orchestrator,
|
||||
|
||||
scm *scm.SCM,
|
||||
) *Service {
|
||||
return NewService(tx, gitspaceStore, gitspaceInstanceStore, eventReporter,
|
||||
gitspaceEventStore, spaceStore, infraProviderSvc, orchestrator)
|
||||
gitspaceEventStore, spaceStore, infraProviderSvc, orchestrator, scm)
|
||||
}
|
||||
|
|
|
@ -323,7 +323,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
passwordResolver := secret.ProvidePasswordResolver()
|
||||
resolverFactory := secret.ProvideResolverFactory(passwordResolver)
|
||||
orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, infraProviderResourceStore, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory)
|
||||
gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, eventsReporter, gitspaceEventStore, spaceStore, infraproviderService, orchestratorOrchestrator)
|
||||
gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, eventsReporter, gitspaceEventStore, spaceStore, infraproviderService, orchestratorOrchestrator, scmSCM)
|
||||
spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, listService, repository, exporterRepository, resourceLimiter, publicaccessService, auditService, gitspaceService, labelService, instrumentService)
|
||||
reporter3, err := events5.ProvideReporter(eventsSystem)
|
||||
if err != nil {
|
||||
|
|
|
@ -152,15 +152,7 @@ func (d DockerProvider) Find(
|
|||
infrastructure.SpaceID = spaceID
|
||||
infrastructure.SpacePath = spacePath
|
||||
infrastructure.GitspaceConfigIdentifier = gitspaceConfigIdentifier
|
||||
|
||||
name := volumeName(spacePath, gitspaceConfigIdentifier)
|
||||
|
||||
volumeInspect, err := dockerClient.VolumeInspect(ctx, name)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("couldn't find the volume for %s", name)
|
||||
}
|
||||
|
||||
infrastructure.Storage = volumeInspect.Name
|
||||
infrastructure.Storage = volumeName(spacePath, gitspaceConfigIdentifier)
|
||||
|
||||
return infrastructure, nil
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ type CodeRepo struct {
|
|||
Ref *string `json:"code_repo_ref"`
|
||||
Type enum.GitspaceCodeRepoType `json:"code_repo_type"`
|
||||
Branch string `json:"branch"`
|
||||
BranchURL string `json:"branch_url,omitempty"`
|
||||
DevcontainerPath *string `json:"devcontainer_path,omitempty"`
|
||||
IsPrivate bool `json:"code_repo_is_private"`
|
||||
AuthType string `json:"-"`
|
||||
|
|
Loading…
Reference in New Issue