feat: [CDE-508]: add image auth (#3053)

* add comment
* split pull image func into smaller parts
* split pull image func into smaller parts
* use MaskSecret type
* fix lint
* add image auth
* Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into add-image-auth
* add image auth
This commit is contained in:
Deepak Bhatt 2024-11-27 10:24:33 +00:00 committed by Harness
parent dbe74b149d
commit 3b2db60eea
3 changed files with 74 additions and 10 deletions

View File

@ -16,6 +16,7 @@ package container
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -32,6 +33,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
@ -304,6 +306,7 @@ func PullImage(
dockerClient *client.Client,
runArgsMap map[types.RunArg]*types.RunArgValue,
gitspaceLogger gitspaceTypes.GitspaceLogger,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) error {
imagePullRunArg := getImagePullPolicy(runArgsMap)
gitspaceLogger.Info("Image pull policy is: " + imagePullRunArg)
@ -312,26 +315,28 @@ func PullImage(
}
if imagePullRunArg == "missing" {
gitspaceLogger.Info("Checking if image " + imageName + " is present locally")
filterArgs := filters.NewArgs()
filterArgs.Add("reference", imageName)
images, err := dockerClient.ImageList(ctx, image.ListOptions{Filters: filterArgs})
ok, err := isImagePresentLocally(ctx, imageName, dockerClient)
if err != nil {
gitspaceLogger.Error("Error listing images locally", err)
return err
}
if len(images) > 0 {
if ok {
gitspaceLogger.Info("Image " + imageName + " is present locally")
return nil
}
gitspaceLogger.Info("Image " + imageName + " is not present locally")
}
gitspaceLogger.Info("Pulling image: " + imageName)
pullResponse, err := dockerClient.ImagePull(ctx, imageName, image.PullOptions{Platform: getPlatform(runArgsMap)})
pullOpts, err := buildImagePullOptions(getPlatform(runArgsMap), dockerRegistryAuth)
if err != nil {
return logStreamWrapError(gitspaceLogger, "Error building image pull options", err)
}
pullResponse, err := dockerClient.ImagePull(ctx, imageName, pullOpts)
defer func() {
if pullResponse == nil {
return
@ -382,6 +387,40 @@ func PullImage(
return nil
}
func isImagePresentLocally(ctx context.Context, imageName string, dockerClient *client.Client) (bool, error) {
filterArgs := filters.NewArgs()
filterArgs.Add("reference", imageName)
images, err := dockerClient.ImageList(ctx, image.ListOptions{Filters: filterArgs})
if err != nil {
return false, err
}
return len(images) > 0, nil
}
func buildImagePullOptions(
platform string,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) (image.PullOptions, error) {
pullOpts := image.PullOptions{Platform: platform}
if dockerRegistryAuth.RegistryURL != "" {
authConfig := registry.AuthConfig{
Username: dockerRegistryAuth.Username.Value(),
Password: dockerRegistryAuth.Password.Value(),
ServerAddress: dockerRegistryAuth.RegistryURL,
}
auth, err := encodeAuthToBase64(authConfig)
if err != nil {
return image.PullOptions{}, fmt.Errorf("encoding auth for docker registry: %w", err)
}
pullOpts.RegistryAuth = auth
}
return pullOpts, nil
}
func ExtractRunArgsWithLogging(
ctx context.Context,
spaceID int64,
@ -404,7 +443,7 @@ func ExtractRunArgsWithLogging(
return runArgsMap, nil
}
// getContainerResponse retrieves container information and prepares the start response.
// GetContainerResponse retrieves container information and prepares the start response.
func GetContainerResponse(
ctx context.Context,
dockerClient *client.Client,
@ -423,3 +462,12 @@ func GetContainerResponse(
AbsoluteRepoPath: codeRepoDir,
}, nil
}
// Helper function to encode the AuthConfig into a Base64 string.
func encodeAuthToBase64(authConfig registry.AuthConfig) (string, error) {
authJSON, err := json.Marshal(authConfig)
if err != nil {
return "", fmt.Errorf("encoding auth config: %w", err)
}
return base64.URLEncoding.EncodeToString(authJSON), nil
}

View File

@ -114,6 +114,9 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
}
defer e.closeDockerClient(dockerClient)
// todo : update the code when private repository integration is supported in gitness
dockerRegistryAuth := gitspaceTypes.DockerRegistryAuth{}
// Step 3: Check the current state of the container
state, err := e.checkContainerState(ctx, dockerClient, containerName)
if err != nil {
@ -144,7 +147,8 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
resolvedRepoDetails,
infra,
defaultBaseImage,
ideService); err != nil {
ideService,
dockerRegistryAuth); err != nil {
return nil, err
}
case ContainerStatePaused, ContainerStateCreated, ContainerStateUnknown, ContainerStateDead:
@ -369,6 +373,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
resolvedRepoDetails scm.ResolvedDetails,
defaultBaseImage string,
gitspaceLogger gitspaceTypes.GitspaceLogger,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) error {
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
containerName := GetGitspaceContainerName(gitspaceConfig)
@ -386,7 +391,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
}
// Pull the required image
if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger); err != nil {
if err := PullImage(ctx, imageName, dockerClient, runArgsMap, gitspaceLogger, dockerRegistryAuth); err != nil {
return err
}
portMappings := infrastructure.GitspacePortMappings
@ -665,6 +670,7 @@ func (e *EmbeddedDockerOrchestrator) createAndStartNewGitspace(
infrastructure types.Infrastructure,
defaultBaseImage string,
ideService ide.IDE,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) error {
logStreamInstance, err := e.statefulLogger.CreateLogStream(ctx, gitspaceConfig.ID)
if err != nil {
@ -681,6 +687,7 @@ func (e *EmbeddedDockerOrchestrator) createAndStartNewGitspace(
resolvedRepoDetails,
defaultBaseImage,
logStreamInstance,
dockerRegistryAuth,
)
if startErr != nil {
return fmt.Errorf("failed to start gitspace %s: %w", gitspaceConfig.Identifier, startErr)

View File

@ -18,6 +18,7 @@ import (
"context"
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
"github.com/harness/gitness/types"
"github.com/rs/zerolog"
)
@ -38,3 +39,11 @@ type Step struct {
type ZerologAdapter struct {
logger *zerolog.Logger
}
type DockerRegistryAuth struct {
// only host name is required
// eg: docker.io instead of https://docker.io
RegistryURL string
Username *types.MaskSecret
Password *types.MaskSecret
}