Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding in RunAlways, run order, and ability to generate HTML Report #379

Merged
merged 12 commits into from
Nov 14, 2018
Merged

Adding in RunAlways, run order, and ability to generate HTML Report #379

merged 12 commits into from
Nov 14, 2018

Conversation

BobJWalker
Copy link
Contributor

@BobJWalker BobJWalker commented Oct 24, 2018

This pull request will add the following functionality to DbUp

Adding in Script Types

Currently, all scripts are Run Once. There are a lot of instances where it would be useful to have scripts which could always be run during a deployment. For example, you want to ensure a specific role always exists. Or you want to refresh all the views after a deployment so it gets any schema updates. Or you want to run a stored procedure to generate some same data.

A new enum called ScriptType has been added. At this point, it has two types of scripts, RunOnce and RunAlways. RunAlways scripts will always be run, even if there is an entry in the journal. RunOnce is the default and will continue to function as before. In the future, I plan on adding in "RunOnChanged." But that involved changing a lot more than I wanted to (plus I would need migration scripts to add that column to all the table types we support).

var upgradeEngineBuilder = DeployChanges.To
                            .SqlDatabase(connectionString, null) //null or "" for default schema for user
                            .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), script => script.StartsWith("SampleApplication.Scripts."), new SqlScriptOptions { ScriptType = ScriptType.RunOnce })
                            .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), script => script.StartsWith("SampleApplication.RunAlways."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways })
                            .LogToConsole();

The advantages of using a script type over say a null journal is these scripts will be recorded in the journal for each deployment and you combine RunOnce and RunAlways scripts together on the same deployment.

I added these as overloaded methods to the existing "WithScripts[xxx]" because I felt the script type should be defined when adding scripts from an assembly. You shouldn't do it after the fact because you would have to write a regular expression or something to match on the file name.

Run Group Order

By default, all scripts are run in alphabetical order. This works great when we only had to worry about run once scripts. With the addition of RunAlways scripts that adds a bit of complexity. Perhaps you have a set of scripts to run before a deployment and a set of scripts to run afterward. The run group order will take that into account and order by that first and then by alphabetical.

var upgradeEngineBuilder = DeployChanges.To
    .SqlDatabase(connectionString, null) //null or "" for default schema for user
    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), script => script.StartsWith("SampleApplication.PreDeployment."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 1})
    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), script => script.StartsWith("SampleApplication.Scripts."), new SqlScriptOptions { ScriptType = ScriptType.RunOnce, RunGroupOrder = 2})
    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), script => script.StartsWith("SampleApplication.PostDeployment."), new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 3})
    .LogToConsole();

Example:

  • PostDeployment
    • 0001_InsertDefaultData.sql
  • PreDeployment
    • 0001_CreateRolesIfNotExists.sql
  • Scripts
    • 0001_AddTable.sql
    • 0002_CreateStoredProcedure.sql

The order those scripts would be run in are:

  • 0001_CreateRolesIfNotExist.sql
  • 0001_AddTable.sql
  • 0002_CreateStoredProcedure.sql
  • 0001_InsertDefaultData.sql

HTML Reports

Two great features are provided by Octopus Deploy, artifacts and manual interventions. HTML reports will allow people to generate HTML reports and upload them to Octopus Deploy as an artifact so an approver in a manual intervention can review the changes prior to deploying them.

This was added as an extension method to the upgrade engine.

    var connectionString =
        args.FirstOrDefault()
        ?? "Server=(local)\\SqlExpress; Database=MyApp; Trusted_connection=true";

    var upgrader =
        DeployChanges.To
            .SqlDatabase(connectionString)
            .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
            .LogToConsole()
            .Build();

    // --generateReport is the name of the example argument.  You can call it anything
    if (args.Any(a => "--generateReport".Equals(a, StringComparison.InvariantCultureIgnoreCase))) 
    {
        upgrader.GenerateUpgradeHtmlReport("C:\\DeploymentLocation\\UpgradeReport.html");
    }
    else
    {
        var result = upgrader.PerformUpgrade();

        if (!result.Successful)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(result.Error);
            Console.ResetColor();
#if DEBUG
            Console.ReadLine();
#endif                
            return -1;
        }
    }


    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("Success!");
    Console.ResetColor();
    return 0;

samplehtmlreport

@dazinator
Copy link
Contributor

dazinator commented Oct 24, 2018

This looks very useful, especially the report, I was about to look at doing something like that myself!

My only thought really is about the Run Order api, it's a shame we can't just infer the order based on order of method calls via the API usage. i.e so you don't have to say runOrder: 1 , runOrder:2 etc. I would say can't we just increment that counter ourselves each time WithScripts is called, except I realise there can be cases where you want to include scripts from multiple sources / assemblies, but execute them all in one go / the same run Order so incrementing the run order automatically wouldn't be desirable in that scenario.

So with that in mind, I guess without some rethinking of the general API, having that parameter is the easiest path..

@BobJWalker
Copy link
Contributor Author

Thank you!

I was thinking the same thing on the runOrder. My initial version had it incrementally increasing a counter by one. I'd rather it be up to the developer than trying to figure it out. In addition, if you don't specify the run order then all the scripts are run in alphabetical order, which is the default behavior. My goal with all these changes it to keep the default behavior the same.

Also, for some reason, this test keeps failing.

image

I updated dbup-tests/approvalfiles/dbup-core.approved.cs to include the additional methods. Anything else I need to do? "It works on my machine" :)

@AdrianJSClark
Copy link
Member

The exception that is failing those tests looks like something is misconfigured in AppVeyor:
System.Exception : Could not find a diff program to use

I'm a little concerned that we are adding three new features ("Run Always" script type, "Ordering" of groups of scripts, HTML Reports) in one huge PR. It makes it difficult to tease apart the items and consider their impact individually. I don't want to discourage this great work, and apologies for flying by and only having time right now to look over this PR on the surface, but is there any chance of having these three features as three separate PRs? It would certainly make it less imposing for me personally to find time to properly review things.

@BobJWalker
Copy link
Contributor Author

The HTML report can be pulled into a separate PR. That is an extension class with some doco around it.

ScriptType and RunOrder are a little trickier to separate. Both are new properties on SqlScript. 95% of the changes in the PR is adding the appropriate overloaded methods to set those properties and documentation. Those two properties are only used in DefaultScriptFilter.cs and UpgradeEngine.cs. I could separate them out, but then you'd have two large PRs instead changing almost the exact same set of files instead of one :)

Copy link
Member

@jburger jburger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is heading a good direction.

The HTML report renders nicely (mostly) in Chrome, Firefox and Edge, and I like that this is a first class citizen with documentation.

I like the idea of RunAlways for idempotent tasks and that appears to work as intended.

I gave a quick look over the 'Add ScriptOrderer' PR, it appeared that the kind of script grouping proposed in this PR would be a little clunky to implement using the ScriptOrderer, but it has advantages for those who want fine control over script order. In short, I think both approaches could ultimately coexist without issue.

I'm concerned that the sample application we have is getting a bit overgrown now though, but that is probably best left for a different PR.

I've requested some minor changes, and I'd be happy to approve this once those things are addressed, provided there were no objections from others.

@jburger
Copy link
Member

jburger commented Oct 27, 2018

FYI, I've merged #380 so if you pull that into this PR, your build should pass.

</head>
<body>
<noscript>
<div class=""alert alert-danger"">& nbsp;JavaScript execution is currently disabled. Enable JavaScript on your browser to view the change report.</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div class=""alert alert-danger"">& nbsp;JavaScript execution is currently disabled. Enable JavaScript on your browser to view the change report.</div>
<div class=""alert alert-danger"">JavaScript execution is currently disabled. Enable JavaScript on your browser to view the change report.</div>

Copy link
Member

@jburger jburger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating this and addressing those comments. Just a final minor markup thing there - happy to approve this though. Thanks again @OctopusBob

@jburger jburger merged commit 976d91c into DbUp:master Nov 14, 2018
@rupoase
Copy link

rupoase commented Jan 17, 2020

Hello everyone,
I'm having an issue when I'm trying to use the Filter on script.StartsWith(string) and RunAlways ScriptType for the WithScriptsEmbeddedInAssembly method.
What I want is always use the last version of one stored procedure. Also, I want to always log to the journal in order to know what was the actual version of the file (lookup in Git). The problem is that DbUp tells me that there isn't any new scripts to execute:

Beginning database upgrade
Checking whether journal table exists..
Fetching list of already executed scripts.
No new scripts need to be executed - completing.

If I remove the filter, the script is picked up and executed against the database again, and logged into the journal.

var result = DeployChanges.To
                .SqlDatabase(connectionString)
                .WithScriptsEmbeddedInAssembly(
                    Assembly.GetEntryAssembly(),
                    (string script) => script.StartsWith("script"),
                    new SqlScriptOptions
                    {
                        ScriptType = ScriptType.RunAlways
                    }
                )
                .LogToConsole()
                .Build();

I'm using .NetCore 1.6 with .NetStandard 1.6 but I'll migrate to NetStandard 2.0 in order to be able to use .NetCore 2.1+

L.E. using Contains did the trick. So, please update the docs with the following information:
-> the filter needs the Fully Qualified Name. In my case I had the file nested inside a folder structure of the project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants