Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reverse Library precedence during loading to prevent library overriding #132

Merged
merged 10 commits into from
Nov 23, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,16 @@ class PipelineDecorator extends InvisibleAction {
String pipeline_template = null
Boolean allow_scm_jenkinsfile = true
Boolean permissive_initialization = false
Boolean prevent_library_override = false

static LinkedHashMap getSchema(){
return [
fields: [
optional: [
allow_scm_jenkinsfile: Boolean,
pipeline_template: String,
permissive_initialization: Boolean
permissive_initialization: Boolean,
prevent_library_override: Boolean
]
]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.boozallen.plugins.jte.init.governance.config.dsl

import org.boozallen.plugins.jte.init.PipelineDecorator
import org.boozallen.plugins.jte.util.TemplateLogger
import org.codehaus.groovy.runtime.InvokerHelper
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner
Expand All @@ -38,6 +39,10 @@ class PipelineConfigurationObject implements Serializable{
this.flowOwner = flowOwner
}

PipelineDecorator.JteBlockWrapper getJteBlockWrapper(){
return (config.jte ?: [:]) as PipelineDecorator.JteBlockWrapper
}

PipelineConfigurationObject plus(PipelineConfigurationObject child){
/*
If this is the first call to join, then there is no pre-existing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ import org.jenkinsci.plugins.workflow.job.WorkflowJob
LinkedHashMap aggregatedConfig = config.getConfig()
AggregateException errors = new AggregateException()
List<LibraryProvider> providers = getLibraryProviders(flowOwner)
boolean reverseProviders = config.jteBlockWrapper.prevent_library_override
if(reverseProviders) {
providers = providers.reverse()
}
ConfigValidator validator = new ConfigValidator(flowOwner)
aggregatedConfig[KEY].each { libName, libConfig ->
LibraryProvider provider = providers.find{ provider ->
Expand Down Expand Up @@ -86,7 +90,12 @@ import org.jenkinsci.plugins.workflow.job.WorkflowJob
@Override
void injectPrimitives(FlowExecutionOwner flowOwner, PipelineConfigurationObject config, TemplateBinding binding){
LinkedHashMap aggregatedConfig = config.getConfig()

List<LibraryProvider> providers = getLibraryProviders(flowOwner)
boolean reverseProviders = config.jteBlockWrapper.prevent_library_override
if(reverseProviders) {
providers = providers.reverse()
}
aggregatedConfig[KEY].each{ libName, libConfig ->
LibraryProvider provider = providers.find{ provider ->
provider.hasLibrary(flowOwner, libName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
/*
Copyright 2018 Booz Allen Hamilton

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.boozallen.plugins.jte.init.primitives.injectors

import org.boozallen.plugins.jte.init.PipelineDecorator
import org.boozallen.plugins.jte.init.governance.GovernanceTier
import org.boozallen.plugins.jte.init.governance.config.dsl.PipelineConfigurationObject
import org.boozallen.plugins.jte.init.governance.libs.LibraryProvider
import org.boozallen.plugins.jte.init.governance.libs.LibrarySource
import org.boozallen.plugins.jte.init.primitives.TemplateBinding
import org.boozallen.plugins.jte.util.AggregateException
import org.jenkinsci.plugins.workflow.cps.CpsScript
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner
import org.jenkinsci.plugins.workflow.job.WorkflowJob
import org.junit.ClassRule
import org.jvnet.hudson.test.JenkinsRule
import org.jvnet.hudson.test.WithoutJenkins
import spock.lang.Shared
import spock.lang.Specification

class LibraryStepInjectorSpec extends Specification{

class JobChild {
WorkflowJob getParent(){ return null }
}

@Shared @ClassRule JenkinsRule jenkins = new JenkinsRule()
CpsScript script = Mock()
PrintStream logger = Mock()

WorkflowJob job = GroovyMock()
LibraryStepInjector injector = new LibraryStepInjector()
FlowExecutionOwner flowExecutionOwner = GroovyMock{
run() >> GroovyMock(JobChild){
getParent() >> job
}
}

TemplateBinding templateBinding = Mock()
LinkedHashMap config = [
jte: [:],
libraries: [:]
]

PipelineConfigurationObject pipelineConfigurationObject = pipelineConfigurationObject = Mock{
getConfig() >> config

getJteBlockWrapper() >> { return config.jte as PipelineDecorator.JteBlockWrapper }
}

class MockLibraryProvider extends LibraryProvider{

@Override
Boolean hasLibrary(FlowExecutionOwner flowOwner, String libraryName) {
return false
}

@Override
String getLibrarySchema(FlowExecutionOwner flowOwner, String libraryName) {
return null
}

@Override
void loadLibrary(FlowExecutionOwner flowOwner, Binding binding, String libName, Map libConfig) {
}

}

@WithoutJenkins
def "when library source has library, loadLibrary is called"(){
setup:
String libraryName = "libA"
config.libraries["libA"] = [:]

MockLibraryProvider p1 = Mock{
hasLibrary(flowExecutionOwner, libraryName) >> true
}

LibrarySource s1 = Mock{
getLibraryProvider() >> p1
}

GovernanceTier t1 = GroovyMock(global:true){
getLibrarySources() >> [ s1 ]
}

GovernanceTier.getHierarchy(_) >> [ t1 ]

when:
injector.injectPrimitives(flowExecutionOwner, pipelineConfigurationObject, templateBinding)

then:
1 * p1.loadLibrary(flowExecutionOwner, templateBinding, libraryName, _)
}

@WithoutJenkins
def "Libraries can be loaded across library sources in a governance tier"(){
setup:
config.libraries["libA"] = [:]
config.libraries["libB"] = [:]

MockLibraryProvider p1 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
}
MockLibraryProvider p2 = Mock{
hasLibrary(flowExecutionOwner, "libB") >> true
}

LibrarySource s1 = Mock{
getLibraryProvider() >> p1
}
LibrarySource s2 = Mock{
getLibraryProvider() >> p2
}

GovernanceTier t1 = GroovyMock(global:true){
getLibrarySources() >> [ s1, s2 ]
}

GovernanceTier.getHierarchy(_) >> [ t1 ]

when:
injector.injectPrimitives(flowExecutionOwner, pipelineConfigurationObject, templateBinding)

then:
1 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
0 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libB", _)
1 * p2.loadLibrary(flowExecutionOwner, templateBinding, "libB", _)
0 * p2.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
}

@WithoutJenkins
def "Libraries can be loaded across library sources in different governance tiers"(){
setup:
config.libraries["libA"] = [:]
config.libraries["libB"] = [:]

MockLibraryProvider p1 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
}
MockLibraryProvider p2 = Mock{
hasLibrary(flowExecutionOwner, "libB") >> true
}

LibrarySource s1 = Mock{
getLibraryProvider() >> p1
}
LibrarySource s2 = Mock{
getLibraryProvider() >> p2
}

GovernanceTier tier1 = Mock{
getLibrarySources() >> [ s1, s2 ]
}

GovernanceTier tier2 = GroovyMock(global:true){
getLibrarySources() >> [ s1, s2 ]
}

GovernanceTier.getHierarchy(_) >> [ tier1, tier2 ]

when:
injector.injectPrimitives(flowExecutionOwner, pipelineConfigurationObject, templateBinding)

then:
1 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
0 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libB", _)
0 * p2.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
1 * p2.loadLibrary(flowExecutionOwner, templateBinding, "libB", _)
}

@WithoutJenkins
def "library on more granular governance tier gets loaded"(){
setup:
config.libraries["libA"] = [:]

MockLibraryProvider p1 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
}
MockLibraryProvider p2 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
}

LibrarySource s1 = Mock{
getLibraryProvider() >> p1
}
LibrarySource s2 = Mock{
getLibraryProvider() >> p2
}

GovernanceTier tier1 = Mock{
getLibrarySources() >> [ s1 ]
}

GovernanceTier tier2 = GroovyMock(global:true){
getLibrarySources() >> [ s2 ]
}

GovernanceTier.getHierarchy(_) >> [ tier1, tier2 ]

when:
injector.injectPrimitives(flowExecutionOwner, pipelineConfigurationObject, templateBinding)

then:
1 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
0 * p2.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
}

@WithoutJenkins
def "library on higher governance tier (last in hierarchy array) gets loaded if library override set to false"(){
setup:
config.jte['prevent_library_override'] = true
config.libraries["libA"] = [:]

MockLibraryProvider p1 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
}
MockLibraryProvider p2 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
}

LibrarySource s1 = Mock{
getLibraryProvider() >> p1
}
LibrarySource s2 = Mock{
getLibraryProvider() >> p2
}

GovernanceTier t1 = Mock{
getLibrarySources() >> [ s1 ]
}

GovernanceTier t2 = GroovyMock(global:true){
getLibrarySources() >> [ s2 ]
}

GovernanceTier.getHierarchy(_) >> [ t1, t2 ]

when:
injector.injectPrimitives(flowExecutionOwner, pipelineConfigurationObject, templateBinding)

then:
0 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
1 * p2.loadLibrary(flowExecutionOwner, templateBinding, "libA", _)
}

@WithoutJenkins
def "library loader correctly passes step config"(){
setup:
config.libraries = [
libA: [
fieldA: "A"
],
libB: [
fieldB: "B"
]
]

MockLibraryProvider p1 = Mock{
hasLibrary(flowExecutionOwner, "libA") >> true
hasLibrary(flowExecutionOwner, "libB") >> true
}

LibrarySource s1 = Mock{
getLibraryProvider() >> p1
}

GovernanceTier t1 = GroovyMock(global:true){
getLibrarySources() >> [ s1 ]
}

GovernanceTier.getHierarchy(_) >> [ t1 ]

when:
injector.injectPrimitives(flowExecutionOwner, pipelineConfigurationObject, templateBinding)

then:
1 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libA", [fieldA: "A"])
1 * p1.loadLibrary(flowExecutionOwner, templateBinding, "libB", [fieldB: "B"])
}

@WithoutJenkins
def "Missing library throws exception"(){
// now, when a library isn't found, we push a message onto the `libConfigErrors` array
// and throw the exception later after validating all the libraries.
// so this test represents making sure that an exception is thrown if a library does not exist.
setup:
config.libraries = [
libA: [
fieldA: "A"
],
libB: [
fieldB: "B"
]
]

MockLibraryProvider p = Mock{
1 * hasLibrary(flowExecutionOwner, "libA") >> true
1 * hasLibrary(flowExecutionOwner, "libB") >> false
}

LibrarySource s = Mock{
getLibraryProvider() >> p
}

GovernanceTier tier = GroovyMock(global:true){
getLibrarySources() >> [ s ]
}

GovernanceTier.getHierarchy(_) >> [ tier ]

when:
injector.validateConfiguration(flowExecutionOwner, pipelineConfigurationObject)

then:
thrown(AggregateException)
}

}