allansun/kubernetes-php-client

the object provided is unrecognized

Closed this issue · 11 comments

When I try to replace an Ingress, I get a error message:

the object provided is unrecognized (must be of type Ingress): couldn't get version/kind; json parse error: unexpected end of JSON input (<empty>)

If I get the $ingress->toJson(), and save that to a file, and execute kubectl apply -f file.json it works perfectly....

I have no idea whats wrong, hoping you can give me a idea?

Edit

kubectl version:

Client Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.3", GitCommit:"721bfa751924da8d1680787490c54b9179b1fed0", GitTreeState:"clean", BuildDate:"2019-02-01T20:08:12Z", GoVersion:"go1.11.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"12+", GitVersion:"v1.12.5-gke.5", GitCommit:"2c44750044d8aeeb6b51386ddb9c274ff0beb50b", GitTreeState:"clean", BuildDate:"2019-02-01T23:53:25Z", GoVersion:"go1.10.8b4", Compiler:"gc", Platform:"linux/amd64"}

And kubernetes related packages:

google/apiclient                  v2.2.2  Client library for Google APIs
google/apiclient-services         v0.88   Client library for Google APIs
google/auth                       v1.4.0  Google Auth Library for PHP
kubernetes/php-client             v1.13.4 Full Kubernetes API client
kubernetes/php-runtime            v1.0    Runtime for kubernetes/php-client

@Richard87

First thing I can tell is that your server is v1.12.5, so you might want to use v1.12.* of kubernetes/php-client. However that should be irrelevant to your question.

I would be better to paste your code so I can help you more. It appears to me that the model object you passed to the API object isn't quite right.

No, it's really weird...

I'm dumping the data sent to kubernetes with a guzzle middlestack, and saving the exact same data in a test.json file, kubectl apply -f charts/test.json works perfectly...

the model I try to apply is:

{
  "apiVersion": "extensions\/v1beta1",
  "kind": "Ingress",
  "metadata": {
    "annotations": {
      "certmanager.k8s.io\/cluster-issuer": "letsencrypt-prod",
      "ingress.kubernetes.io\/backends": "{\"k8s-be-32275--10fcf8be91b0fcd6\":\"HEALTHY\",\"k8s-be-32612--10fcf8be91b0fcd6\":\"HEALTHY\"}",
      "ingress.kubernetes.io\/forwarding-rule": "k8s-fw-nefle-nefle--10fcf8be91b0fcd6",
      "ingress.kubernetes.io\/https-forwarding-rule": "k8s-fws-nefle-nefle--10fcf8be91b0fcd6",
      "ingress.kubernetes.io\/https-target-proxy": "k8s-tps-nefle-nefle--10fcf8be91b0fcd6",
      "ingress.kubernetes.io\/ssl-cert": "k8s-ssl-3624758c6ea95320-533b015e04cbec6b--10fcf8be91b0fcd6,k8s-ssl-3624758c6ea95320-286023a4225a5767--10fcf8be91b0fcd6,k8s-ssl-3624758c6ea95320-ab12f5b2f66a0578--10fcf8be91b0fcd6,k8s-ssl-3624758c6ea95320-4bb76e7ee9b5dee6--10fcf8be91b0fcd6",
      "ingress.kubernetes.io\/static-ip": "k8s-fw-nefle-nefle--10fcf8be91b0fcd6",
      "ingress.kubernetes.io\/target-proxy": "k8s-tp-nefle-nefle--10fcf8be91b0fcd6",
      "ingress.kubernetes.io\/url-map": "k8s-um-nefle-nefle--10fcf8be91b0fcd6",
      "kubectl.kubernetes.io\/last-applied-configuration": "{\"apiVersion\":\"extensions\/v1beta1\",\"kind\":\"Ingress\",\"metadata\":{\"annotations\":{\"certmanager.k8s.io\/cluster-issuer\":\"letsencrypt-prod\",\"ingress.kubernetes.io\/backends\":\"{\\\"k8s-be-32275--10fcf8be91b0fcd6\\\":\\\"HEALTHY\\\",\\\"k8s-be-32612--10fcf8be91b0fcd6\\\":\\\"HEALTHY\\\"}\",\"ingress.kubernetes.io\/forwarding-rule\":\"k8s-fw-nefle-nefle--10fcf8be91b0fcd6\",\"ingress.kubernetes.io\/https-forwarding-rule\":\"k8s-fws-nefle-nefle--10fcf8be91b0fcd6\",\"ingress.kubernetes.io\/https-target-proxy\":\"k8s-tps-nefle-nefle--10fcf8be91b0fcd6\",\"ingress.kubernetes.io\/ssl-cert\":\"k8s-ssl-3624758c6ea95320-533b015e04cbec6b--10fcf8be91b0fcd6,k8s-ssl-3624758c6ea95320-286023a4225a5767--10fcf8be91b0fcd6,k8s-ssl-3624758c6ea95320-ab12f5b2f66a0578--10fcf8be91b0fcd6,k8s-ssl-3624758c6ea95320-52716285af89d0dc--10fcf8be91b0fcd6\",\"ingress.kubernetes.io\/static-ip\":\"k8s-fw-nefle-nefle--10fcf8be91b0fcd6\",\"ingress.kubernetes.io\/target-proxy\":\"k8s-tp-nefle-nefle--10fcf8be91b0fcd6\",\"ingress.kubernetes.io\/url-map\":\"k8s-um-nefle-nefle--10fcf8be91b0fcd6\",\"nginx.ingress.kubernetes.io\/affinity\":\"cookie\"},\"creationTimestamp\":\"2019-02-09T16:45:40Z\",\"generation\":20,\"name\":\"nefle\",\"namespace\":\"nefle\",\"resourceVersion\":\"23736840\",\"selfLink\":\"\/apis\/extensions\/v1beta1\/namespaces\/nefle\/ingresses\/nefle\",\"uid\":\"225fbd7c-2c8a-11e9-84b7-42010aa6007c\"},\"spec\":{\"rules\":[{\"host\":\"demo.nefle.no\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"nefle\",\"servicePort\":3000},\"path\":\"\/\"}]}},{\"host\":\"1.demo.nefle.no\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"nefle\",\"servicePort\":3000},\"path\":\"\/\"}]}},{\"host\":\"9.demo.nefle.no\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"nefle\",\"servicePort\":3000},\"path\":\"\/\"}]}},{\"host\":\"cms.nefle.no\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"nefle\",\"servicePort\":3000},\"path\":\"\/\"}]}}],\"tls\":[{\"hosts\":[\"demo.nefle.no\"],\"secretName\":\"demo-nefle-cert\"},{\"hosts\":[\"1.demo.nefle.no\"],\"secretName\":\"1-demo-nefle-cert\"},{\"hosts\":[\"9.demo.nefle.no\"],\"secretName\":\"9-demo-nefle-cert\"},{\"hosts\":[\"cms.nefle.no\"],\"secretName\":\"cms-nefle-cert\"}]},\"status\":{\"loadBalancer\":{\"ingress\":[{\"ip\":\"35.201.69.124\"}]}}}\n",
      "nginx.ingress.kubernetes.io\/affinity": "cookie"
    },
    "creationTimestamp": "2019-02-09T16:45:40Z",
    "generation": 21,
    "name": "nefle",
    "namespace": "nefle",
    "resourceVersion": "23745433",
    "selfLink": "\/apis\/extensions\/v1beta1\/namespaces\/nefle\/ingresses\/nefle",
    "uid": "225fbd7c-2c8a-11e9-84b7-42010aa6007c"
  },
  "spec": {
    "rules": [
      {
        "host": "1.demo.nefle.no",
        "http": {
          "paths": [
            {
              "backend": {
                "serviceName": "nefle",
                "servicePort": 3000
              },
              "path": "\/"
            }
          ]
        }
      },
      {
        "host": "9.demo.nefle.no",
        "http": {
          "paths": [
            {
              "backend": {
                "serviceName": "nefle",
                "servicePort": 3000
              },
              "path": "\/"
            }
          ]
        }
      },
      {
        "host": "cms.nefle.no",
        "http": {
          "paths": [
            {
              "backend": {
                "serviceName": "nefle",
                "servicePort": 3000
              },
              "path": "\/"
            }
          ]
        }
      },
      {
        "host": "demo2.nefle.no",
        "http": {
          "paths": [
            {
              "backend": {
                "serviceName": "nefle",
                "servicePort": 3000
              },
              "path": "\/"
            }
          ]
        }
      }
    ],
    "tls": [
      {
        "hosts": [
          "1.demo.nefle.no"
        ],
        "secretName": "1-demo-nefle-cert"
      },
      {
        "hosts": [
          "9.demo.nefle.no"
        ],
        "secretName": "9-demo-nefle-cert"
      },
      {
        "hosts": [
          "cms.nefle.no"
        ],
        "secretName": "cms-nefle-cert"
      },
      {
        "hosts": [
          "demo2.nefle.no"
        ],
        "secretName": "demo2-nefle-cert"
      }
    ]
  },
  "status": {
    "loadBalancer": {
      "ingress": [
        {
          "ip": "35.201.69.124"
        }
      ]
    }
  }
}

the object provided is unrecognized (must be of type Ingress): couldn't get version/kind; json parse error: unexpected end of JSON input (<empty>) {"exception":"[object] (DomainException(code: 400): the object provided is unrecognized (must be of type Ingress): couldn't get version/kind; json parse error: unexpected end of JSON input (<empty>)

(But I use the (new Kubernetes\API\Ingress)->replace()) while I use kubectl apply...

(thanks for extremely fast feedback!)

My code to hook into guzzle:

$tapMiddleware = Middleware::tap(function (\GuzzleHttp\Psr7\Request $request) {
    file_put_contents("/home/richard/.PhpStorm2018.3/config/scratches/scratch_4.txt", $request->getBody());
});

$handler = new CurlHandler();
$stack = HandlerStack::create($handler);
$stack->push($tapMiddleware);
Client::configure($master, [
    'caCert' => $caFile,
    'token' => $token,
], [
    'handler' => $stack
]);

Notice, I use Ingress->read() first, then I modify the response, before I send it back with ->replace()

[EDIT] For the record, I have the same error with and without my middleware ;)

One difference is that KPC sends a PUT request, while kubectl sends a patch request...

(But kubectl replace -f xxx also works perfectly)

Have you tried to create a dummy Ingress by extending the Model\Io\K8s\Api\Extensions\V1beta1\Ingress class and then create() or replace()?

Here's my code used in production, I never had similar problem

        foreach ($this->ingresses as $Ingress) {
            $this->logger->info('Deploying ingress [' . $Ingress->metadata->name . ']');
            if ('Ingress' != $IngressAPI->create(Labels::NAMESPACE_DEFAULT, $Ingress)->kind) {
                if ($forceIngressRedeploy) {
                    $this->logger->info('Ingress [' . $Ingress->metadata->name . '] existed already, reploying...');
                    $IngressAPI->replace(Labels::NAMESPACE_DEFAULT, $Ingress->metadata->name, $Ingress);
                }
            }
            $this->logger->info('Ingress [' . $Ingress->metadata->name . '] successfully deployed.');
        }

I've never tried to read() first and modify it then replace() it though...

I do agree it's a very weird problem you are experiencing, I've never seen similar error even when I was creating KPC...

@Richard87 One thing I could think of is that... are you using the replace function correctly?

the function signature is:

public function replace($namespace = 'default', $name, \Kubernetes\Model\Io\K8s\Api\Extensions\V1beta1\Ingress $Model)

Be aware that there are THREE mandatory parameters: $namespace, $NameOfIngress, $IngressModel.

One common mistake I think could happen is that you overlooked the 'name' part, just given the $namespace and $model directly. That could be why the server complaining not receiving JSON correctly.

Hi!

My main function:

    public function updateIngressWithDomains($domains)
    {
        $this->logger->info("Loading ingress");
        /** @var Ingress|Status $ingress */
        $ingress = $this->ingressApi->read('nefle','nefle');
        if ($ingress instanceof Status && $ingress->code >= 300)
            throw new \DomainException($ingress->message, $ingress->code);

        if (is_string($ingress))
            throw  new \DomainException($ingress);

        $this->logger->debug("Found ingress", $ingress->getArrayCopy());


        $updated = $this->deleteUnused($domains, $ingress->spec);
        $updated = $this->addMissing($domains, $ingress->spec) || $updated;

        $ingress->spec->tls = array_values($ingress->spec->tls);
        $ingress->spec->rules = array_values($ingress->spec->rules);

        if ($updated) {
            $this->logger->debug("Ingress JSON", $ingress->getArrayCopy());

            $status = $this->ingressApi->replace('nefle', 'nefle',$ingress);
            if ($status instanceof Status && $status->code >= 300)
                throw new \DomainException($status->message, $status->code);

            $this->logger->info("Ingress recreated");
        } else {
            $this->logger->info("No changes found");
        }
    }

The weird thin is, it worked when I created it a while back, but when adding some more features to the app (it's still in development, so not testet regularly), I noticed it didn't work anymore...

Okay, so to troubleshoot, I replaed KPC with a pure Guzzle Client:

$kubeClient = new \GuzzleHttp\Client([
    'verify' => $caFile,
    'headers' => ['Authorization' => "Bearer $token", 'Accept' => 'application/json'],
    'base_uri' => $master,
    'handler' => $stack
]);
        $response = $kubeClient->request("PUT", self::API_ENDPOINT,[
            'json' => $json,
            'timeout' => 15,
            'http_errors' => false
        ]);

With the same error as above....
But if I run the pure curl command, with the exact same json payload, it works perfectly fine...:

curl --data-binary "@/home/richard/.PhpStorm2018.3/config/scratches/scratch_12.json"
-H "Authorization: Bearer eyJhbXXX.xxx" 
-X PUT https://35.555.555.555/apis/extensions/v1beta1/namespaces/nefle/ingresses/nefle
--cacert ca.crt 
-H "Content-Type: application/json" 
-vvv

Even more weird...

The code fails with the same error as always, while the "identical" command succeeds...

<?php

use Symfony\Component\Dotenv\Dotenv;

require "/home/richard/Projects/skil/ingress-controller/vendor/autoload.php";

(new Dotenv())->loadEnv('/home/richard/Projects/skil/ingress-controller/.env');

$token = file_get_contents(getenv('KUBE_TOKEN_FILE'));
$headers = [
    "Authorization: Bearer $token",
    'Accept: application/json',
    'Content-Type: application/json'
];
$url = getenv("KUBE_API_URL") . "/apis/extensions/v1beta1/namespaces/nefle/ingresses/nefle";
$body = file_get_contents("/home/richard/.PhpStorm2018.3/config/scratches/scratch_12.json");

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

curl_setopt($ch, CURLOPT_CAINFO, "/home/richard/Projects/skil/ingress-controller/ca.crt");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

$head = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

Response from the php script:

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "the object provided is unrecognized (must be of type Ingress): couldn't get version/kind; json parse error: unexpected end of JSON input (\u003cempty\u003e)",
  "reason": "BadRequest",
  "code": 400
}

This command works (responds with updated ingress and status code 200):

  curl
            -H "Authorization: Bearer eyJhbGXXXXXXXXXXncm8w"
            -H "Content-Type: application/json"
            --cacert ca.crt
            -X PUT https://35.228.202.221/apis/extensions/v1beta1/namespaces/nefle/ingresses/nefle
            --data "@/home/richard/.PhpStorm2018.3/config/scratches/scratch_12.json"

Solved

there was a newline in my token that messed up guzzle and curl

I'm glad to hear the problem solved