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

Need to inject custom HTTP headers into PactBrokerClient #1835

Open
mickaeltavares opened this issue Oct 29, 2024 · 1 comment
Open

Need to inject custom HTTP headers into PactBrokerClient #1835

mickaeltavares opened this issue Oct 29, 2024 · 1 comment
Labels
enhancement Indicates new feature requests

Comments

@mickaeltavares
Copy link

mickaeltavares commented Oct 29, 2024

In my company, we've been trying to use a Pactbroker hosted on private infrastructure, which is protected behind a Cloudflare Zero trust Gateway (using this is a hard constraint).

This means that in order to access our pactbroker from our tests (written in Kotlin, SpringBoot/Junit5) we need to set up a header like this : "cf-access-token=<CF_AUTHORIZATION_COOKIE>"

Sadly, there is no easy way to add such custom HTTP header in the @PactBroker annotation, and the header is needed in all HTTP interactions with the broker.

Here is our current solution, but I find it "hacky" and I hope there is a better and simpler way to do this?

  1. First, I had to redefine an annotation MyPactBroker to add the customHttpHeaders option :
@PactSource(MyPactBrokerLoader.class)
@Inherited
public @interface MyPactBroker {
  // The same properties as PactBroker were duplicated here
  String[] customHttpHeaders() default "${pactbroker.customHttpHeaders:}";
}

and use it like this:

@MyPactBroker(
    url = "https://pactbroker.mycompany.net",
    customHttpHeaders = ["cf-access-token:OUR_CF_TOKEN"])
class ContractVerificationTest { ... }
  1. Then we had to override the PactBrokerLoader with our own to redefine the function newPactBrokerClient:
class MyPactBrokerLoader(pactBroker: MyPactBroker) :
    PactBrokerLoader(
        pactBroker.host,
        pactBroker.port,
        pactBroker.scheme,
        pactBroker.tags.toList(),
        pactBroker.consumerVersionSelectors.toList(),
        pactBroker.consumers.toList(),
        true,
        pactBroker.authentication,
        pactBroker.valueResolver,
        null,
        pactBroker.enablePendingPacts,
        pactBroker.providerTags.toList(),
        pactBroker.providerBranch,
        pactBroker.includeWipPactsSince,
        pactBroker.url,
        pactBroker.enableInsecureTls),
    PactLoader {

  private var customHttpHeaders: Array<String> = emptyArray()

  init {
    this.customHttpHeaders = pactBroker.customHttpHeaders
    this.pactReader = MyPactReader
  }

  override fun newPactBrokerClient(url: URI, resolver: ValueResolver): IPactBrokerClient {
    var options = mapOf<String, Any>()
    val insecureTls = ep.parseExpression(enableInsecureTls, DataType.BOOLEAN, resolver) as Boolean
    val config = PactBrokerClientConfig(insecureTLS = insecureTls)

    // Same code as in the library

    val customHeaders =
        customHttpHeaders
            .mapNotNull {
              val parts = it.split(":")
              if (parts.size == 2 && parts[0].isNotEmpty() && parts[1].isNotEmpty()) {
                parts[0] to parts[1]
              } else {
                logger.warn { "Ignoring ill-formatted custom HTTP header: $it" }
                null
              }
            }
            .toMap()
    options = options.plus("customHeaders" to customHeaders)

    return MyPactBrokerClient(url.toString(), options.toMutableMap(), config)
  }
}
  1. Then we had to override PactBrokerClient#newHalClient to use those new options:
class MyPactBrokerClient(
    pactBrokerUrl: String,
    options: MutableMap<String, Any>,
    config: PactBrokerClientConfig
) : PactBrokerClient(pactBrokerUrl, options, config), IPactBrokerClient {
  override fun newHalClient(): IHalClient {
    return MyHalClient(pactBrokerUrl, options, config)
  }
}
class MyHalClient(
    baseUrl: String,
    options: Map<String, Any> = mapOf(),
    config: PactBrokerClientConfig
) : HalClient(baseUrl, options, config), IHalClient {
  init {
    defaultHeaders.put(
        "cf-access-token", (options["customHeaders"] as Map<String, String>)["cf-access-token"]!!)
  }
}
  1. And finally, we had to override PactReader#loadPactFromUrl and slightly change the implementation of DefaultPactReader#loadFile to use the new loadPactFromUrl.
    (Sadly, DefaultPactReader is an object, so it cannot be extended for overrides, and we had to duplicate everything...)
fun loadPactFromUrl(
  source: UrlPactSource,
  options: Map<String, Any>,
  http: CloseableHttpClient
): Pair<JsonValue.Object, PactSource> {
  return when (source) {
    is BrokerUrlSource -> {
      val insecureTLS = Utils.lookupInMap(options, "insecureTLS", Boolean::class.java, false)
-     val brokerClient = PactBrokerClient(source.pactBrokerUrl, options.toMutableMap(), PactBrokerClientConfig(insecureTLS = insecureTLS))
+     val brokerClient = MyPactBrokerClient(source.pactBrokerUrl, options.toMutableMap(), PactBrokerClientConfig(insecureTLS = insecureTLS))
      val pactResponse = brokerClient.fetchPact(source.url, source.encodePath)
      pactResponse.pactFile to source.copy(attributes = pactResponse.links, options = options, tag = source.tag)
    }
    else -> when (val jsonResource = fetchJsonResource(http, source)) {
      is Result.Ok -> if (jsonResource.value.first is JsonValue.Object) {
        jsonResource.value.first.asObject()!! to jsonResource.value.second
      } else {
        throw UnsupportedOperationException("Was expected a JSON document, got ${jsonResource.value}")
      }
      is Result.Err -> throw jsonResource.error
    }
  }
}

Ultimately, it is a lot of duplicated code just to add one HTTP header. I believe other users will also need to add custom HTTP headers at some point.
Did I miss something already implemented?
If you feel like it could be easily solved by updating the library itself, I would be happy to open a PR myself. What do you think?

@mefellows mefellows added the enhancement Indicates new feature requests label Oct 29, 2024
@rholshausen
Copy link
Contributor

A PR would be awesome!

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

No branches or pull requests

3 participants