diff --git a/.gitmodules b/.gitmodules index 20e0b98686..a3c5d9a4dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/emojis"] path = vendor/emojis url = https://github.com/buildkite/emojis.git +[submodule "vendor/migration"] + path = vendor/migration + url = https://github.com/buildkite/migration diff --git a/Gemfile b/Gemfile index 9f6ac4c776..d8605732c6 100644 --- a/Gemfile +++ b/Gemfile @@ -79,3 +79,5 @@ end group :test do gem "buildkite-test_collector" end + +gem "parslet" diff --git a/Gemfile.lock b/Gemfile.lock index c5a439b522..ed88c47af4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,6 +81,7 @@ GEM nokogiri (1.15.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) + parslet (2.0.0) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) @@ -175,6 +176,7 @@ DEPENDENCIES graphql-client lograge matrix + parslet pry puma railties (~> 6.0) diff --git a/app/controllers/migration_controller.rb b/app/controllers/migration_controller.rb new file mode 100644 index 0000000000..9c966b5c22 --- /dev/null +++ b/app/controllers/migration_controller.rb @@ -0,0 +1,20 @@ +class MigrationController < ApplicationController + skip_forgery_protection + + def show + @nav = default_nav + + render template: "migrate", layout: "homepage" + end + + def migrate + # Some request path rewriting for the compat server to slot in. + request.env["REQUEST_PATH"] = "/" + request.env["REQUEST_URI"] = "/" + request.env["PATH_INFO"] = "/" + + res = BK::Compat::Server.new.call(request.env) + + render body: res[2].string, status: res[0], content_type: res[1]["content-type"] + end +end diff --git a/app/models/nav.rb b/app/models/nav.rb index 7c74adda07..ddcba649b5 100644 --- a/app/models/nav.rb +++ b/app/models/nav.rb @@ -15,12 +15,7 @@ def current_item_root(request) # Returns the current nav item def current_item(request) - return nil if request.path == "/docs" - - item = route_map[request.path.sub("/docs/", "")] - raise ActionController::RoutingError.new("Missing navigation for #{request.path}") unless item - - item + route_map[request.path.sub("/docs/", "")] end # Returns a hash of routes, indexed by path diff --git a/app/views/migrate.html.erb b/app/views/migrate.html.erb new file mode 100644 index 0000000000..ab2369446c --- /dev/null +++ b/app/views/migrate.html.erb @@ -0,0 +1,175 @@ +<style> + body { + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Helvetica, sans-serif; + padding: 0; + margin: 0; + } + .flex { + display: flex; + } + .flex-column { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + } + .flex-auto { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + } + .items-center { + align-items: center; + } + .items-stretch { + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + } + .justify-center { + justify-content: center; + } + .center { + text-align: center; + } + input[type=submit] { + border-radius: 4px; + background-color: #14cc80; + border: 1px solid transparent; + color: white; + font-size: 1em; + padding: .75em 1em; + line-height: 1.2; + font-weight: bold; + font-family: inherit; + cursor: pointer; + } + input[type=submit]:hover { + box-shadow: inset 0 0 0 20rem rgba(0, 0, 0, .0625); + } + input[type=submit]:disabled { + opacity: 0.5; + } + textarea { + display: block; + width: 100%; + padding: 15px; + font-size: 12px; + font-family: "SFMono-Regular", Monaco, Menlo, Consolas, "Liberation Mono", Courier, monospace; + line-height: 1.42857143; + color: #555555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: .3em; + resize: none; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + + /* disable iOS' extra inner shadows */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none + } + textarea:focus { + border-color: #888; + outline: 0; + } + textarea[readonly] { + background: #f9fafb; + } + .error { + background: #ff000044 !important; + } +</style> + + +<div class="flex items-stretch justify-center" style="min-height: 100%"> + <form onSubmit="return transform(event)" class="flex items-stretch center flex-column" style="width: 100%; padding: 5%;"> + <div class="flex flex-auto" style="margin-bottom: 20px;"> + <textarea rows="20" style="width: 50%; margin-right: 20px" placeholder="👋 Paste or drag a config file here!" id="config" class="flex-auto"></textarea> + <textarea rows="20" style="width: 50%" readonly="true" id="results" placeholder="👈 Look over there" class="flex-auto"></textarea> + </div> + <div> + <input type="submit" value="Buildkite-ify!" id="button" /> + </div> + </form> +</div> + +<script> + function transform() { + var button = document.getElementById("button"); + + var configTextarea = document.getElementById("config"); + var formData = new FormData(); + formData.append("file", new Blob([configTextarea.value], { type: "text/plain" })); + + button.disabled = true; + + var resultsTextarea = document.getElementById("results"); + + fetch("<%= migrate_url %>", { + method: "POST", + credentials: 'same-origin', + body: formData + }).then(function(response) { + button.disabled = false; + if (!response.ok) { + resultsTextarea.classList.add('error') + } else { + resultsTextarea.classList.remove('error') + } + return response; + }).then(function(response) { + response.text().then(function(text) { + resultsTextarea.value = text; + }); + }).catch((function(error) { + alert(error.message); + resultsTextarea.value = ''; + })); + + return false; + } + + function handleFormSubmit() { + event.preventDefault(); + + transform(); + } + + function handleFileSelect(evt) { + evt.stopPropagation(); + evt.preventDefault(); + + var file = evt.dataTransfer.files[0]; + + if (file) { + if (file.type == "application/x-yaml") { + var reader = new FileReader(); + + reader.onload = function(e) { + document.getElementById("config").value = e.target.result; + transform(); + }; + + reader.readAsText(file); + } else { + alert("Only YAML files are supported - you uploaded a: " + file.type); + } + } + } + + function handleDragOver(evt) { + evt.stopPropagation(); + evt.preventDefault(); + evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy. + } + + // Setup the dnd listeners. + var dropZone = document.body; + dropZone.addEventListener('dragover', handleDragOver, false); + dropZone.addEventListener('drop', handleFileSelect, false); +</script> diff --git a/config/initializers/migration.rb b/config/initializers/migration.rb new file mode 100644 index 0000000000..0756a728e5 --- /dev/null +++ b/config/initializers/migration.rb @@ -0,0 +1,2 @@ +require Rails.root.join('vendor/migration/app/lib/bk/compat').to_s +require Rails.root.join('vendor/migration/app/lib/bk/compat/server').to_s diff --git a/config/routes.rb b/config/routes.rb index 95b36f42df..020374fbcd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -143,6 +143,10 @@ # Homepage get "/docs" => "pages#index", as: :home_page + # Hosted migration/transform tool + get "/docs/migrate" => "migration#show" + post "/docs/migrate" => "migration#migrate", as: :migrate + # All other standard docs pages get "/docs/*path" => "pages#show", as: :docs_page diff --git a/vendor/migration b/vendor/migration new file mode 160000 index 0000000000..c9c42116eb --- /dev/null +++ b/vendor/migration @@ -0,0 +1 @@ +Subproject commit c9c42116eb9253b2a6d8d4e3674805c07b095205