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 ( import (
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -32,6 +33,7 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount" "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/api/types/strslice"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
@ -304,6 +306,7 @@ func PullImage(
dockerClient *client.Client, dockerClient *client.Client,
runArgsMap map[types.RunArg]*types.RunArgValue, runArgsMap map[types.RunArg]*types.RunArgValue,
gitspaceLogger gitspaceTypes.GitspaceLogger, gitspaceLogger gitspaceTypes.GitspaceLogger,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) error { ) error {
imagePullRunArg := getImagePullPolicy(runArgsMap) imagePullRunArg := getImagePullPolicy(runArgsMap)
gitspaceLogger.Info("Image pull policy is: " + imagePullRunArg) gitspaceLogger.Info("Image pull policy is: " + imagePullRunArg)
@ -312,26 +315,28 @@ func PullImage(
} }
if imagePullRunArg == "missing" { if imagePullRunArg == "missing" {
gitspaceLogger.Info("Checking if image " + imageName + " is present locally") gitspaceLogger.Info("Checking if image " + imageName + " is present locally")
ok, err := isImagePresentLocally(ctx, imageName, dockerClient)
filterArgs := filters.NewArgs()
filterArgs.Add("reference", imageName)
images, err := dockerClient.ImageList(ctx, image.ListOptions{Filters: filterArgs})
if err != nil { if err != nil {
gitspaceLogger.Error("Error listing images locally", err) gitspaceLogger.Error("Error listing images locally", err)
return err return err
} }
if len(images) > 0 { if ok {
gitspaceLogger.Info("Image " + imageName + " is present locally") gitspaceLogger.Info("Image " + imageName + " is present locally")
return nil return nil
} }
gitspaceLogger.Info("Image " + imageName + " is not present locally") gitspaceLogger.Info("Image " + imageName + " is not present locally")
} }
gitspaceLogger.Info("Pulling image: " + imageName) 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() { defer func() {
if pullResponse == nil { if pullResponse == nil {
return return
@ -382,6 +387,40 @@ func PullImage(
return nil 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( func ExtractRunArgsWithLogging(
ctx context.Context, ctx context.Context,
spaceID int64, spaceID int64,
@ -404,7 +443,7 @@ func ExtractRunArgsWithLogging(
return runArgsMap, nil return runArgsMap, nil
} }
// getContainerResponse retrieves container information and prepares the start response. // GetContainerResponse retrieves container information and prepares the start response.
func GetContainerResponse( func GetContainerResponse(
ctx context.Context, ctx context.Context,
dockerClient *client.Client, dockerClient *client.Client,
@ -423,3 +462,12 @@ func GetContainerResponse(
AbsoluteRepoPath: codeRepoDir, AbsoluteRepoPath: codeRepoDir,
}, nil }, 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) 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 // Step 3: Check the current state of the container
state, err := e.checkContainerState(ctx, dockerClient, containerName) state, err := e.checkContainerState(ctx, dockerClient, containerName)
if err != nil { if err != nil {
@ -144,7 +147,8 @@ func (e *EmbeddedDockerOrchestrator) CreateAndStartGitspace(
resolvedRepoDetails, resolvedRepoDetails,
infra, infra,
defaultBaseImage, defaultBaseImage,
ideService); err != nil { ideService,
dockerRegistryAuth); err != nil {
return nil, err return nil, err
} }
case ContainerStatePaused, ContainerStateCreated, ContainerStateUnknown, ContainerStateDead: case ContainerStatePaused, ContainerStateCreated, ContainerStateUnknown, ContainerStateDead:
@ -369,6 +373,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
resolvedRepoDetails scm.ResolvedDetails, resolvedRepoDetails scm.ResolvedDetails,
defaultBaseImage string, defaultBaseImage string,
gitspaceLogger gitspaceTypes.GitspaceLogger, gitspaceLogger gitspaceTypes.GitspaceLogger,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) error { ) error {
homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier) homeDir := GetUserHomeDir(gitspaceConfig.GitspaceUser.Identifier)
containerName := GetGitspaceContainerName(gitspaceConfig) containerName := GetGitspaceContainerName(gitspaceConfig)
@ -386,7 +391,7 @@ func (e *EmbeddedDockerOrchestrator) runGitspaceSetupSteps(
} }
// Pull the required image // 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 return err
} }
portMappings := infrastructure.GitspacePortMappings portMappings := infrastructure.GitspacePortMappings
@ -665,6 +670,7 @@ func (e *EmbeddedDockerOrchestrator) createAndStartNewGitspace(
infrastructure types.Infrastructure, infrastructure types.Infrastructure,
defaultBaseImage string, defaultBaseImage string,
ideService ide.IDE, ideService ide.IDE,
dockerRegistryAuth gitspaceTypes.DockerRegistryAuth,
) error { ) error {
logStreamInstance, err := e.statefulLogger.CreateLogStream(ctx, gitspaceConfig.ID) logStreamInstance, err := e.statefulLogger.CreateLogStream(ctx, gitspaceConfig.ID)
if err != nil { if err != nil {
@ -681,6 +687,7 @@ func (e *EmbeddedDockerOrchestrator) createAndStartNewGitspace(
resolvedRepoDetails, resolvedRepoDetails,
defaultBaseImage, defaultBaseImage,
logStreamInstance, logStreamInstance,
dockerRegistryAuth,
) )
if startErr != nil { if startErr != nil {
return fmt.Errorf("failed to start gitspace %s: %w", gitspaceConfig.Identifier, startErr) return fmt.Errorf("failed to start gitspace %s: %w", gitspaceConfig.Identifier, startErr)

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"github.com/harness/gitness/app/gitspace/orchestrator/devcontainer" "github.com/harness/gitness/app/gitspace/orchestrator/devcontainer"
"github.com/harness/gitness/types"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
@ -38,3 +39,11 @@ type Step struct {
type ZerologAdapter struct { type ZerologAdapter struct {
logger *zerolog.Logger 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
}