Esempio n. 1
0
        protected override void ValidateParameters()
        {
            if (string.IsNullOrWhiteSpace(ProjectName))
            {
                throw new CommandException("Please specify a project name using the parameter: --project=XYZ");
            }
            if (IsTenantedDeployment && DeployToEnvironmentNames.Count > 1)
            {
                throw new CommandException("Please specify only one environment at a time when deploying to tenants.");
            }
            if (Tenants.Contains("*") && (Tenants.Count > 1 || TenantTags.Count > 0))
            {
                throw new CommandException("When deploying to all tenants using --tenant=* wildcard no other tenant filters can be provided");
            }

            if (IsTenantedDeployment && !Repository.SupportsTenants())
            {
                throw new CommandException("Your Octopus server does not support tenants, which was introduced in Octopus 3.4. Please upgrade your Octopus server, enable the multi-tenancy feature or remove the --tenant and --tenanttag arguments.");
            }

            base.ValidateParameters();
        }
Esempio n. 2
0
        private async Task <List <TenantResource> > GetTenants(ProjectResource project, string environmentName, ReleaseResource release,
                                                               DeploymentTemplateResource releaseTemplate)
        {
            if (!Tenants.Any() && !TenantTags.Any())
            {
                return(new List <TenantResource>());
            }

            var deployableTenants = new List <TenantResource>();

            if (Tenants.Contains("*"))
            {
                var tenantPromotions = releaseTemplate.TenantPromotions.Where(
                    tp => tp.PromoteTo.Any(
                        promo => promo.Name.Equals(environmentName, StringComparison.CurrentCultureIgnoreCase))).Select(tp => tp.Id).ToArray();

                var tentats = await Repository.Tenants.Get(tenantPromotions).ConfigureAwait(false);

                deployableTenants.AddRange(tentats);

                Log.Information("Found {NumberOfTenants} Tenants who can deploy {Project:l} {Version:l} to {Environment:l}", deployableTenants.Count, project.Name, release.Version, environmentName);
            }
            else
            {
                if (Tenants.Any())
                {
                    var tenantsByName = await Repository.Tenants.FindByNames(Tenants).ConfigureAwait(false);

                    var missing = tenantsByName == null || !tenantsByName.Any()
                        ? Tenants.ToArray()
                        : Tenants.Except(tenantsByName.Select(e => e.Name), StringComparer.OrdinalIgnoreCase).ToArray();

                    var tenantsById = await Repository.Tenants.Get(missing).ConfigureAwait(false);

                    missing = tenantsById == null || !tenantsById.Any()
                        ? missing
                        : missing.Except(tenantsById.Select(e => e.Id), StringComparer.OrdinalIgnoreCase).ToArray();

                    if (missing.Any())
                    {
                        throw new ArgumentException(
                                  $"Could not find the {"tenant" + (missing.Length == 1 ? "" : "s")} {string.Join(", ", missing)} on the Octopus server.");
                    }

                    deployableTenants.AddRange(tenantsByName);
                    deployableTenants.AddRange(tenantsById);

                    var unDeployableTenants =
                        deployableTenants.Where(dt => !dt.ProjectEnvironments.ContainsKey(project.Id))
                        .Select(dt => $"'{dt.Name}'")
                        .ToList();
                    if (unDeployableTenants.Any())
                    {
                        throw new CommandException(
                                  string.Format(
                                      "Release '{0}' of project '{1}' cannot be deployed for tenant{2} {3}. This may be because either a) {4} not connected to this project, or b) you do not have permission to deploy {5} to this project.",
                                      release.Version,
                                      project.Name,
                                      unDeployableTenants.Count == 1 ? "" : "s",
                                      string.Join(" or ", unDeployableTenants),
                                      unDeployableTenants.Count == 1 ? "it is" : "they are",
                                      unDeployableTenants.Count == 1 ? "it" : "them"));
                    }

                    unDeployableTenants = deployableTenants.Where(dt =>
                    {
                        var tenantPromo = releaseTemplate.TenantPromotions.FirstOrDefault(tp => tp.Id == dt.Id);
                        return(tenantPromo == null ||
                               !tenantPromo.PromoteTo.Any(
                                   tdt => tdt.Name.Equals(environmentName, StringComparison.CurrentCultureIgnoreCase)));
                    }).Select(dt => $"'{dt.Name}'").ToList();
                    if (unDeployableTenants.Any())
                    {
                        throw new CommandException(
                                  string.Format(
                                      "Release '{0}' of project '{1}' cannot be deployed for tenant{2} {3} to environment '{4}'. This may be because a) the tenant{2} {5} not connected to this environment, a) the environment does not exist or is misspelled, b) The lifecycle has not reached this phase, possibly due to previous deployment failure,  c) you don't have permission to deploy to this environment, d) the environment is not in the list of environments defined by the lifecycle, or e) {6} unable to deploy to this channel.",
                                      release.Version,
                                      project.Name,
                                      unDeployableTenants.Count == 1 ? "" : "s",
                                      string.Join(" or ", unDeployableTenants),
                                      environmentName,
                                      unDeployableTenants.Count == 1 ? "is" : "are",
                                      unDeployableTenants.Count == 1 ? "it is" : "they are"));
                    }
                }

                if (TenantTags.Any())
                {
                    var tenantsByTag = await Repository.Tenants.FindAll(null, TenantTags.ToArray()).ConfigureAwait(false);

                    var deployableByTag = tenantsByTag.Where(dt =>
                    {
                        var tenantPromo = releaseTemplate.TenantPromotions.FirstOrDefault(tp => tp.Id == dt.Id);
                        return(tenantPromo != null && tenantPromo.PromoteTo.Any(tdt => tdt.Name.Equals(environmentName, StringComparison.CurrentCultureIgnoreCase)));
                    }).Where(tenant => !deployableTenants.Any(deployable => deployable.Id == tenant.Id));
                    deployableTenants.AddRange(deployableByTag);
                }
            }

            if (!deployableTenants.Any())
            {
                throw new CommandException(
                          string.Format(
                              "No tenants are available to be deployed for release '{0}' of project '{1}' to environment '{2}'.  This may be because a) No tenants matched the tags provided b) The tenants that do match are not connected to this project or environment, c) The tenants that do match are not yet able to release to this lifecycle phase, or d) you do not have the appropriate deployment permissions.",
                              release.Version, project.Name, environmentName));
            }


            return(deployableTenants);
        }
Esempio n. 3
0
        protected override async Task ValidateParameters()
        {
            if (string.IsNullOrWhiteSpace(ProjectName))
            {
                throw new CommandException("Please specify a project name using the parameter: --project=XYZ");
            }
            if (IsTenantedDeployment && DeployToEnvironmentNames.Count > 1)
            {
                throw new CommandException("Please specify only one environment at a time when deploying to tenants.");
            }
            if (Tenants.Contains("*") && (Tenants.Count > 1 || TenantTags.Count > 0))
            {
                throw new CommandException("When deploying to all tenants using --tenant=* wildcard no other tenant filters can be provided");
            }
            if (IsTenantedDeployment && !await Repository.SupportsTenants().ConfigureAwait(false))
            {
                throw new CommandException("Your Octopus Server does not support tenants, which was introduced in Octopus 3.4. Please upgrade your Octopus Server, enable the multi-tenancy feature or remove the --tenant and --tenanttag arguments.");
            }
            if ((DeployAt ?? DateTimeOffset.Now) > NoDeployAfter)
            {
                throw new CommandException("The deployment will expire before it has a chance to execute.  Please select an expiry time that occurs after the deployment is scheduled to begin");
            }

            /*
             * A create release operation can also optionally deploy the release, however any invalid options that
             * are specific only to the deployment will fail after the release has been created. This can leave
             * a deployment in a half finished state, so this validation ensures that the input relating to the
             * deployment is valid so missing or incorrect input doesn't stop stop the deployment after a release is
             * created.
             *
             * Note that certain validations still need to be done on the server. Permissions and lifecycle progression
             * still rely on server side validation.
             */

            // We might query the same tagset repeatedly, so store old queries here
            var tagSetResources = new Dictionary <string, TagSetResource>();

            // Make sure the tags are valid
            foreach (var tenantTag in TenantTags)
            {
                // Verify the format of the tag
                var parts = tenantTag.Split(Separator);
                if (parts.Length != 2 || string.IsNullOrEmpty(parts[0]) || string.IsNullOrEmpty(parts[1]))
                {
                    throw new CommandException(
                              $"Canonical Tag Name expected in the format of `TagSetName{Separator}TagName`");
                }

                // Query the api if the results were not previously found
                if (!tagSetResources.ContainsKey(parts[0]))
                {
                    tagSetResources.Add(parts[0], await Repository.TagSets.FindByName(parts[0]).ConfigureAwait(false));
                }

                // Verify the presence of the tag
                if (tagSetResources[parts[0]]?.Tags?.All(tag => parts[1] != tag.Name) ?? true)
                {
                    throw new CommandException(
                              $"Unable to find matching tag from canonical tag name `{tenantTag}`");
                }
            }

            // Make sure the tenants are valid
            foreach (var tenantName in Tenants)
            {
                if (tenantName != "*")
                {
                    var tenant = await Repository.Tenants.FindByName(tenantName).ConfigureAwait(false);

                    if (tenant == null)
                    {
                        throw new CommandException(
                                  $"Could not find the tenant {tenantName} on the Octopus Server");
                    }
                }
            }

            // Make sure environment is valid
            var environments = await Repository.Environments.FindByNames(DeployToEnvironmentNames).ConfigureAwait(false);

            var missingEnvironment = DeployToEnvironmentNames
                                     .Where(env => environments.All(env2 => !env2.Name.Equals(env, StringComparison.OrdinalIgnoreCase)))
                                     .ToList();

            if (missingEnvironment.Count != 0)
            {
                throw new CommandException(
                          $"The environment{(missingEnvironment.Count == 1 ? "" : "s")} {string.Join(", ", missingEnvironment)} " +
                          $"do{(missingEnvironment.Count == 1 ? "es" : "")} not exist or {(missingEnvironment.Count == 1 ? "is" : "are")} misspelled");
            }


            // Make sure the machines are valid
            await GetSpecificMachines();

            await base.ValidateParameters();
        }