drone/app/api/controller/gitspace/create.go
Dhruv Dhruv 6dbe85fbfd 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.
2024-10-29 04:46:03 +00:00

257 lines
8.2 KiB
Go

// 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 gitspace
import (
"context"
"fmt"
"strconv"
"strings"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/services/gitspace"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"
gonanoid "github.com/matoous/go-nanoid"
)
const defaultResourceIdentifier = "default"
var (
// errSecretRequiresParent if the user tries to create a secret without a parent space.
ErrGitspaceRequiresParent = usererror.BadRequest(
"Parent space required - standalone gitspace are not supported.")
)
// CreateInput is the input used for create operations.
type CreateInput struct {
Identifier string `json:"identifier"`
Name string `json:"name"`
SpaceRef string `json:"space_ref"` // Ref of the parent space
IDE enum.IDEType `json:"ide"`
ResourceIdentifier string `json:"resource_identifier"`
ResourceSpaceRef string `json:"resource_space_ref"`
CodeRepoURL string `json:"code_repo_url"`
CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"`
CodeRepoRef *string `json:"code_repo_ref"`
Branch string `json:"branch"`
DevcontainerPath *string `json:"devcontainer_path"`
Metadata map[string]string `json:"metadata"`
SSHTokenIdentifier string `json:"ssh_token_identifier"`
}
// Create creates a new gitspace.
func (c *Controller) Create(
ctx context.Context,
session *auth.Session,
in *CreateInput,
) (*types.GitspaceConfig, error) {
space, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
if err = c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("invalid input: %w", err)
}
if err = apiauth.CheckGitspace(
ctx,
c.authorizer,
session,
space.Path,
"",
enum.PermissionGitspaceEdit); err != nil {
return nil, err
}
err = c.gitspaceLimiter.Usage(ctx, space.ID)
if err != nil {
return nil, err
}
// check if it's an internal repo
if in.CodeRepoType == enum.CodeRepoTypeGitness && *in.CodeRepoRef != "" {
repo, err := c.repoStore.FindByRef(ctx, *in.CodeRepoRef)
if err != nil {
return nil, fmt.Errorf("couldn't fetch repo for the user: %w", err)
}
if err = apiauth.CheckRepo(
ctx,
c.authorizer,
session,
repo,
enum.PermissionRepoView); err != nil {
return nil, err
}
}
suffixUID, err := gonanoid.Generate(gitspace.AllowedUIDAlphabet, 6)
if err != nil {
return nil, fmt.Errorf("could not generate UID for gitspace config : %q %w", in.Identifier, err)
}
identifier := strings.ToLower(in.Identifier + "-" + suffixUID)
now := time.Now().UnixMilli()
var gitspaceConfig *types.GitspaceConfig
resourceIdentifier := in.ResourceIdentifier
// assume resource to be in same space if it's not explicitly specified.
if in.ResourceSpaceRef == "" {
in.ResourceSpaceRef = in.SpaceRef
}
resourceSpace, err := c.spaceStore.FindByRef(ctx, in.ResourceSpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
if err = apiauth.CheckInfraProvider(
ctx,
c.authorizer,
session,
resourceSpace.Path,
resourceIdentifier,
enum.PermissionInfraProviderAccess); err != nil {
return nil, err
}
err = c.createOrFindInfraProviderResource(ctx, resourceSpace, resourceIdentifier, now)
if err != nil {
return nil, err
}
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
infraProviderResource, err := c.infraProviderSvc.FindResourceByIdentifier(
ctx,
resourceSpace.ID,
resourceIdentifier)
if err != nil {
return fmt.Errorf("could not find infra provider resource : %q %w", resourceIdentifier, err)
}
codeRepo := types.CodeRepo{
URL: in.CodeRepoURL,
Ref: in.CodeRepoRef,
Type: in.CodeRepoType,
Branch: in.Branch,
DevcontainerPath: in.DevcontainerPath,
}
principal := session.Principal
principalID := principal.ID
user := types.GitspaceUser{
Identifier: principal.UID,
Email: principal.Email,
DisplayName: principal.DisplayName,
ID: &principalID}
gitspaceConfig = &types.GitspaceConfig{
Identifier: identifier,
Name: in.Name,
IDE: in.IDE,
State: enum.GitspaceStateUninitialized,
SpaceID: space.ID,
SpacePath: space.Path,
Created: now,
Updated: now,
SSHTokenIdentifier: in.SSHTokenIdentifier,
CodeRepo: codeRepo,
GitspaceUser: user,
}
gitspaceConfig.InfraProviderResource = *infraProviderResource
err = c.gitspaceConfigStore.Create(ctx, gitspaceConfig)
if err != nil {
return fmt.Errorf("failed to create gitspace config for : %q %w", identifier, err)
}
return nil
})
if err != nil {
return nil, err
}
gitspaceConfig.BranchURL = c.gitspaceSvc.GetBranchURL(ctx, gitspaceConfig)
return gitspaceConfig, nil
}
func (c *Controller) createOrFindInfraProviderResource(
ctx context.Context,
parentSpace *types.Space,
resourceIdentifier string,
now int64,
) error {
_, err := c.infraProviderSvc.FindResourceByIdentifier(
ctx,
parentSpace.ID,
resourceIdentifier)
if err != nil &&
errors.Is(err, store.ErrResourceNotFound) &&
resourceIdentifier == defaultResourceIdentifier {
err = c.autoCreateDefaultResource(ctx, parentSpace, now)
if err != nil {
return err
}
} else if err != nil {
return fmt.Errorf("could not find infra provider resource : %q %w", resourceIdentifier, err)
}
return err
}
func (c *Controller) autoCreateDefaultResource(ctx context.Context, parentSpace *types.Space, now int64) error {
defaultDockerConfig := &types.InfraProviderConfig{
Identifier: defaultResourceIdentifier,
Name: "default docker infrastructure",
Type: enum.InfraProviderTypeDocker,
SpaceID: parentSpace.ID,
SpacePath: parentSpace.Path,
Created: now,
Updated: now,
}
defaultResource := types.InfraProviderResource{
UID: defaultResourceIdentifier,
Name: "Standard Docker Resource",
InfraProviderConfigIdentifier: defaultDockerConfig.Identifier,
InfraProviderType: enum.InfraProviderTypeDocker,
CPU: wrapString("any"),
Memory: wrapString("any"),
Disk: wrapString("any"),
Network: wrapString("standard"),
SpaceID: parentSpace.ID,
SpacePath: parentSpace.Path,
Created: now,
Updated: now,
}
defaultDockerConfig.Resources = []types.InfraProviderResource{defaultResource}
err := c.infraProviderSvc.CreateInfraProvider(ctx, defaultDockerConfig)
if err != nil {
return fmt.Errorf("could not auto-create the infra provider: %w", err)
}
return nil
}
func wrapString(str string) *string {
return &str
}
func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
if err := check.Identifier(in.Identifier); err != nil {
return err
}
if err := check.Identifier(in.ResourceIdentifier); err != nil {
return err
}
parentRefAsID, err := strconv.ParseInt(in.SpaceRef, 10, 64)
if (err == nil && parentRefAsID <= 0) || (len(strings.TrimSpace(in.SpaceRef)) == 0) {
return ErrGitspaceRequiresParent
}
return nil
}