#3 Fix custom auth override on Jenkins restart inside pod

This commit is contained in:
Tomasz Sęk 2019-02-21 23:56:13 +01:00
parent 3b8f0f7d10
commit b715f82557
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
15 changed files with 258 additions and 91 deletions

View File

@ -6,7 +6,10 @@ By default **jenkins-operator** performs an initial security hardening of Jenkin
Currently **jenkins-operator** generates a username and random password and stores them in a Kubernetes Secret. Currently **jenkins-operator** generates a username and random password and stores them in a Kubernetes Secret.
However any other authorization mechanisms are possible and can be done via groovy scripts or configuration as code plugin. However any other authorization mechanisms are possible and can be done via groovy scripts or configuration as code plugin.
For more information take a look at [getting-started#jenkins-customization](getting-started.md#jenkins-customisation). For more information take a look at [getting-started#jenkins-customization](getting-started.md#jenkins-customisation).
Any change to Security Realm or Authorization requires that user called `jenkins-operator` must have admin rights
because **jenkins-operator** calls Jenkins API.
## Jenkins Hardening ## Jenkins Hardening

19
internal/errors/format.go Normal file
View File

@ -0,0 +1,19 @@
package errors
import (
"fmt"
"io"
"github.com/pkg/errors"
)
// Format helps to implement fmt.Formatter used by Sprint(f) or Fprint(f) etc.
func Format(err error, s fmt.State, verb rune) {
formatter, ok := errors.WithStack(err).(fmt.Formatter)
if !ok {
// should never occur if the error was wrapped properly
panic(errors.New("this was unexpected, merged error is not fmt.Formatter"))
}
_, _ = io.WriteString(s, err.Error())
formatter.Format(s, verb)
}

13
internal/time/time.go Normal file
View File

@ -0,0 +1,13 @@
package time
import "time"
// Every will send the time with a period specified by the duration argument.
// It id equivalent to time.NewTicker(d).C
// It adjusts the intervals or drops ticks to make up for slow receivers.
// The duration d must be greater than zero; if not, NewTicker will panic.
// If efficiency is a concern, use NewTicker and call Ticker.Stop
// if the ticker is no longer needed.
func Every(d time.Duration) <-chan time.Time {
return time.NewTicker(d).C
}

53
internal/try/until.go Normal file
View File

@ -0,0 +1,53 @@
package try
import (
"fmt"
"time"
"github.com/jenkinsci/kubernetes-operator/internal/errors"
time2 "github.com/jenkinsci/kubernetes-operator/internal/time"
)
// ErrTimeout is used when the set timeout has been reached
type ErrTimeout struct {
text string
cause error
}
func (e *ErrTimeout) Error() string {
return fmt.Sprintf("%s: %s", e.text, e.cause.Error())
}
// Cause returns the error that caused ErrTimeout
func (e *ErrTimeout) Cause() error {
return e.cause
}
// Format implements fmt.Formatter used by Sprint(f) or Fprint(f) etc.
func (e *ErrTimeout) Format(s fmt.State, verb rune) {
errors.Format(e.cause, s, verb)
}
// Until keeps trying until timeout or there is a result or an error
func Until(something func() (end bool, err error), tick, timeout time.Duration) error {
counter := 0
tickChan := time2.Every(tick)
timeoutChan := time.After(timeout)
var lastErr error
for {
select {
case <-tickChan:
end, err := something()
lastErr = err
if end {
return err
}
counter = counter + 1
case <-timeoutChan:
return &ErrTimeout{
text: fmt.Sprintf("timed out after: %s, tries: %d", timeout, counter),
cause: lastErr,
}
}
}
}

View File

@ -51,7 +51,7 @@ type Jenkins interface {
GetAllViews() ([]*gojenkins.View, error) GetAllViews() ([]*gojenkins.View, error)
CreateView(name string, viewType string) (*gojenkins.View, error) CreateView(name string, viewType string) (*gojenkins.View, error)
Poll() (int, error) Poll() (int, error)
ExecuteScript(groovyScript string) (output string, err error) ExecuteScript(groovyScript string) (logs string, err error)
} }
type jenkins struct { type jenkins struct {

View File

@ -237,7 +237,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createBaseConfigurationConfigMap(met
func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationConfigMap(meta metav1.ObjectMeta) error { func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationConfigMap(meta metav1.ObjectMeta) error {
currentConfigMap := &corev1.ConfigMap{} currentConfigMap := &corev1.ConfigMap{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: resources.GetUserConfigurationConfigMapName(r.jenkins), Namespace: r.jenkins.Namespace}, currentConfigMap) err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: resources.GetUserConfigurationConfigMapNameFromJenkins(r.jenkins), Namespace: r.jenkins.Namespace}, currentConfigMap)
if err != nil && errors.IsNotFound(err) { if err != nil && errors.IsNotFound(err) {
return stackerr.WithStack(r.k8sClient.Create(context.TODO(), resources.NewUserConfigurationConfigMap(r.jenkins))) return stackerr.WithStack(r.k8sClient.Create(context.TODO(), resources.NewUserConfigurationConfigMap(r.jenkins)))
} else if err != nil { } else if err != nil {

View File

@ -17,28 +17,35 @@ var createOperatorUserGroovyFmtTemplate = template.Must(template.New(createOpera
import hudson.security.* import hudson.security.*
def jenkins = jenkins.model.Jenkins.getInstance() def jenkins = jenkins.model.Jenkins.getInstance()
def operatorUserCreatedFile = new File('{{ .OperatorUserCreatedFilePath }}')
def hudsonRealm = new HudsonPrivateSecurityRealm(false) if (!operatorUserCreatedFile.exists()) {
hudsonRealm.createAccount( def hudsonRealm = new HudsonPrivateSecurityRealm(false)
new File('{{ .OperatorCredentialsPath }}/{{ .OperatorUserNameFile }}').text, hudsonRealm.createAccount(
new File('{{ .OperatorCredentialsPath }}/{{ .OperatorPasswordFile }}').text) new File('{{ .OperatorCredentialsPath }}/{{ .OperatorUserNameFile }}').text,
jenkins.setSecurityRealm(hudsonRealm) new File('{{ .OperatorCredentialsPath }}/{{ .OperatorPasswordFile }}').text)
jenkins.setSecurityRealm(hudsonRealm)
def strategy = new FullControlOnceLoggedInAuthorizationStrategy() def strategy = new FullControlOnceLoggedInAuthorizationStrategy()
strategy.setAllowAnonymousRead(false) strategy.setAllowAnonymousRead(false)
jenkins.setAuthorizationStrategy(strategy) jenkins.setAuthorizationStrategy(strategy)
jenkins.save() jenkins.save()
operatorUserCreatedFile.createNewFile()
}
`)) `))
func buildCreateJenkinsOperatorUserGroovyScript() (*string, error) { func buildCreateJenkinsOperatorUserGroovyScript() (*string, error) {
data := struct { data := struct {
OperatorCredentialsPath string OperatorCredentialsPath string
OperatorUserNameFile string OperatorUserNameFile string
OperatorPasswordFile string OperatorPasswordFile string
OperatorUserCreatedFilePath string
}{ }{
OperatorCredentialsPath: jenkinsOperatorCredentialsVolumePath, OperatorCredentialsPath: jenkinsOperatorCredentialsVolumePath,
OperatorUserNameFile: OperatorCredentialsSecretUserNameKey, OperatorUserNameFile: OperatorCredentialsSecretUserNameKey,
OperatorPasswordFile: OperatorCredentialsSecretPasswordKey, OperatorPasswordFile: OperatorCredentialsSecretPasswordKey,
OperatorUserCreatedFilePath: jenkinsHomePath + "/operatorUserCreated",
} }
output, err := render(createOperatorUserGroovyFmtTemplate, data) output, err := render(createOperatorUserGroovyFmtTemplate, data)

View File

@ -2,6 +2,7 @@ package resources
import ( import (
"fmt" "fmt"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -11,27 +12,28 @@ import (
const ( const (
jenkinsHomeVolumeName = "home" jenkinsHomeVolumeName = "home"
jenkinsHomePath = "/var/jenkins/home" jenkinsPath = "/var/jenkins"
jenkinsHomePath = jenkinsPath + "/home"
jenkinsScriptsVolumeName = "scripts" jenkinsScriptsVolumeName = "scripts"
jenkinsScriptsVolumePath = "/var/jenkins/scripts" jenkinsScriptsVolumePath = jenkinsPath + "/scripts"
initScriptName = "init.sh" initScriptName = "init.sh"
jenkinsOperatorCredentialsVolumeName = "operator-credentials" jenkinsOperatorCredentialsVolumeName = "operator-credentials"
jenkinsOperatorCredentialsVolumePath = "/var/jenkins/operator-credentials" jenkinsOperatorCredentialsVolumePath = jenkinsPath + "/operator-credentials"
jenkinsInitConfigurationVolumeName = "init-configuration" jenkinsInitConfigurationVolumeName = "init-configuration"
jenkinsInitConfigurationVolumePath = "/var/jenkins/init-configuration" jenkinsInitConfigurationVolumePath = jenkinsPath + "/init-configuration"
jenkinsBaseConfigurationVolumeName = "base-configuration" jenkinsBaseConfigurationVolumeName = "base-configuration"
// JenkinsBaseConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins // JenkinsBaseConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins
// this scripts are provided by jenkins-operator // this scripts are provided by jenkins-operator
JenkinsBaseConfigurationVolumePath = "/var/jenkins/base-configuration" JenkinsBaseConfigurationVolumePath = jenkinsPath + "/base-configuration"
jenkinsUserConfigurationVolumeName = "user-configuration" jenkinsUserConfigurationVolumeName = "user-configuration"
// JenkinsUserConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins // JenkinsUserConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins
// this scripts are provided by user // this scripts are provided by user
JenkinsUserConfigurationVolumePath = "/var/jenkins/user-configuration" JenkinsUserConfigurationVolumePath = jenkinsPath + "/user-configuration"
httpPortName = "http" httpPortName = "http"
slavePortName = "slavelistener" slavePortName = "slavelistener"
@ -197,7 +199,7 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins
VolumeSource: corev1.VolumeSource{ VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{ LocalObjectReference: corev1.LocalObjectReference{
Name: GetUserConfigurationConfigMapName(jenkins), Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins),
}, },
}, },
}, },

View File

@ -32,15 +32,20 @@ decorator.save();
jenkins.save() jenkins.save()
` `
// GetUserConfigurationConfigMapName returns name of Kubernetes config map used to user configuration // GetUserConfigurationConfigMapNameFromJenkins returns name of Kubernetes config map used to user configuration
func GetUserConfigurationConfigMapName(jenkins *v1alpha1.Jenkins) string { func GetUserConfigurationConfigMapNameFromJenkins(jenkins *v1alpha1.Jenkins) string {
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name) return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
} }
// GetUserConfigurationConfigMapName returns name of Kubernetes config map used to user configuration
func GetUserConfigurationConfigMapName(jenkinsCRName string) string {
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkinsCRName)
}
// NewUserConfigurationConfigMap builds Kubernetes config map used to user configuration // NewUserConfigurationConfigMap builds Kubernetes config map used to user configuration
func NewUserConfigurationConfigMap(jenkins *v1alpha1.Jenkins) *corev1.ConfigMap { func NewUserConfigurationConfigMap(jenkins *v1alpha1.Jenkins) *corev1.ConfigMap {
meta := metav1.ObjectMeta{ meta := metav1.ObjectMeta{
Name: GetUserConfigurationConfigMapName(jenkins), Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins),
Namespace: jenkins.ObjectMeta.Namespace, Namespace: jenkins.ObjectMeta.Namespace,
Labels: BuildLabelsForWatchedResources(jenkins), Labels: BuildLabelsForWatchedResources(jenkins),
} }

View File

@ -92,7 +92,7 @@ func (r *ReconcileUserConfiguration) ensureUserConfiguration(jenkinsClient jenki
} }
configuration := &corev1.ConfigMap{} configuration := &corev1.ConfigMap{}
namespaceName := types.NamespacedName{Namespace: r.jenkins.Namespace, Name: resources.GetUserConfigurationConfigMapName(r.jenkins)} namespaceName := types.NamespacedName{Namespace: r.jenkins.Namespace, Name: resources.GetUserConfigurationConfigMapNameFromJenkins(r.jenkins)}
err = r.k8sClient.Get(context.TODO(), namespaceName, configuration) err = r.k8sClient.Get(context.TODO(), namespaceName, configuration)
if err != nil { if err != nil {
return reconcile.Result{}, errors.WithStack(err) return reconcile.Result{}, errors.WithStack(err)

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
@ -27,7 +28,7 @@ func TestConfiguration(t *testing.T) {
defer ctx.Cleanup() defer ctx.Cleanup()
// base // base
jenkins := createJenkinsCR(t, namespace) jenkins := createJenkinsCR(t, "e2e", namespace)
createDefaultLimitsForContainersInNamespace(t, namespace) createDefaultLimitsForContainersInNamespace(t, namespace)
waitForJenkinsBaseConfigurationToComplete(t, jenkins) waitForJenkinsBaseConfigurationToComplete(t, jenkins)
@ -90,7 +91,7 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha1.Jenkins) {
t.Log("Jenkins pod attributes are valid") t.Log("Jenkins pod attributes are valid")
} }
func verifyPlugins(t *testing.T, jenkinsClient *gojenkins.Jenkins, jenkins *v1alpha1.Jenkins) { func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) {
installedPlugins, err := jenkinsClient.GetPlugins(1) installedPlugins, err := jenkinsClient.GetPlugins(1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -134,7 +135,7 @@ func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (*
return p, requiredPlugin.Version == p.Version return p, requiredPlugin.Version == p.Version
} }
func verifyJenkinsSeedJobs(t *testing.T, client *gojenkins.Jenkins, jenkins *v1alpha1.Jenkins) { func verifyJenkinsSeedJobs(t *testing.T, client jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) {
t.Logf("Attempting to get configure seed job status '%v'", seedjobs.ConfigureSeedJobsName) t.Logf("Attempting to get configure seed job status '%v'", seedjobs.ConfigureSeedJobsName)
configureSeedJobs, err := client.GetJob(seedjobs.ConfigureSeedJobsName) configureSeedJobs, err := client.GetJob(seedjobs.ConfigureSeedJobsName)

View File

@ -2,15 +2,12 @@ package e2e
import ( import (
"context" "context"
"fmt"
"net/http"
"testing" "testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
"github.com/bndr/gojenkins"
framework "github.com/operator-framework/operator-sdk/pkg/test" framework "github.com/operator-framework/operator-sdk/pkg/test"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -42,7 +39,7 @@ func getJenkinsMasterPod(t *testing.T, jenkins *v1alpha1.Jenkins) *v1.Pod {
return &podList.Items[0] return &podList.Items[0]
} }
func createJenkinsAPIClient(jenkins *v1alpha1.Jenkins) (*gojenkins.Jenkins, error) { func createJenkinsAPIClient(jenkins *v1alpha1.Jenkins) (jenkinsclient.Jenkins, error) {
adminSecret := &v1.Secret{} adminSecret := &v1.Secret{}
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetOperatorCredentialsSecretName(jenkins)} namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetOperatorCredentialsSecretName(jenkins)}
if err := framework.Global.Client.Get(context.TODO(), namespaceName, adminSecret); err != nil { if err := framework.Global.Client.Get(context.TODO(), namespaceName, adminSecret); err != nil {
@ -54,31 +51,17 @@ func createJenkinsAPIClient(jenkins *v1alpha1.Jenkins) (*gojenkins.Jenkins, erro
return nil, err return nil, err
} }
jenkinsClient := gojenkins.CreateJenkins( return jenkinsclient.New(
nil,
jenkinsAPIURL, jenkinsAPIURL,
string(adminSecret.Data[resources.OperatorCredentialsSecretUserNameKey]), string(adminSecret.Data[resources.OperatorCredentialsSecretUserNameKey]),
string(adminSecret.Data[resources.OperatorCredentialsSecretTokenKey]), string(adminSecret.Data[resources.OperatorCredentialsSecretTokenKey]),
) )
if _, err := jenkinsClient.Init(); err != nil {
return nil, err
}
status, err := jenkinsClient.Poll()
if err != nil {
return nil, err
}
if status != http.StatusOK {
return nil, fmt.Errorf("invalid status code returned: %d", status)
}
return jenkinsClient, nil
} }
func createJenkinsCR(t *testing.T, namespace string) *v1alpha1.Jenkins { func createJenkinsCR(t *testing.T, name, namespace string) *v1alpha1.Jenkins {
jenkins := &v1alpha1.Jenkins{ jenkins := &v1alpha1.Jenkins{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "e2e", Name: name,
Namespace: namespace, Namespace: namespace,
}, },
Spec: v1alpha1.JenkinsSpec{ Spec: v1alpha1.JenkinsSpec{
@ -111,7 +94,7 @@ func createJenkinsCR(t *testing.T, namespace string) *v1alpha1.Jenkins {
return jenkins return jenkins
} }
func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha1.Jenkins) *gojenkins.Jenkins { func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha1.Jenkins) jenkinsclient.Jenkins {
client, err := createJenkinsAPIClient(jenkins) client, err := createJenkinsAPIClient(jenkins)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -1,37 +0,0 @@
package e2e
import (
"context"
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"k8s.io/apimachinery/pkg/types"
)
func TestJenkinsMasterPodRestart(t *testing.T) {
t.Parallel()
namespace, ctx := setupTest(t)
// Deletes test namespace
defer ctx.Cleanup()
jenkins := createJenkinsCR(t, namespace)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
restartJenkinsMasterPod(t, jenkins)
waitForRecreateJenkinsMasterPod(t, jenkins)
checkBaseConfigurationCompleteTimeIsNotSet(t, jenkins)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
}
func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha1.Jenkins) {
jenkinsStatus := &v1alpha1.Jenkins{}
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}
err := framework.Global.Client.Get(context.TODO(), namespaceName, jenkinsStatus)
if err != nil {
t.Fatal(err)
}
if jenkinsStatus.Status.BaseConfigurationCompletedTime != nil {
t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status)
}
}

99
test/e2e/restart_test.go Normal file
View File

@ -0,0 +1,99 @@
package e2e
import (
"context"
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
func TestJenkinsMasterPodRestart(t *testing.T) {
t.Parallel()
namespace, ctx := setupTest(t)
// Deletes test namespace
defer ctx.Cleanup()
jenkins := createJenkinsCR(t, "e2e", namespace)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
restartJenkinsMasterPod(t, jenkins)
waitForRecreateJenkinsMasterPod(t, jenkins)
checkBaseConfigurationCompleteTimeIsNotSet(t, jenkins)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
}
func TestSafeRestart(t *testing.T) {
t.Parallel()
namespace, ctx := setupTest(t)
// Deletes test namespace
defer ctx.Cleanup()
jenkinsCRName := "e2e"
configureAuthorizationToUnSecure(t, jenkinsCRName, namespace)
jenkins := createJenkinsCR(t, jenkinsCRName, namespace)
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
waitForJenkinsUserConfigurationToComplete(t, jenkins)
jenkinsClient := verifyJenkinsAPIConnection(t, jenkins)
checkIfAuthorizationStrategyUnsecuredIsSet(t, jenkinsClient)
err := jenkinsClient.SafeRestart()
require.NoError(t, err)
waitForJenkinsSafeRestart(t, jenkinsClient)
checkIfAuthorizationStrategyUnsecuredIsSet(t, jenkinsClient)
}
func configureAuthorizationToUnSecure(t *testing.T, jenkinsCRName, namespace string) {
limitRange := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: resources.GetUserConfigurationConfigMapName(jenkinsCRName),
Namespace: namespace,
},
Data: map[string]string{
"set-unsecured-authorization.groovy": `
import hudson.security.*
def jenkins = jenkins.model.Jenkins.getInstance()
def strategy = new AuthorizationStrategy.Unsecured()
jenkins.setAuthorizationStrategy(strategy)
jenkins.save()
`,
},
}
err := framework.Global.Client.Create(context.TODO(), limitRange, nil)
require.NoError(t, err)
}
func checkIfAuthorizationStrategyUnsecuredIsSet(t *testing.T, jenkinsClient jenkinsclient.Jenkins) {
logs, err := jenkinsClient.ExecuteScript(`
import hudson.security.*
def jenkins = jenkins.model.Jenkins.getInstance()
if (!(jenkins.getAuthorizationStrategy() instanceof AuthorizationStrategy.Unsecured)) {
throw new Exception('AuthorizationStrategy.Unsecured is not set')
}
`)
require.NoError(t, err, logs)
}
func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha1.Jenkins) {
jenkinsStatus := &v1alpha1.Jenkins{}
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}
err := framework.Global.Client.Get(context.TODO(), namespaceName, jenkinsStatus)
if err != nil {
t.Fatal(err)
}
if jenkinsStatus.Status.BaseConfigurationCompletedTime != nil {
t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status)
}
}

View File

@ -3,13 +3,18 @@ package e2e
import ( import (
goctx "context" goctx "context"
"fmt" "fmt"
"net/http"
"testing" "testing"
"time" "time"
"github.com/jenkinsci/kubernetes-operator/internal/try"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
framework "github.com/operator-framework/operator-sdk/pkg/test" framework "github.com/operator-framework/operator-sdk/pkg/test"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
@ -69,6 +74,20 @@ func waitForJenkinsUserConfigurationToComplete(t *testing.T, jenkins *v1alpha1.J
t.Log("Jenkins pod is running") t.Log("Jenkins pod is running")
} }
func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) {
err := try.Until(func() (end bool, err error) {
status, err := jenkinsClient.Poll()
if err != nil {
return false, err
}
if status != http.StatusOK {
return false, errors.Wrap(err, "couldn't poll data from Jenkins API")
}
return true, nil
}, time.Second, time.Second*70)
require.NoError(t, err)
}
// WaitUntilJenkinsConditionTrue retries until the specified condition check becomes true for the jenkins CR // WaitUntilJenkinsConditionTrue retries until the specified condition check becomes true for the jenkins CR
func WaitUntilJenkinsConditionTrue(retryInterval time.Duration, retries int, jenkins *v1alpha1.Jenkins, checkCondition checkConditionFunc) (*v1alpha1.Jenkins, error) { func WaitUntilJenkinsConditionTrue(retryInterval time.Duration, retries int, jenkins *v1alpha1.Jenkins, checkCondition checkConditionFunc) (*v1alpha1.Jenkins, error) {
jenkinsStatus := &v1alpha1.Jenkins{} jenkinsStatus := &v1alpha1.Jenkins{}