mirror of https://github.com/harness/drone.git
feat: [CDE-195]: read devcontainer.json directly for gitness (#2294)
* feat: [CDE-195]: Run VS Code Web as non-root user. * feat: [CDE-195]: read devcontainer.json directly for gitnesspull/3545/head
parent
9232029c74
commit
b6034a25aa
|
@ -58,12 +58,29 @@ func (c *Controller) Action(
|
|||
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)
|
||||
}
|
||||
|
||||
// check if it's an internal repo
|
||||
if gitspaceConfig.CodeRepoType == enum.CodeRepoTypeGitness && gitspaceConfig.CodeRepoURL != "" {
|
||||
repo, err := c.repoStore.FindByRef(ctx, gitspaceConfig.CodeRepoURL)
|
||||
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
|
||||
}
|
||||
}
|
||||
// All the actions should be idempotent.
|
||||
switch in.Action {
|
||||
case enum.GitspaceActionTypeStart:
|
||||
|
|
|
@ -37,6 +37,7 @@ type Controller struct {
|
|||
tx dbtx.Transactor
|
||||
statefulLogger *logutil.StatefulLogger
|
||||
scm scm.SCM
|
||||
repoStore store.RepoStore
|
||||
}
|
||||
|
||||
func NewController(
|
||||
|
@ -51,6 +52,7 @@ func NewController(
|
|||
gitspaceEventStore store.GitspaceEventStore,
|
||||
statefulLogger *logutil.StatefulLogger,
|
||||
scm scm.SCM,
|
||||
repoStore store.RepoStore,
|
||||
) *Controller {
|
||||
return &Controller{
|
||||
tx: tx,
|
||||
|
@ -64,5 +66,6 @@ func NewController(
|
|||
gitspaceEventStore: gitspaceEventStore,
|
||||
statefulLogger: statefulLogger,
|
||||
scm: scm,
|
||||
repoStore: repoStore,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,16 +44,17 @@ var (
|
|||
|
||||
// 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"`
|
||||
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"`
|
||||
ResourceSpaceRef string `json:"resource_space_ref"`
|
||||
CodeRepoURL string `json:"code_repo_url"`
|
||||
CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"`
|
||||
Branch string `json:"branch"`
|
||||
DevcontainerPath *string `json:"devcontainer_path"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
// Create creates a new gitspace.
|
||||
|
@ -75,6 +76,21 @@ func (c *Controller) Create(
|
|||
enum.PermissionGitspaceEdit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check if it's an internal repo
|
||||
if in.CodeRepoType == enum.CodeRepoTypeGitness && in.CodeRepoURL != "" {
|
||||
repo, err := c.repoStore.FindByRef(ctx, in.CodeRepoURL)
|
||||
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(allowedUIDAlphabet, 6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate UID for gitspace config : %q %w", in.Identifier, err)
|
||||
|
@ -86,7 +102,7 @@ func (c *Controller) Create(
|
|||
now := time.Now().UnixMilli()
|
||||
var gitspaceConfig *types.GitspaceConfig
|
||||
resourceIdentifier := in.ResourceIdentifier
|
||||
// assume resource to be in same space if its not explicitly specified.
|
||||
// assume resource to be in same space if it's not explicitly specified.
|
||||
if in.ResourceSpaceRef == "" {
|
||||
in.ResourceSpaceRef = in.SpaceRef
|
||||
}
|
||||
|
@ -121,7 +137,7 @@ func (c *Controller) Create(
|
|||
IDE: in.IDE,
|
||||
InfraProviderResourceID: infraProviderResource.ID,
|
||||
InfraProviderResourceIdentifier: infraProviderResource.Identifier,
|
||||
CodeRepoType: enum.CodeRepoTypeUnknown,
|
||||
CodeRepoType: in.CodeRepoType,
|
||||
State: enum.GitspaceStateUninitialized,
|
||||
CodeRepoURL: in.CodeRepoURL,
|
||||
Branch: in.Branch,
|
||||
|
|
|
@ -73,29 +73,29 @@ func (c *Controller) Events(
|
|||
func eventsMessageMapping() map[enum.GitspaceEventType]string {
|
||||
var gitspaceConfigsMap = make(map[enum.GitspaceEventType]string)
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStart] = "Starting Gitspace..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartCompleted] = "Started Gitspace"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartFailed] = "Starting Gitspace Failed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStart] = "Starting gitspace..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartCompleted] = "Started gitspace"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStartFailed] = "Starting gitspace failed"
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStop] = "Stopping Gitspace"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopCompleted] = "Stopped Gitspace"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopFailed] = "Stopping Gitspace Failed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStop] = "Stopping gitspace"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopCompleted] = "Stopped gitspace"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeGitspaceActionStopFailed] = "Stopping gitspace failed"
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerStart] = "Fetching devcontainer config..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerCompleted] = "Fetched devcontainer config"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerFailed] = "Fetching devcontainer config failed"
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningStart] = "Provisioning Infrastructure..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningCompleted] = "Provisioning Infrastructure Completed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningFailed] = "Provisioning Infrastructure Failed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningStart] = "Provisioning infrastructure..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningCompleted] = "Provisioning infrastructure completed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningFailed] = "Provisioning infrastructure failed"
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopStart] = "Stopping Infrastructure..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopCompleted] = "Stopping Infrastructure Completed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopFailed] = "Stopping Infrastructure Failed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopStart] = "Stopping infrastructure..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopCompleted] = "Stopping infrastructure completed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraStopFailed] = "Stopping infrastructure failed"
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningStart] = "Deprovisioning Infrastructure..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningCompleted] = "Deprovisioning Infrastructure Completed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningFailed] = "Deprovisioning Infrastructure Failed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningStart] = "Deprovisioning infrastructure..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningCompleted] = "Deprovisioning infrastructure completed"
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeInfraDeprovisioningFailed] = "Deprovisioning infrastructure failed"
|
||||
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectStart] = "Connecting to the gitspace agent..."
|
||||
gitspaceConfigsMap[enum.GitspaceEventTypeAgentConnectCompleted] = "Connected to the gitspace agent"
|
||||
|
|
|
@ -44,6 +44,7 @@ func ProvideController(
|
|||
eventStore store.GitspaceEventStore,
|
||||
statefulLogger *logutil.StatefulLogger,
|
||||
scm scm.SCM,
|
||||
repoStore store.RepoStore,
|
||||
) *Controller {
|
||||
return NewController(
|
||||
tx,
|
||||
|
@ -57,5 +58,6 @@ func ProvideController(
|
|||
eventStore,
|
||||
statefulLogger,
|
||||
scm,
|
||||
repoStore,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -52,6 +52,17 @@ func NewPipelineServiceSession() *auth.Session {
|
|||
}
|
||||
}
|
||||
|
||||
// gitspaceServicePrincipal is the principal that is used during
|
||||
// gitspace token injection for calling gitness APIs.
|
||||
var gitspaceServicePrincipal *types.Principal
|
||||
|
||||
func NewGitspaceServiceSession() *auth.Session {
|
||||
return &auth.Session{
|
||||
Principal: *gitspaceServicePrincipal,
|
||||
Metadata: &auth.EmptyMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap is an abstraction of a function that bootstraps a system.
|
||||
type Bootstrap func(context.Context) error
|
||||
|
||||
|
@ -65,6 +76,9 @@ func System(config *types.Config, userCtrl *user.Controller,
|
|||
if err := PipelineService(ctx, config, serviceCtrl); err != nil {
|
||||
return fmt.Errorf("failed to setup pipeline service: %w", err)
|
||||
}
|
||||
if err := GitspaceService(ctx, config, serviceCtrl); err != nil {
|
||||
return fmt.Errorf("failed to setup gitspace service: %w", err)
|
||||
}
|
||||
|
||||
if err := AdminUser(ctx, config, userCtrl); err != nil {
|
||||
return fmt.Errorf("failed to setup admin user: %w", err)
|
||||
|
@ -196,6 +210,36 @@ func PipelineService(
|
|||
return nil
|
||||
}
|
||||
|
||||
// GitspaceService sets up the gitspace service principal that is used during
|
||||
// gitspace credential injection for calling gitness APIs.
|
||||
func GitspaceService(
|
||||
ctx context.Context,
|
||||
config *types.Config,
|
||||
serviceCtrl *service.Controller,
|
||||
) error {
|
||||
svc, err := serviceCtrl.FindNoAuth(ctx, config.Principal.Gitspace.UID)
|
||||
if errors.Is(err, store.ErrResourceNotFound) {
|
||||
svc, err = createServicePrincipal(
|
||||
ctx,
|
||||
serviceCtrl,
|
||||
config.Principal.Gitspace.UID,
|
||||
config.Principal.Gitspace.Email,
|
||||
config.Principal.Gitspace.DisplayName,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup gitspace service: %w", err)
|
||||
}
|
||||
|
||||
gitspaceServicePrincipal = svc.ToPrincipal()
|
||||
|
||||
log.Ctx(ctx).Info().Msgf("Completed setup of gitspace service '%s' (id: %d).", svc.UID, svc.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createServicePrincipal(
|
||||
ctx context.Context,
|
||||
serviceCtrl *service.Controller,
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator/ide"
|
||||
"github.com/harness/gitness/app/gitspace/scm"
|
||||
"github.com/harness/gitness/infraprovider"
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
@ -29,9 +30,8 @@ type Orchestrator interface {
|
|||
CreateAndStartGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
infra *infraprovider.Infrastructure,
|
||||
repoName string,
|
||||
resolvedDetails *scm.ResolvedDetails,
|
||||
defaultBaseImage string,
|
||||
ideService ide.IDE,
|
||||
) (*StartResponse, error)
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator/ide"
|
||||
"github.com/harness/gitness/app/gitspace/orchestrator/template"
|
||||
"github.com/harness/gitness/app/gitspace/scm"
|
||||
"github.com/harness/gitness/infraprovider"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
|
@ -40,12 +41,15 @@ import (
|
|||
var _ Orchestrator = (*EmbeddedDockerOrchestrator)(nil)
|
||||
|
||||
const (
|
||||
loggingKey = "gitspace.container"
|
||||
catchAllIP = "0.0.0.0"
|
||||
containerStateRunning = "running"
|
||||
containerStateRemoved = "removed"
|
||||
containerStateStopped = "exited"
|
||||
templateCloneGit = "clone_git.sh"
|
||||
loggingKey = "gitspace.container"
|
||||
catchAllIP = "0.0.0.0"
|
||||
containerStateRunning = "running"
|
||||
containerStateRemoved = "removed"
|
||||
containerStateStopped = "exited"
|
||||
templateCloneGit = "clone_git.sh"
|
||||
templateAuthenticateGit = "authenticate_git.sh"
|
||||
templateManageUser = "manage_user.sh"
|
||||
harnessUser = "harness"
|
||||
)
|
||||
|
||||
type EmbeddedDockerOrchestrator struct {
|
||||
|
@ -70,9 +74,8 @@ func NewEmbeddedDockerOrchestrator(
|
|||
func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
infra *infraprovider.Infrastructure,
|
||||
repoName string,
|
||||
resolvedRepoDetails *scm.ResolvedDetails,
|
||||
defaultBaseImage string,
|
||||
ideService ide.IDE,
|
||||
) (*StartResponse, error) {
|
||||
|
@ -122,12 +125,18 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
|
|||
return nil, startErr
|
||||
}
|
||||
|
||||
devcontainer := &devcontainer.Devcontainer{
|
||||
devcontainer := &devcontainer.Exec{
|
||||
ContainerName: containerName,
|
||||
WorkingDir: e.getWorkingDir(repoName),
|
||||
WorkingDir: e.getWorkingDir(resolvedRepoDetails.RepoName),
|
||||
DockerClient: dockerClient,
|
||||
}
|
||||
|
||||
if resolvedRepoDetails.Credentials != nil {
|
||||
if err := e.authenticateGit(ctx, devcontainer, resolvedRepoDetails); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = e.runIDE(ctx, devcontainer, ideService, logStreamInstance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -154,13 +163,13 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
|
|||
startErr := e.startGitspace(
|
||||
ctx,
|
||||
gitspaceConfig,
|
||||
devcontainerConfig,
|
||||
containerName,
|
||||
dockerClient,
|
||||
ideService,
|
||||
logStreamInstance,
|
||||
infra.Storage,
|
||||
e.getWorkingDir(repoName),
|
||||
e.getWorkingDir(resolvedRepoDetails.RepoName),
|
||||
resolvedRepoDetails,
|
||||
infra.PortMappings,
|
||||
defaultBaseImage,
|
||||
)
|
||||
|
@ -194,17 +203,17 @@ func (e *EmbeddedDockerOrchestrator) getWorkingDir(repoName string) string {
|
|||
func (e *EmbeddedDockerOrchestrator) startGitspace(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
containerName string,
|
||||
dockerClient *client.Client,
|
||||
ideService ide.IDE,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
volumeName string,
|
||||
workingDirectory string,
|
||||
resolvedRepoDetails *scm.ResolvedDetails,
|
||||
portMappings map[int]*infraprovider.PortMapping,
|
||||
defaultBaseImage string,
|
||||
) error {
|
||||
var imageName = devcontainerConfig.Image
|
||||
var imageName = resolvedRepoDetails.DevcontainerConfig.Image
|
||||
if imageName == "" {
|
||||
imageName = defaultBaseImage
|
||||
}
|
||||
|
@ -233,12 +242,17 @@ func (e *EmbeddedDockerOrchestrator) startGitspace(
|
|||
return err
|
||||
}
|
||||
|
||||
var devcontainer = &devcontainer.Devcontainer{
|
||||
var devcontainer = &devcontainer.Exec{
|
||||
ContainerName: containerName,
|
||||
DockerClient: dockerClient,
|
||||
WorkingDir: workingDirectory,
|
||||
}
|
||||
|
||||
err = e.manageUser(ctx, devcontainer, logStreamInstance, gitspaceConfig.GitspaceInstance.AccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.setupIDE(ctx, gitspaceConfig.GitspaceInstance, devcontainer, ideService, logStreamInstance)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -249,12 +263,12 @@ func (e *EmbeddedDockerOrchestrator) startGitspace(
|
|||
return err
|
||||
}
|
||||
|
||||
err = e.cloneCode(ctx, gitspaceConfig, devcontainer, defaultBaseImage, logStreamInstance)
|
||||
err = e.cloneCode(ctx, devcontainer, defaultBaseImage, logStreamInstance, resolvedRepoDetails)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = e.executePostCreateCommand(ctx, devcontainerConfig, devcontainer, logStreamInstance)
|
||||
err = e.executePostCreateCommand(ctx, resolvedRepoDetails.DevcontainerConfig, devcontainer, logStreamInstance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -266,7 +280,7 @@ func (e *EmbeddedDockerOrchestrator) startGitspace(
|
|||
|
||||
func (e *EmbeddedDockerOrchestrator) runIDE(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
ideService ide.IDE,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
) error {
|
||||
|
@ -304,7 +318,7 @@ func (e *EmbeddedDockerOrchestrator) runIDE(
|
|||
func (e *EmbeddedDockerOrchestrator) setupIDE(
|
||||
ctx context.Context,
|
||||
gitspaceInstance *types.GitspaceInstance,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
ideService ide.IDE,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
) error {
|
||||
|
@ -366,30 +380,103 @@ func (e *EmbeddedDockerOrchestrator) getContainerInfo(
|
|||
return inspectResp.ID, usedPorts, nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) cloneCode(
|
||||
func (e *EmbeddedDockerOrchestrator) authenticateGit(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
defaultBaseImage string,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
devcontainer *devcontainer.Exec,
|
||||
resolvedRepoDetails *scm.ResolvedDetails,
|
||||
) error {
|
||||
gitCloneScript, err := template.GenerateScriptFromTemplate(
|
||||
templateCloneGit, &template.CloneGitPayload{
|
||||
RepoURL: gitspaceConfig.CodeRepoURL,
|
||||
Image: defaultBaseImage,
|
||||
Branch: gitspaceConfig.Branch,
|
||||
})
|
||||
data := &template.AuthenticateGitPayload{
|
||||
Password: resolvedRepoDetails.Credentials.Password,
|
||||
}
|
||||
gitAuthenticateScript, err := template.GenerateScriptFromTemplate(
|
||||
templateAuthenticateGit, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate scipt to clone git from template %s: %w", templateCloneGit, err)
|
||||
return fmt.Errorf("failed to generate scipt to authenticate git from template %s: %w", templateAuthenticateGit, err)
|
||||
}
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, gitAuthenticateScript, false, harnessUser)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to authenticate git in container: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) manageUser(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Exec,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
accessKey *string,
|
||||
) error {
|
||||
data := template.SetupSSHServerPayload{
|
||||
Username: "harness",
|
||||
Password: *accessKey,
|
||||
WorkingDirectory: devcontainer.WorkingDir,
|
||||
}
|
||||
manageUserScript, err := template.GenerateScriptFromTemplate(
|
||||
templateManageUser, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate scipt to manage user from template %s: %w", templateManageUser, err)
|
||||
}
|
||||
loggingErr := logStreamInstance.Write(
|
||||
"Cloning git repo inside container: " + gitspaceConfig.CodeRepoURL + " branch: " + gitspaceConfig.Branch)
|
||||
"creating user inside container: " + data.Username)
|
||||
if loggingErr != nil {
|
||||
return fmt.Errorf("logging error: %w", loggingErr)
|
||||
}
|
||||
|
||||
output, err := devcontainer.ExecuteCommand(ctx, gitCloneScript, false)
|
||||
output, err := devcontainer.ExecuteCommand(ctx, manageUserScript, false, "root")
|
||||
if err != nil {
|
||||
loggingErr = logStreamInstance.Write("Error while creating user inside container : " + err.Error())
|
||||
|
||||
err = fmt.Errorf("failed to create user: %w", err)
|
||||
|
||||
if loggingErr != nil {
|
||||
err = fmt.Errorf("original error: %w; logging error: %w", err, loggingErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
loggingErr = logStreamInstance.Write("Managing user output...\n" + string(output))
|
||||
if loggingErr != nil {
|
||||
return fmt.Errorf("logging error: %w", loggingErr)
|
||||
}
|
||||
|
||||
loggingErr = logStreamInstance.Write("Successfully created user inside container")
|
||||
if loggingErr != nil {
|
||||
return fmt.Errorf("logging error: %w", loggingErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmbeddedDockerOrchestrator) cloneCode(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Exec,
|
||||
defaultBaseImage string,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
resolvedRepoDetails *scm.ResolvedDetails,
|
||||
) error {
|
||||
data := &template.CloneGitPayload{
|
||||
RepoURL: resolvedRepoDetails.CloneURL,
|
||||
Image: defaultBaseImage,
|
||||
Branch: resolvedRepoDetails.Branch,
|
||||
}
|
||||
if resolvedRepoDetails.Credentials != nil {
|
||||
data.Password = resolvedRepoDetails.Credentials.Password
|
||||
}
|
||||
gitCloneScript, err := template.GenerateScriptFromTemplate(
|
||||
templateCloneGit, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate scipt to clone git from template %s: %w", templateCloneGit, err)
|
||||
}
|
||||
loggingErr := logStreamInstance.Write(
|
||||
"Cloning git repo inside container: " + resolvedRepoDetails.CloneURL + " branch: " + resolvedRepoDetails.Branch)
|
||||
if loggingErr != nil {
|
||||
return fmt.Errorf("logging error: %w", loggingErr)
|
||||
}
|
||||
|
||||
output, err := devcontainer.ExecuteCommand(ctx, gitCloneScript, false, harnessUser)
|
||||
if err != nil {
|
||||
loggingErr = logStreamInstance.Write("Error while cloning git repo inside container: " + err.Error())
|
||||
|
||||
|
@ -418,7 +505,7 @@ func (e *EmbeddedDockerOrchestrator) cloneCode(
|
|||
func (e *EmbeddedDockerOrchestrator) executePostCreateCommand(
|
||||
ctx context.Context,
|
||||
devcontainerConfig *types.DevcontainerConfig,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
logStreamInstance *logutil.LogStreamInstance,
|
||||
) error {
|
||||
if devcontainerConfig.PostCreateCommand == "" {
|
||||
|
@ -435,7 +522,7 @@ func (e *EmbeddedDockerOrchestrator) executePostCreateCommand(
|
|||
return fmt.Errorf("logging error: %w", loggingErr)
|
||||
}
|
||||
|
||||
output, err := devcontainer.ExecuteCommand(ctx, devcontainerConfig.PostCreateCommand, false)
|
||||
output, err := devcontainer.ExecuteCommand(ctx, devcontainerConfig.PostCreateCommand, false, harnessUser)
|
||||
if err != nil {
|
||||
loggingErr = logStreamInstance.Write("Error while executing postCreate command")
|
||||
|
||||
|
|
|
@ -23,32 +23,32 @@ import (
|
|||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type Devcontainer struct {
|
||||
type Exec struct {
|
||||
ContainerName string
|
||||
WorkingDir string
|
||||
DockerClient *client.Client
|
||||
}
|
||||
|
||||
func (d *Devcontainer) ExecuteCommand(ctx context.Context, command string, detach bool) ([]byte, error) {
|
||||
func (e *Exec) ExecuteCommand(ctx context.Context, command string, detach bool, userName string) ([]byte, error) {
|
||||
cmd := []string{"/bin/sh", "-c", command}
|
||||
|
||||
execConfig := dockerTypes.ExecConfig{
|
||||
User: "root",
|
||||
User: userName,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Cmd: cmd,
|
||||
Detach: detach,
|
||||
WorkingDir: d.WorkingDir,
|
||||
WorkingDir: e.WorkingDir,
|
||||
}
|
||||
|
||||
execID, err := d.DockerClient.ContainerExecCreate(ctx, d.ContainerName, execConfig)
|
||||
execID, err := e.DockerClient.ContainerExecCreate(ctx, e.ContainerName, execConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create docker exec for container %s: %w", d.ContainerName, err)
|
||||
return nil, fmt.Errorf("failed to create docker exec for container %s: %w", e.ContainerName, err)
|
||||
}
|
||||
|
||||
execResponse, err := d.DockerClient.ContainerExecAttach(ctx, execID.ID, dockerTypes.ExecStartCheck{Detach: detach})
|
||||
execResponse, err := e.DockerClient.ContainerExecAttach(ctx, execID.ID, dockerTypes.ExecStartCheck{Detach: detach})
|
||||
if err != nil && err.Error() != "unable to upgrade to tcp, received 200" {
|
||||
return nil, fmt.Errorf("failed to start docker exec for container %s: %w", d.ContainerName, err)
|
||||
return nil, fmt.Errorf("failed to start docker exec for container %s: %w", e.ContainerName, err)
|
||||
}
|
||||
|
||||
if execResponse.Conn != nil {
|
||||
|
|
|
@ -27,12 +27,12 @@ type IDE interface {
|
|||
// copying settings and configurations.
|
||||
Setup(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
gitspaceInstance *types.GitspaceInstance,
|
||||
) ([]byte, error)
|
||||
|
||||
// Run runs the IDE and supporting services.
|
||||
Run(ctx context.Context, devcontainer *devcontainer.Devcontainer) ([]byte, error)
|
||||
Run(ctx context.Context, devcontainer *devcontainer.Exec) ([]byte, error)
|
||||
|
||||
// Port provides the port which will be used by this IDE.
|
||||
Port() int
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Installing VSCode Web"
|
||||
|
||||
curl -fsSL https://code-server.dev/install.sh | sh
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Running VSCode Web"
|
||||
|
||||
code-server
|
|
@ -43,7 +43,7 @@ func NewVsCodeService() *VSCode {
|
|||
// Setup installs the SSH server inside the container.
|
||||
func (v *VSCode) Setup(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
gitspaceInstance *types.GitspaceInstance,
|
||||
) ([]byte, error) {
|
||||
sshServerScript, err := template.GenerateScriptFromTemplate(
|
||||
|
@ -59,7 +59,7 @@ func (v *VSCode) Setup(
|
|||
|
||||
output := "Installing ssh-server inside container\n"
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, sshServerScript, false)
|
||||
_, err = devcontainer.ExecuteCommand(ctx, sshServerScript, false, rootUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to setup SSH serverr: %w", err)
|
||||
}
|
||||
|
@ -70,10 +70,10 @@ func (v *VSCode) Setup(
|
|||
}
|
||||
|
||||
// Run runs the SSH server inside the container.
|
||||
func (v *VSCode) Run(ctx context.Context, devcontainer *devcontainer.Devcontainer) ([]byte, error) {
|
||||
func (v *VSCode) Run(ctx context.Context, devcontainer *devcontainer.Exec) ([]byte, error) {
|
||||
var output = ""
|
||||
|
||||
execOutput, err := devcontainer.ExecuteCommand(ctx, runSSHScript, false)
|
||||
execOutput, err := devcontainer.ExecuteCommand(ctx, runSSHScript, false, rootUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run SSH serverr: %w", err)
|
||||
}
|
||||
|
|
|
@ -35,8 +35,8 @@ import (
|
|||
|
||||
var _ IDE = (*VSCodeWeb)(nil)
|
||||
|
||||
//go:embed script/run_vscode_web.sh
|
||||
var runScript string
|
||||
//go:embed script/install_vscode_web.sh
|
||||
var installScript string
|
||||
|
||||
//go:embed script/find_vscode_web_path.sh
|
||||
var findPathScript string
|
||||
|
@ -44,9 +44,11 @@ var findPathScript string
|
|||
//go:embed media/vscodeweb/*
|
||||
var mediaFiles embed.FS
|
||||
|
||||
const templateInstallVSCodeWeb = "install_vscode_web.sh"
|
||||
const templateRunVSCodeWeb = "run_vscode_web.sh"
|
||||
const startMarker = "START_MARKER"
|
||||
const endMarker = "END_MARKER"
|
||||
const rootUser = "root"
|
||||
const harnessUser = "harness"
|
||||
|
||||
type VSCodeWebConfig struct {
|
||||
Port int
|
||||
|
@ -63,29 +65,17 @@ func NewVsCodeWebService(config *VSCodeWebConfig) *VSCodeWeb {
|
|||
// Setup runs the installScript which downloads the required version of the code-server binary.
|
||||
func (v *VSCodeWeb) Setup(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
_ *types.GitspaceInstance,
|
||||
) ([]byte, error) {
|
||||
installScript, err := template.GenerateScriptFromTemplate(
|
||||
templateInstallVSCodeWeb, &template.InstallVSCodeWebPayload{
|
||||
Port: strconv.Itoa(v.config.Port),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to generate scipt to install VSCode Web from template %s: %w",
|
||||
templateInstallVSCodeWeb,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
output := "Installing VSCode Web inside container.\n"
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, installScript, false)
|
||||
_, err := devcontainer.ExecuteCommand(ctx, installScript, false, rootUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to install VSCode Web: %w", err)
|
||||
}
|
||||
|
||||
findOutput, err := devcontainer.ExecuteCommand(ctx, findPathScript, false)
|
||||
findOutput, err := devcontainer.ExecuteCommand(ctx, findPathScript, false, rootUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find VSCode Web install path: %w", err)
|
||||
}
|
||||
|
@ -109,10 +99,22 @@ func (v *VSCodeWeb) Setup(
|
|||
}
|
||||
|
||||
// Run runs the code-server binary.
|
||||
func (v *VSCodeWeb) Run(ctx context.Context, devcontainer *devcontainer.Devcontainer) ([]byte, error) {
|
||||
func (v *VSCodeWeb) Run(ctx context.Context, devcontainer *devcontainer.Exec) ([]byte, error) {
|
||||
var output []byte
|
||||
|
||||
_, err := devcontainer.ExecuteCommand(ctx, runScript, true)
|
||||
runScript, err := template.GenerateScriptFromTemplate(
|
||||
templateRunVSCodeWeb, &template.RunVSCodeWebPayload{
|
||||
Port: strconv.Itoa(v.config.Port),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to generate scipt to run VSCode Web from template %s: %w",
|
||||
templateRunVSCodeWeb,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
_, err = devcontainer.ExecuteCommand(ctx, runScript, true, harnessUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run VSCode Web: %w", err)
|
||||
}
|
||||
|
@ -130,7 +132,7 @@ func (v *VSCodeWeb) Type() enum.IDEType {
|
|||
|
||||
func (v *VSCodeWeb) copyMediaToContainer(
|
||||
ctx context.Context,
|
||||
devcontainer *devcontainer.Devcontainer,
|
||||
devcontainer *devcontainer.Exec,
|
||||
path string,
|
||||
) error {
|
||||
// Create a buffer to hold the tar data
|
||||
|
|
|
@ -83,16 +83,17 @@ func (o orchestrator) StartGitspace(
|
|||
|
||||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerStart)
|
||||
|
||||
repoName, devcontainerConfig, err := o.scm.RepoNameAndDevcontainerConfig(ctx, gitspaceConfig)
|
||||
scmResolvedDetails, err := o.scm.Resolve(ctx, gitspaceConfig)
|
||||
if err != nil {
|
||||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerFailed)
|
||||
|
||||
return fmt.Errorf("failed to fetch code repo details for gitspace config ID %d", gitspaceConfig.ID)
|
||||
return fmt.Errorf("failed to fetch code repo details for gitspace config ID %w %d", err, gitspaceConfig.ID)
|
||||
}
|
||||
devcontainerConfig := scmResolvedDetails.DevcontainerConfig
|
||||
repoName := scmResolvedDetails.RepoName
|
||||
|
||||
if devcontainerConfig == nil {
|
||||
log.Warn().Err(err).Msg("devcontainer config is nil, using empty config")
|
||||
devcontainerConfig = &types.DevcontainerConfig{}
|
||||
}
|
||||
|
||||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerCompleted)
|
||||
|
@ -137,7 +138,7 @@ func (o orchestrator) StartGitspace(
|
|||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationStart)
|
||||
|
||||
startResponse, err := o.containerOrchestrator.CreateAndStartGitspace(
|
||||
ctx, gitspaceConfig, devcontainerConfig, infra, repoName, o.config.DefaultBaseImage, ideSvc)
|
||||
ctx, gitspaceConfig, infra, scmResolvedDetails, o.config.DefaultBaseImage, ideSvc)
|
||||
if err != nil {
|
||||
o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationFailed)
|
||||
|
||||
|
|
|
@ -37,9 +37,14 @@ type CloneGitPayload struct {
|
|||
RepoURL string
|
||||
Image string
|
||||
Branch string
|
||||
AuthenticateGitPayload
|
||||
}
|
||||
|
||||
type InstallVSCodeWebPayload struct {
|
||||
type AuthenticateGitPayload struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
type RunVSCodeWebPayload struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
password={{ .Password }}
|
||||
|
||||
# Create or overwrite the config file with new settings
|
||||
touch $HOME/.git-askpass
|
||||
cat > $HOME/.git-askpass <<EOF
|
||||
echo $password
|
||||
EOF
|
||||
chmod 700 $HOME/.git-askpass
|
||||
git config --global credential.helper 'cache --timeout=2592000'
|
||||
#run git operation to cache the credential in memory
|
||||
export GIT_ASKPASS=$HOME/.git-askpass
|
||||
git ls-remote
|
||||
rm $HOME/.git-askpass
|
|
@ -3,10 +3,20 @@
|
|||
repo_url={{ .RepoURL }}
|
||||
image={{ .Image }}
|
||||
branch={{ .Branch }}
|
||||
password={{ .Password }}
|
||||
|
||||
# Extract the repository name from the URL
|
||||
repo_name=$(basename -s .git "$repo_url")
|
||||
|
||||
# Create or overwrite the config file with new settings
|
||||
touch $HOME/.git-askpass
|
||||
cat > $HOME/.git-askpass <<EOF
|
||||
echo $password
|
||||
EOF
|
||||
chmod 700 $HOME/.git-askpass
|
||||
export GIT_ASKPASS=$HOME/.git-askpass
|
||||
git config --global credential.helper 'cache --timeout=2592000'
|
||||
|
||||
# Check if Git is installed
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
echo "Git is not installed. Installing Git..."
|
||||
|
@ -18,20 +28,20 @@ if ! command -v git >/dev/null 2>&1; then
|
|||
echo "Git is not installed. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git config --global --add safe.directory /$repo_name
|
||||
# Clone the repository inside the working directory if it doesn't exist
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "Cloning the repository..."
|
||||
git clone "$repo_url" --branch "$branch" .
|
||||
git clone "$repo_url" --branch "$branch" /$repo_name
|
||||
else
|
||||
echo "Repository already exists. Skipping clone."
|
||||
fi
|
||||
|
||||
rm $HOME/.git-askpass
|
||||
# Check if .devcontainer/devcontainer.json exists
|
||||
if [ ! -f ".devcontainer/devcontainer.json" ]; then
|
||||
echo "Creating .devcontainer directory and devcontainer.json..."
|
||||
mkdir -p ".devcontainer"
|
||||
cat <<EOL > ".devcontainer/devcontainer.json"
|
||||
mkdir -p /$repo_name/.devcontainer
|
||||
cat <<EOL > /$repo_name/.devcontainer/devcontainer.json
|
||||
{
|
||||
"image": "$image"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
|
||||
username={{ .Username }}
|
||||
password={{ .Password }}
|
||||
workingDir={{ .WorkingDirectory }}
|
||||
|
||||
# Check if the user already exists
|
||||
if id "$username" >/dev/null 2>&1; then
|
||||
echo "User $username already exists."
|
||||
else
|
||||
# Create a new user
|
||||
adduser --disabled-password --gecos "" "$username"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create user $username."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set or update the user's password using chpasswd
|
||||
echo "$username:$password" | chpasswd
|
||||
|
||||
# Changing ownership of everything inside user home to the newly created user
|
||||
chown -R $username $workingDir
|
||||
echo "Changing ownership of dir $workingDir to user $username."
|
|
@ -1,17 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Installing VSCode Web"
|
||||
|
||||
curl -fsSL https://code-server.dev/install.sh | sh
|
||||
echo "Running VSCode Web"
|
||||
|
||||
port={{ .Port }}
|
||||
|
||||
# Ensure the configuration directory exists
|
||||
mkdir -p /root/.config/code-server
|
||||
mkdir -p $HOME/.config/code-server
|
||||
|
||||
# Create or overwrite the config file with new settings
|
||||
cat > /root/.config/code-server/config.yaml <<EOF
|
||||
cat > $HOME/.config/code-server/config.yaml <<EOF
|
||||
bind-addr: 0.0.0.0:$port
|
||||
auth: none
|
||||
cert: false
|
||||
EOF
|
||||
EOF
|
||||
|
||||
code-server --disable-workspace-trust
|
|
@ -13,21 +13,6 @@ username={{ .Username }}
|
|||
password={{ .Password }}
|
||||
workingDir={{ .WorkingDirectory }}
|
||||
|
||||
# Check if the user already exists
|
||||
if id "$username" >/dev/null 2>&1; then
|
||||
echo "User $username already exists."
|
||||
else
|
||||
# Create a new user
|
||||
adduser --disabled-password --home "$workingDir" --gecos "" "$username"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create user $username."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set or update the user's password using chpasswd
|
||||
echo "$username:$password" | chpasswd
|
||||
|
||||
# Configure SSH to allow this user
|
||||
config_file='/etc/ssh/sshd_config'
|
||||
grep -q "^AllowUsers" $config_file
|
||||
|
@ -45,7 +30,4 @@ if ! grep -q "^PasswordAuthentication yes" $config_file; then
|
|||
echo "PasswordAuthentication yes" >> $config_file
|
||||
fi
|
||||
|
||||
# Changing ownership of everything inside user home to the newly created user
|
||||
chown -R $username .
|
||||
|
||||
mkdir /var/run/sshd
|
|
@ -26,9 +26,18 @@ import (
|
|||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
"github.com/harness/gitness/app/jwt"
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/app/token"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/git"
|
||||
"github.com/harness/gitness/git/command"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -38,23 +47,49 @@ var (
|
|||
ErrNoDefaultBranch = errors.New("no default branch")
|
||||
)
|
||||
|
||||
var _ SCM = (*scm)(nil)
|
||||
var gitspaceJWTLifetime = 720 * 24 * time.Hour
|
||||
|
||||
const defaultGitspacePATIdentifier = "Gitspace_Default"
|
||||
|
||||
var _ SCM = (*genericSCM)(nil)
|
||||
|
||||
type SCM interface {
|
||||
// RepoNameAndDevcontainerConfig fetches repository name & devcontainer config file from the given repo and branch.
|
||||
RepoNameAndDevcontainerConfig(
|
||||
Resolve(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
) (string, *types.DevcontainerConfig, error)
|
||||
) (*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 genericSCM struct {
|
||||
git git.Interface
|
||||
repoStore store.RepoStore
|
||||
tokenStore store.TokenStore
|
||||
principalStore store.PrincipalStore
|
||||
urlProvider urlprovider.Provider
|
||||
}
|
||||
|
||||
func (s scm) CheckValidCodeRepo(ctx context.Context, request CodeRepositoryRequest) (*CodeRepositoryResponse, error) {
|
||||
func NewSCM(repoStore store.RepoStore, git git.Interface,
|
||||
tokenStore store.TokenStore,
|
||||
principalStore store.PrincipalStore,
|
||||
urlProvider urlprovider.Provider) SCM {
|
||||
return &genericSCM{
|
||||
repoStore: repoStore,
|
||||
git: git,
|
||||
tokenStore: tokenStore,
|
||||
principalStore: principalStore,
|
||||
urlProvider: urlProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func (s genericSCM) CheckValidCodeRepo(
|
||||
ctx context.Context,
|
||||
request CodeRepositoryRequest,
|
||||
) (*CodeRepositoryResponse, error) {
|
||||
err := validateURL(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URL, %w", err)
|
||||
|
@ -78,27 +113,24 @@ func (s scm) CheckValidCodeRepo(ctx context.Context, request CodeRepositoryReque
|
|||
return codeRepositoryResponse, nil
|
||||
}
|
||||
|
||||
func NewSCM() SCM {
|
||||
return &scm{}
|
||||
}
|
||||
|
||||
func (s scm) RepoNameAndDevcontainerConfig(
|
||||
func (s genericSCM) Resolve(
|
||||
ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
) (string, *types.DevcontainerConfig, error) {
|
||||
) (*ResolvedDetails, error) {
|
||||
resolvedDetails := &ResolvedDetails{Branch: gitspaceConfig.Branch, CloneURL: gitspaceConfig.CodeRepoURL}
|
||||
repoURL, err := url.Parse(gitspaceConfig.CodeRepoURL)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to parse repository URL %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
return nil, fmt.Errorf("failed to parse repository URL %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
}
|
||||
repoName := strings.TrimSuffix(path.Base(repoURL.Path), ".git")
|
||||
|
||||
resolvedDetails.RepoName = repoName
|
||||
gitWorkingDirectory := "/tmp/git/"
|
||||
|
||||
cloneDir := gitWorkingDirectory + uuid.New().String()
|
||||
|
||||
err = os.MkdirAll(cloneDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error creating directory %s: %w", cloneDir, err)
|
||||
return nil, fmt.Errorf("error creating directory %s: %w", cloneDir, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
@ -109,11 +141,90 @@ func (s scm) RepoNameAndDevcontainerConfig(
|
|||
}()
|
||||
|
||||
filePath := ".devcontainer/devcontainer.json"
|
||||
err = validateArgs(gitspaceConfig)
|
||||
var catFileOutputBytes []byte
|
||||
switch gitspaceConfig.CodeRepoType { //nolint:exhaustive
|
||||
case enum.CodeRepoTypeGitness:
|
||||
repo, err := s.repoStore.FindByRef(ctx, gitspaceConfig.CodeRepoURL)
|
||||
|
||||
// Backfill clone URL
|
||||
repo.GitURL = s.urlProvider.GenerateContainerGITCloneURL(ctx, repo.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find repository: %w", err)
|
||||
}
|
||||
resolvedDetails.CloneURL = repo.GitURL
|
||||
catFileOutputBytes, err = s.getDevContainerConfigInternal(ctx, gitspaceConfig, filePath, repo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read devcontainer file : %w", err)
|
||||
}
|
||||
netrc, err := s.gitnessCredentials(ctx, repo, gitspaceConfig.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve repo credentials: %w", err)
|
||||
}
|
||||
resolvedDetails.Credentials = netrc
|
||||
default:
|
||||
catFileOutputBytes, err = s.getDevContainerConfigPublic(ctx, gitspaceConfig, cloneDir, filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(catFileOutputBytes) == 0 {
|
||||
resolvedDetails.DevcontainerConfig = &types.DevcontainerConfig{}
|
||||
return resolvedDetails, nil
|
||||
}
|
||||
sanitizedJSON := removeComments(catFileOutputBytes)
|
||||
var config *types.DevcontainerConfig
|
||||
err = json.Unmarshal(sanitizedJSON, &config)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid branch or url: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse devcontainer json: %w", err)
|
||||
}
|
||||
resolvedDetails.DevcontainerConfig = config
|
||||
return resolvedDetails, nil
|
||||
}
|
||||
|
||||
func (s genericSCM) getDevContainerConfigInternal(ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
filePath string,
|
||||
repo *types.Repository,
|
||||
) ([]byte, error) {
|
||||
// create read params once
|
||||
readParams := git.CreateReadParams(repo)
|
||||
treeNodeOutput, err := s.git.GetTreeNode(ctx, &git.GetTreeNodeParams{
|
||||
ReadParams: readParams,
|
||||
GitREF: gitspaceConfig.Branch,
|
||||
Path: filePath,
|
||||
IncludeLatestCommit: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read tree node: %w", err)
|
||||
}
|
||||
|
||||
// viewing Raw content is only supported for blob content
|
||||
if treeNodeOutput.Node.Type != git.TreeNodeTypeBlob {
|
||||
return nil, usererror.BadRequestf(
|
||||
"Object in '%s' at '/%s' is of type '%s'. Only objects of type %s support raw viewing.",
|
||||
gitspaceConfig.Branch, filePath, treeNodeOutput.Node.Type, git.TreeNodeTypeBlob)
|
||||
}
|
||||
|
||||
blobReader, err := s.git.GetBlob(ctx, &git.GetBlobParams{
|
||||
ReadParams: readParams,
|
||||
SHA: treeNodeOutput.Node.SHA,
|
||||
SizeLimit: 0, // no size limit, we stream whatever data there is
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read blob: %w", err)
|
||||
}
|
||||
catFileOutput, err := io.ReadAll(blobReader.Content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||
}
|
||||
return catFileOutput, nil
|
||||
}
|
||||
|
||||
func (s genericSCM) getDevContainerConfigPublic(ctx context.Context,
|
||||
gitspaceConfig *types.GitspaceConfig,
|
||||
cloneDir string,
|
||||
filePath string,
|
||||
) ([]byte, error) {
|
||||
log.Info().Msg("Cloning the repository...")
|
||||
cmd := command.New("clone",
|
||||
command.WithFlag("--branch", gitspaceConfig.Branch),
|
||||
|
@ -122,12 +233,8 @@ func (s scm) RepoNameAndDevcontainerConfig(
|
|||
command.WithArg(gitspaceConfig.CodeRepoURL),
|
||||
command.WithArg(cloneDir),
|
||||
)
|
||||
err = cmd.Run(
|
||||
ctx,
|
||||
command.WithDir(cloneDir),
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to clone repository %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
if err := cmd.Run(ctx, command.WithDir(cloneDir)); err != nil {
|
||||
return nil, fmt.Errorf("failed to clone repository %s: %w", gitspaceConfig.CodeRepoURL, err)
|
||||
}
|
||||
|
||||
var lsTreeOutput bytes.Buffer
|
||||
|
@ -135,19 +242,14 @@ func (s scm) RepoNameAndDevcontainerConfig(
|
|||
command.WithArg("HEAD"),
|
||||
command.WithArg(filePath),
|
||||
)
|
||||
err = lsTreeCmd.Run(
|
||||
ctx,
|
||||
command.WithDir(cloneDir),
|
||||
command.WithStdout(&lsTreeOutput),
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to list files in repository %s: %w", cloneDir, err)
|
||||
|
||||
if err := lsTreeCmd.Run(ctx, command.WithDir(cloneDir), command.WithStdout(&lsTreeOutput)); err != nil {
|
||||
return nil, fmt.Errorf("failed to list files in repository %s: %w", cloneDir, err)
|
||||
}
|
||||
|
||||
if lsTreeOutput.Len() == 0 {
|
||||
log.Info().Msg("File not found, returning empty devcontainerConfig")
|
||||
emptyConfig := &types.DevcontainerConfig{}
|
||||
return repoName, emptyConfig, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fields := strings.Fields(lsTreeOutput.String())
|
||||
|
@ -155,25 +257,16 @@ func (s scm) RepoNameAndDevcontainerConfig(
|
|||
|
||||
var catFileOutput bytes.Buffer
|
||||
catFileCmd := command.New("cat-file", command.WithFlag("-p"), command.WithArg(blobSHA))
|
||||
err = catFileCmd.Run(
|
||||
err := catFileCmd.Run(
|
||||
ctx,
|
||||
command.WithDir(cloneDir),
|
||||
command.WithStderr(io.Discard),
|
||||
command.WithStdout(&catFileOutput),
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to read devcontainer file from path %s: %w", filePath, err)
|
||||
return nil, fmt.Errorf("failed to read devcontainer file from path %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
sanitizedJSON := removeComments(catFileOutput.Bytes())
|
||||
|
||||
var config types.DevcontainerConfig
|
||||
err = json.Unmarshal(sanitizedJSON, &config)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to parse devcontainer json: %w", err)
|
||||
}
|
||||
|
||||
return repoName, &config, nil
|
||||
return catFileOutput.Bytes(), nil
|
||||
}
|
||||
|
||||
func removeComments(input []byte) []byte {
|
||||
|
@ -209,7 +302,49 @@ func validateURL(request CodeRepositoryRequest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateArgs(_ *types.GitspaceConfig) error {
|
||||
// TODO Validate the args
|
||||
return nil
|
||||
func findUserFromUID(ctx context.Context,
|
||||
principalStore store.PrincipalStore, userUID string,
|
||||
) (*types.User, error) {
|
||||
return principalStore.FindUserByUID(ctx, userUID)
|
||||
}
|
||||
|
||||
func (s genericSCM) gitnessCredentials(
|
||||
ctx context.Context,
|
||||
repo *types.Repository,
|
||||
userUID string,
|
||||
) (*Credentials, error) {
|
||||
gitspacePrincipal := bootstrap.NewGitspaceServiceSession().Principal
|
||||
user, err := findUserFromUID(ctx, s.principalStore, userUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jwtToken string
|
||||
existingToken, _ := s.tokenStore.FindByIdentifier(ctx, user.ID, defaultGitspacePATIdentifier)
|
||||
if existingToken != nil {
|
||||
// create jwt token.
|
||||
jwtToken, err = jwt.GenerateForToken(existingToken, user.ToPrincipal().Salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create JWT token: %w", err)
|
||||
}
|
||||
} else {
|
||||
_, jwtToken, err = token.CreatePAT(
|
||||
ctx,
|
||||
s.tokenStore,
|
||||
&gitspacePrincipal,
|
||||
user,
|
||||
defaultGitspacePATIdentifier,
|
||||
&gitspaceJWTLifetime)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create JWT: %w", err)
|
||||
}
|
||||
|
||||
cloneURL, err := url.Parse(repo.GitURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse clone url '%s': %w", cloneURL, err)
|
||||
}
|
||||
|
||||
return &Credentials{
|
||||
Password: jwtToken,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// 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 "github.com/harness/gitness/types"
|
||||
|
||||
type (
|
||||
ResolvedDetails struct {
|
||||
RepoName string
|
||||
DevcontainerConfig *types.DevcontainerConfig
|
||||
Credentials *Credentials
|
||||
Branch string
|
||||
CloneURL string
|
||||
}
|
||||
|
||||
// Credentials contains login and initialization information used
|
||||
// by an automated login process.
|
||||
Credentials struct {
|
||||
Password string
|
||||
}
|
||||
)
|
|
@ -14,13 +14,24 @@
|
|||
|
||||
package scm
|
||||
|
||||
import "github.com/google/wire"
|
||||
import (
|
||||
"github.com/harness/gitness/app/store"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/git"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideSCM,
|
||||
)
|
||||
|
||||
func ProvideSCM() SCM {
|
||||
return NewSCM()
|
||||
func ProvideSCM(repoStore store.RepoStore,
|
||||
rpcClient git.Interface,
|
||||
tokenStore store.TokenStore,
|
||||
principalStore store.PrincipalStore,
|
||||
urlProvider urlprovider.Provider,
|
||||
) SCM {
|
||||
return NewSCM(repoStore, rpcClient, tokenStore, principalStore, urlProvider)
|
||||
}
|
||||
|
|
|
@ -343,7 +343,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scmSCM := scm.ProvideSCM()
|
||||
scmSCM := scm.ProvideSCM(repoStore, gitInterface, tokenStore, principalStore, provider)
|
||||
infraProvisioner := infrastructure.ProvideInfraProvisionerService(infraProviderConfigStore, infraProviderResourceStore, factory)
|
||||
statefulLogger := logutil.ProvideStatefulLogger(logStream)
|
||||
containerOrchestrator := container.ProvideEmbeddedDockerOrchestrator(dockerClientFactory, statefulLogger)
|
||||
|
@ -353,7 +353,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
vsCodeWeb := ide.ProvideVSCodeWebService(vsCodeWebConfig)
|
||||
orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, infraProviderResourceStore, infraProvisioner, containerOrchestrator, reporter3, orchestratorConfig, vsCode, vsCodeWeb)
|
||||
gitspaceEventStore := database.ProvideGitspaceEventStore(db)
|
||||
gitspaceController := gitspace2.ProvideController(transactor, authorizer, infraproviderService, gitspaceConfigStore, gitspaceInstanceStore, spaceStore, reporter3, orchestratorOrchestrator, gitspaceEventStore, statefulLogger, scmSCM)
|
||||
gitspaceController := gitspace2.ProvideController(transactor, authorizer, infraproviderService, gitspaceConfigStore, gitspaceInstanceStore, spaceStore, reporter3, orchestratorOrchestrator, gitspaceEventStore, statefulLogger, scmSCM, repoStore)
|
||||
migrateController := migrate.ProvideController(authorizer, principalStore)
|
||||
openapiService := openapi.ProvideOpenAPIService()
|
||||
routerRouter := router.ProvideRouter(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, checkController, systemController, uploadController, keywordsearchController, infraproviderController, gitspaceController, migrateController, provider, openapiService)
|
||||
|
|
|
@ -253,6 +253,14 @@ type Config struct {
|
|||
DisplayName string `envconfig:"GITNESS_PRINCIPAL_PIPELINE_DISPLAY_NAME" default:"Gitness Pipeline"`
|
||||
Email string `envconfig:"GITNESS_PRINCIPAL_PIPELINE_EMAIL" default:"pipeline@gitness.io"`
|
||||
}
|
||||
|
||||
// Pipeline defines the principal information used to create the pipeline service.
|
||||
Gitspace struct {
|
||||
UID string `envconfig:"GITNESS_PRINCIPAL_GITSPACE_UID" default:"gitspace"`
|
||||
DisplayName string `envconfig:"GITNESS_PRINCIPAL_GITSPACE_DISPLAY_NAME" default:"Gitness Gitspace"`
|
||||
Email string `envconfig:"GITNESS_PRINCIPAL_GITSPACE_EMAIL" default:"gitspace@gitness.io"`
|
||||
}
|
||||
|
||||
// Admin defines the principal information used to create the admin user.
|
||||
// NOTE: The admin user is only auto-created in case a password and an email is provided.
|
||||
Admin struct {
|
||||
|
|
|
@ -19,12 +19,14 @@ type GitspaceCodeRepoType string
|
|||
func (GitspaceCodeRepoType) Enum() []interface{} { return toInterfaceSlice(codeRepoTypes) }
|
||||
|
||||
var codeRepoTypes = []GitspaceCodeRepoType{
|
||||
CodeRepoTypeGithub, CodeRepoTypeGitlab, CodeRepoTypeHarnessCode, CodeRepoTypeBitbucket, CodeRepoTypeUnknown,
|
||||
CodeRepoTypeGithub, CodeRepoTypeGitlab, CodeRepoTypeHarnessCode,
|
||||
CodeRepoTypeBitbucket, CodeRepoTypeUnknown, CodeRepoTypeGitness,
|
||||
}
|
||||
|
||||
const (
|
||||
CodeRepoTypeGithub GitspaceCodeRepoType = "github"
|
||||
CodeRepoTypeGitlab GitspaceCodeRepoType = "gitlab"
|
||||
CodeRepoTypeGitness GitspaceCodeRepoType = "gitness"
|
||||
CodeRepoTypeHarnessCode GitspaceCodeRepoType = "harness_code"
|
||||
CodeRepoTypeBitbucket GitspaceCodeRepoType = "bitbucket"
|
||||
CodeRepoTypeUnknown GitspaceCodeRepoType = "unknown"
|
||||
|
|
|
@ -27,7 +27,7 @@ type GitspaceConfig struct {
|
|||
InfraProviderResourceID int64 `json:"-"`
|
||||
InfraProviderResourceIdentifier string `json:"resource_identifier"`
|
||||
CodeRepoURL string `json:"code_repo_url"`
|
||||
CodeRepoType enum.GitspaceCodeRepoType `json:"-"`
|
||||
CodeRepoType enum.GitspaceCodeRepoType `json:"code_repo_type"`
|
||||
Branch string `json:"branch"`
|
||||
DevcontainerPath *string `json:"devcontainer_path,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
|
|
Loading…
Reference in New Issue