octokit / go-sdk

A generated Go SDK from GitHub's OpenAPI specification. Built on Kiota.
MIT License
64 stars 7 forks source link

[BUG]: PATCH request for repos/OWNER/REPO/properties/values broken #90

Open felixlut opened 1 month ago

felixlut commented 1 month ago

What happened?

I'm playing around with this sdk as a means of contributing to https://github.com/integrations/terraform-provider-github/issues/1956, and as such my focus is on the endpoints related to repository custom properties.

The issue I'm facing occurs when calling octokitClient.Repos().ByOwnerId(owner).ByRepoId(repoName).Properties().Values().Patch(), which is mapped to the repos/OWNER/REPO/properties/values PATCH endpoint. I'll list the code I'm using further down in the message.

When running the code below I receive the following error:

I've ran the debugger and dug deeper into the call chain, and found that the error code is 400, but haven't been able to find an error message. Digging down all the way to the net/http layer and inspecting the request body being sent, it looks like this:

Which isn't too dissimilar to what the API expects (example from the docs):

After playing around with curl a bit, I've managed to receive a 400 error by using malformed JSON in the body (see example below). My guess is that this SDK somewhere along the line parses the custom property incorrectly, or that I'm passing the property_name and value incorrectly to it.

curl -L \
  -X PATCH \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $(gh auth token)" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/YesWeKanske/test-custom-properties/properties/values -d '"{"properties":[{"property_name":"text","value":"test"}]}"'
{
  "message": "Problems parsing JSON",
  "documentation_url": "https://docs.github.com/rest/repos/custom-properties#create-or-update-custom-property-values-for-a-repository",
  "status": "400"
}
package main

import (
    "context"
    "log"
    "os"
    "time"

    abs "github.com/microsoft/kiota-abstractions-go"
    "github.com/octokit/go-sdk/pkg"
    "github.com/octokit/go-sdk/pkg/github/models"
    "github.com/octokit/go-sdk/pkg/github/repos"
)

func main() {
    octokitClient, err := pkg.NewApiClient(
        pkg.WithUserAgent("my-user-agent"),
        pkg.WithRequestTimeout(60*time.Second),
        pkg.WithBaseUrl("https://api.github.com"),
        pkg.WithTokenAuthentication(os.Getenv("GITHUB_TOKEN")),
    )
    ctx := context.Background()

    if err != nil {
        log.Fatalf("error creating client: %v", err)
    }

    owner := "YesWeKanske"
    repoName := "test-custom-properties"

    repoURL := octokitClient.Repos().ByOwnerId(owner).ByRepoId(repoName)
    propURL := repoURL.Properties().Values()

    headers := abs.NewRequestHeaders()
    _ = headers.TryAdd("Accept", "application/vnd.github.v3+json")
    _ = headers.TryAdd("X-GitHub-Api-Version", "2022-11-28")
    defaultRequestConfig := &abs.RequestConfiguration[abs.DefaultQueryParameters]{
        QueryParameters: &abs.DefaultQueryParameters{},
        Headers: headers,
    }

    propertyName := "text"
    propertyValue := "test"
    testProps := make(map[string]interface{})
    testProps[propertyName] = propertyValue

    testProperty := models.NewCustomPropertyValue()
    testProperty.SetPropertyName(&propertyName)

    propertyValueModel := models.NewCustomPropertyValue_CustomPropertyValue_value()
    propertyValueModel.SetString(&propertyValue)
    testProperty.SetValue(propertyValueModel)
    // testProperty.SetAdditionalData(testProps)

    patchBody := repos.NewItemItemPropertiesValuesPatchRequestBody()
    patchBody.SetProperties([]models.CustomPropertyValueable{
        testProperty,
    })
    // patchBody.SetAdditionalData(testProps)

    err = propURL.Patch(ctx, patchBody, defaultRequestConfig)
    if err != nil {
        panic(err)
    }
}

I've also explored the following:

Versions

I've used the latest version of the provider at the time of writing this bug report (go get github.com/octokit/go-sdk@a74a3a2a13654bd84794fd91a6a0fbc96f883722, where a74a3a2a13654bd84794fd91a6a0fbc96f883722 is a commit to this repo). My go.mod file looks like so:

module example/hello

go 1.22.3

require (
    github.com/microsoft/kiota-abstractions-go v1.6.0
    github.com/octokit/go-sdk v0.0.21-0.20240709170617-a74a3a2a1365
)

require (
    github.com/cjlapao/common-go v0.0.39 // indirect
    github.com/davecgh/go-spew v1.1.1 // indirect
    github.com/go-logr/logr v1.4.1 // indirect
    github.com/go-logr/stdr v1.2.2 // indirect
    github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
    github.com/google/uuid v1.6.0 // indirect
    github.com/kfcampbell/ghinstallation v0.0.6 // indirect
    github.com/microsoft/kiota-http-go v1.3.3 // indirect
    github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect
    github.com/microsoft/kiota-serialization-json-go v1.0.7 // indirect
    github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect
    github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect
    github.com/pmezard/go-difflib v1.0.0 // indirect
    github.com/std-uritemplate/std-uritemplate/go v0.0.55 // indirect
    github.com/stretchr/testify v1.9.0 // indirect
    go.opentelemetry.io/otel v1.24.0 // indirect
    go.opentelemetry.io/otel/metric v1.24.0 // indirect
    go.opentelemetry.io/otel/trace v1.24.0 // indirect
    gopkg.in/yaml.v3 v3.0.1 // indirect
)

Code of Conduct

github-actions[bot] commented 1 month ago

👋 Hi! Thank you for this contribution! Just to let you know, our GitHub SDK team does a round of issue and PR reviews twice a week, every Monday and Friday! We have a process in place for prioritizing and responding to your input. Because you are a part of this community please feel free to comment, add to, or pick up any issues/PRs that are labeled with Status: Up for grabs. You & others like you are the reason all of this works! So thank you & happy coding! 🚀

kfcampbell commented 1 month ago

I appreciate this super thoughtful bug report, and thank you for working to bring the new SDK into the Terraform provider. That's great!

I've reproduced what you've done here on a dummy repo (kfcampbell-terraform-provider/tf-acc-test-destroy-trfiz), and I agree there's something wonky going on in the generated code. I'm able to add custom properties successfully via the UI, and via a CURL request like the following:

curl -L   -X PATCH   -H "Accept: application/vnd.github+json"   -H "Authorization: Bearer $(gh auth token)"   -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/kfcampbell-terraform-provider/tf-acc-test-destroy-trfiz/properties/values -d '{"properties":[{"property_name":"environment","value":"test-from-curl"}]}'

When using the SDK, I'm using the following code (after creating the client as we do in the example token CLI):

    customPropertyValue := models.NewCustomPropertyValue()
    propertyName := "environment"
    propertyValue := "test-from-go-sdk"

    customPropertyValue.SetPropertyName(&propertyName)
    value := models.NewCustomPropertyValue_CustomPropertyValue_value()
    value.SetString(&propertyValue)
    customPropertyValue.SetValue(value)

    patchRequestBody := repos.NewItemItemPropertiesValuesPatchRequestBody()
    patchRequestBody.SetProperties([]models.CustomPropertyValueable{customPropertyValue})

    err = client.Repos().ByOwnerId("kfcampbell-terraform-provider").ByRepoId("tf-acc-test-destroy-trfiz").
        Properties().Values().Patch(context.Background(), patchRequestBody, nil)
    if err != nil {
        log.Fatalf("error setting custom properties: %v", err)
    }

This fails with a 400 Bad Request error, and I see the payload going out as:

"{\"properties\":[{\"property_name\":\"environment\",\"value\":\"test-from-go-sdk\"}]}"

When I attempt a curl request with that as the payload (as I'm somewhat suspicious of the backslashes), I get a 422, not a 400.

I think this is an error with Kiota, our generation engine, so I've filed https://github.com/microsoft/kiota/issues/4995 and will do some investigation with them.

kfcampbell commented 4 weeks ago

@felixlut this should be fixed as of v0.0.23!

felixlut commented 3 weeks ago

Thanks! I'll take a stab after work today or tomorrow

felixlut commented 3 weeks ago

@kfcampbell setting non-multi_select property values works now!

multi_select values are still broken though from what I can tell, for the reason I stated in the original issue:

  • The value attribute of an CustomPropertyValue should implement the CustomPropertyValue_CustomPropertyValue_valueable interface, which only have the two methods GetString() and SetString, which implies that the value of a custom property can only be a string. But it can be either a string or a list of string (multi_select custom property type). Due to this this SDK can only work with the other property types currently (except for the stuff mentioned above 😄). The curl call below is valid, but there is no way of replicating it with this SDK from what I can tell
curl -L \
  -X PATCH \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $(gh auth token)" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/YesWeKanske/test-custom-properties/properties/values -d '{"properties": [{"property_name": "multi-select","value": ["option1","option2"]}]}'

This might warrant its own issue though 😄

I'll also note that the Organization Ruleset API seems to handle the fact that a property value can be either a string or a list of strings in the way I proposed here, i.e., by just treating it as a list of strings at all times. It's pretty deeply nested, but Org Rulesets can target repos by custom properties, and this is where this occurs. .conditions.repository_property_and_ref_name.repository_property.include.property_values always takes a list of strings