Commit 01562c72 authored by Tomasz Maczukin's avatar Tomasz Maczukin

Merge branch '3896-refactor-helper-image-package' into 'master'

Refactor Helper Image package to work with Kubernetes

See merge request !1306
parents 337bf41c 2fda3dcd
Pipeline #57937755 failed with stages
in 55 minutes and 43 seconds
......@@ -20,7 +20,7 @@ import (
api "k8s.io/api/core/v1"
"gitlab.com/gitlab-org/gitlab-runner/helpers"
"gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
docker_helpers "gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
"gitlab.com/gitlab-org/gitlab-runner/helpers/ssh"
"gitlab.com/gitlab-org/gitlab-runner/helpers/timeperiod"
)
......@@ -32,8 +32,6 @@ const (
PullPolicyAlways = "always"
PullPolicyNever = "never"
PullPolicyIfNotPresent = "if-not-present"
defaultHelperImage = "gitlab/gitlab-runner-helper"
)
// Get returns one of the predefined values or returns an error if the value can't match the predefined
......@@ -504,19 +502,6 @@ func (c *DockerConfig) GetOomKillDisable() *bool {
return &c.OomKillDisable
}
func (c *KubernetesConfig) GetHelperImage() string {
if len(c.HelperImage) > 0 {
return c.HelperImage
}
rev := REVISION
if rev == "HEAD" {
rev = "latest"
}
return fmt.Sprintf("%s:x86_64-%s", defaultHelperImage, rev)
}
func (c *KubernetesConfig) GetPollAttempts() int {
if c.PollTimeout <= 0 {
c.PollTimeout = KubernetesPollTimeout
......
......@@ -278,49 +278,41 @@ func (e *executor) getPrebuiltImage() (*types.ImageInspect, error) {
return e.getDockerImage(imageNameFromConfig)
}
helperImageInfo, err := helperimage.GetInfo(e.info)
helperImageInfo, err := helperimage.Get(common.REVISION, helperimage.Config{
OSType: e.info.OSType,
Architecture: e.info.Architecture,
OperatingSystem: e.info.OperatingSystem,
})
if err != nil {
return nil, err
}
revision := "latest"
if common.REVISION != "HEAD" {
revision = common.REVISION
}
tag, err := helperImageInfo.Tag(revision)
if err != nil {
return nil, err
}
// Try to find already loaded prebuilt image
imageName := fmt.Sprintf("%s:%s", prebuiltImageName, tag)
e.Debugln("Looking for prebuilt image", imageName, "...")
image, _, err := e.client.ImageInspectWithRaw(e.Context, imageName)
e.Debugln(fmt.Sprintf("Looking for prebuilt image %s...", helperImageInfo))
image, _, err := e.client.ImageInspectWithRaw(e.Context, helperImageInfo.String())
if err == nil {
return &image, nil
}
// Try to load prebuilt image from local filesystem
loadedImage := e.getLocalDockerImage(helperImageInfo, tag)
loadedImage := e.getLocalDockerImage(helperImageInfo)
if loadedImage != nil {
return loadedImage, nil
}
// Fallback to getting image from DockerHub
e.Debugln("Loading image from registry:", imageName)
return e.getDockerImage(imageName)
e.Debugln(fmt.Sprintf("Loading image form registry: %s", helperImageInfo))
return e.getDockerImage(helperImageInfo.String())
}
func (e *executor) getLocalDockerImage(helperImageInfo helperimage.Info, tag string) *types.ImageInspect {
if !helperImageInfo.IsSupportingLocalImport() {
func (e *executor) getLocalDockerImage(helperImageInfo helperimage.Info) *types.ImageInspect {
if !helperImageInfo.IsSupportingLocalImport {
return nil
}
architecture := helperImageInfo.Architecture()
architecture := helperImageInfo.Architecture
for _, dockerPrebuiltImagesPath := range DockerPrebuiltImagesPaths {
dockerPrebuiltImageFilePath := filepath.Join(dockerPrebuiltImagesPath, "prebuilt-"+architecture+prebuiltImageExtension)
image, err := e.loadPrebuiltImage(dockerPrebuiltImageFilePath, prebuiltImageName, tag)
image, err := e.loadPrebuiltImage(dockerPrebuiltImageFilePath, prebuiltImageName, helperImageInfo.Tag)
if err != nil {
e.Debugln("Failed to load prebuilt image from:", dockerPrebuiltImageFilePath, "error:", err)
continue
......
......@@ -8,19 +8,20 @@ import (
"net/url"
"strings"
"gitlab.com/gitlab-org/gitlab-terminal"
"github.com/sirupsen/logrus"
terminal "gitlab.com/gitlab-org/gitlab-terminal"
"golang.org/x/net/context"
api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
// Register all available authentication methods
_ "k8s.io/client-go/plugin/pkg/client/auth"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Register all available authentication methods
restclient "k8s.io/client-go/rest"
"gitlab.com/gitlab-org/gitlab-runner/common"
"gitlab.com/gitlab-org/gitlab-runner/executors"
"gitlab.com/gitlab-org/gitlab-runner/helpers/dns"
"gitlab.com/gitlab-org/gitlab-runner/helpers/docker/helperimage"
"gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags"
terminalsession "gitlab.com/gitlab-org/gitlab-runner/session/terminal"
)
......@@ -61,6 +62,8 @@ type executor struct {
serviceRequests api.ResourceList
helperRequests api.ResourceList
pullPolicy common.KubernetesPullPolicy
helperImageInfo helperimage.Info
}
func (s *executor) setupResources() error {
......@@ -463,7 +466,6 @@ func (s *executor) setupBuildPod() error {
}
buildImage := s.Build.GetAllVariables().ExpandValue(s.options.Image.Name)
helperImage := common.AppVersion.Variables().ExpandValue(s.Config.Kubernetes.GetHelperImage())
pod, err := s.kubeClient.CoreV1().Pods(s.configurationOverwrites.namespace).Create(&api.Pod{
ObjectMeta: metav1.ObjectMeta{
......@@ -481,7 +483,7 @@ func (s *executor) setupBuildPod() error {
Containers: append([]api.Container{
// TODO use the build and helper template here
s.buildContainer("build", buildImage, s.options.Image, s.buildRequests, s.buildLimits, s.BuildShell.DockerCommand...),
s.buildContainer("helper", helperImage, common.Image{}, s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...),
s.buildContainer("helper", s.getHelperImage(), common.Image{}, s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...),
}, services...),
TerminationGracePeriodSeconds: &s.Config.Kubernetes.TerminationGracePeriodSeconds,
ImagePullSecrets: imagePullSecrets,
......@@ -497,6 +499,14 @@ func (s *executor) setupBuildPod() error {
return nil
}
func (s *executor) getHelperImage() string {
if len(s.Config.Kubernetes.HelperImage) > 0 {
return common.AppVersion.Variables().ExpandValue(s.Config.Kubernetes.HelperImage)
}
return s.helperImageInfo.String()
}
func (s *executor) runInContainer(ctx context.Context, name string, command []string, script string) <-chan error {
errc := make(chan error, 1)
go func() {
......@@ -670,10 +680,19 @@ func (s *executor) checkDefaults() error {
}
func createFn() common.Executor {
helperImageInfo, err := helperimage.Get(common.REVISION, helperimage.Config{
OSType: helperimage.OSTypeLinux,
Architecture: "amd64",
})
if err != nil {
logrus.WithError(err).Fatal("Failed to set up helper image for kubernetes executor")
}
return &executor{
AbstractExecutor: executors.AbstractExecutor{
ExecutorOptions: executorOptions,
},
helperImageInfo: helperImageInfo,
}
}
......
......@@ -30,6 +30,7 @@ import (
"gitlab.com/gitlab-org/gitlab-runner/executors"
"gitlab.com/gitlab-org/gitlab-runner/helpers"
dns_test "gitlab.com/gitlab-org/gitlab-runner/helpers/dns/test"
"gitlab.com/gitlab-org/gitlab-runner/helpers/docker/helperimage"
"gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags"
"gitlab.com/gitlab-org/gitlab-runner/session"
)
......@@ -1460,6 +1461,12 @@ func TestSetupBuildPod(t *testing.T) {
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
helperImageInfo, err := helperimage.Get(common.REVISION, helperimage.Config{
OSType: helperimage.OSTypeLinux,
Architecture: "amd64",
})
require.NoError(t, err)
vars := test.Variables
if vars == nil {
vars = []common.JobVariable{}
......@@ -1488,13 +1495,14 @@ func TestSetupBuildPod(t *testing.T) {
Runner: &test.RunnerConfig,
},
},
helperImageInfo: helperImageInfo,
}
if test.PrepareFn != nil {
test.PrepareFn(t, test, &ex)
}
err := ex.prepareOverwrites(make(common.JobVariables, 0))
err = ex.prepareOverwrites(make(common.JobVariables, 0))
assert.NoError(t, err, "error preparing overwrites")
err = ex.setupBuildPod()
......
package helperimage
import (
"github.com/docker/docker/api/types"
"fmt"
"gitlab.com/gitlab-org/gitlab-runner/helpers/docker/errors"
)
......@@ -9,28 +9,54 @@ import (
const (
OSTypeLinux = "linux"
OSTypeWindows = "windows"
name = "gitlab/gitlab-runner-helper"
headRevision = "HEAD"
latestImageRevision = "latest"
)
// Info provides information about the helper image that can be used to
// pull from Docker Hub.
type Info interface {
Architecture() string
Tag(revision string) (string, error)
IsSupportingLocalImport() bool
type Info struct {
Architecture string
Name string
Tag string
IsSupportingLocalImport bool
}
func (i Info) String() string {
return fmt.Sprintf("%s:%s", i.Name, i.Tag)
}
type infoFactory func(info types.Info) Info
// Config specifies details about the consumer of this package that need to be
// taken in consideration when building Container.
type Config struct {
OSType string
Architecture string
OperatingSystem string
}
var supportedOsTypesFactories = map[string]infoFactory{
OSTypeWindows: newWindowsInfo,
OSTypeLinux: newLinuxInfo,
type creator interface {
Create(revision string, cfg Config) (Info, error)
}
func GetInfo(info types.Info) (Info, error) {
factory, ok := supportedOsTypesFactories[info.OSType]
var supportedOsTypesFactories = map[string]creator{
OSTypeWindows: new(windowsInfo),
OSTypeLinux: new(linuxInfo),
}
func Get(revision string, cfg Config) (Info, error) {
factory, ok := supportedOsTypesFactories[cfg.OSType]
if !ok {
return nil, errors.NewErrOSNotSupported(info.OSType)
return Info{}, errors.NewErrOSNotSupported(cfg.OSType)
}
return factory.Create(imageRevision(revision), cfg)
}
func imageRevision(revision string) string {
if revision != headRevision {
return revision
}
return factory(info), nil
return latestImageRevision
}
......@@ -3,7 +3,6 @@ package helperimage
import (
"testing"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"gitlab.com/gitlab-org/gitlab-runner/helpers/docker/errors"
......@@ -15,17 +14,48 @@ func TestGetInfo(t *testing.T) {
expectedHelperImageType interface{}
expectedError interface{}
}{
{osType: OSTypeLinux, expectedHelperImageType: &linuxInfo{}, expectedError: nil},
{osType: OSTypeWindows, expectedHelperImageType: &windowsInfo{}, expectedError: nil},
{osType: "unsupported", expectedHelperImageType: nil, expectedError: errors.NewErrOSNotSupported("unsupported")},
{osType: OSTypeLinux, expectedError: nil},
{osType: OSTypeWindows, expectedError: ErrUnsupportedOSVersion},
{osType: "unsupported", expectedError: errors.NewErrOSNotSupported("unsupported")},
}
for _, testCase := range testCases {
t.Run(testCase.osType, func(t *testing.T) {
i, err := GetInfo(types.Info{OSType: testCase.osType})
_, err := Get(headRevision, Config{OSType: testCase.osType})
assert.IsType(t, testCase.expectedHelperImageType, i)
assert.Equal(t, testCase.expectedError, err)
})
}
}
func TestContainerImage_String(t *testing.T) {
image := Info{
Name: "abc",
Tag: "1234",
}
assert.Equal(t, "abc:1234", image.String())
}
func Test_imageRevision(t *testing.T) {
testCases := []struct {
revision string
expectedTag string
}{
{
revision: headRevision,
expectedTag: latestImageRevision,
},
{
revision: "1234",
expectedTag: "1234",
},
}
for _, testCase := range testCases {
t.Run(testCase.revision, func(t *testing.T) {
assert.Equal(t, testCase.expectedTag, imageRevision(testCase.revision))
})
}
}
......@@ -3,24 +3,32 @@ package helperimage
import (
"fmt"
"runtime"
"github.com/docker/docker/api/types"
)
type linuxInfo struct {
dockerArch string
type linuxInfo struct{}
func (l *linuxInfo) Create(revision string, cfg Config) (Info, error) {
arch := l.architecture(cfg.Architecture)
return Info{
Architecture: arch,
Name: name,
Tag: fmt.Sprintf("%s-%s", arch, revision),
IsSupportingLocalImport: true,
}, nil
}
func (u *linuxInfo) Architecture() string {
switch u.dockerArch {
func (l *linuxInfo) architecture(arch string) string {
switch arch {
case "armv6l", "armv7l", "aarch64":
return "arm"
case "amd64":
return "x86_64"
}
if u.dockerArch != "" {
return u.dockerArch
if arch != "" {
return arch
}
switch runtime.GOARCH {
......@@ -30,17 +38,3 @@ func (u *linuxInfo) Architecture() string {
return runtime.GOARCH
}
}
func (u *linuxInfo) Tag(revision string) (string, error) {
return fmt.Sprintf("%s-%s", u.Architecture(), revision), nil
}
func (u *linuxInfo) IsSupportingLocalImport() bool {
return true
}
func newLinuxInfo(info types.Info) Info {
return &linuxInfo{
dockerArch: info.Architecture,
}
}
......@@ -5,45 +5,59 @@ import (
"runtime"
"testing"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
)
func Test_linuxInfo_Tag(t *testing.T) {
func Test_linuxInfo_create(t *testing.T) {
cases := []struct {
name string
dockerArch string
revision string
expectedTag string
name string
dockerArch string
revision string
expectedInfo Info
}{
{
name: "When dockerArch not specified we fallback to runtime arch",
dockerArch: "",
revision: "2923a43",
expectedTag: fmt.Sprintf("%s-2923a43", getExpectedArch()),
name: "When dockerArch not specified we fallback to runtime arch",
dockerArch: "",
revision: "2923a43",
expectedInfo: Info{
Architecture: getExpectedArch(),
Name: name,
Tag: fmt.Sprintf("%s-2923a43", getExpectedArch()),
IsSupportingLocalImport: true,
},
},
{
name: "Docker runs on armv6l",
dockerArch: "armv6l",
revision: "2923a43",
expectedTag: "arm-2923a43",
name: "Docker runs on armv6l",
dockerArch: "armv6l",
revision: "2923a43",
expectedInfo: Info{
Architecture: "arm",
Name: name,
Tag: "arm-2923a43",
IsSupportingLocalImport: true,
},
},
{
name: "Docker runs on amd64",
dockerArch: "amd64",
revision: "2923a43",
expectedTag: "x86_64-2923a43",
name: "Docker runs on amd64",
dockerArch: "amd64",
revision: "2923a43",
expectedInfo: Info{
Architecture: "x86_64",
Name: name,
Tag: "x86_64-2923a43",
IsSupportingLocalImport: true,
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
u := newLinuxInfo(types.Info{Architecture: c.dockerArch})
l := new(linuxInfo)
tag, err := u.Tag(c.revision)
image, err := l.Create(c.revision, Config{Architecture: c.dockerArch})
assert.NoError(t, err)
assert.Equal(t, c.expectedTag, tag)
assert.Equal(t, c.expectedInfo, image)
})
}
}
......@@ -58,8 +72,3 @@ func getExpectedArch() string {
return runtime.GOARCH
}
func Test_linuxInfo_IsSupportingLocalImport(t *testing.T) {
u := newLinuxInfo(types.Info{})
assert.True(t, u.IsSupportingLocalImport())
}
......@@ -4,8 +4,6 @@ import (
"errors"
"fmt"
"strings"
"github.com/docker/docker/api/types"
)
const (
......@@ -25,39 +23,29 @@ var supportedOSVersions = map[string]string{
var ErrUnsupportedOSVersion = errors.New("could not determine windows version")
type windowsInfo struct {
operatingSystem string
}
type windowsInfo struct{}
func (*windowsInfo) Architecture() string {
return windowsSupportedArchitecture
}
func (u *windowsInfo) Tag(revision string) (string, error) {
osVersion, err := u.osVersion()
func (w *windowsInfo) Create(revision string, cfg Config) (Info, error) {
osVersion, err := w.osVersion(cfg.OperatingSystem)
if err != nil {
return "", err
return Info{}, err
}
return fmt.Sprintf("%s-%s-%s", u.Architecture(), revision, osVersion), nil
return Info{
Architecture: windowsSupportedArchitecture,
Name: name,
Tag: fmt.Sprintf("%s-%s-%s", windowsSupportedArchitecture, revision, osVersion),
IsSupportingLocalImport: false,
}, nil
}
func (u *windowsInfo) osVersion() (string, error) {
for operatingSystem, osVersion := range supportedOSVersions {
if strings.Contains(u.operatingSystem, fmt.Sprintf(" %s ", operatingSystem)) {
return osVersion, nil
func (w *windowsInfo) osVersion(operatingSystem string) (string, error) {
for osVersion, baseImage := range supportedOSVersions {
if strings.Contains(operatingSystem, fmt.Sprintf(" %s ", osVersion)) {
return baseImage, nil
}
}
return "", ErrUnsupportedOSVersion
}
func (u *windowsInfo) IsSupportingLocalImport() bool {
return false
}
func newWindowsInfo(info types.Info) Info {
return &windowsInfo{
operatingSystem: info.OperatingSystem,
}
}
......@@ -4,31 +4,45 @@ import (
"fmt"
"testing"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
)
func Test_windowsInfo_Tag(t *testing.T) {
func Test_windowsInfo_create(t *testing.T) {
revision := "4011f186"
cases := []struct {
operatingSystem string
expectedVersion string
expectedInfo Info
expectedErr error
}{
{
operatingSystem: "Windows Server 2019 Datacenter Evaluation Version 1809 (OS Build 17763.316)",
expectedVersion: fmt.Sprintf("%s-%s-%s", "x86_64", revision, baseImage1809),
expectedErr: nil,
expectedInfo: Info{
Architecture: windowsSupportedArchitecture,
Name: name,
Tag: fmt.Sprintf("%s-%s-%s", "x86_64", revision, baseImage1809),
IsSupportingLocalImport: false,
},
expectedErr: nil,
},
{
operatingSystem: "Windows Server Datacenter Version 1809 (OS Build 1803.590)",
expectedVersion: fmt.Sprintf("%s-%s-%s", "x86_64", revision, baseImage1809),
expectedErr: nil,
expectedInfo: Info{
Architecture: windowsSupportedArchitecture,
Name: name,
Tag: fmt.Sprintf("%s-%s-%s", "x86_64", revision, baseImage1809),
IsSupportingLocalImport: false,
},
expectedErr: nil,
},
{
operatingSystem: "Windows Server Datacenter Version 1803 (OS Build 17134.590)",
expectedVersion: fmt.Sprintf("%s-%s-%s", "x86_64", revision, baseImage1803),
expectedErr: nil,
expectedInfo: Info{
Architecture: windowsSupportedArchitecture,
Name: name,
Tag: fmt.Sprintf("%s-%s-%s", "x86_64", revision, baseImage1803),
IsSupportingLocalImport: false,
},
expectedErr: nil,
},
{
operatingSystem: "some random string",
......@@ -38,17 +52,12 @@ func Test_windowsInfo_Tag(t *testing.T) {
for _, c := range cases {
t.Run(c.operatingSystem, func(t *testing.T) {
w := newWindowsInfo(types.Info{OperatingSystem: c.operatingSystem})
w := new(windowsInfo)
tag, err := w.Tag(revision)
image, err := w.Create(revision, Config{OperatingSystem: c.operatingSystem})
assert.Equal(t, c.expectedVersion, tag)
assert.Equal(t, c.expectedInfo, image)
assert.Equal(t, c.expectedErr, err)
})
}
}
func Test_windowsInfo_IsSupportingLocalImport(t *testing.T) {
u := newWindowsInfo(types.Info{})
assert.False(t, u.IsSupportingLocalImport())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment