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

Add optional usage restriction for a cloud using folder properties #282

Merged
merged 7 commits into from
Jul 31, 2018

Conversation

sixtyeight
Copy link
Contributor

In our Jenkins setup we would like to control (at least at a basic level) who can use a cloud definition.

I added an optional "secret" field to the KubernetesCloud which is, if not empty, compared with the value of the "cloudSecret" attribute in the PodTemplateStepExecution before actually provisioning the new agent.
Using this approach enables us to define the secret credential for example at folder scope.

@sixtyeight
Copy link
Contributor Author

Pipeline usage example:

// points to a simple jenkins "secret text" credential defined in folder for team1
withCredentials([string(credentialsId: 'openshift-team1-secret', variable: 'CLOUDSECRET')]) {

  podTemplate(cloudSecret: "${CLOUDSECRET}", cloud: 'openshift-team1', label: 'mypod', containers: [
    containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
  ] )
    {
      node('mypod') {
        stage('Get a Maven project') {
          git 'https://github.com/metalab-kassomat/kassomat-changeomatic.git'
          container('maven') {
            stage('Build a Maven project') {
              sh 'mvn -B clean install'
            }
         }
       }
    }
}

@Vlatombe
Copy link
Member

Vlatombe commented Feb 8, 2018

I understand the intent but I'm not sure requiring a secret string is the most elegant solution. Why not add a boolean property in the cloud, as well as a folder property?

Thinking further, one could imagine an extension point checking whether a given podTemplate is allowed given the context (e.g. forbid privileged containers in all folders but allow them in a specific one)

@sixtyeight
Copy link
Contributor Author

I understand the intent but I'm not sure requiring a secret string is the most elegant solution. Why not add a boolean property in the cloud, as well as a folder property?

@Vlatombe
Could you please explain your proposal in more detail, I'm afraid I'm missing something.
What would the Boolean indicate?
What is the purpose of the folder property and how would it correlate with a specific cloud definition?

@Vlatombe
Copy link
Member

Vlatombe commented Feb 9, 2018

The way I see it, you would define a boolean property in the cloud to allow/disallow usage of pod templates in pipeline by default.

Then, the folder property would define the list of Kubernetes clouds allowed to be used inside the folder.

@sixtyeight
Copy link
Contributor Author

Then, the folder property would define the list of Kubernetes clouds allowed to be used inside the folder.

Thanks for the clarification, got you now. I assume this would require the Folders Plus Version of the Plugin because on my install it seems I can't add any Properties at all (v6.3)? In the configure view of the folder only the header "Properties" is visible but no add button.

screenshot from 2018-02-09 14-16-31

@Vlatombe
Copy link
Member

Vlatombe commented Feb 9, 2018

The FolderProperty is defined in the folder plugin (https://github.com/jenkinsci/cloudbees-folder-plugin/blob/master/src/main/java/com/cloudbees/hudson/plugins/folder/FolderProperty.java).

This section lists properties contributed by plugins. In your screenshot, we can see the JIRA plugin is contributing a FolderProperty implementation.

@sixtyeight
Copy link
Contributor Author

In your screenshot, we can see the JIRA plugin is contributing a FolderProperty implementation.

Aaah, a Layer 8 problem on my side.

After adding the Folder Plugin Maven Dependency to the POM I added a KubernetesFolderProperty:

screenshot from 2018-02-09 15-56-46

Any pointers for how to check this value from within the Kubernetes Plugin are highly appreciated :-)

@Vlatombe
Copy link
Member

Vlatombe commented Feb 9, 2018

From PodTemplateStepExecution, there is already Run<?, ?> run = getContext().get(Run.class);.
Then you can retrieve the owning Job, then you can walk back the folder hierarchy and check the property.

@sixtyeight sixtyeight changed the title added optional usage restriction for a cloud [WIP] added optional usage restriction for a cloud Feb 9, 2018
@sixtyeight sixtyeight changed the title [WIP] added optional usage restriction for a cloud [WiP] added optional usage restriction for a cloud Feb 9, 2018
@sixtyeight sixtyeight changed the title [WiP] added optional usage restriction for a cloud added optional usage restriction for a cloud Feb 10, 2018
Copy link
Member

@Vlatombe Vlatombe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going in the right direction, added some additional comments

}

@Deprecated
public KubernetesCloud(String name, List<? extends PodTemplate> templates, String serverUrl, String namespace,
String jenkinsUrl, String containerCapStr, int connectTimeout, int readTimeout, int retentionTimeout) {
String jenkinsUrl, String containerCapStr, int connectTimeout, int readTimeout, int retentionTimeout, boolean usageRestricted) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed (since you have @DataBoundSetter on the setter method) and it would break potential usages of this constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

pom.xml Outdated
@@ -89,6 +89,12 @@
<version>${jenkins-kubernetes-credentials.version}</version>
</dependency>

<dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent with spaces.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ItemGroup<?> parent = job.getParent(); // Inspect the Parent of the Job

Set<String> allowedClouds = new HashSet<>();
if(parent instanceof Folder) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AbstractFolder is a better check than Folder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ItemGroup<?> parent = job.getParent(); // Inspect the Parent of the Job

Set<String> allowedClouds = new HashSet<>();
if(parent instanceof Folder) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could recurse into folder hierarchy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collectAllowedClouds is already traversing the hierarchy internally.

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="Kubernetes Plugin">
<f:entry title="${%Allowed Clouds}" field="allowedClouds">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be more structured, like providing a list of checkboxes (one line per cloud instance) and storing a collection of cloud names in the folder property class.

@sixtyeight
Copy link
Contributor Author

Could please someone with more Jenkins and Jelly knowledge help me out with the UI stuff.

I tried to implement a list of Checkboxes (one for each available cloud) which, if a cloud is inherited from a parent folder, would render the corresponding Checkbox as selected and read-only. Only non-inherited selected clouds should be saved in the folder property.

@sixtyeight
Copy link
Contributor Author

phew!

Folder for an admin user:

screenshot from 2018-02-25 01-39-47

Same Folder for a non-admin user:

screenshot from 2018-02-25 01-39-22

@sixtyeight
Copy link
Contributor Author

Grants for clouds inherited from a parent folder are now shown as readonly:

screenshot from 2018-02-25 04-02-54

@sixtyeight
Copy link
Contributor Author

Initially the whole section is hidden inside an advanced tag:

screenshot from 2018-02-25 04-06-59

@sixtyeight
Copy link
Contributor Author

Any feedback highly appreciated :-)
@Vlatombe @carlossg

Copy link
Member

@Vlatombe Vlatombe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suggesting some changes to clean up the patch but this looks good overall. Will try the PR locally as well.



public boolean isUsageRestricted() {
return usageRestricted;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return isModifyable() ? "" : "disabled=\"disabled\"";
}

private boolean isModifyable() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isModifiable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

*
* @return
*/
public List<UsagePermission> getEffectivePermissions() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add

@SuppressWarnings("unused") // Used by jelly
@Restricted(DoNotUse.class) // Used by jelly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


String name = kubernetesCloud.name;
p.setName(name);
p.setGranted(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary, a boolean is false by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

String name = kubernetesCloud.name;
p.setName(name);
p.setGranted(false);
p.setInherited(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Cloud cloud = Jenkins.getInstance().getCloud(cloudName);
if (cloud == null) {
throw new AbortException(String.format("Cloud does not exist: %s", cloudName));
}
if (!(cloud instanceof KubernetesCloud)) {
throw new AbortException(String.format("Cloud is not a Kubernetes cloud: %s (%s)", cloudName,
cloud.getClass().getName()));
throw new AbortException(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting change, should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

PodTemplateAction podTemplateAction = run.getAction(PodTemplateAction.class);
NamespaceAction namespaceAction = run.getAction(NamespaceAction.class);
String parentTemplates = podTemplateAction != null ? podTemplateAction.getParentTemplates() : null;

//Let's generate a random name based on the user specified to make sure that we don't have
//issues with concurrent builds, or messing with pre-existing configuration
// Let's generate a random name based on the user specified to make sure that we
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove formatting change (older is better)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
KubernetesCloud kubernetesCloud = (KubernetesCloud) cloud;

Run<?, ?> run = getContext().get(Run.class);
if (kubernetesCloud.isUsageRestricted()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the ideal design (ideally PodTemplateStepExecution should not depend on KubernetesFolderProperty) but could be revisited later by introducing a new extension point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea how to do this and, to be totally honest, I don't feel the urge to dig any deeper into the internals of Jenkins and its quirks.

@@ -127,8 +156,8 @@ public void onResume() {
throw new RuntimeException(String.format("Cloud does not exist: %s", cloudName));
}
if (!(cloud instanceof KubernetesCloud)) {
throw new RuntimeException(String.format("Cloud is not a Kubernetes cloud: %s (%s)", cloudName,
cloud.getClass().getName()));
throw new RuntimeException(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid formatting change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -46,6 +50,18 @@

private Map<String, String> systemProperties = new HashMap<>();

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem related to the main change but ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -55,6 +55,10 @@
<f:entry title="${%Container Cleanup Timeout (minutes)}" field="retentionTimeout">
<f:textbox default="5"/>
</f:entry>

<f:entry title="${%Requires permission}" field="usageRestricted">
Copy link
Member

@Vlatombe Vlatombe Mar 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing inline help (Add help-usageRestricted.html).
Maybe the title could specify that this applies to pipeline jobs. (Requires permission to use in pipeline jobs)

Having the permission in the advanced section is not very discoverable (but this is a general problem in the plugin due to the high number of settings).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added help file but still in the advanced section.

<f:advanced>
<f:entry title="Usage restricted Kubernetes Clouds available to any Pipeline jobs inside this folder:"/>
<j:forEach var="permission" items="${instance.getEffectivePermissions()}">
<f:entry title="${permission.name}"><j:expr value="${permission.htmlCheckbox()}" escapeText="false" /></f:entry>
Copy link
Member

@Vlatombe Vlatombe Mar 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use <f:checkbox> here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't figure out how to get the checkbox visually rendered as disabled in jelly.
by using readonly it looked like an active checkbox. i thought this would be confusing for the user.

@sixtyeight
Copy link
Contributor Author

fixed most of the issues. sorry for letting this pr get stale but I didn't find time to do this sooner.

https://github.com/jenkinsci/kubernetes-plugin.git into
usage-restriction

Conflicts:
	src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/PodTemplateStepExecution.java
@Vlatombe Vlatombe dismissed their stale review June 14, 2018 15:59

Code has changed

@Vlatombe
Copy link
Member

Vlatombe commented Jul 2, 2018

Sorry for the review delay, going through a new round now.

* Merge with master
* Reuse KubernetesFolderProperty instance on save
* Some labels re-wording
* Create taglib for checkbox with disabled attribute support
Copy link
Member

@Vlatombe Vlatombe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have submitted sixtyeight#1 with my suggested changes to this PR.

@carlossg
Copy link
Contributor

@Vlatombe does it look good to you now?

I think it deserves an entry in the README

@sixtyeight
Copy link
Contributor Author

sixtyeight commented Jul 11, 2018 via email

@Vlatombe
Copy link
Member

Vlatombe commented Jul 11, 2018 via email

@carlossg carlossg changed the title added optional usage restriction for a cloud Add optional usage restriction for a cloud using folder properties Jul 31, 2018
@carlossg carlossg merged commit e2f46de into jenkinsci:master Jul 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants