diff --git a/app/api/controller/gitspace/events.go b/app/api/controller/gitspace/events.go index b7134d9f6..a0a8f1553 100644 --- a/app/api/controller/gitspace/events.go +++ b/app/api/controller/gitspace/events.go @@ -90,6 +90,10 @@ func eventsMessageMapping() map[enum.GitspaceEventType]string { gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerCompleted] = "Fetched devcontainer config" gitspaceConfigsMap[enum.GitspaceEventTypeFetchDevcontainerFailed] = "Fetching devcontainer config failed" + gitspaceConfigsMap[enum.GitspaceEventTypeFetchConnectorsDetailsStart] = "Fetching connectors details..." + gitspaceConfigsMap[enum.GitspaceEventTypeFetchConnectorsDetailsCompleted] = "Fetched connectors details" + gitspaceConfigsMap[enum.GitspaceEventTypeFetchConnectorsDetailsFailed] = "Fetching connectors details failed" + gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningStart] = "Provisioning infrastructure..." gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningCompleted] = "Provisioning infrastructure completed" gitspaceConfigsMap[enum.GitspaceEventTypeInfraProvisioningFailed] = "Provisioning infrastructure failed" diff --git a/app/gitspace/orchestrator/orchestrator_resume.go b/app/gitspace/orchestrator/orchestrator_resume.go index d70055970..1e34825d8 100644 --- a/app/gitspace/orchestrator/orchestrator_resume.go +++ b/app/gitspace/orchestrator/orchestrator_resume.go @@ -135,6 +135,21 @@ func (o orchestrator) ResumeStartGitspace( o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeAgentGitspaceCreationStart) + // fetch connector information and send details to gitspace agent + gitspaceSpecs := scmResolvedDetails.DevcontainerConfig.Customizations.ExtractGitspaceSpec() + connectors, err := o.platformConnector.FetchConnectors(ctx, getConnectorIDs(gitspaceSpecs)) + if err != nil { + fetchConnectorErr := fmt.Errorf("failed to fetch connectors for gitspace: %v :%w", + getConnectorIDs(gitspaceSpecs), + err, + ) + return *gitspaceInstance, &types.GitspaceError{ + Error: fetchConnectorErr, + ErrorMessage: ptr.String(fetchConnectorErr.Error()), + } + } + gitspaceConfig.Connectors = connectors + // NOTE: Currently we use a static identifier as the Gitspace user. gitspaceConfig.GitspaceUser.Identifier = harnessUser diff --git a/app/gitspace/orchestrator/orchestrator_trigger.go b/app/gitspace/orchestrator/orchestrator_trigger.go index 2b52d2f43..0f22345eb 100644 --- a/app/gitspace/orchestrator/orchestrator_trigger.go +++ b/app/gitspace/orchestrator/orchestrator_trigger.go @@ -23,6 +23,7 @@ import ( "github.com/harness/gitness/app/gitspace/infrastructure" "github.com/harness/gitness/app/gitspace/orchestrator/container" "github.com/harness/gitness/app/gitspace/orchestrator/ide" + "github.com/harness/gitness/app/gitspace/platformconnector" "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/app/gitspace/secret" "github.com/harness/gitness/app/store" @@ -42,6 +43,7 @@ type Config struct { type orchestrator struct { scm *scm.SCM + platformConnector platformconnector.PlatformConnector infraProviderResourceStore store.InfraProviderResourceStore infraProvisioner infrastructure.InfraProvisioner containerOrchestrator container.Orchestrator @@ -56,6 +58,7 @@ var _ Orchestrator = (*orchestrator)(nil) func NewOrchestrator( scm *scm.SCM, + platformConnector platformconnector.PlatformConnector, infraProviderResourceStore store.InfraProviderResourceStore, infraProvisioner infrastructure.InfraProvisioner, containerOrchestrator container.Orchestrator, @@ -67,6 +70,7 @@ func NewOrchestrator( ) Orchestrator { return orchestrator{ scm: scm, + platformConnector: platformConnector, infraProviderResourceStore: infraProviderResourceStore, infraProvisioner: infraProvisioner, containerOrchestrator: containerOrchestrator, @@ -96,6 +100,26 @@ func (o orchestrator) TriggerStartGitspace( o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchDevcontainerCompleted) + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchConnectorsDetailsStart) + gitspaceSpecs := devcontainerConfig.Customizations.ExtractGitspaceSpec() + connectors, err := o.platformConnector.FetchConnectors(ctx, getConnectorIDs(gitspaceSpecs)) + if err != nil { + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchConnectorsDetailsFailed) + log.Ctx(ctx).Err(err).Msgf("failed to fetch connectors for gitspace: %v", + getConnectorIDs(gitspaceSpecs), + ) + return &types.GitspaceError{ + Error: fmt.Errorf("failed to fetch connectors for gitspace: %v :%w", + getConnectorIDs(gitspaceSpecs), + err, + ), + ErrorMessage: ptr.String(err.Error()), + } + } + o.emitGitspaceEvent(ctx, gitspaceConfig, enum.GitspaceEventTypeFetchConnectorsDetailsCompleted) + + gitspaceConfig.Connectors = connectors + requiredGitspacePorts, err := o.getPortsRequiredForGitspace(gitspaceConfig, devcontainerConfig) if err != nil { err = fmt.Errorf("cannot get the ports required for gitspace during start: %w", err) @@ -120,6 +144,19 @@ func (o orchestrator) TriggerStartGitspace( return nil } +func getConnectorIDs(specs *types.GitspaceCustomizationSpecs) []string { + if specs == nil { + return nil + } + + var connectorIDs []string + for _, connector := range specs.Connectors { + connectorIDs = append(connectorIDs, connector.ID) + } + + return connectorIDs +} + func (o orchestrator) TriggerStopGitspace( ctx context.Context, gitspaceConfig types.GitspaceConfig, diff --git a/app/gitspace/orchestrator/wire.go b/app/gitspace/orchestrator/wire.go index 7d580d1f1..6cdc14ad5 100644 --- a/app/gitspace/orchestrator/wire.go +++ b/app/gitspace/orchestrator/wire.go @@ -19,6 +19,7 @@ import ( "github.com/harness/gitness/app/gitspace/infrastructure" "github.com/harness/gitness/app/gitspace/orchestrator/container" "github.com/harness/gitness/app/gitspace/orchestrator/ide" + "github.com/harness/gitness/app/gitspace/platformconnector" "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/app/gitspace/secret" "github.com/harness/gitness/app/store" @@ -33,6 +34,7 @@ var WireSet = wire.NewSet( func ProvideOrchestrator( scm *scm.SCM, + platformConnector platformconnector.PlatformConnector, infraProviderResourceStore store.InfraProviderResourceStore, infraProvisioner infrastructure.InfraProvisioner, containerOrchestrator container.Orchestrator, @@ -44,6 +46,7 @@ func ProvideOrchestrator( ) Orchestrator { return NewOrchestrator( scm, + platformConnector, infraProviderResourceStore, infraProvisioner, containerOrchestrator, diff --git a/app/gitspace/platformconnector/gitnessplatformconnector.go b/app/gitspace/platformconnector/gitnessplatformconnector.go new file mode 100644 index 000000000..21930463b --- /dev/null +++ b/app/gitspace/platformconnector/gitnessplatformconnector.go @@ -0,0 +1,40 @@ +// 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 platformconnector + +import ( + "context" + + "github.com/harness/gitness/types" +) + +var _ PlatformConnector = (*GitnessPlatformConnector)(nil) + +type GitnessPlatformConnector struct{} + +func NewGitnessPlatformConnector() *GitnessPlatformConnector { + return &GitnessPlatformConnector{} +} + +func (g *GitnessPlatformConnector) FetchConnectors( + _ context.Context, + ids []string, +) ([]types.PlatformConnector, error) { + result := make([]types.PlatformConnector, len(ids)) + for i, id := range ids { + result[i] = types.PlatformConnector{ID: id} + } + return result, nil +} diff --git a/app/gitspace/platformconnector/platformconnector.go b/app/gitspace/platformconnector/platformconnector.go new file mode 100644 index 000000000..76db60dfe --- /dev/null +++ b/app/gitspace/platformconnector/platformconnector.go @@ -0,0 +1,29 @@ +// 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 platformconnector + +import ( + "context" + + "github.com/harness/gitness/types" +) + +type PlatformConnector interface { + // FetchConnectors fetches connector details from given list of connector IDs + FetchConnectors( + ctx context.Context, + connectorIDs []string, + ) ([]types.PlatformConnector, error) +} diff --git a/app/gitspace/platformconnector/wire.go b/app/gitspace/platformconnector/wire.go new file mode 100644 index 000000000..5dc03aa55 --- /dev/null +++ b/app/gitspace/platformconnector/wire.go @@ -0,0 +1,28 @@ +// 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 platformconnector + +import ( + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideGitnessPlatformConnector, +) + +func ProvideGitnessPlatformConnector() PlatformConnector { + return NewGitnessPlatformConnector() +} diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index e2dcf8777..1619cceaa 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -67,6 +67,7 @@ import ( containerGit "github.com/harness/gitness/app/gitspace/orchestrator/git" "github.com/harness/gitness/app/gitspace/orchestrator/ide" containerUser "github.com/harness/gitness/app/gitspace/orchestrator/user" + "github.com/harness/gitness/app/gitspace/platformconnector" "github.com/harness/gitness/app/gitspace/scm" gitspacesecret "github.com/harness/gitness/app/gitspace/secret" "github.com/harness/gitness/app/pipeline/canceler" @@ -256,6 +257,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e publickey.WireSet, migrate.WireSet, scm.WireSet, + platformconnector.WireSet, gitspacesecret.WireSet, orchestrator.WireSet, containerorchestrator.WireSet, diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index ea649467d..0a7210105 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -56,6 +56,7 @@ import ( git2 "github.com/harness/gitness/app/gitspace/orchestrator/git" "github.com/harness/gitness/app/gitspace/orchestrator/ide" user2 "github.com/harness/gitness/app/gitspace/orchestrator/user" + "github.com/harness/gitness/app/gitspace/platformconnector" "github.com/harness/gitness/app/gitspace/scm" "github.com/harness/gitness/app/gitspace/secret" "github.com/harness/gitness/app/pipeline/canceler" @@ -310,6 +311,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro genericSCM := scm.ProvideGenericSCM() scmFactory := scm.ProvideFactory(gitnessSCM, genericSCM) scmSCM := scm.ProvideSCM(scmFactory) + platformConnector := platformconnector.ProvideGitnessPlatformConnector() infraProvisionedStore := database.ProvideInfraProvisionedStore(db) infrastructureConfig := server.ProvideGitspaceInfraProvisionerConfig(config) infraProvisioner := infrastructure.ProvideInfraProvisionerService(infraProviderConfigStore, infraProviderResourceStore, factory, infraProviderTemplateStore, infraProvisionedStore, infrastructureConfig) @@ -324,7 +326,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro vsCodeWeb := ide.ProvideVSCodeWebService(vsCodeWebConfig) passwordResolver := secret.ProvidePasswordResolver() resolverFactory := secret.ProvideResolverFactory(passwordResolver) - orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, infraProviderResourceStore, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory) + orchestratorOrchestrator := orchestrator.ProvideOrchestrator(scmSCM, platformConnector, infraProviderResourceStore, infraProvisioner, containerOrchestrator, eventsReporter, orchestratorConfig, vsCode, vsCodeWeb, resolverFactory) gitspaceService := gitspace.ProvideGitspace(transactor, gitspaceConfigStore, gitspaceInstanceStore, eventsReporter, gitspaceEventStore, spaceStore, infraproviderService, orchestratorOrchestrator, scmSCM, config) 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, executionStore, rulesService) reporter3, err := events5.ProvideReporter(eventsSystem) diff --git a/types/devcontainer_config.go b/types/devcontainer_config.go index e666e0904..ec80e0d92 100644 --- a/types/devcontainer_config.go +++ b/types/devcontainer_config.go @@ -18,9 +18,10 @@ import "encoding/json" // DevcontainerConfig is parsed from code repos and follows the devcontainer.json spec. It uses camelCase. type DevcontainerConfig struct { - Image string `json:"image"` - PostCreateCommand string `json:"postCreateCommand"` //nolint:tagliatelle - PostStartCommand string `json:"postStartCommand"` //nolint:tagliatelle - ForwardPorts []json.Number `json:"forwardPorts"` //nolint:tagliatelle - ContainerEnv map[string]string `json:"containerEnv"` //nolint:tagliatelle + Image string `json:"image"` + PostCreateCommand string `json:"postCreateCommand"` //nolint:tagliatelle + PostStartCommand string `json:"postStartCommand"` //nolint:tagliatelle + ForwardPorts []json.Number `json:"forwardPorts"` //nolint:tagliatelle + ContainerEnv map[string]string `json:"containerEnv"` //nolint:tagliatelle + Customizations DevContainerConfigCustomizations `json:"customizations"` } diff --git a/types/devcontainer_config_customizations.go b/types/devcontainer_config_customizations.go new file mode 100644 index 000000000..35c2651e8 --- /dev/null +++ b/types/devcontainer_config_customizations.go @@ -0,0 +1,53 @@ +// 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 types + +import "encoding/json" + +const ( + GitspaceCustomizationsKey CustomizationsKey = "harnessGitspaces" +) + +type CustomizationsKey string + +func (ck CustomizationsKey) String() string { + return string(ck) +} + +type DevContainerConfigCustomizations map[string]interface{} + +func (dcc DevContainerConfigCustomizations) ExtractGitspaceSpec() *GitspaceCustomizationSpecs { + val, ok := dcc[GitspaceCustomizationsKey.String()] + if !ok { + return nil + } + + // val has underlying map[string]interface{} type as it is default for JSON objects + // converting to json so that val can be marshaled to GitspaceCustomizationSpecs type. + rawData, _ := json.Marshal(&val) + + var gitspaceSpecs GitspaceCustomizationSpecs + if err := json.Unmarshal(rawData, &gitspaceSpecs); err != nil { + return nil + } + return &gitspaceSpecs +} + +type GitspaceCustomizationSpecs struct { + Connectors []struct { + Type string `json:"type"` + ID string `json:"identifier"` + } `json:"connectors"` +} diff --git a/types/enum/gitspace_event_type.go b/types/enum/gitspace_event_type.go index 93ed25fc7..986eb1933 100644 --- a/types/enum/gitspace_event_type.go +++ b/types/enum/gitspace_event_type.go @@ -85,6 +85,11 @@ const ( GitspaceEventTypeFetchDevcontainerCompleted GitspaceEventType = "fetch_devcontainer_completed" GitspaceEventTypeFetchDevcontainerFailed GitspaceEventType = "fetch_devcontainer_failed" + // Fetch artifact registry secret. + GitspaceEventTypeFetchConnectorsDetailsStart GitspaceEventType = "fetch_connectors_details_start" + GitspaceEventTypeFetchConnectorsDetailsCompleted GitspaceEventType = "fetch_connectors_details_completed" //nolint + GitspaceEventTypeFetchConnectorsDetailsFailed GitspaceEventType = "fetch_connectors_details_failed" + // Infra provisioning events. GitspaceEventTypeInfraProvisioningStart GitspaceEventType = "infra_provisioning_start" GitspaceEventTypeInfraProvisioningCompleted GitspaceEventType = "infra_provisioning_completed" diff --git a/types/gitspace.go b/types/gitspace.go index 1deb690a7..90193c968 100644 --- a/types/gitspace.go +++ b/types/gitspace.go @@ -35,6 +35,7 @@ type GitspaceConfig struct { InfraProviderResource InfraProviderResource `json:"resource"` CodeRepo GitspaceUser + Connectors []PlatformConnector `json:"-"` } type CodeRepo struct { diff --git a/types/platform_connector.go b/types/platform_connector.go new file mode 100644 index 000000000..f01964063 --- /dev/null +++ b/types/platform_connector.go @@ -0,0 +1,136 @@ +// 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 types + +const ( + UnknownPlatformConnectorType PlatformConnectorType = "unknown" + ArtifactoryPlatformConnectorType PlatformConnectorType = "Artifactory" + DockerRegistryPlatformConnectorType PlatformConnectorType = "DockerRegistry" + + UnknownPlatformConnectorAuthType PlatformConnectorAuthType = "unknown" + UserNamePasswordPlatformConnectorAuthType PlatformConnectorAuthType = "UsernamePassword" + AnonymousPlatformConnectorAuthType PlatformConnectorAuthType = "Anonymous" +) + +var ( + platformConnectorTypeMapping = map[string]PlatformConnectorType{ + ArtifactoryPlatformConnectorType.String(): ArtifactoryPlatformConnectorType, + DockerRegistryPlatformConnectorType.String(): DockerRegistryPlatformConnectorType, + } + + platformConnectorAuthTypeMapping = map[string]PlatformConnectorAuthType{ + UserNamePasswordPlatformConnectorAuthType.String(): UserNamePasswordPlatformConnectorAuthType, + AnonymousPlatformConnectorAuthType.String(): AnonymousPlatformConnectorAuthType, + } +) + +type PlatformConnectorType string + +func (t PlatformConnectorType) String() string { return string(t) } + +func ToPlatformConnectorType(s string) PlatformConnectorType { + if val, ok := platformConnectorTypeMapping[s]; ok { + return val + } + + return UnknownPlatformConnectorType +} + +type PlatformConnectorAuthType string + +func (t PlatformConnectorAuthType) String() string { return string(t) } + +func ToPlatformConnectorAuthType(s string) PlatformConnectorAuthType { + if val, ok := platformConnectorAuthTypeMapping[s]; ok { + return val + } + + return UnknownPlatformConnectorAuthType +} + +type PlatformConnector struct { + ID string + Name string + ConnectorSpec PlatformConnectorSpec +} + +type PlatformConnectorSpec struct { + Type PlatformConnectorType + // ArtifactoryURL is for ArtifactoryPlatformConnectorType + ArtifactoryURL string + // DockerRegistryURL is for DockerRegistryPlatformConnectorType + DockerRegistryURL string + AuthSpec PlatformConnectorAuthSpec + EnabledProxy bool +} + +// PlatformConnectorAuthSpec provide auth details. +// PlatformConnectorAuthSpec is empty for AnonymousPlatformConnectorAuthType. +type PlatformConnectorAuthSpec struct { + AuthType PlatformConnectorAuthType + // userName can be empty when userName is encrypted. + UserName string + // UserNameRef can be empty when userName is not encrypted + UserNameRef string + Password string + PasswordRef string +} + +func (c PlatformConnectorSpec) ExtractRegistryURL() string { + switch c.Type { + case DockerRegistryPlatformConnectorType: + return c.DockerRegistryURL + case ArtifactoryPlatformConnectorType: + return c.ArtifactoryURL + case UnknownPlatformConnectorType: + return "" + default: + return "" + } +} + +func (c PlatformConnectorAuthSpec) ExtractUserName() string { + if c.AuthType == UserNamePasswordPlatformConnectorAuthType && + c.UserNameRef == "" { + return c.UserName + } + + return "" +} + +func (c PlatformConnectorAuthSpec) ExtractUserNameRef() string { + if c.AuthType == UserNamePasswordPlatformConnectorAuthType && + c.UserName == "" { + return c.UserNameRef + } + + return "" +} + +func (c PlatformConnectorAuthSpec) ExtractPasswordRef() string { + if c.AuthType == UserNamePasswordPlatformConnectorAuthType { + return c.PasswordRef + } + + return "" +} + +func (c PlatformConnectorAuthSpec) ExtractPassword() string { + if c.AuthType == UserNamePasswordPlatformConnectorAuthType { + return c.Password + } + + return "" +}