All Downloads are FREE. Search and download functionalities are using the official Maven repository.

controllers.impl.StandardEnvironment Maven / Gradle / Ivy

/**
 * Copyright 2015 Groupon.com
 *
 * 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 controllers.impl;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import controllers.Environment;
import controllers.routes;
import forms.ConfigForm;
import forms.NewEnvironment;
import forms.NewStage;
import io.ebean.Ebean;
import io.ebean.Transaction;
import models.EnvironmentType;
import models.Manifest;
import models.ManifestHistory;
import models.Owner;
import models.PackageVersion;
import models.Stage;
import models.UserMembership;
import org.joda.time.DateTime;
import play.Logger;
import play.data.Form;
import play.data.FormFactory;
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Security;
import utils.AuthN;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.persistence.PersistenceException;

/**
 * Controller for Environments.
 *
 * @author Brandon Arp (barp at groupon dot com)
 */
@Singleton
@Security.Authenticated(AuthN.class)
public class StandardEnvironment extends Controller implements Environment {
    /**
     * Public constructor.
     *
     * @param formFactory form factory to create forms
     */
    @Inject
    public StandardEnvironment(final FormFactory formFactory) {
        _formFactory = formFactory;
    }

    @Override
    public CompletionStage detail(final String envName) {
        final models.Environment environment = models.Environment.getByName(envName);
        if (environment == null) {
            return CompletableFuture.completedFuture(notFound());
        } else {
            final Form newStageForm = NewStage.form(_formFactory);
            final Form configForm = ConfigForm.form(environment, _formFactory);
            return CompletableFuture.completedFuture(ok(views.html.environment.render(environment, newStageForm, configForm, false)));
        }
    }

    @Override
    public CompletionStage newEnvironment(final String parentEnv) {
        Form form = NewEnvironment.form(_formFactory);
        final models.Environment parent = models.Environment.getByName(parentEnv);
        if (parent != null) {
            final NewEnvironment env = new NewEnvironment();
            env.setParent(parent.getId());
            form = form.fill(env);
        }
        final List owners = UserMembership.getOrgsForUser(request().attrs().get(Security.USERNAME));
        final List envTypes = Arrays.asList(EnvironmentType.values());
        return CompletableFuture.completedFuture(ok(views.html.newEnvironment.render(form, owners, envTypes)));
    }

    @Override
    public CompletionStage prepareRelease(final String envName) {
        final models.Environment environment = models.Environment.getByName(envName);
        if (environment == null) {
            return CompletableFuture.completedFuture(notFound());
        } else {
            final String currentVersion;
            final Manifest manifest = Manifest.getLatestManifest(environment);
            if (manifest != null) {
                currentVersion = manifest.getVersion();
            } else {
                currentVersion = "0";
            }

            final String nextVersion = incrementVersion(currentVersion);

            return CompletableFuture.completedFuture(ok(views.html.createReleasePrep.render(environment, manifest, nextVersion)));
        }
    }

    private String incrementVersion(final String currentVersion) {
        if (currentVersion == null) {
            return "1";
        }
        final StringBuilder lastNumber = new StringBuilder();
        Boolean newNum = true;
        int numIndex = 0;
        for (int x = 0; x < currentVersion.length(); x++) {
            if (Character.isDigit(currentVersion.charAt(x))) {
                if (newNum) {
                    lastNumber.setLength(0);
                    newNum = false;
                    numIndex = x;
                }
                lastNumber.append(currentVersion.charAt(x));
            } else {
                newNum = true;
            }
        }
        if (lastNumber.length() > 0) {
            final int parsed = Integer.parseInt(lastNumber.toString());
            final String nextVal = Integer.toString(parsed + 1);
            return currentVersion.substring(0, numIndex) + nextVal  + currentVersion.substring(numIndex + lastNumber.length());
        } else {
            return currentVersion + "-1";
        }
    }

    @Override
    public CompletionStage createRelease(final String envName) {
        final models.Environment environment = models.Environment.getByName(envName);
        if (environment == null) {
            return CompletableFuture.completedFuture(notFound());
        }
        // Make sure the user is an owner of the env
        if (!validateAuth(environment)) {
            return CompletableFuture.completedFuture(unauthorized());
        }

        final Map formUrlEncoded = request().body().asFormUrlEncoded();

        final String[] manifestVersion = formUrlEncoded.get("version");
        if (manifestVersion == null || manifestVersion.length > 1) {
            return CompletableFuture.completedFuture(badRequest());
        }

        final ArrayList packages = Lists.newArrayList(Optional.ofNullable(formUrlEncoded.get("packages")).orElse(new String[0]));
        final ArrayList versions = Lists.newArrayList(Optional.ofNullable(formUrlEncoded.get("versions")).orElse(new String[0]));

        if (packages.size() != versions.size()) {
            return CompletableFuture.completedFuture(badRequest());
        }

        final Map pkgs = Maps.newHashMap();
        for (int i = 0; i < packages.size(); i++) {
            final String aPackage = packages.get(i);
            final String version = versions.get(i);
            if (environment.getEnvironmentType() == EnvironmentType.ROLLER && (aPackage.contains("-") || version.contains("-"))) {
                return CompletableFuture.completedFuture(badRequest());
            } else if (Strings.isNullOrEmpty(version)) {
                return CompletableFuture.completedFuture(badRequest());
            }
            pkgs.put(aPackage, version);
        }

        // Check conflicts in package versions
        final Manifest manifest = createManifest(pkgs);
        manifest.setEnvironment(environment);
        manifest.setVersion(manifestVersion[0]);
        try {
            manifest.save();
        } catch (final PersistenceException e) {
            return CompletableFuture.completedFuture(badRequest());
        }
        return CompletableFuture.completedFuture(redirect(routes.Environment.detail(envName)));
    }

    @Override
    public CompletionStage create() {

        final Form bound = NewEnvironment.form(_formFactory).bindFromRequest();
        if (bound.hasErrors()) {
            final List owners = UserMembership.getOrgsForUser(request().attrs().get(Security.USERNAME));
            final List envTypes = Arrays.asList(EnvironmentType.values());
            return CompletableFuture.completedFuture(badRequest(views.html.newEnvironment.render(bound, owners, envTypes)));
        } else {
            try (Transaction transaction = Ebean.beginTransaction()) {
                final models.Environment environment = new models.Environment();
                final NewEnvironment env = bound.get();

                // This valueOf should have been checked with the validation of the form
                environment.setEnvironmentType(env.getEnvironmentType());

                environment.setName(env.getName());
                environment.setOwner(Owner.getById(env.getOwner()));

                if (env.getParent() != null) {
                    final models.Environment parent = models.Environment.getById(env.getParent());
                    environment.setParent(parent);
                    environment.setName(String.format("%s/%s", parent.getName(), env.getName()));
                }
                environment.save();

                final Manifest manifest = new Manifest();
                manifest.setCreatedBy(request().attrs().get(Security.USERNAME));
                manifest.setEnvironment(environment);
                manifest.setVersion("0");
                manifest.save();

                createStage(environment, manifest, "Production");
                createStage(environment, manifest, "Staging");
                createStage(environment, manifest, "UAT");

                transaction.commit();
                return CompletableFuture.completedFuture(redirect(controllers.routes.Environment.detail(environment.getName())));
            }
        }
    }

    private void createStage(final models.Environment environment, final Manifest manifest, final String name) {
        final Stage stagingStage = new Stage();
        stagingStage.setName(name);
        stagingStage.setEnvironment(environment);
        stagingStage.save();
        final ManifestHistory stagingManifestHistory = new ManifestHistory();
        stagingManifestHistory.setStart(DateTime.now());
        stagingManifestHistory.setStage(stagingStage);
        stagingManifestHistory.setManifest(manifest);
        stagingManifestHistory.setConfig("");
        stagingManifestHistory.save();
    }

    @Override
    public CompletionStage save(final String envName) {
        final models.Environment environment = models.Environment.getByName(envName);
        final Form newStageForm = NewStage.form(_formFactory);
        Form configForm = ConfigForm.form(_formFactory).bindFromRequest();
        final Map configData = configForm.rawData();
        final String config = configData.get("config");
        final Long version = Long.parseLong(configData.get("version"));
        if (version != environment.getVersion()) {
            configForm = configForm.withError("VersionConflict", "There was a version conflict. Please try saving again.");
        }

        if (configForm.hasErrors()) {
            return CompletableFuture.completedFuture(
                    badRequest(views.html.environment.render(environment, newStageForm, configForm, true)));
        }
        environment.setConfig(config);
        try {
            environment.save();
            return CompletableFuture.completedFuture(
                    ok(views.html.environment.render(environment, newStageForm, configForm, true)));
        } catch (final PersistenceException e) {
            return CompletableFuture.completedFuture(
                    badRequest(views.html.environment.render(environment, newStageForm, configForm, true)));
        }
    }


    private boolean validateAuth(final models.Environment environment) {
        final Set userGroups = Sets.newHashSet(UserMembership.getOrgsForUser(request().attrs().get(Security.USERNAME)));
        if (!userGroups.contains(environment.getOwner())) {
            Logger.warn(
                    String.format(
                            "Attempt at unauthorized deployment; environment=%s, owner=%s, user=%s, users_orgs=%s",
                            environment.getName(),
                            environment.getOwner().getOrgName(),
                            request().attrs().get(Security.USERNAME),
                            userGroups));
            return false;
        }
        return true;
    }

    private Manifest createManifest(final Map pkgs) {
        final List newPackages = Lists.newArrayList();
        for (final Map.Entry entry : pkgs.entrySet()) {
            final String pkg = entry.getKey();
            final String version = entry.getValue();
            models.Package p = models.Package.getByName(pkg);
            if (p == null) {
                p = new models.Package();
                p.setName(pkg);
                p.save();
            }

            PackageVersion packageVersion = PackageVersion.getByPackageAndVersion(p, version);
            if (packageVersion == null) {
                packageVersion = new PackageVersion();
                packageVersion.setPkg(p);
                packageVersion.setVersion(version);
                packageVersion.save();
            }

            newPackages.add(packageVersion);
        }
        final Manifest manifest = new Manifest();
        manifest.setPackages(newPackages);
        manifest.setCreatedBy(request().attrs().get(Security.USERNAME));
        manifest.save();
        return manifest;
    }

    private final FormFactory _formFactory;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy