0a58af807b
We have access to c++17, so use it. As a result, replace boost::optional with std::optional. Change-Id: Iba59bfd516264e817f2da14ded47d4d63a37e399
192 lines
5.7 KiB
C++
192 lines
5.7 KiB
C++
/* -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
|
|
* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
|
|
*
|
|
* Copyright (C) 2019 Red Hat, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* This program reads one line at a time on standard input, expecting
|
|
* a specially formatted hostname from Apache's RewriteMap and uses
|
|
* that to look up a build URL which it emits on standard output.
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
#include <cpprest/http_client.h>
|
|
#include <bits/stdc++.h>
|
|
|
|
using namespace std;
|
|
|
|
vector<string> split(const string &in, char delim)
|
|
{
|
|
istringstream stream(in);
|
|
vector<string> parts;
|
|
string part;
|
|
while (getline(stream, part, delim)) {
|
|
parts.push_back(part);
|
|
}
|
|
return parts;
|
|
}
|
|
|
|
// An LRU cache of hostname->URL mappings.
|
|
class Cache {
|
|
// A queue of hostname, URL pairs. The head of the queue is always
|
|
// the most recently accessed entry, the tail is the least.
|
|
list<pair<const string, const string>> queue;
|
|
|
|
// A map of hostname -> iterator that points into the queue, for
|
|
// quick lookup.
|
|
unordered_map<string, list<pair<const string, const string>>::iterator> map;
|
|
|
|
// The maximum size of the cache.
|
|
const uint32_t size;
|
|
|
|
public:
|
|
Cache(uint s)
|
|
: queue {}, map {}, size{s}
|
|
{ }
|
|
|
|
// Lookup the hostname in the cache and return the URL if present.
|
|
// If the entry is present, it is moved to the head of the queue.
|
|
std::optional<const string> get(const string &key)
|
|
{
|
|
auto location = map.find(key);
|
|
if (location == map.end())
|
|
return {};
|
|
|
|
auto val = *(location->second);
|
|
queue.splice(queue.begin(), queue, location->second);
|
|
return val.second;
|
|
}
|
|
|
|
// Add an entry to the cache. If the cache is full, drop the least
|
|
// recently used entry.
|
|
void put(const string &key, const string &value)
|
|
{
|
|
auto location = map.find(key);
|
|
if (location != map.end())
|
|
return;
|
|
|
|
if (queue.size() == size) {
|
|
auto last = queue.back();
|
|
queue.pop_back();
|
|
map.erase(last.first);
|
|
}
|
|
|
|
queue.push_front(make_pair(key, value));
|
|
map[key] = queue.begin();
|
|
}
|
|
};
|
|
|
|
class ClientCache {
|
|
unordered_map<string, web::http::client::http_client> clients;
|
|
public:
|
|
ClientCache() : clients{} { }
|
|
|
|
web::http::client::http_client get(const string &key)
|
|
{
|
|
auto location = clients.find(key);
|
|
if (location == clients.end()) {
|
|
auto value = web::http::client::http_client(key);
|
|
clients.insert(make_pair(key, value));
|
|
return value;
|
|
}
|
|
return location->second;
|
|
}
|
|
};
|
|
|
|
int main(int, char**)
|
|
{
|
|
string input;
|
|
Cache cache{1024};
|
|
ClientCache clients;
|
|
|
|
// For each request apache receieves, it sends us the HTTP host name
|
|
// on standard input. We use that to look up the build URL and emit
|
|
// it on standard output. Apache will send us one request at a time
|
|
// (protected by an internal mutex) and expect exactly one line of
|
|
// output for each.
|
|
// Expected input:
|
|
// https://zuul.opendev.org site.167715b656ee4504baa940c5bd9f3821.openstack.preview.opendev.org
|
|
// https://zuul.opendev.org javascript_content.7cf36db4b8574ef6a5ba1a64cb827741.zuul.preview.opendev.org
|
|
while (getline(cin, input)) {
|
|
|
|
// Split the input into api_url, hostname
|
|
auto parts = split(input, ' ');
|
|
if (parts.size() != 2) {
|
|
cout << "NULL" << endl;
|
|
continue;
|
|
}
|
|
auto api_url = parts[0];
|
|
auto hostname = parts[1];
|
|
|
|
// If we have the value in the cache, return it.
|
|
if (auto val = cache.get(hostname)) {
|
|
cout << val.value() << endl;
|
|
continue;
|
|
}
|
|
|
|
// We use the first three parts of the hostname to look up the
|
|
// build url.
|
|
parts = split(hostname, '.');
|
|
if (parts.size() < 3) {
|
|
cout << "NULL" << endl;
|
|
continue;
|
|
}
|
|
auto artifact = parts[0];
|
|
auto buildid = parts[1];
|
|
auto tenant = parts[2];
|
|
|
|
try {
|
|
// Use the Zuul API to look up the artifact URL.
|
|
|
|
auto client = clients.get(api_url);
|
|
auto uri = web::uri_builder("api/tenant");
|
|
uri.append_path(tenant);
|
|
uri.append_path("build");
|
|
uri.append_path(buildid);
|
|
auto response = client.request(
|
|
web::http::methods::GET, uri.to_string()).get();
|
|
// body is a web::json::value
|
|
auto body = response.extract_json().get();
|
|
auto artifacts = body["artifacts"].as_array();
|
|
|
|
string artifact_url = "NULL";
|
|
for (uint i = 0; i < artifacts.size(); i++) {
|
|
if (artifacts[i].has_field("metadata") &&
|
|
artifacts[i]["metadata"].has_field("type") &&
|
|
artifacts[i]["metadata"]["type"].as_string() == artifact &&
|
|
artifacts[i].has_field("url")) {
|
|
artifact_url = artifacts[i]["url"].as_string();
|
|
}
|
|
}
|
|
|
|
// The apache config is guaranteed to add a / to this, so avoid
|
|
// double slashes on the end.
|
|
if (artifact_url.back() == '/') {
|
|
artifact_url.pop_back();
|
|
}
|
|
cout << artifact_url << endl;
|
|
|
|
cache.put(hostname, artifact_url);
|
|
} catch (...) {
|
|
// If anything goes wrong, we still need to return only a single
|
|
// string to apache, and recover for the next request, so we
|
|
// have a general exception handler here.
|
|
cout << "NULL" << endl;
|
|
}
|
|
}
|
|
}
|