/*
* Copyright 2013 Google Inc. All Rights Reserved.
*
* 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 com.google.jenkins.plugins.dsl;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.servlet.ServletException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
import com.google.jenkins.plugins.delegate.AbstractRunnableItemGroup;
import com.google.jenkins.plugins.delegate.ReadOnlyWorkspaceTask;
import com.google.jenkins.plugins.dsl.restrict.AbstractRestriction;
import com.google.jenkins.plugins.dsl.restrict.NoRestriction;
import com.google.jenkins.plugins.dsl.restrict.RestrictedProject;
import com.google.jenkins.plugins.dsl.tag.YamlTag;
import com.google.jenkins.plugins.dsl.tag.YamlTags;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.ItemGroup;
import hudson.model.Queue;
import hudson.model.SCMedItem;
import hudson.model.TopLevelItem;
import hudson.model.View;
import hudson.scm.NullSCM;
import hudson.views.DefaultViewsTabBar;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
/**
* A project kind designed to load the bulk of its execution logic from a
* versioned DSL file. It expects to:
* <ol>
* <li>Sync an {@link SCM}
* <li>Instantiate and run an {@link TopLevelItem} from a DSL file
* loaded from a file fetched from source control.
* <li>Publish results (e.g. trigger other jobs)
* </ol>
* @param <T> The type of element contained
* @see YamlMultiBranchProject
* @see YamlAction
* @see YamlHistoryAction
* @see YamlBuild
* @see YamlDecorator
*/
@YamlTags({@YamlTag(tag = "!yaml"), @YamlTag(tag = "!dsl", arg = "yaml")})
public class YamlProject<T extends AbstractProject & TopLevelItem>
extends AbstractRunnableItemGroup<T, YamlProject<T>, YamlBuild<T>>
implements TopLevelItem, Queue.FlyweightTask, ReadOnlyWorkspaceTask,
SCMedItem, RestrictedProject<YamlProject<T>> {
private static Logger logger = Logger.getLogger(
YamlProject.class.getName());
/**
* Build up our Yaml project shell, which gets populated in
* {@link #submit(StaplerRequest, StaplerResponse)}.
*/
public YamlProject(ItemGroup parent, String name,
@Nullable YamlModule module) throws IOException {
super(parent, name);
this.module = (module != null) ? module : new YamlModule();
this.yamlPath = DEFAULT_YAML;
}
public static final String DEFAULT_YAML = ".jenkins.yaml";
/** Fetch our module for resolving dependency objects. */
public YamlModule getModule() {
return module;
}
private final YamlModule module;
/** {@inheritDoc} */
@Override
protected Class<YamlBuild<T>> getBuildClass() {
return (Class<YamlBuild<T>>) new TypeToken<YamlBuild<T>>() {}.getRawType();
}
/** {@inheritDoc} */
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) checkNotNull(Jenkins.getInstance())
.getDescriptorOrDie(getClass());
}
/** {@inheritDoc} */
@Override
public void onViewRenamed(View view, String oldName, String newName) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override
public void deleteView(View view) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override
public void onDeleted(T item) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override
public boolean canDelete(View view) {
return false;
}
/** {@inheritDoc} */
@Override
public void onRenamed(T item, String oldName, String newName) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Override
public List<Action> getActions() {
YamlHistoryAction action = YamlHistoryAction.of(getLastBuild());
if (action == null) {
return super.getActions();
}
// Delegate to the nested build.
return action.getProject(this).getActions();
}
/** {@inheritDoc} */
@Override
public YamlProject<T> asProject() {
return this;
}
/** {@inheritDoc} */
@Override
public AbstractRestriction getRestriction() {
return restriction;
}
/** Sets the path from which our Yaml DSL is loaded */
public YamlProject<T> setRestriction(AbstractRestriction restriction)
throws IOException {
this.restriction = checkNotNull(restriction);
save();
return this;
}
private AbstractRestriction restriction;
/** {@inheritDoc} */
@Override
protected void init() throws IOException {
if (this.viewsTabBar == null) {
this.viewsTabBar = new DefaultViewsTabBar();
}
if (this.views == null) {
this.views = Lists.newArrayList();
}
if (this.lastProjectView == null) {
this.lastProjectView = new LastProjectView(this);
views.add(lastProjectView);
lastProjectView.save();
}
if (this.jobHistoryView == null) {
jobHistoryView = new JobHistoryView(this);
views.add(jobHistoryView);
jobHistoryView.save();
}
if (Strings.isNullOrEmpty(this.primaryViewName)) {
this.primaryViewName = lastProjectView.getViewName();
}
super.init();
}
/**
* Retrieves our specialized {@link JobHistoryView} for displaying the series
* of jobs we have instantiated as the underlying DSL has changed.
*/
public JobHistoryView getJobHistoryView() {
return jobHistoryView;
}
/** @see #getJobHistoryView */
private JobHistoryView jobHistoryView;
/**
* Retrieves our specialized {@link LastProjectView} for displaying an
* embedded view of the last project that was instantiated for the
* underlying DSL.
*/
public LastProjectView getLastProjectView() {
return lastProjectView;
}
/** @see #getLastProjectView */
private LastProjectView lastProjectView;
/** Retrieves the latest version of our embedded project. */
public AbstractProject getLastProject() {
final YamlHistoryAction action =
YamlHistoryAction.of(getLastBuild());
// No builds yet.
if (action == null) {
return null;
}
return action.getProject(this);
}
/** {@inheritDoc} */
@Override
protected void submit(StaplerRequest req, StaplerResponse rsp)
throws IOException, ServletException, FormException {
super.submit(req, rsp);
final JSONObject json = req.getSubmittedForm();
setYamlPath(json.optString("yamlPath"));
if (json.containsKey("restriction")) {
setRestriction(req.bindJSON(AbstractRestriction.class,
json.getJSONObject("restriction")));
} else {
setRestriction(new NoRestriction());
}
}
/** Fetch the path from which our Yaml DSL can be loaded */
public String getYamlPath() {
return yamlPath;
}
/** Sets the path from which our Yaml DSL is loaded */
public YamlProject<T> setYamlPath(String yamlPath) throws IOException {
this.yamlPath = checkNotNull(yamlPath);
save();
return this;
}
private String yamlPath;
/** Boilerplate extension code */
@Extension
public static class DescriptorImpl extends AbstractProjectDescriptor {
public DescriptorImpl() {
load();
}
/** {@inheritDoc} */
@Override
public String getDisplayName() {
return Messages.YamlProject_DisplayName();
}
/** {@inheritDoc} */
@Override
public boolean configure(StaplerRequest req, JSONObject json)
throws FormException {
json = json.getJSONObject(getDisplayName());
if (json.optBoolean("verboseLogging", false)) {
verboseLogging = true;
} else {
verboseLogging = false;
}
save();
return true;
}
/**
* @return whether to enable verbose output in the execution log
* of YamlBuilds
*/
public boolean isVerbose() {
return verboseLogging;
}
private boolean verboseLogging;
/** {@inheritDoc} */
@Override
public boolean isApplicable(Descriptor descriptor) {
if (!super.isApplicable(descriptor)) {
return false;
}
// Disallow the null SCM from being used with this project.
if (NullSCM.class.isAssignableFrom(descriptor.clazz)) {
return false;
}
return true;
}
/** {@inheritDoc} */
@Override
public YamlProject newInstance(ItemGroup parent, String name) {
try {
return new YamlProject(parent, name, null /* module */);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
}