github.com/nilium/gitlab-runner@v12.5.0+incompatible/commands/register_test.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/imdario/mergo"
    12  	"github.com/pkg/errors"
    13  	"github.com/sirupsen/logrus"
    14  	"github.com/sirupsen/logrus/hooks/test"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/mock"
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/urfave/cli"
    19  	"gitlab.com/ayufan/golang-cli-helpers"
    20  
    21  	"gitlab.com/gitlab-org/gitlab-runner/common"
    22  )
    23  
    24  func setupDockerRegisterCommand(dockerConfig *common.DockerConfig) *RegisterCommand {
    25  	fs := flag.NewFlagSet("", flag.ExitOnError)
    26  	ctx := cli.NewContext(cli.NewApp(), fs, nil)
    27  	fs.String("docker-image", "ruby:2.1", "")
    28  
    29  	s := &RegisterCommand{
    30  		context:        ctx,
    31  		NonInteractive: true,
    32  	}
    33  	s.Docker = dockerConfig
    34  
    35  	return s
    36  }
    37  
    38  func TestRegisterDefaultDockerCacheVolume(t *testing.T) {
    39  	s := setupDockerRegisterCommand(&common.DockerConfig{
    40  		Volumes: []string{},
    41  	})
    42  
    43  	s.askDocker()
    44  
    45  	assert.Equal(t, 1, len(s.Docker.Volumes))
    46  	assert.Equal(t, "/cache", s.Docker.Volumes[0])
    47  }
    48  
    49  func TestRegisterCustomDockerCacheVolume(t *testing.T) {
    50  	s := setupDockerRegisterCommand(&common.DockerConfig{
    51  		Volumes: []string{"/cache"},
    52  	})
    53  
    54  	s.askDocker()
    55  
    56  	assert.Equal(t, 1, len(s.Docker.Volumes))
    57  	assert.Equal(t, "/cache", s.Docker.Volumes[0])
    58  }
    59  
    60  func TestRegisterCustomMappedDockerCacheVolume(t *testing.T) {
    61  	s := setupDockerRegisterCommand(&common.DockerConfig{
    62  		Volumes: []string{"/my/cache:/cache"},
    63  	})
    64  
    65  	s.askDocker()
    66  
    67  	assert.Equal(t, 1, len(s.Docker.Volumes))
    68  	assert.Equal(t, "/my/cache:/cache", s.Docker.Volumes[0])
    69  }
    70  
    71  func getLogrusOutput(t *testing.T, hook *test.Hook) string {
    72  	buf := &bytes.Buffer{}
    73  	for _, entry := range hook.AllEntries() {
    74  		message, err := entry.String()
    75  		require.NoError(t, err)
    76  
    77  		buf.WriteString(message)
    78  	}
    79  
    80  	return buf.String()
    81  }
    82  
    83  func testRegisterCommandRun(t *testing.T, network common.Network, args ...string) (content string, output string, err error) {
    84  	hook := test.NewGlobal()
    85  
    86  	defer func() {
    87  		output = getLogrusOutput(t, hook)
    88  
    89  		if r := recover(); r != nil {
    90  			// log panics forces exit
    91  			if e, ok := r.(*logrus.Entry); ok {
    92  				err = fmt.Errorf("command error: %s", e.Message)
    93  			}
    94  		}
    95  	}()
    96  
    97  	cmd := newRegisterCommand()
    98  	cmd.network = network
    99  
   100  	app := cli.NewApp()
   101  	app.Commands = []cli.Command{
   102  		{
   103  			Name:   "register",
   104  			Action: cmd.Execute,
   105  			Flags:  clihelpers.GetFlagsFromStruct(cmd),
   106  		},
   107  	}
   108  
   109  	configFile, err := ioutil.TempFile("", "config.toml")
   110  	require.NoError(t, err)
   111  
   112  	err = configFile.Close()
   113  	require.NoError(t, err)
   114  
   115  	defer os.Remove(configFile.Name())
   116  
   117  	args = append([]string{
   118  		"binary", "register",
   119  		"-n",
   120  		"--config", configFile.Name(),
   121  		"--url", "http://gitlab.example.com/",
   122  		"--registration-token", "test-registration-token",
   123  		"--executor", "shell",
   124  	}, args...)
   125  
   126  	comandErr := app.Run(args)
   127  
   128  	fileContent, err := ioutil.ReadFile(configFile.Name())
   129  	require.NoError(t, err)
   130  
   131  	err = comandErr
   132  
   133  	return string(fileContent), "", err
   134  }
   135  
   136  func TestAccessLevelSetting(t *testing.T) {
   137  	tests := map[string]struct {
   138  		accessLevel     AccessLevel
   139  		failureExpected bool
   140  	}{
   141  		"access level not defined": {},
   142  		"ref_protected used": {
   143  			accessLevel: RefProtected,
   144  		},
   145  		"not_protected used": {
   146  			accessLevel: NotProtected,
   147  		},
   148  		"unknown access level": {
   149  			accessLevel:     AccessLevel("unknown"),
   150  			failureExpected: true,
   151  		},
   152  	}
   153  
   154  	for testName, testCase := range tests {
   155  		t.Run(testName, func(t *testing.T) {
   156  			network := new(common.MockNetwork)
   157  			defer network.AssertExpectations(t)
   158  
   159  			if !testCase.failureExpected {
   160  				parametersMocker := mock.MatchedBy(func(parameters common.RegisterRunnerParameters) bool {
   161  					return AccessLevel(parameters.AccessLevel) == testCase.accessLevel
   162  				})
   163  
   164  				network.On("RegisterRunner", mock.Anything, parametersMocker).
   165  					Return(&common.RegisterRunnerResponse{
   166  						Token: "test-runner-token",
   167  					}).
   168  					Once()
   169  			}
   170  
   171  			arguments := []string{
   172  				"--access-level", string(testCase.accessLevel),
   173  			}
   174  
   175  			_, output, err := testRegisterCommandRun(t, network, arguments...)
   176  
   177  			if testCase.failureExpected {
   178  				assert.EqualError(t, err, "command error: Given access-level is not valid. Please refer to gitlab-runner register -h for the correct options.")
   179  				assert.NotContains(t, output, "Runner registered successfully.")
   180  
   181  				return
   182  			}
   183  
   184  			assert.NoError(t, err)
   185  			assert.Contains(t, output, "Runner registered successfully.")
   186  		})
   187  	}
   188  }
   189  
   190  func TestConfigTemplate_Enabled(t *testing.T) {
   191  	tests := map[string]struct {
   192  		path          string
   193  		expectedValue bool
   194  	}{
   195  		"configuration file defined": {
   196  			path:          "/path/to/file",
   197  			expectedValue: true,
   198  		},
   199  		"configuration file not defined": {
   200  			path:          "",
   201  			expectedValue: false,
   202  		},
   203  	}
   204  
   205  	for tn, tc := range tests {
   206  		t.Run(tn, func(t *testing.T) {
   207  			configTemplate := &configTemplate{ConfigFile: tc.path}
   208  			assert.Equal(t, tc.expectedValue, configTemplate.Enabled())
   209  		})
   210  	}
   211  }
   212  
   213  func prepareConfigurationTemplateFile(t *testing.T, content string) (string, func()) {
   214  	file, err := ioutil.TempFile("", "config.template.toml")
   215  	require.NoError(t, err)
   216  
   217  	defer func() {
   218  		err = file.Close()
   219  		require.NoError(t, err)
   220  	}()
   221  
   222  	_, err = file.WriteString(content)
   223  	require.NoError(t, err)
   224  
   225  	cleanup := func() {
   226  		_ = os.Remove(file.Name())
   227  	}
   228  
   229  	return file.Name(), cleanup
   230  }
   231  
   232  var (
   233  	configTemplateMergeToInvalidConfiguration = `- , ;`
   234  
   235  	configTemplateMergeToEmptyConfiguration = ``
   236  
   237  	configTemplateMergeToTwoRunnerSectionsConfiguration = `
   238  [[runners]]
   239  [[runners]]`
   240  
   241  	configTemplateMergeToOverwritingConfiguration = `
   242  [[runners]]
   243    token = "different_token"
   244    executor = "docker"
   245    limit = 100`
   246  
   247  	configTemplateMergeToAdditionalConfiguration = `
   248  [[runners]]
   249    [runners.kubernetes]
   250      [runners.kubernetes.volumes]
   251        [[runners.kubernetes.volumes.empty_dir]]
   252          name = "empty_dir"
   253  	    mount_path = "/path/to/empty_dir"
   254  	    medium = "Memory"`
   255  
   256  	configTemplateMergeToBaseConfiguration = &common.RunnerConfig{
   257  		RunnerCredentials: common.RunnerCredentials{
   258  			Token: "test-runner-token",
   259  		},
   260  		RunnerSettings: common.RunnerSettings{
   261  			Executor: "shell",
   262  		},
   263  	}
   264  )
   265  
   266  func TestConfigTemplate_MergeTo(t *testing.T) {
   267  	tests := map[string]struct {
   268  		templateContent string
   269  		config          *common.RunnerConfig
   270  
   271  		expectedError       error
   272  		assertConfiguration func(t *testing.T, config *common.RunnerConfig)
   273  	}{
   274  		"invalid template file": {
   275  			templateContent: configTemplateMergeToInvalidConfiguration,
   276  			config:          configTemplateMergeToBaseConfiguration,
   277  			expectedError:   errors.New("couldn't load configuration template file: Near line 1 (last key parsed '-'): expected key separator '=', but got ',' instead"),
   278  		},
   279  		"no runners in template": {
   280  			templateContent: configTemplateMergeToEmptyConfiguration,
   281  			config:          configTemplateMergeToBaseConfiguration,
   282  			expectedError:   errors.New("configuration template must contain exactly one [[runners]] entry"),
   283  		},
   284  		"multiple runners in template": {
   285  			templateContent: configTemplateMergeToTwoRunnerSectionsConfiguration,
   286  			config:          configTemplateMergeToBaseConfiguration,
   287  			expectedError:   errors.New("configuration template must contain exactly one [[runners]] entry"),
   288  		},
   289  		"template doesn't overwrite existing settings": {
   290  			templateContent: configTemplateMergeToOverwritingConfiguration,
   291  			config:          configTemplateMergeToBaseConfiguration,
   292  			assertConfiguration: func(t *testing.T, config *common.RunnerConfig) {
   293  				assert.Equal(t, configTemplateMergeToBaseConfiguration.Token, config.RunnerCredentials.Token)
   294  				assert.Equal(t, configTemplateMergeToBaseConfiguration.Executor, config.RunnerSettings.Executor)
   295  				assert.Equal(t, 100, config.Limit)
   296  			},
   297  			expectedError: nil,
   298  		},
   299  		"template adds additional content": {
   300  			templateContent: configTemplateMergeToAdditionalConfiguration,
   301  			config:          configTemplateMergeToBaseConfiguration,
   302  			assertConfiguration: func(t *testing.T, config *common.RunnerConfig) {
   303  				k8s := config.RunnerSettings.Kubernetes
   304  
   305  				require.NotNil(t, k8s)
   306  				require.NotEmpty(t, k8s.Volumes.EmptyDirs)
   307  				assert.Len(t, k8s.Volumes.EmptyDirs, 1)
   308  
   309  				emptyDir := k8s.Volumes.EmptyDirs[0]
   310  				assert.Equal(t, "empty_dir", emptyDir.Name)
   311  				assert.Equal(t, "/path/to/empty_dir", emptyDir.MountPath)
   312  				assert.Equal(t, "Memory", emptyDir.Medium)
   313  			},
   314  			expectedError: nil,
   315  		},
   316  		"error on merging": {
   317  			templateContent: configTemplateMergeToAdditionalConfiguration,
   318  			expectedError:   errors.Wrap(mergo.ErrNotSupported, "error while merging configuration with configuration template"),
   319  		},
   320  	}
   321  
   322  	for tn, tc := range tests {
   323  		t.Run(tn, func(t *testing.T) {
   324  			file, cleanup := prepareConfigurationTemplateFile(t, tc.templateContent)
   325  			defer cleanup()
   326  
   327  			configTemplate := &configTemplate{ConfigFile: file}
   328  			err := configTemplate.MergeTo(tc.config)
   329  
   330  			if tc.expectedError != nil {
   331  				assert.EqualError(t, err, tc.expectedError.Error())
   332  
   333  				return
   334  			}
   335  
   336  			assert.NoError(t, err)
   337  			tc.assertConfiguration(t, tc.config)
   338  		})
   339  	}
   340  }