Skip to content

Commit

Permalink
gitspaces API Implementation (harness#2136)
Browse files Browse the repository at this point in the history
* feat: [CDE-83]: fixed linting issues.
* feat: [CDE-83]: fixed linting issues.
* feat: [CDE-83]: fixed linting issues.
* feat: [CDE-83]: fixed types
* feat: [CDE-83]: fixed types
* feat: [CDE-83]: fixed types
* feat: [CDE-83]: fixed types
* feat: [CDE-83]: fixed types
* feat: [CDE-83]: fixed DB comments.
* feat: [CDE-83]: fixed DB comments.
* feat: [CDE-83]: Implemented the action API
* gitspaces API Implementation
  • Loading branch information
n00bitax authored and Harness committed Jul 9, 2024
1 parent 34df7cf commit f784395
Show file tree
Hide file tree
Showing 39 changed files with 1,858 additions and 241 deletions.
45 changes: 45 additions & 0 deletions app/api/auth/infraprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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 auth

import (
"context"

"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)

// CheckInfraProvider checks if a gitspace specific permission is granted for the current auth session
// in the scope of its parent.
// Returns nil if the permission is granted, otherwise returns an error.
// NotAuthenticated, NotAuthorized, or any underlying error.
func CheckInfraProvider(
ctx context.Context,
authorizer authz.Authorizer,
session *auth.Session,
parentPath,
identifier string,
permission enum.Permission,
) error {
scope := &types.Scope{SpacePath: parentPath}
resource := &types.Resource{
Type: enum.ResourceTypeInfraProvider,
Identifier: identifier,
}

return Check(ctx, authorizer, session, scope, resource, permission)
}
165 changes: 157 additions & 8 deletions app/api/controller/gitspace/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,172 @@ package gitspace

import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"

apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
events "github.com/harness/gitness/app/events/gitspace"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"

gonanoid "github.com/matoous/go-nanoid"
)

const defaultAccessKey = "Harness@123"
const defaultMachineUser = "harness"

type ActionInput struct {
Action enum.GitspaceActionType `json:"action"`
Action enum.GitspaceActionType `json:"action"`
Identifier string `json:"-"`
SpaceRef string `json:"-"` // Ref of the parent space
}

func (c *Controller) Action(
_ context.Context,
_ *auth.Session,
_ string,
_ string,
_ *ActionInput,
ctx context.Context,
session *auth.Session,
in *ActionInput,
) (*types.GitspaceConfig, error) {
return nil, errors.New("unimplemented")
if err := c.sanitizeActionInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
space, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find space: %w", err)
}
err = apiauth.CheckGitspace(ctx, c.authorizer, session, space.Path, in.Identifier, enum.PermissionGitspaceAccess)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
gitspaceConfig, err := c.gitspaceConfigStore.FindByIdentifier(ctx, space.ID, in.Identifier)
gitspaceConfig.SpacePath = space.Path
gitspaceConfig.SpaceID = space.ID
if err != nil {
return nil, fmt.Errorf("failed to find gitspace config: %w", err)
}
// All the actions should be idempotent.
switch in.Action {
case enum.GitspaceActionTypeStart:
c.emitGitspaceConfigEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStart)
gitspace, err := c.startGitspace(ctx, gitspaceConfig)
if err != nil {
c.emitGitspaceConfigEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStartFailed)
}
return gitspace, err
case enum.GitspaceActionTypeStop:
c.emitGitspaceConfigEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStop)
gitspace, err := c.stopGitspace(ctx, gitspaceConfig)
if err != nil {
c.emitGitspaceConfigEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeGitspaceActionStopFailed)
}
return gitspace, err
default:
return nil, fmt.Errorf("unknown action %s on gitspace : %s", string(in.Action), gitspaceConfig.Identifier)
}
}

func (c *Controller) startGitspace(ctx context.Context, config *types.GitspaceConfig) (*types.GitspaceConfig, error) {
savedGitspaceInstance, err := c.gitspaceInstanceStore.FindLatestByGitspaceConfigID(ctx, config.ID, config.SpaceID)
const resourceNotFoundErr = "Failed to find gitspace: resource not found"
if err != nil && err.Error() != resourceNotFoundErr { // TODO fix this
return nil, fmt.Errorf("failed to find gitspace with config ID : %s %w", config.Identifier, err)
}
config.GitspaceInstance = savedGitspaceInstance
if savedGitspaceInstance == nil || savedGitspaceInstance.State.IsFinalStatus() {
codeServerPassword := defaultAccessKey
gitspaceMachineUser := defaultMachineUser
now := time.Now().UnixMilli()
suffixUID, err := gonanoid.Generate(allowedUIDAlphabet, 6)
if err != nil {
return nil, fmt.Errorf("could not generate UID for gitspace config : %q %w", config.Identifier, err)
}
identifier := strings.ToLower(config.Identifier + "-" + suffixUID)
var gitspaceInstance = &types.GitspaceInstance{
GitSpaceConfigID: config.ID,
Identifier: identifier,
State: enum.GitspaceInstanceStateUninitialized,
UserID: config.UserID,
SpaceID: config.SpaceID,
SpacePath: config.SpacePath,
Created: now,
Updated: now,
TotalTimeUsed: 0,
}
if config.IDE == enum.IDETypeVSCodeWeb || config.IDE == enum.IDETypeVSCode {
gitspaceInstance.AccessKey = &codeServerPassword
gitspaceInstance.AccessType = enum.GitspaceAccessTypeUserCredentials
gitspaceInstance.MachineUser = &gitspaceMachineUser
}
if err = c.gitspaceInstanceStore.Create(ctx, gitspaceInstance); err != nil {
return nil, fmt.Errorf("failed to create gitspace instance for %s %w", config.Identifier, err)
}
newGitspaceInstance, err := c.gitspaceInstanceStore.FindLatestByGitspaceConfigID(ctx, config.ID, config.SpaceID)
newGitspaceInstance.SpacePath = config.SpacePath
if err != nil {
return nil, fmt.Errorf("failed to find gitspace with config ID : %s %w", config.Identifier, err)
}
config.GitspaceInstance = newGitspaceInstance
}

updatedGitspace, err := c.orchestrator.StartGitspace(ctx, config)
if err != nil {
return nil, fmt.Errorf("failed to find start gitspace : %s %w", config.Identifier, err)
}
if err = c.gitspaceInstanceStore.Update(ctx, updatedGitspace); err != nil {
return nil, fmt.Errorf("failed to update gitspace %w", err)
}
config.GitspaceInstance = updatedGitspace
config.State, _ = enum.GetGitspaceStateFromInstance(updatedGitspace.State)
return config, nil
}

func (c *Controller) stopGitspace(ctx context.Context, config *types.GitspaceConfig) (*types.GitspaceConfig, error) {
savedGitspace, err := c.gitspaceInstanceStore.FindLatestByGitspaceConfigID(ctx, config.ID, config.SpaceID)
if err != nil {
return nil, fmt.Errorf("failed to find gitspace with config ID : %s %w", config.Identifier, err)
}
if savedGitspace.State.IsFinalStatus() {
return nil, fmt.Errorf(
"gitspace Instance cannot be stopped with ID %s %w", savedGitspace.Identifier, err)
}
config.GitspaceInstance = savedGitspace
if updatedGitspace, stopErr := c.orchestrator.StopGitspace(ctx, config); stopErr != nil {
return nil, fmt.Errorf(
"failed to stop gitspace instance with ID %s %w", savedGitspace.Identifier, stopErr)
} else if updatedGitspace != nil {
if stopErr = c.gitspaceInstanceStore.Update(ctx, updatedGitspace); stopErr != nil {
return nil, fmt.Errorf(
"unable to update the gitspace with config id %s %w", savedGitspace.Identifier, stopErr)
}
config.GitspaceInstance = updatedGitspace
config.State, _ = enum.GetGitspaceStateFromInstance(updatedGitspace.State)
}
return config, nil
}

func (c *Controller) sanitizeActionInput(in *ActionInput) error {
if err := check.Identifier(in.Identifier); 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
}

func (c *Controller) emitGitspaceConfigEvent(
ctx context.Context,
config *types.GitspaceConfig,
eventType enum.GitspaceEventType) {
c.eventReporter.EmitGitspaceEvent(ctx, events.GitspaceEvent, &events.GitspaceEventPayload{
QueryKey: config.Identifier,
EntityID: config.ID,
EntityType: enum.GitspaceEntityTypeGitspaceConfig,
EventType: eventType,
Created: time.Now().UnixMilli(),
})
}
9 changes: 8 additions & 1 deletion app/api/controller/gitspace/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package gitspace

import (
"github.com/harness/gitness/app/auth/authz"
gitspaceevents "github.com/harness/gitness/app/events/gitspace"
"github.com/harness/gitness/app/gitspace/orchestrator"
"github.com/harness/gitness/app/store"
)

Expand All @@ -25,16 +27,19 @@ type Controller struct {
gitspaceConfigStore store.GitspaceConfigStore
gitspaceInstanceStore store.GitspaceInstanceStore
spaceStore store.SpaceStore
eventReporter *gitspaceevents.Reporter
orchestrator orchestrator.Orchestrator
gitspaceEventStore store.GitspaceEventStore
}

// TODO Stubbed Impl
func NewController(
authorizer authz.Authorizer,
infraProviderResourceStore store.InfraProviderResourceStore,
gitspaceConfigStore store.GitspaceConfigStore,
gitspaceInstanceStore store.GitspaceInstanceStore,
spaceStore store.SpaceStore,
eventReporter *gitspaceevents.Reporter,
orchestrator orchestrator.Orchestrator,
gitspaceEventStore store.GitspaceEventStore,
) *Controller {
return &Controller{
Expand All @@ -43,6 +48,8 @@ func NewController(
gitspaceConfigStore: gitspaceConfigStore,
gitspaceInstanceStore: gitspaceInstanceStore,
spaceStore: spaceStore,
eventReporter: eventReporter,
orchestrator: orchestrator,
gitspaceEventStore: gitspaceEventStore,
}
}
106 changes: 91 additions & 15 deletions app/api/controller/gitspace/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,108 @@ package gitspace

import (
"context"
"errors"
"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/types"
"github.com/harness/gitness/types/check"
"github.com/harness/gitness/types/enum"

gonanoid "github.com/matoous/go-nanoid"
)

const allowedUIDAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789"

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.")
)

// TODO Stubbed Impl
// 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"`
InfraProviderResourceID string `json:"resource_identifier"`
CodeRepoURL string `json:"code_repo_url"`
Branch string `json:"branch"`
DevcontainerPath string `json:"devcontainer_path"`
Metadata map[string]string `json:"metadata"`
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"`
CodeRepoURL string `json:"code_repo_url"`
Branch string `json:"branch"`
DevcontainerPath *string `json:"devcontainer_path"`
Metadata map[string]string `json:"metadata"`
}

// Create creates a new gitspace.
func (c *Controller) Create(
_ context.Context,
_ *auth.Session,
_ *CreateInput,
ctx context.Context,
session *auth.Session,
in *CreateInput,
) (*types.GitspaceConfig, error) {
return nil, errors.New("unimplemented")
parentSpace, err := c.spaceStore.FindByRef(ctx, in.SpaceRef)
if err != nil {
return nil, fmt.Errorf("failed to find parent by ref: %w", err)
}
if err = apiauth.CheckGitspace(
ctx,
c.authorizer,
session,
parentSpace.Path,
"",
enum.PermissionGitspaceEdit); err != nil {
return nil, err
}
suffixUID, err := gonanoid.Generate(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)
if err = c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("invalid input: %w", err)
}
now := time.Now().UnixMilli()
infraProviderResource, err := c.infraProviderResourceStore.FindByIdentifier(
ctx,
parentSpace.ID,
in.ResourceIdentifier)
if err != nil {
return nil, fmt.Errorf("could not find infra provider resource : %q %w", in.ResourceIdentifier, err)
}
gitspaceConfig := &types.GitspaceConfig{
Identifier: identifier,
Name: in.Name,
IDE: in.IDE,
InfraProviderResourceID: infraProviderResource.ID,
InfraProviderResourceIdentifier: infraProviderResource.Identifier,
CodeRepoType: enum.CodeRepoTypeUnknown, // TODO fix this
State: enum.GitspaceStateUninitialized,
CodeRepoURL: in.CodeRepoURL,
Branch: in.Branch,
DevcontainerPath: in.DevcontainerPath,
UserID: session.Principal.UID,
SpaceID: parentSpace.ID,
SpacePath: parentSpace.Path,
Created: now,
Updated: now,
}
err = c.gitspaceConfigStore.Create(ctx, gitspaceConfig)
if err != nil {
return nil, fmt.Errorf("failed to create gitspace config for : %q %w", identifier, err)
}
return gitspaceConfig, nil
}

func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
if err := check.Identifier(in.Identifier); 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
}
Loading

0 comments on commit f784395

Please sign in to comment.