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

com.spotify.styx.util.WorkflowValidator Maven / Gradle / Ivy

/*
 * -\-\-
 * Spotify Styx Scheduler Service
 * --
 * Copyright (C) 2018 Spotify AB
 * --
 * 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.spotify.styx.util;

import static java.lang.String.format;

import com.spotify.styx.model.Workflow;
import com.spotify.styx.model.WorkflowConfiguration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class WorkflowValidator {

  static final int MAX_ID_LENGTH = 256;
  static final int MAX_DOCKER_ARGS_TOTAL = 1000000;
  static final int MAX_RESOURCES = 5;
  static final int MAX_RESOURCE_LENGTH = 256;
  static final int MAX_COMMIT_SHA_LENGTH = 256;
  static final int MAX_SECRET_NAME_LENGTH = 253;
  static final int MAX_SECRET_MOUNT_PATH_LENGTH = 1024;
  static final int MAX_SERVICE_ACCOUNT_LENGTH = 256;

  private final DockerImageValidator dockerImageValidator;

  public WorkflowValidator(DockerImageValidator dockerImageValidator) {
    this.dockerImageValidator = dockerImageValidator;
  }

  public List validateWorkflow(Workflow workflow) {
    final WorkflowConfiguration configuration = workflow.configuration();
    return validateWorkflowConfiguration(configuration);
  }

  public List validateWorkflowConfiguration(WorkflowConfiguration cfg) {
    final List e = new ArrayList<>();

    // TODO: validate more of the contents

    limit(e, cfg.id().length(), MAX_ID_LENGTH, "id too long");
    limit(e, cfg.commitSha().map(String::length), MAX_COMMIT_SHA_LENGTH, "commitSha too long");
    limit(e, cfg.secret().map(s -> s.name().length()), MAX_SECRET_NAME_LENGTH, "secret name too long");
    limit(e, cfg.secret().map(s -> s.mountPath().length()), MAX_SECRET_MOUNT_PATH_LENGTH, "secret mount path too long");
    limit(e, cfg.serviceAccount().map(String::length), MAX_SERVICE_ACCOUNT_LENGTH, "service account too long");
    limit(e, cfg.resources().size(), MAX_RESOURCES, "too many resources");

    cfg.dockerImage().ifPresent(image ->
        dockerImageValidator.validateImageReference(image).stream()
            .map(s -> "invalid image: " + s)
            .forEach(e::add));

    cfg.resources().stream().map(String::length).forEach(v ->
        limit(e, v, MAX_RESOURCE_LENGTH, "resource name too long"));

    limit(e, cfg.dockerArgs().map(args -> args.size() + args.stream().mapToInt(String::length).sum()),
        MAX_DOCKER_ARGS_TOTAL, "docker args is too large");

    cfg.offset().ifPresent(offset -> {
      try {
        TimeUtil.addOffset(ZonedDateTime.now(), offset);
      } catch (DateTimeParseException ex) {
        e.add(format("invalid offset: %s", ex.getMessage()));
      }
    });

    try {
      TimeUtil.cron(cfg.schedule());
    } catch (IllegalArgumentException ex) {
      e.add("invalid schedule");
    }

    return e;
  }

  private void limit(List errors, Optional value, int limit, String message) {
    value.ifPresent(v -> limit(errors, v, limit, message));
  }

  private void limit(List errors, int value, int limit, String message) {
    if (value > limit) {
      errors.add(message + ": " + value + ", limit = " + limit);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy