sharpliner / sharpliner

Use C# instead of YAML to define your Azure DevOps pipelines
https://www.nuget.org/packages/Sharpliner/
MIT License
294 stars 22 forks source link

Schedules is written out as singular instead of plural #162

Closed ModernRonin closed 2 years ago

ModernRonin commented 2 years ago

Example:

public class CreatePulumiImagePipeline : SingleStagePipelineDefinition
{
    public override SingleStagePipeline Pipeline =>
        new()
        {
            Name = "CreatePulumiImages",
            Schedule = new List<ScheduledTrigger> { new("31 01 * * *") },
            Variables = { Group("CommonPipelineVariables") },
            Jobs = ...
        };
    public override string TargetFile => "../Pipelines/CreatePulumiImage.yml";
}

comes out as

name: CreatePulumiImages

schedule:                       # should be "schedules"
- cron: 31 01 * * *

variables:
- group: CommonPipelineVariables

jobs: ...

Thanks for this library! Before I tried Nuke, but that is really rather ugly. Conversely, Sharpliner has a much cleaner concept (generating yamls) and allows to describe ones pipelines very nicely.

premun commented 2 years ago

Thanks! This should be easy to fix. I can have a new version in an hour.

Thanks for the feedback as well - glad this library did some good. If there are any other friction points in the syntax or so, I would also like to hear about it!

ModernRonin commented 2 years ago

Hi,

I ain't got no friction points for you (yet ;P), but here's a piece of code for using the docker task. I am under pressure and don't have the time to do a proper PR, and I am also not sure how much this follows the general library style (simply because I've just started using it), but maybe it's of some help:

EDIT: a few things yet need generalization, this was just for my use-case:

and ofc, sorry, I didn't have the time to write an approvals test.

public class DockerBuildAndPushSteps : StepLibrary
{
    public override List<Conditioned<Step>> Steps =>
        new()
        {
            new Login(ServiceConnection).Step,
            new Build(ServiceConnection, Repository, Tags, DockerfilePath, Arguments).Step,
            new Push(ServiceConnection, Repository, Tags).Step,
            new Logout(ServiceConnection).Step
        };
    public IDictionary<string, string> Arguments { get; init; }
    public string DockerfilePath { get; init; }
    public string Repository { get; init; }

    public string ServiceConnection { get; init; }
    public string[] Tags { get; init; }

    record Build(string ServiceConnection, string Repository, string[] Tags, string DockerfilePath, IDictionary<string, string> Arguments)
        : RepositoryTask(ServiceConnection, Repository, Tags)
    {
        protected override string Command => "build";
        protected override string DisplayName => "build image";
        protected override ImmutableDictionary<string, object> Inputs
        {
            get
            {
                var args = string.Join(' ', Arguments.Select(toSecretArg));
                return base.Inputs.Add("Dockerfile", DockerfilePath).Add("arguments", args);

                string toSecretArg(KeyValuePair<string, string> kvp) => $"--build-arg {kvp.Key}={kvp.Value}";
            }
        }
    }

    abstract record DockerTask(string ServiceConnection)
    {
        public AzureDevOpsTask Step
        {
            get
            {
                var inputs = new TaskInputs();
                foreach (var (key, value) in Inputs.Add("command", Command).Add("containerRegistry", ServiceConnection))
                    inputs.Add(key, value);
                return new ***@***.***")
                {
                    DisplayName = DisplayName,
                    Inputs = inputs
                };
            }
       }
        protected virtual ImmutableDictionary<string, object> Inputs => ImmutableDictionary<string, object>.Empty;
        protected abstract string DisplayName { get; }
        protected abstract string Command { get; }
    }

    record Login(string ServiceConnection) : DockerTask(ServiceConnection)
    {
        protected override string Command => "login";
        protected override string DisplayName => "Login to ACR";
    }

    record Logout(string ServiceConnection) : DockerTask(ServiceConnection)
    {
        protected override string Command => "logout";
        protected override string DisplayName => "Logout from ACR";
    }

    record Push(string ServiceConnection, string Repository, string[] Tags) : RepositoryTask(ServiceConnection, Repository, Tags)
    {
        protected override string Command => "push";
        protected override string DisplayName => "push image";
    }

    abstract record RepositoryTask(string ServiceConnection, string Repository, string[] Tags) : DockerTask(ServiceConnection)
    {
        protected override ImmutableDictionary<string, object> Inputs =>
            ImmutableDictionary<string, object>.Empty.Add("repository", Repository).Add("tags", string.Join(Environment.NewLine, Tags));
    }
}

Usage like this:

StepLibrary(new DockerBuildAndPushSteps
            {
                ServiceConnection = "InternalRegistry",
                Repository = "tools/pulumi",
                Tags = new[] { "latest" },
                DockerfilePath = "$(Build.SourcesDirectory)/tools/docker/Dockerfile.Pulumi",
                Arguments = new Dictionary<string, string>
                {
                    ["AzureLoginApplicationId"] = "$(AzureLoginApplicationId)",
                    ["AzureLoginSecret"] = "$(AzureLoginSecret)",
                    ["AzureLoginTenantId"] = "$(AzureLoginTenantId)"
                }
            })

Cheers/Diky, Markus / MR

premun commented 2 years ago

Thanks for this! I will have a look at it later.

Meanwhile, there will be new version 1.2.5 in about 15 minutes on NuGet.org with the schedules fix