#3 Fix custom auth override on Jenkins restart inside pod
This commit is contained in:
parent
3b8f0f7d10
commit
b715f82557
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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{}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue