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

Conditional steps and menu choices? #11

Open
fabn opened this issue Aug 30, 2019 · 2 comments
Open

Conditional steps and menu choices? #11

fabn opened this issue Aug 30, 2019 · 2 comments

Comments

@fabn
Copy link

fabn commented Aug 30, 2019

I really like the approach of this gem but I'm wondering if there's a way to enable conditional steps or there's a way to present a menu to the user to choice from.

I.e. something like (pseudo code)

optional_step 'Do you want to do this?' do
  # if user choose yes this step is executed
end

choices 'Choose your fruit', from: %w(banana apple) do |f|
  case 'banana'
    ask ...
    command ...
  case 'apple'
    # something else
end

Since you're already using tty-prompt gem you have building blocks for that, so it should be a nice addition to this gem.

@fabn
Copy link
Author

fabn commented Aug 30, 2019

Currently I'm trying to configure a k8s cluster with this gem and I want to make a component optional. What I did until now is the following (just a proof of concept) but it doesn't follow the runbook approach and it doesn't work with auto mode obviously

  section "Additional tools" do
    step 'Install External DNS Chart' do
      ruby_command do
        prompt = TTY::Prompt.new
        if prompt.yes?('Install External DNS Chart?')
          options = case prompt.select("Choose your DNS Provider?", %w(DigitalOcean Cloudflare))
                    when 'DigitalOcean'
                      prompt.collect do
                        key('digitalocean.apiToken').ask('DigitalOcean Api Key:', required: true)
                      end

                    when 'Cloudflare'
                      prompt.collect do
                        key(:cloudflare) do
                          key(:email).ask('Cloudflare Email:', validate: :email, required: true)
                          key(:apiKey).ask('Cloudflare Api Key:', required: true)
                        end
                      end
                    else
                      raise 'Unnkown provider given'
                    end
          puts "DNS options: #{options.inspect}"
          command "helm install stable/external-dns #{dns_options}" # To be completed
        end
      end
    end
  end

It would be awesome to fully expose TTY::Prompt api in runbook, I don't know if it's possible.

@pblesi
Copy link
Contributor

pblesi commented Aug 30, 2019

Regarding your initial comment, this can be accomplished by extending the runbook DSL to include your own statements

Implementation for optional_step is essentially a mashup of the confirm and ruby_command statements. It would go something like this:

$ runbook generate statement optional_step --root lib/runbook/extensions

# lib/runbook/extensions/optional_step.rb
module Runbook::Statements
  class OptionalStep < Runbook::Statement
    attr_reader :prompt, :block

    def initialize(prompt, &block)
      @prompt = prompt
      @block = block
    end
  end
end

module RunbookOptionalStep
  module RunbookExtensions
    module OptionalStepMarkdown
      def runbook__statements__optional_step(object, output, metadata)
        # Format how your statement will be displayed when rendered with markdown
        output << "   if: #{object.prompt}\n"
        output << "   then run:\n"
        output << "   ```ruby\n"
        begin
          output << "#{deindent(object.block.source, padding: 3)}\n"
        rescue ::MethodSource::SourceNotFoundError => e
          output << "   Unable to retrieve source code\n"
        end
        output << "   ```\n\n"
      end
    end
    Runbook::Views::Markdown.singleton_class.prepend(OptionalStepMarkdown)

    module OptionalStepRun
      def runbook__statements__optional_step(object, metadata)
        auto = metadata[:auto]

        if metadata[:noop]
          metadata[:toolbox].output("[NOOP] Prompt: #{object.prompt}") unless auto
          metadata[:toolbox].output("[NOOP] #{auto ? "Run" : "If yes, run"} the following Ruby block:\n")
          begin
            source = deindent(object.block.source)
            metadata[:toolbox].output("```ruby\n#{source}\n```\n")
          rescue ::MethodSource::SourceNotFoundError => e
            metadata[:toolbox].output("Unable to retrieve source code")
          end
          return
        end

        if auto
          metadata[:toolbox].output("Skipping confirmation (auto): #{object.prompt}")
          # Not sure if the default should be execute or don't execute under auto
          result = true
        else
          result = metadata[:toolbox].yes?(object.prompt)
        end

        return unless result
        next_index = metadata[:index] + 1
        parent_items = object.parent.items
        remaining_items = parent_items.slice!(next_index..-1)
        object.parent.dsl.instance_exec(object, metadata, &object.block)
        parent_items[next_index..-1].each { |item| item.dynamic! }
        parent_items.push(*remaining_items)
      end
    end
    Runbook::Runs::SSHKit.singleton_class.prepend(OptionalStepRun)
  end
end

Remember to include this in your Runbookfile or equivalent config file.

This will allow you to execute a runbook like the following:

runbook = Runbook.book "Test Optional Step" do
  section "Test Optional Step" do
    step "Test me" do
      optional_step "Exec the following?" do
        puts "I've been executed"
      end
    end
  end
end

One of the main use cases for the ruby_command is a one-off way to define your own statements. The block has access to the ruby_command object and the metadata. This allows you to do pretty much anything with it that you can when defining your own statements. For example, you can do the following:

ruby_command do |rb_cmd, metadata|
  prompt = metadata[:toolbox].prompt
  if metadata[:auto] || prompt.yes?('Install External DNS Chart?')
  # ...
end

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

No branches or pull requests

2 participants