@@ -15,7 +15,6 @@ import org.cobbzilla.util.http.HttpRequestBean; | |||||
import org.cobbzilla.util.http.HttpResponseBean; | import org.cobbzilla.util.http.HttpResponseBean; | ||||
import org.cobbzilla.util.http.HttpUtil; | import org.cobbzilla.util.http.HttpUtil; | ||||
import java.io.IOException; | |||||
import java.util.*; | import java.util.*; | ||||
import java.util.concurrent.atomic.AtomicReference; | import java.util.concurrent.atomic.AtomicReference; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
@@ -32,6 +31,7 @@ import static org.cobbzilla.util.http.HttpMethods.PATCH; | |||||
import static org.cobbzilla.util.http.HttpMethods.PUT; | import static org.cobbzilla.util.http.HttpMethods.PUT; | ||||
import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; | import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||||
public class GoDaddyDnsDriver extends DnsDriverBase<GoDaddyDnsConfig> { | public class GoDaddyDnsDriver extends DnsDriverBase<GoDaddyDnsConfig> { | ||||
@@ -141,10 +141,6 @@ public class GoDaddyDnsDriver extends DnsDriverBase<GoDaddyDnsConfig> { | |||||
return response.isOk() ? response : die("remove: " + response); | return response.isOk() ? response : die("remove: " + response); | ||||
}, MAX_GODADDY_RETRIES); | }, MAX_GODADDY_RETRIES); | ||||
return record; | return record; | ||||
} catch (IOException e) { | |||||
return die("remove: "+e); | |||||
} finally { | } finally { | ||||
if (lock != null && domain.get() != null) unlockDomain(domain.get().getUuid(), lock); | if (lock != null && domain.get() != null) unlockDomain(domain.get().getUuid(), lock); | ||||
} | } | ||||
@@ -165,18 +161,21 @@ public class GoDaddyDnsDriver extends DnsDriverBase<GoDaddyDnsConfig> { | |||||
if (domain == null) return emptyList(); | if (domain == null) return emptyList(); | ||||
// iterate over all records, return matches | // iterate over all records, return matches | ||||
String url = config.getBaseUri()+domain.getName()+"/records"; | |||||
if (matcher != null) { | |||||
if (matcher.hasType()) { | |||||
url += "/" + matcher.getType().name(); | |||||
} | |||||
if (matcher.hasFqdn()) { | |||||
String fqdn = matcher.getFqdn(); | |||||
fqdn = domain.dropDomainSuffix(fqdn); | |||||
url += "/" + fqdn; | |||||
final var url = new StringBuilder(config.getBaseUri()).append(domain.getName()).append("/records"); | |||||
if (matcher != null && (matcher.hasType() || matcher.hasFqdn())) { | |||||
if (!matcher.hasType() || !matcher.hasPattern()) { | |||||
// as per GoDaddy's docs both type and fqdn must be set here | |||||
// https://developer.godaddy.com/doc/endpoint/domains#/v1/recordGet | |||||
throw invalidEx("err.request.invalid", "Both type and pattern are required"); | |||||
} | } | ||||
url.append("/").append(matcher.getType().name()); | |||||
var fqdn = matcher.getPattern(); | |||||
fqdn = domain.dropDomainSuffix(fqdn); | |||||
url.append("/").append(fqdn); | |||||
} | } | ||||
return readRecords(domain, url, matcher); | |||||
return readRecords(domain, url.toString(), matcher); | |||||
} | } | ||||
public Collection<DnsRecord> readRecords(BubbleDomain domain, String url, DnsRecordMatch matcher) { | public Collection<DnsRecord> readRecords(BubbleDomain domain, String url, DnsRecordMatch matcher) { | ||||
@@ -195,17 +194,17 @@ public class GoDaddyDnsDriver extends DnsDriverBase<GoDaddyDnsConfig> { | |||||
private final Map<String, GoDaddyDnsRecord[]> listCache = new ExpirationMap<>(SECONDS.toMillis(10)); | private final Map<String, GoDaddyDnsRecord[]> listCache = new ExpirationMap<>(SECONDS.toMillis(10)); | ||||
public GoDaddyDnsRecord[] listGoDaddyDnsRecords(String url) throws IOException { | |||||
final HttpRequestBean request = auth(url); | |||||
return listCache.computeIfAbsent(url, k -> { | |||||
public GoDaddyDnsRecord[] listGoDaddyDnsRecords(final String goDaddyApiUrl) { | |||||
return listCache.computeIfAbsent(goDaddyApiUrl, url -> { | |||||
final var request = auth(url); | |||||
final HttpResponseBean response; | final HttpResponseBean response; | ||||
try { | try { | ||||
response = HttpUtil.getResponse(request); | response = HttpUtil.getResponse(request); | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
log.error("listGoDaddyDnsRecords("+url+"): "+e); | |||||
log.error("listGoDaddyDnsRecords(" + url + "): " + e, e); | |||||
return GoDaddyDnsRecord.EMPTY_ARRAY; | return GoDaddyDnsRecord.EMPTY_ARRAY; | ||||
} | } | ||||
if (!response.isOk()) throw new IllegalStateException("readRecords: "+response); | |||||
if (!response.isOk()) throw new IllegalStateException("listGoDaddyDnsRecords: " + response); | |||||
return json(response.getEntityString(), GoDaddyDnsRecord[].class); | return json(response.getEntityString(), GoDaddyDnsRecord[].class); | ||||
}); | }); | ||||
} | } | ||||
@@ -18,10 +18,7 @@ import org.cobbzilla.util.dns.DnsRecord; | |||||
import org.cobbzilla.util.dns.DnsRecordMatch; | import org.cobbzilla.util.dns.DnsRecordMatch; | ||||
import org.cobbzilla.util.dns.DnsType; | import org.cobbzilla.util.dns.DnsType; | ||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.*; | |||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import static java.util.Collections.emptyList; | import static java.util.Collections.emptyList; | ||||
@@ -57,15 +54,20 @@ public class Route53DnsDriver extends DnsDriverBase<Route53DnsConfig> { | |||||
@Getter(lazy=true) private final Map<String, HostedZone> cachedZoneLookups = new ExpirationMap<>(); | @Getter(lazy=true) private final Map<String, HostedZone> cachedZoneLookups = new ExpirationMap<>(); | ||||
private HostedZone getHostedZone(BubbleDomain domain) { | private HostedZone getHostedZone(BubbleDomain domain) { | ||||
return getCachedZoneLookups().computeIfAbsent(domain.getName(), key -> { | return getCachedZoneLookups().computeIfAbsent(domain.getName(), key -> { | ||||
final var keyDot = key + "."; | |||||
final Optional<HostedZone> found; | |||||
try { | try { | ||||
final ListHostedZonesResult zones = getRoute53client().listHostedZones(new ListHostedZonesRequest().withMaxItems(MAX_ITEMS)); | |||||
for (HostedZone z : zones.getHostedZones()) { | |||||
if (z.getName().equalsIgnoreCase(key + ".")) return z; | |||||
} | |||||
return die("HostedZone with name '"+key+".' not found"); | |||||
found = getRoute53client().listHostedZones(new ListHostedZonesRequest().withMaxItems(MAX_ITEMS)) | |||||
.getHostedZones() | |||||
.stream() | |||||
.filter(z -> z.getName().equalsIgnoreCase(keyDot)) | |||||
.findFirst(); | |||||
} catch (Exception e) { | } catch (Exception e) { | ||||
return die("getHostedZone: "+e); | |||||
return die("getHostedZone: " + e); | |||||
} | } | ||||
return found.isPresent() ? found.get() : die("getHostedZone: HostedZone not found with name: " + keyDot); | |||||
}); | }); | ||||
} | } | ||||
@@ -165,57 +165,36 @@ | |||||
}, | }, | ||||
{ | { | ||||
"comment": "list DNS for the network, should now see a DNS A record for new instance", | |||||
"before": "await_url me/networks/<<network>>/dns/find?type=A&name=.<<network>>.<<domain>> 40m 10s await_json.length > 0", | |||||
"request": { | |||||
"uri": "me/networks/<<network>>/dns/find?type=A&name=.<<network>>.<<domain>>" | |||||
}, | |||||
"response": { | |||||
"store": "dnsRecords", | |||||
"check": [ | |||||
{"condition": "json.length == 1"} | |||||
] | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "call API of deployed node, ensure it is running", | |||||
"before": "await_url .bubble 40m 20s", | |||||
"comment": "call API of deployed node after some grace period, ensure it is running", | |||||
"before": "await_url .bubble 20m:40m 20s", | |||||
"connection": { | "connection": { | ||||
"name": "<<bubbleConnectionVar>>", | "name": "<<bubbleConnectionVar>>", | ||||
"baseUri": "https://{{<<networkVar>>.host}}.<<network>>.<<domain>>:{{serverConfig.nginxPort}}/api" | "baseUri": "https://{{<<networkVar>>.host}}.<<network>>.<<domain>>:{{serverConfig.nginxPort}}/api" | ||||
}, | }, | ||||
"request": { "uri" : ".bubble" }, | "request": { "uri" : ".bubble" }, | ||||
"response": { | |||||
"raw": true, | |||||
"check": [ | |||||
{"condition": "response.json == 'you are ok. the magic is ok too.'"} | |||||
] | |||||
} | |||||
"response": { "raw": true, "check": [{ "condition": "response.json == 'you are ok. the magic is ok too.'" }] } | |||||
}, | }, | ||||
// { | |||||
// "comment": "verify new node has said hello, and requested another node", | |||||
// "connection": { "name": "<<sageFqdn>>_connection" }, | |||||
// "request": { | |||||
// "session": "rootSession", | |||||
// "uri": "me/notifications/inbox" | |||||
// }, | |||||
// "response": { | |||||
// "check": [ | |||||
// {"condition": "json.length >= 2"}, | |||||
// {"condition": "_find(json, function (n) { n.getType().name() == 'upstream_hello' }) != null"}, | |||||
// {"condition": "_find(json, function (n) { n.getType().name() == 'new_node' }) != null"} | |||||
// ] | |||||
// } | |||||
// }, | |||||
{ | |||||
"comment": "now list DNS for the network, should now see a DNS A record for new instance", | |||||
"connection": { "name": "<<sageConnectionVar>>" }, | |||||
"request": { "uri": "me/networks/<<network>>/dns/find?type=A&name={{<<networkVar>>.host}}.<<network>>.<<domain>>" }, | |||||
"response": { "store": "dnsRecords", "check": [{ "condition": "json.length == 1" }] } | |||||
}, | |||||
{ | { | ||||
"comment": "login to deployed node", | |||||
"comment": "check unauthorized access to debug mailbox required for this test (BUBBLE_TEST_MODE has to be true)", | |||||
"connection": { "name": "<<bubbleConnectionVar>>" }, | "connection": { "name": "<<bubbleConnectionVar>>" }, | ||||
"request": { "uri": "debug/inbox/email/<<email>>?type=request&action=verify&target=network" }, | |||||
"response": { "status": 200 }, // confirming status is not 401 here | |||||
"after": "await_url debug/inbox/email/<<email>>?type=request&action=verify&target=network 10m 10s len(await_json) > 0" | |||||
}, | |||||
{ | |||||
"comment": "activate and login to deployed node", | |||||
"request": { | "request": { | ||||
"session": "new", | "session": "new", | ||||
"uri" : "auth/login", | |||||
"uri" : "auth/login?k={{await_json.[0].ctx.message.data}}", | |||||
"entity": { | "entity": { | ||||
"name": "<<username>>", | "name": "<<username>>", | ||||
"password": "<<password>>" | "password": "<<password>>" | ||||
@@ -54,8 +54,7 @@ | |||||
}, | }, | ||||
{ | { | ||||
"comment": "add file to storage", | |||||
"before": "await_url .bubble 40m 20s", | |||||
"comment": "add test file to storage", | |||||
"connection": { "name": "bubbleConnection" }, | "connection": { "name": "bubbleConnection" }, | ||||
"request": { | "request": { | ||||
"session": "bubbleUserSession", | "session": "bubbleUserSession", | ||||