diff --git a/infraprovider/client.go b/infraprovider/client.go new file mode 100644 index 0000000000..355dc4f528 --- /dev/null +++ b/infraprovider/client.go @@ -0,0 +1,22 @@ +// 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 infraprovider + +import "context" + +type Client interface { + // Close closes the underlying client. + Close(ctx context.Context) +} diff --git a/infraprovider/docker_client.go b/infraprovider/docker_client.go new file mode 100644 index 0000000000..e169ca3bc1 --- /dev/null +++ b/infraprovider/docker_client.go @@ -0,0 +1,32 @@ +// 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 infraprovider + +import ( + "context" + + "github.com/docker/docker/client" +) + +var _ Client = (*DockerClient)(nil) + +type DockerClient struct { + dockerClient *client.Client + closeFunc func(ctx context.Context) +} + +func (d DockerClient) Close(ctx context.Context) { + d.closeFunc(ctx) +} diff --git a/infraprovider/docker_provider.go b/infraprovider/docker_provider.go new file mode 100644 index 0000000000..7a440ab585 --- /dev/null +++ b/infraprovider/docker_provider.go @@ -0,0 +1,172 @@ +// 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 infraprovider + +import ( + "context" + "fmt" + "io" + "net/http" + "path/filepath" + + "github.com/harness/gitness/infraprovider/enum" + + "github.com/docker/docker/client" + "github.com/docker/go-connections/tlsconfig" + "github.com/rs/zerolog/log" +) + +var _ InfraProvider = (*dockerProvider)(nil) + +type Config struct { + DockerHost string + DockerAPIVersion string + DockerCertPath string + DockerTLSVerify string +} + +type dockerProvider struct { + config *Config +} + +func NewDockerProvider(config *Config) InfraProvider { + return &dockerProvider{ + config: config, + } +} + +// Provision assumes a docker engine is already running on the Gitness host machine and re-uses that as infra. +// It does not start docker engine. +func (d dockerProvider) Provision(ctx context.Context, _ string, params []Parameter) (Infrastructure, error) { + dockerClient, closeFunc, err := d.getClient(params) + if err != nil { + return Infrastructure{}, err + } + defer closeFunc(ctx) + info, err := dockerClient.Info(ctx) + if err != nil { + return Infrastructure{}, fmt.Errorf("unable to connect to docker engine: %w", err) + } + return Infrastructure{ + Identifier: info.ID, + ProviderType: enum.InfraProviderTypeDocker, + Status: enum.InfraStatusProvisioned, + }, nil +} + +func (d dockerProvider) Find(_ context.Context, _ string, _ []Parameter) (Infrastructure, error) { + // TODO implement me + panic("implement me") +} + +// Stop is NOOP as this provider uses already running docker engine. It does not stop the docker engine. +func (d dockerProvider) Stop(_ context.Context, infra Infrastructure) (Infrastructure, error) { + return infra, nil +} + +// Destroy is NOOP as this provider uses already running docker engine. It does not stop the docker engine. +func (d dockerProvider) Destroy(_ context.Context, infra Infrastructure) (Infrastructure, error) { + return infra, nil +} + +func (d dockerProvider) Status(_ context.Context, _ Infrastructure) (enum.InfraStatus, error) { + // TODO implement me + panic("implement me") +} + +// AvailableParams returns empty slice as no params are defined. +func (d dockerProvider) AvailableParams() []ParameterSchema { + return []ParameterSchema{} +} + +// ValidateParams returns nil as no params are defined. +func (d dockerProvider) ValidateParams(_ []Parameter) error { + return nil +} + +// TemplateParams returns nil as no template params are used. +func (d dockerProvider) TemplateParams() []ParameterSchema { + return nil +} + +// ProvisioningType returns existing as docker provider doesn't create new resources. +func (d dockerProvider) ProvisioningType() enum.InfraProvisioningType { + return enum.InfraProvisioningTypeExisting +} + +func (d dockerProvider) Exec(_ context.Context, _ Infrastructure, _ []string) (io.Reader, io.Reader, error) { + // TODO implement me + panic("implement me") +} + +// Client returns a new docker client created using params. +func (d dockerProvider) Client(_ context.Context, infra Infrastructure) (Client, error) { + dockerClient, closeFunc, err := d.getClient(infra.Parameters) + if err != nil { + return nil, err + } + return &DockerClient{ + dockerClient: dockerClient, + closeFunc: closeFunc, + }, nil +} + +// getClient returns a new docker client created using values from gitness docker config. +func (d dockerProvider) getClient(_ []Parameter) (*client.Client, func(context.Context), error) { + var opts []client.Opt + + opts = append(opts, client.WithHost(d.config.DockerHost)) + + opts = append(opts, client.WithVersion(d.config.DockerAPIVersion)) + + if d.config.DockerCertPath != "" { + httpsClient, err := d.getHTTPSClient() + if err != nil { + return nil, nil, fmt.Errorf("unable to create https client for docker client: %w", err) + } + opts = append(opts, client.WithHTTPClient(httpsClient)) + } + + dockerClient, err := client.NewClientWithOpts(opts...) + if err != nil { + return nil, nil, fmt.Errorf("unable to create docker client: %w", err) + } + + closeFunc := func(ctx context.Context) { + closingErr := dockerClient.Close() + if closingErr != nil { + log.Ctx(ctx).Warn().Err(closingErr).Msg("failed to close docker client") + } + } + + return dockerClient, closeFunc, nil +} + +func (d dockerProvider) getHTTPSClient() (*http.Client, error) { + options := tlsconfig.Options{ + CAFile: filepath.Join(d.config.DockerCertPath, "ca.pem"), + CertFile: filepath.Join(d.config.DockerCertPath, "cert.pem"), + KeyFile: filepath.Join(d.config.DockerCertPath, "key.pem"), + InsecureSkipVerify: d.config.DockerTLSVerify == "", + } + tlsc, err := tlsconfig.Client(options) + if err != nil { + return nil, err + } + return &http.Client{ + Transport: &http.Transport{TLSClientConfig: tlsc}, + CheckRedirect: client.CheckRedirect, + }, nil +} diff --git a/infraprovider/enum/provider_type.go b/infraprovider/enum/provider_type.go index 27c140bca7..79439d364d 100644 --- a/infraprovider/enum/provider_type.go +++ b/infraprovider/enum/provider_type.go @@ -14,14 +14,14 @@ package enum -type ProviderType string +type InfraProviderType string -func (ProviderType) Enum() []interface{} { return toInterfaceSlice(providerTypes) } +func (InfraProviderType) Enum() []interface{} { return toInterfaceSlice(providerTypes) } -var providerTypes = []ProviderType{ - ProviderTypeDocker, +var providerTypes = []InfraProviderType{ + InfraProviderTypeDocker, } const ( - ProviderTypeDocker ProviderType = "docker" + InfraProviderTypeDocker InfraProviderType = "docker" ) diff --git a/infraprovider/enum/provisioning_type.go b/infraprovider/enum/provisioning_type.go new file mode 100644 index 0000000000..be7ca81841 --- /dev/null +++ b/infraprovider/enum/provisioning_type.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 enum + +type InfraProvisioningType string + +func (InfraProvisioningType) Enum() []interface{} { return toInterfaceSlice(provisioningTypes) } + +var provisioningTypes = []InfraProvisioningType{ + InfraProvisioningTypeExisting, InfraProvisioningTypeNew, +} + +const ( + InfraProvisioningTypeExisting InfraProvisioningType = "existing" + InfraProvisioningTypeNew InfraProvisioningType = "new" +) diff --git a/infraprovider/infra_provider.go b/infraprovider/infra_provider.go index bd8f8a5711..69673e421f 100644 --- a/infraprovider/infra_provider.go +++ b/infraprovider/infra_provider.go @@ -36,6 +36,13 @@ type InfraProvider interface { AvailableParams() []ParameterSchema // ValidateParams validates the supplied params before defining the infrastructure resource . ValidateParams(parameters []Parameter) error + // TemplateParams provides a list of params which are of type template. + TemplateParams() []ParameterSchema + // ProvisioningType specifies whether the provider will provision new infra resources or it will reuse existing. + ProvisioningType() enum.InfraProvisioningType // Exec executes a shell command in the infrastructure. Exec(ctx context.Context, infra Infrastructure, cmd []string) (io.Reader, io.Reader, error) + // Client returns a client which can be used to connect the provided infra. + // The responsibility of calling the close func lies with the user. + Client(ctx context.Context, infra Infrastructure) (Client, error) } diff --git a/infraprovider/types.go b/infraprovider/types.go index c9fe40ca3e..6dabb8bcf2 100644 --- a/infraprovider/types.go +++ b/infraprovider/types.go @@ -33,7 +33,7 @@ type Parameter struct { type Infrastructure struct { Identifier string ResourceKey string - ProviderType enum.ProviderType + ProviderType enum.InfraProviderType Parameters []Parameter Status enum.InfraStatus Host string diff --git a/infraprovider/wire.go b/infraprovider/wire.go new file mode 100644 index 0000000000..d7e65faf95 --- /dev/null +++ b/infraprovider/wire.go @@ -0,0 +1,36 @@ +// 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 infraprovider + +import ( + "github.com/harness/gitness/types" + + "github.com/google/wire" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideDockerProvider, +) + +func ProvideDockerProvider(config *types.Config) InfraProvider { + dockerConfig := Config{ + DockerHost: config.Docker.Host, + DockerAPIVersion: config.Docker.APIVersion, + DockerCertPath: config.Docker.CertPath, + DockerTLSVerify: config.Docker.TLSVerify, + } + return NewDockerProvider(&dockerConfig) +} diff --git a/types/config.go b/types/config.go index c2f0444ce9..1e88c9763b 100644 --- a/types/config.go +++ b/types/config.go @@ -375,4 +375,15 @@ type Config struct { // DeletedRetentionTime is the duration after which deleted repositories will be purged. DeletedRetentionTime time.Duration `envconfig:"GITNESS_REPOS_DELETED_RETENTION_TIME" default:"2160h"` // 90 days } + + Docker struct { + // Host sets the url to the docker server. + Host string `envconfig:"GITNESS_DOCKER_HOST"` + // APIVersion sets the version of the API to reach, leave empty for latest. + APIVersion string `envconfig:"GITNESS_DOCKER_API_VERSION"` + // CertPath sets the path to load the TLS certificates from. + CertPath string `envconfig:"GITNESS_DOCKER_CERT_PATH"` + // TLSVerify enables or disables TLS verification, off by default. + TLSVerify string `envconfig:"GITNESS_DOCKER_TLS_VERIFY"` + } }