<commit_before>/*
 * Copyright 2019 ThoughtWorks, Inc.
 *
 * 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.thoughtworks.go.server.service;

import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.exceptions.GoConfigInvalidException;
import com.thoughtworks.go.config.update.AgentsUpdateValidator;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.listener.AgentChangeListener;
import com.thoughtworks.go.listener.DatabaseEntityChangeListener;
import com.thoughtworks.go.remote.AgentIdentifier;
import com.thoughtworks.go.security.Registration;
import com.thoughtworks.go.server.domain.AgentInstances;
import com.thoughtworks.go.server.domain.ElasticAgentMetadata;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.messaging.notifications.AgentStatusChangeNotifier;
import com.thoughtworks.go.server.persistence.AgentDao;
import com.thoughtworks.go.server.service.result.HttpOperationResult;
import com.thoughtworks.go.server.service.result.LocalizedOperationResult;
import com.thoughtworks.go.server.service.result.OperationResult;
import com.thoughtworks.go.server.ui.AgentViewModel;
import com.thoughtworks.go.server.ui.AgentsViewModel;
import com.thoughtworks.go.server.util.UuidGenerator;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import com.thoughtworks.go.serverhealth.ServerHealthState;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TriState;
import com.thoughtworks.go.util.comparator.AlphaAsciiComparator;
import com.thoughtworks.go.utils.Timeout;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;

import java.util.*;
import java.util.stream.Collectors;

import static com.thoughtworks.go.CurrentGoCDVersion.docsUrl;
import static com.thoughtworks.go.i18n.LocalizedMessage.entityConfigValidationFailed;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
import static com.thoughtworks.go.util.ExceptionUtils.bombIfNull;
import static java.lang.String.format;


@Service
public class AgentService implements DatabaseEntityChangeListener<AgentConfig> {
    private final SystemEnvironment systemEnvironment;
    private final SecurityService securityService;
    private final EnvironmentConfigService environmentConfigService;
    private final UuidGenerator uuidGenerator;
    private final ServerHealthService serverHealthService;
    private AgentStatusChangeNotifier agentStatusChangeNotifier;
    private GoConfigService goConfigService;
    private final AgentDao agentDao;

    private AgentInstances agentInstances;

    private List<AgentChangeListener> listeners = new ArrayList<>();
    private static final Logger LOGGER = LoggerFactory.getLogger(AgentService.class);

    @Autowired
    public AgentService(SystemEnvironment systemEnvironment, final EnvironmentConfigService environmentConfigService,
                        SecurityService securityService, AgentDao agentDao, UuidGenerator uuidGenerator, ServerHealthService serverHealthService,
                        final EmailSender emailSender, AgentStatusChangeNotifier agentStatusChangeNotifier, GoConfigService goConfigService) {
        this(systemEnvironment, null, environmentConfigService, securityService, agentDao, uuidGenerator, serverHealthService,
                agentStatusChangeNotifier, goConfigService);
        this.agentInstances = new AgentInstances(agentStatusChangeNotifier);
    }

    AgentService(SystemEnvironment systemEnvironment, AgentInstances agentInstances,
                 EnvironmentConfigService environmentConfigService, SecurityService securityService, AgentDao agentDao, UuidGenerator uuidGenerator,
                 ServerHealthService serverHealthService, AgentStatusChangeNotifier agentStatusChangeNotifier, GoConfigService goConfigService) {
        this.systemEnvironment = systemEnvironment;
        this.environmentConfigService = environmentConfigService;
        this.securityService = securityService;
        this.agentInstances = agentInstances;
        this.agentDao = agentDao;
        this.uuidGenerator = uuidGenerator;
        this.serverHealthService = serverHealthService;
        this.agentStatusChangeNotifier = agentStatusChangeNotifier;
        this.goConfigService = goConfigService;
    }

    public void initialize() {
        this.sync();
        agentDao.registerListener(this);
    }

    private void sync() {
        agentInstances.sync(new Agents(agentDao.allAgents()));
    }

    public AgentInstances agentInstances() {
        return agentInstances;
    }

    public Agents agents() {
        List<AgentConfig> agentConfigs = agentInstances.values().stream().map(agentInstance -> agentInstance.agentConfig()).collect(Collectors.toList());
        return new Agents(agentConfigs);
    }

    public Map<AgentInstance, Collection<String>> agentsToEnvNameMap() {
        Map<AgentInstance, Collection<String>> allAgents = new LinkedHashMap<>();
        for (AgentInstance agentInstance : agentInstances.sort()) {
            TreeSet<String> sortedEnvSet = new TreeSet<>(new AlphaAsciiComparator());
            sortedEnvSet.addAll(agentInstance.agentConfig().getEnvironmentsAsList());
            allAgents.put(agentInstance, sortedEnvSet);
        }
        return allAgents;
    }

    public AgentsViewModel registeredAgents() {
        return toAgentViewModels(agentInstances.findRegisteredAgents());
    }

    private AgentsViewModel toAgentViewModels(AgentInstances instances) {
        AgentsViewModel agents = new AgentsViewModel();
        for (AgentInstance instance : instances) {
            agents.add(toAgentViewModel(instance));
        }
        return agents;
    }

    private AgentViewModel toAgentViewModel(AgentInstance instance) {
        return new AgentViewModel(instance, instance.agentConfig().getEnvironmentsAsList());
    }

    public AgentInstances findRegisteredAgents() {
        return agentInstances.findRegisteredAgents();
    }

    private boolean isUnknownAgent(AgentInstance agentInstance, OperationResult operationResult) {
        if (agentInstance.isNullAgent()) {
            String agentNotFoundMessage = String.format("Agent '%s' not found", agentInstance.getUuid());
            operationResult.notFound("Agent not found.", agentNotFoundMessage, HealthStateType.general(HealthStateScope.GLOBAL));
            return true;
        }
        return false;
    }

    private boolean hasOperatePermission(Username username, OperationResult operationResult) {
        if (!securityService.hasOperatePermissionForAgents(username)) {
            String message = "Unauthorized to operate on agent";
            operationResult.forbidden(message, message, HealthStateType.general(HealthStateScope.GLOBAL));
            return false;
        }
        return true;
    }

    public AgentInstance updateAgentAttributes(Username username, HttpOperationResult result, String uuid, String newHostname, String resources, String environments, TriState enable) {
        if (!hasOperatePermission(username, result)) {
            return null;
        }

        AgentInstance agentInstance = findAgent(uuid);
        if (isUnknownAgent(agentInstance, result)) {
            return null;
        }

        AgentConfig agentConfig = AgentConfig.newInstanceFrom(agentInstance.agentConfig());

        if (enable.isTrue()) {
            agentConfig.enable();
        }

        if (enable.isFalse()) {
            agentConfig.disable();
        }

        if (newHostname != null) {
            agentConfig.setHostname(newHostname);
        }

        if (resources != null) {
            agentConfig.setResources(new ResourceConfigs(resources));
        }

        if (environments != null) {
            agentConfig.setEnvironments(environments);
        }

        try {
            saveOrUpdate(agentConfig);

            if (agentConfig.hasErrors()) {
                result.unprocessibleEntity("Updating agent failed:", "", HealthStateType.general(HealthStateScope.GLOBAL));
            } else {
                result.ok(String.format("Updated agent with uuid %s.", agentConfig.getUuid()));
            }
        } catch (Exception e) {
            result.internalServerError("Updating agent failed: " + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
            return null;
        }

        if (agentConfig != null) {
            return AgentInstance.createFromConfig(agentConfig, systemEnvironment, agentStatusChangeNotifier);
        }
        return null;
    }

    public void bulkUpdateAgentAttributes(Username username, LocalizedOperationResult result, List<String> uuids,
                                          List<String> resourcesToAdd, List<String> resourcesToRemove,
                                          List<String> environmentsToAdd, List<String> environmentsToRemove,
                                          TriState enable) {
        AgentsUpdateValidator agentsUpdateValidator = new AgentsUpdateValidator(agentInstances,
                username, result, uuids, environmentsToAdd, environmentsToRemove, enable, resourcesToAdd, resourcesToRemove, goConfigService);
        try {
            if (agentsUpdateValidator.canContinue()) {
                agentsUpdateValidator.validate();
                agentDao.bulkUpdateAttributes(uuids, resourcesToAdd, resourcesToRemove, environmentsToAdd, environmentsToRemove, enable, agentInstances);
                result.setMessage("Updated agent(s) with uuid(s): [" + StringUtils.join(uuids, ", ") + "].");
            }
        } catch (Exception e) {
            LOGGER.error("There was an error bulk updating agents", e);
            if (e instanceof IllegalArgumentException && !result.hasMessage()) {
                result.unprocessableEntity(entityConfigValidationFailed(Agents.class.getAnnotation(ConfigTag.class).value(), StringUtils.join(uuids, ","), e.getMessage()));
            } else if (!result.hasMessage()) {
                result.internalServerError("Server error occured. Check log for details.");
            }
        }
    }

    private boolean populateAgentInstancesForUUIDs(OperationResult operationResult, List<String> uuids, List<AgentInstance> agents) {
        for (String uuid : uuids) {
            AgentInstance agentInstance = findAgentAndRefreshStatus(uuid);
            if (isUnknownAgent(agentInstance, operationResult)) {
                return false;
            }
            agents.add(agentInstance);
        }
        return true;
    }

    public void deleteAgents(Username username, HttpOperationResult operationResult, List<String> uuids) {
        if (!hasOperatePermission(username, operationResult)) {
            return;
        }
        List<AgentInstance> agents = new ArrayList<>();
        if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
            return;
        }

        for (AgentInstance agentInstance : agents) {
            if (!agentInstance.canBeDeleted()) {
                operationResult.notAcceptable(String.format("Failed to delete %s agent(s), as agent(s) might not be disabled or are still building.", agents.size()),
                        HealthStateType.general(HealthStateScope.GLOBAL));
                return;
            }
        }

        try {
            List<AgentConfig> allAgents = filterAgents(uuids);
            if (allAgents.size() != uuids.size()) {
                List<String> uuidsOfAgentsInDatabase = allAgents.stream().map(agent -> agent.getUuid()).collect(Collectors.toList());
                List<String> nonExistentAgentIds = uuids.stream().filter(uuid -> !uuidsOfAgentsInDatabase.contains(uuid)).collect(Collectors.toList());
                //TODO : Revisit this for checking whether to throw this error
                if (!nonExistentAgentIds.isEmpty()) {
                    bomb("Unable to delete agent; Agent [" + nonExistentAgentIds + "] not found.");
                }
            }
            agentDao.bulkSoftDelete(uuids);
            operationResult.ok(String.format("Deleted %s agent(s).", agents.size()));
        } catch (Exception e) {
            operationResult.internalServerError("Deleting agents failed:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
        }
    }

    private List<AgentConfig> filterAgents(List<String> uuids) {
        return agentInstances.values().stream().filter(agentInstance -> uuids.contains(agentInstance.getUuid()))
                .map(AgentInstance::agentConfig)
                .collect(Collectors.toList());
    }

    public void updateRuntimeInfo(AgentRuntimeInfo info) {
        if (!info.hasCookie()) {
            LOGGER.warn("Agent [{}] has no cookie set", info.agentInfoDebugString());
            throw new AgentNoCookieSetException(format("Agent [%s] has no cookie set", info.agentInfoDebugString()));
        }
        if (info.hasDuplicateCookie(agentDao.cookieFor(info.getIdentifier()))) {
            LOGGER.warn("Found agent [{}] with duplicate uuid. Please check the agent installation.", info.agentInfoDebugString());
            serverHealthService.update(
                    ServerHealthState.warning(format("[%s] has duplicate unique identifier which conflicts with [%s]", info.agentInfoForDisplay(), findAgentAndRefreshStatus(info.getUUId()).agentInfoForDisplay()),
                            "Please check the agent installation. Click <a href='" + docsUrl("/faq/agent_guid_issue.html") + "' target='_blank'>here</a> for more info.",
                            HealthStateType.duplicateAgent(HealthStateScope.forAgent(info.getCookie())), Timeout.THIRTY_SECONDS));
            throw new AgentWithDuplicateUUIDException(format("Agent [%s] has invalid cookie", info.agentInfoDebugString()));
        }
        AgentInstance agentInstance = findAgentAndRefreshStatus(info.getUUId());
        if (agentInstance.isIpChangeRequired(info.getIpAdress())) {
            AgentConfig agentConfig = agentInstance.agentConfig();
            LOGGER.warn("Agent with UUID [{}] changed IP Address from [{}] to [{}]", info.getUUId(), agentConfig.getIpaddress(), info.getIpAdress());
            String uuid = agentConfig.getUuid();

            AgentConfig agent = (agentInstance.isRegistered() ? agentConfig : null);
            bombIfNull(agent, "Unable to set agent ipAddress; Agent [" + uuid + "] not found.");
            agent.setIpaddress(info.getIpAdress());
            saveOrUpdate(agent);
        }
        agentInstances.updateAgentRuntimeInfo(info);
    }


    public Username agentUsername(String uuId, String ipAddress, String hostNameForDisplay) {
        return new Username(String.format("agent_%s_%s_%s", uuId, ipAddress, hostNameForDisplay));
    }

    public Registration requestRegistration(Username username, AgentRuntimeInfo agentRuntimeInfo) {
        LOGGER.debug("Agent is requesting registration {}", agentRuntimeInfo);
        AgentInstance agentInstance = agentInstances.register(agentRuntimeInfo);
        Registration registration = agentInstance.assignCertification();
        if (agentInstance.isRegistered()) {
            AgentConfig agentConfig = agentInstance.agentConfig();
            if (agentConfig.getCookie() == null) {
                String cookie = uuidGenerator.randomUuid();
                agentConfig.setCookie(cookie);
            }
            saveOrUpdateAgentInstance(agentInstance, username);
            if (agentConfig.hasErrors()) {
                List<ConfigErrors> errors = ErrorCollector.getAllErrors(agentConfig);

                throw new GoConfigInvalidException(null, new AllConfigErrors(errors));
            }
            LOGGER.debug("New Agent approved {}", agentRuntimeInfo);
        }
        return registration;
    }

    public void updateAgentApprovalStatus(final String uuid, final Boolean isDenied, Username currentUser) {
        AgentInstance agentInstance = agentInstances.findAgent(uuid);
        AgentConfig agent = (agentInstance.isRegistered() ? agentInstance.agentConfig() : null);
        bombIfNull(agent, "Unable to update agent approval status; Agent [" + uuid + "] not found.");
        agent.setDisabled(isDenied);
        saveOrUpdate(agent);
    }

    public void saveOrUpdateAgentInstance(AgentInstance agentInstance, Username currentUser) {<commit_msg>In registerAgents call on AgentController - earlier we used to add the incoming agent to the agentInstances of the agent service and then add the same to config via agent config service. Since the agents from config were the source of truth - any validation failure would lead to agent not getting registered.<commit_after>/*
 * Copyright 2019 ThoughtWorks, Inc.
 *
 * 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.thoughtworks.go.server.service;

import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.exceptions.GoConfigInvalidException;
import com.thoughtworks.go.config.update.AgentsUpdateValidator;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.listener.AgentChangeListener;
import com.thoughtworks.go.listener.DatabaseEntityChangeListener;
import com.thoughtworks.go.remote.AgentIdentifier;
import com.thoughtworks.go.security.Registration;
import com.thoughtworks.go.server.domain.AgentInstances;
import com.thoughtworks.go.server.domain.ElasticAgentMetadata;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.messaging.notifications.AgentStatusChangeNotifier;
import com.thoughtworks.go.server.persistence.AgentDao;
import com.thoughtworks.go.server.service.result.HttpOperationResult;
import com.thoughtworks.go.server.service.result.LocalizedOperationResult;
import com.thoughtworks.go.server.service.result.OperationResult;
import com.thoughtworks.go.server.ui.AgentViewModel;
import com.thoughtworks.go.server.ui.AgentsViewModel;
import com.thoughtworks.go.server.util.UuidGenerator;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import com.thoughtworks.go.serverhealth.ServerHealthState;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TriState;
import com.thoughtworks.go.util.comparator.AlphaAsciiComparator;
import com.thoughtworks.go.utils.Timeout;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;

import java.util.*;
import java.util.stream.Collectors;

import static com.thoughtworks.go.CurrentGoCDVersion.docsUrl;
import static com.thoughtworks.go.i18n.LocalizedMessage.entityConfigValidationFailed;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
import static com.thoughtworks.go.util.ExceptionUtils.bombIfNull;
import static java.lang.String.format;


@Service
public class AgentService implements DatabaseEntityChangeListener<AgentConfig> {
    private final SystemEnvironment systemEnvironment;
    private final SecurityService securityService;
    private final EnvironmentConfigService environmentConfigService;
    private final UuidGenerator uuidGenerator;
    private final ServerHealthService serverHealthService;
    private AgentStatusChangeNotifier agentStatusChangeNotifier;
    private GoConfigService goConfigService;
    private final AgentDao agentDao;

    private AgentInstances agentInstances;

    private List<AgentChangeListener> listeners = new ArrayList<>();
    private static final Logger LOGGER = LoggerFactory.getLogger(AgentService.class);

    @Autowired
    public AgentService(SystemEnvironment systemEnvironment, final EnvironmentConfigService environmentConfigService,
                        SecurityService securityService, AgentDao agentDao, UuidGenerator uuidGenerator, ServerHealthService serverHealthService,
                        final EmailSender emailSender, AgentStatusChangeNotifier agentStatusChangeNotifier, GoConfigService goConfigService) {
        this(systemEnvironment, null, environmentConfigService, securityService, agentDao, uuidGenerator, serverHealthService,
                agentStatusChangeNotifier, goConfigService);
        this.agentInstances = new AgentInstances(agentStatusChangeNotifier);
    }

    AgentService(SystemEnvironment systemEnvironment, AgentInstances agentInstances,
                 EnvironmentConfigService environmentConfigService, SecurityService securityService, AgentDao agentDao, UuidGenerator uuidGenerator,
                 ServerHealthService serverHealthService, AgentStatusChangeNotifier agentStatusChangeNotifier, GoConfigService goConfigService) {
        this.systemEnvironment = systemEnvironment;
        this.environmentConfigService = environmentConfigService;
        this.securityService = securityService;
        this.agentInstances = agentInstances;
        this.agentDao = agentDao;
        this.uuidGenerator = uuidGenerator;
        this.serverHealthService = serverHealthService;
        this.agentStatusChangeNotifier = agentStatusChangeNotifier;
        this.goConfigService = goConfigService;
    }

    public void initialize() {
        this.sync();
        agentDao.registerListener(this);
    }

    private void sync() {
        agentInstances.sync(new Agents(agentDao.allAgents()));
    }

    public AgentInstances agentInstances() {
        return agentInstances;
    }

    public Agents agents() {
        List<AgentConfig> agentConfigs = agentInstances.values().stream().map(agentInstance -> agentInstance.agentConfig()).collect(Collectors.toList());
        return new Agents(agentConfigs);
    }

    public Map<AgentInstance, Collection<String>> agentsToEnvNameMap() {
        Map<AgentInstance, Collection<String>> allAgents = new LinkedHashMap<>();
        for (AgentInstance agentInstance : agentInstances.sort()) {
            TreeSet<String> sortedEnvSet = new TreeSet<>(new AlphaAsciiComparator());
            sortedEnvSet.addAll(agentInstance.agentConfig().getEnvironmentsAsList());
            allAgents.put(agentInstance, sortedEnvSet);
        }
        return allAgents;
    }

    public AgentsViewModel registeredAgents() {
        return toAgentViewModels(agentInstances.findRegisteredAgents());
    }

    private AgentsViewModel toAgentViewModels(AgentInstances instances) {
        AgentsViewModel agents = new AgentsViewModel();
        for (AgentInstance instance : instances) {
            agents.add(toAgentViewModel(instance));
        }
        return agents;
    }

    private AgentViewModel toAgentViewModel(AgentInstance instance) {
        return new AgentViewModel(instance, instance.agentConfig().getEnvironmentsAsList());
    }

    public AgentInstances findRegisteredAgents() {
        return agentInstances.findRegisteredAgents();
    }

    private boolean isUnknownAgent(AgentInstance agentInstance, OperationResult operationResult) {
        if (agentInstance.isNullAgent()) {
            String agentNotFoundMessage = String.format("Agent '%s' not found", agentInstance.getUuid());
            operationResult.notFound("Agent not found.", agentNotFoundMessage, HealthStateType.general(HealthStateScope.GLOBAL));
            return true;
        }
        return false;
    }

    private boolean hasOperatePermission(Username username, OperationResult operationResult) {
        if (!securityService.hasOperatePermissionForAgents(username)) {
            String message = "Unauthorized to operate on agent";
            operationResult.forbidden(message, message, HealthStateType.general(HealthStateScope.GLOBAL));
            return false;
        }
        return true;
    }

    public AgentInstance updateAgentAttributes(Username username, HttpOperationResult result, String uuid, String newHostname, String resources, String environments, TriState enable) {
        if (!hasOperatePermission(username, result)) {
            return null;
        }

        AgentInstance agentInstance = findAgent(uuid);
        if (isUnknownAgent(agentInstance, result)) {
            return null;
        }

        AgentConfig agentConfig = AgentConfig.newInstanceFrom(agentInstance.agentConfig());

        if (enable.isTrue()) {
            agentConfig.enable();
        }

        if (enable.isFalse()) {
            agentConfig.disable();
        }

        if (newHostname != null) {
            agentConfig.setHostname(newHostname);
        }

        if (resources != null) {
            agentConfig.setResources(new ResourceConfigs(resources));
        }

        if (environments != null) {
            agentConfig.setEnvironments(environments);
        }

        try {
            saveOrUpdate(agentConfig);

            if (agentConfig.hasErrors()) {
                result.unprocessibleEntity("Updating agent failed:", "", HealthStateType.general(HealthStateScope.GLOBAL));
            } else {
                result.ok(String.format("Updated agent with uuid %s.", agentConfig.getUuid()));
            }
        } catch (Exception e) {
            result.internalServerError("Updating agent failed: " + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
            return null;
        }

        if (agentConfig != null) {
            return AgentInstance.createFromConfig(agentConfig, systemEnvironment, agentStatusChangeNotifier);
        }
        return null;
    }

    public void bulkUpdateAgentAttributes(Username username, LocalizedOperationResult result, List<String> uuids,
                                          List<String> resourcesToAdd, List<String> resourcesToRemove,
                                          List<String> environmentsToAdd, List<String> environmentsToRemove,
                                          TriState enable) {
        AgentsUpdateValidator agentsUpdateValidator = new AgentsUpdateValidator(agentInstances,
                username, result, uuids, environmentsToAdd, environmentsToRemove, enable, resourcesToAdd, resourcesToRemove, goConfigService);
        try {
            if (agentsUpdateValidator.canContinue()) {
                agentsUpdateValidator.validate();
                agentDao.bulkUpdateAttributes(uuids, resourcesToAdd, resourcesToRemove, environmentsToAdd, environmentsToRemove, enable, agentInstances);
                result.setMessage("Updated agent(s) with uuid(s): [" + StringUtils.join(uuids, ", ") + "].");
            }
        } catch (Exception e) {
            LOGGER.error("There was an error bulk updating agents", e);
            if (e instanceof IllegalArgumentException && !result.hasMessage()) {
                result.unprocessableEntity(entityConfigValidationFailed(Agents.class.getAnnotation(ConfigTag.class).value(), StringUtils.join(uuids, ","), e.getMessage()));
            } else if (!result.hasMessage()) {
                result.internalServerError("Server error occured. Check log for details.");
            }
        }
    }

    private boolean populateAgentInstancesForUUIDs(OperationResult operationResult, List<String> uuids, List<AgentInstance> agents) {
        for (String uuid : uuids) {
            AgentInstance agentInstance = findAgentAndRefreshStatus(uuid);
            if (isUnknownAgent(agentInstance, operationResult)) {
                return false;
            }
            agents.add(agentInstance);
        }
        return true;
    }

    public void deleteAgents(Username username, HttpOperationResult operationResult, List<String> uuids) {
        if (!hasOperatePermission(username, operationResult)) {
            return;
        }
        List<AgentInstance> agents = new ArrayList<>();
        if (!populateAgentInstancesForUUIDs(operationResult, uuids, agents)) {
            return;
        }

        for (AgentInstance agentInstance : agents) {
            if (!agentInstance.canBeDeleted()) {
                operationResult.notAcceptable(String.format("Failed to delete %s agent(s), as agent(s) might not be disabled or are still building.", agents.size()),
                        HealthStateType.general(HealthStateScope.GLOBAL));
                return;
            }
        }

        try {
            List<AgentConfig> allAgents = filterAgents(uuids);
            if (allAgents.size() != uuids.size()) {
                List<String> uuidsOfAgentsInDatabase = allAgents.stream().map(agent -> agent.getUuid()).collect(Collectors.toList());
                List<String> nonExistentAgentIds = uuids.stream().filter(uuid -> !uuidsOfAgentsInDatabase.contains(uuid)).collect(Collectors.toList());
                //TODO : Revisit this for checking whether to throw this error
                if (!nonExistentAgentIds.isEmpty()) {
                    bomb("Unable to delete agent; Agent [" + nonExistentAgentIds + "] not found.");
                }
            }
            agentDao.bulkSoftDelete(uuids);
            operationResult.ok(String.format("Deleted %s agent(s).", agents.size()));
        } catch (Exception e) {
            operationResult.internalServerError("Deleting agents failed:" + e.getMessage(), HealthStateType.general(HealthStateScope.GLOBAL));
        }
    }

    private List<AgentConfig> filterAgents(List<String> uuids) {
        return agentInstances.values().stream().filter(agentInstance -> uuids.contains(agentInstance.getUuid()))
                .map(AgentInstance::agentConfig)
                .collect(Collectors.toList());
    }

    public void updateRuntimeInfo(AgentRuntimeInfo info) {
        if (!info.hasCookie()) {
            LOGGER.warn("Agent [{}] has no cookie set", info.agentInfoDebugString());
            throw new AgentNoCookieSetException(format("Agent [%s] has no cookie set", info.agentInfoDebugString()));
        }
        if (info.hasDuplicateCookie(agentDao.cookieFor(info.getIdentifier()))) {
            LOGGER.warn("Found agent [{}] with duplicate uuid. Please check the agent installation.", info.agentInfoDebugString());
            serverHealthService.update(
                    ServerHealthState.warning(format("[%s] has duplicate unique identifier which conflicts with [%s]", info.agentInfoForDisplay(), findAgentAndRefreshStatus(info.getUUId()).agentInfoForDisplay()),
                            "Please check the agent installation. Click <a href='" + docsUrl("/faq/agent_guid_issue.html") + "' target='_blank'>here</a> for more info.",
                            HealthStateType.duplicateAgent(HealthStateScope.forAgent(info.getCookie())), Timeout.THIRTY_SECONDS));
            throw new AgentWithDuplicateUUIDException(format("Agent [%s] has invalid cookie", info.agentInfoDebugString()));
        }
        AgentInstance agentInstance = findAgentAndRefreshStatus(info.getUUId());
        if (agentInstance.isIpChangeRequired(info.getIpAdress())) {
            AgentConfig agentConfig = agentInstance.agentConfig();
            LOGGER.warn("Agent with UUID [{}] changed IP Address from [{}] to [{}]", info.getUUId(), agentConfig.getIpaddress(), info.getIpAdress());
            String uuid = agentConfig.getUuid();

            AgentConfig agent = (agentInstance.isRegistered() ? agentConfig : null);
            bombIfNull(agent, "Unable to set agent ipAddress; Agent [" + uuid + "] not found.");
            agent.setIpaddress(info.getIpAdress());
            saveOrUpdate(agent);
        }
        agentInstances.updateAgentRuntimeInfo(info);
    }


    public Username agentUsername(String uuId, String ipAddress, String hostNameForDisplay) {
        return new Username(String.format("agent_%s_%s_%s", uuId, ipAddress, hostNameForDisplay));
    }

    public Registration requestRegistration(Username username, AgentRuntimeInfo agentRuntimeInfo) {
        LOGGER.debug("Agent is requesting registration {}", agentRuntimeInfo);
        AgentConfig agentConfigForValidation = agentRuntimeInfo.agent();
        agentConfigForValidation.validate(null);
        if (agentConfigForValidation.hasErrors()) {
            List<ConfigErrors> errors = ErrorCollector.getAllErrors(agentConfigForValidation);
            throw new GoConfigInvalidException(null, new AllConfigErrors(errors));
        }
        AgentInstance agentInstance = agentInstances.register(agentRuntimeInfo);
        Registration registration = agentInstance.assignCertification();
        if (agentInstance.isRegistered()) {
            AgentConfig agentConfig = agentInstance.agentConfig();
            if (agentConfig.getCookie() == null) {
                String cookie = uuidGenerator.randomUuid();
                agentConfig.setCookie(cookie);
            }
            saveOrUpdateAgentInstance(agentInstance, username);
            if (agentConfig.hasErrors()) {
                List<ConfigErrors> errors = ErrorCollector.getAllErrors(agentConfig);

                throw new GoConfigInvalidException(null, new AllConfigErrors(errors));
            }
            LOGGER.debug("New Agent approved {}", agentRuntimeInfo);
        }
        return registration;
    }

    public void updateAgentApprovalStatus(final String uuid, final Boolean isDenied, Username currentUser) {
        AgentInstance agentInstance = agentInstances.findAgent(uuid);
        AgentConfig agent = (agentInstance.isRegistered() ? agentInstance.agentConfig() : null);
        bombIfNull(agent, "Unable to update agent approval status; Agent [" + uuid + "] not found.");
        agent.setDisabled(isDenied);
        saveOrUpdate(agent);
    }

    public void saveOrUpdateAgentInstance(AgentInstance agentInstance, Username currentUser) {
{'fraction_non_alphanumeric': 0.09183322833743772, 'fraction_numerical': 0.0007445163507244717, 'mean_word_length': 3.286608567570885, 'pattern_counts': {'":': 0, '<': 62, '<?xml version=': 0, '>': 70, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3111574', 'n_tokens_mistral': 8667, 'n_tokens_neox': 8514, 'n_words': 1884}