NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky

Kubernetes::fake() vymení manager — za fasádou aj v kontajneri, takže ho dostanú aj injektované KubernetesManagery — za apiserver v pamäti a vráti objekt KubernetesFake. Nič neodíde do siete. Každý klaster z managera (pomenovaný, predvolený, ad-hoc, obmedzený na namespace) aj každý naň naviazaný zdroj komunikuje s fake, ktorý každú požiadavku zaznamená:

use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
use RoundlyConsulting\KubernetesApi\Resources\Deployment;

it('scales checkout for the sale', function () {
    $fake = Kubernetes::fake()->seed(Deployment::class, [
        ['metadata' => ['name' => 'checkout', 'namespace' => 'shop'], 'spec' => ['replicas' => 2]],
    ]);

    app(PrepareForSale::class)();   // your code: Kubernetes::namespace('shop')->deployments()->setName('checkout')->scale(10)

    $fake->assertScaled(Deployment::class, 'checkout', 10);
    $fake->assertNothingDeleted();
});

Fake sa správa ako malý apiserver: naplnené aj vytvorené objekty môžete vypisovať (selektory podľa labelov a polí, limit a continue, všetky namespace), hľadať, upravovať, patchovať, škálovať a mazať. Chýbajúci objekt vráti skutočnú 404, duplicitné vytvorenie či zastaraná resourceVersion skutočnú 409 a požiadavky s dryRun() sa overia bez uloženia. Každé meno klastra má vlastné úložisko; ad-hoc klastre zdieľajú úložisko predvoleného klastra.

Príprava

PrípravaÚčinok
seed(string $resource, array $items, ?string $cluster = null)Vloží objekty — manifesty alebo objekty zdrojov — na klaster. $resource je trieda alebo zaregistrované meno ('pods').
seedLogs(string $pod, string $logs, string $namespace = 'default', ?string $cluster = null)Čo vrátia logs() a streamLogs(); tailLines sa rešpektuje.
stubExec(ExecResult|Closure $result)Čo vráti exec() — pevný výsledok alebo closure, ktorá dostane RecordedRequest. Predvolene prázdny výstup a návratový kód 0.
stubVersion(VersionInfo|string $version)Čo nahlási version(); predvolene v1.34.0.
unreachable(bool $unreachable = true)Každá požiadavka vyhodí ConnectionException; ping() vráti false.
recorded(?Closure $filter = null)Každý RecordedRequest, ktorý fake dostal, voliteľne filtrovaný.

Logy, exec aj sledovanie zmien fungujú aj proti fake — watch pošle jednu udalosť ADDED za každý vyhovujúci objekt:

use RoundlyConsulting\KubernetesApi\DataTransferObjects\ExecResult;
use RoundlyConsulting\KubernetesApi\DataTransferObjects\PodLogOptions;
use RoundlyConsulting\KubernetesApi\DataTransferObjects\WatchEvent;
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;

it('serves seeded pods, logs and exec', function (): void {
    $fake = Kubernetes::fake()
        ->seed('pods', [
            ['metadata' => ['name' => 'api', 'namespace' => 'shop', 'labels' => ['app' => 'web']]],
        ])
        ->seedLogs('api', "one\ntwo\nthree\n", namespace: 'shop')
        ->stubExec(new ExecResult("hi\n", '', 0));

    $pod = Kubernetes::namespace('shop')->pods()->setName('api');

    expect($pod->logs(new PodLogOptions(tailLines: 2)))->toBe("two\nthree")
        ->and(iterator_to_array($pod->streamLogs(), false))->toBe(['one', 'two', 'three'])
        ->and($pod->exec(['sh', '-c', 'echo hi'])->stdout)->toBe("hi\n");

    // A watch streams one ADDED event per matching object
    $events = [];
    Kubernetes::namespace('shop')->pods()->whereLabel('app', 'web')
        ->watch(function (WatchEvent $event) use (&$events): void {
            $events[] = $event;
        });

    expect($events)->toHaveCount(1)
        ->and($events[0]->isAdded())->toBeTrue();

    $fake->assertExecuted('api', ['sh', '-c', 'echo hi']);
});

Overenia

Overenie · variant „nič“PríkladPrejde, keď
assertSent(Closure) · assertNothingSent()$fake->assertSent(fn (RecordedRequest $r): bool => $r->cluster === 'production')Niektorá požiadavka vyhovuje — alebo sa neodoslala žiadna.
assertCreated($resource, $constraint = null) · assertNothingCreated()$fake->assertCreated('configMaps', 'settings')Zdroj sa vytvoril — voliteľne s týmto menom alebo podľa closure.
assertUpdated($resource, $constraint = null) · assertNothingUpdated()$fake->assertUpdated(Deployment::class, 'checkout')Celá úprava — update() alebo updateOrCreate() nad existujúcim objektom.
assertPatched($resource, $constraint = null) · assertNothingPatched()$fake->assertPatched('deployments', 'checkout')Akýkoľvek PATCH — vrátane patch(), scale() a rolloutRestart().
assertScaled($resource, $name, ?int $replicas = null) · assertNothingScaled()$fake->assertScaled(Deployment::class, 'checkout', 10)Volanie scale() — ak je zadaný počet replík, presne naň.
assertRestarted($resource, $name) · assertNothingRestarted()$fake->assertRestarted(Deployment::class, 'checkout')Volanie rolloutRestart().
assertDeleted($resource, $constraint = null) · assertNothingDeleted()$fake->assertDeleted('pods', 'api')Zmazanie.
assertExecuted(string $pod, ?array $command = null) · assertNothingExecuted()$fake->assertExecuted('api', ['sh', '-c', 'echo hi'])exec() v danom pode — ak je zadaný príkaz, presne tento.

$resource je trieda zdroja alebo zaregistrované meno ('pods'); obmedzením je meno objektu alebo closure, ktorá dostane RecordedRequest. Požiadavky v režime dry-run sa zaznamenajú, no overenia zmien nikdy nesplnia:

use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
use RoundlyConsulting\KubernetesApi\Resources\Deployment;
use RoundlyConsulting\KubernetesApi\Testing\RecordedRequest;

$fake->assertCreated('configMaps', fn (RecordedRequest $request): bool => $request->namespace === 'shop'
    && $request->input('data.FEATURE_X') === 'on');

// Everything the fake received, optionally filtered
$dryRuns = $fake->recorded(fn (RecordedRequest $request): bool => $request->isDryRun());

// The assertions are also callable on the facade once it is faked
Kubernetes::assertScaled(Deployment::class, 'checkout', 10);

RecordedRequest nesie cluster (pri ad-hoc klastroch null), method, path, verb, apiVersion, plural, namespace, name, subresource, query, dekódované body, contentType a pri exec command a container — a k tomu isDryRun(), input('spec.replicas') a isRolloutRestart().

Čo fake nerobí

  • Klastre zaregistrované pred fake() zostávajú a ich definície sa naďalej spúšťajú; pod fake však fromKubeConfig(), inCluster() ani zdroje kubeconfig a in-cluster v konfigurácii nikdy nečítajú prihlasovacie údaje.
  • Rovnako ako apiserver odpovie fake chybou 422 na server-side apply bez fieldManager alebo na force pri inom type patchu.
  • Patche strategic-merge a server-side apply sa aplikujú ako JSON merge patche a logy aj exec odpovedajú pre akýkoľvek pod.
  • Klastre vytvorené mimo managera — new Cluster, Cluster::make() — fake obídu.

Kontrola na úrovni HTTP cez Http::fake()

Http::fake() naďalej funguje — zachytí skutočný HTTP transport, keď chcete overiť presné URL a hlavičky. V takých testoch vypnite obmedzenie požiadaviek na strane klienta, aby veľká sada testov nikdy nečakala na rozpočet v pamäti:

use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\KubernetesApi\Cluster;
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;

beforeEach(function (): void {
    config(['kubernetes.rate_limits.enabled' => false]);

    Kubernetes::registerCluster('testing', fn (Cluster $cluster): Cluster => $cluster
        ->url('https://k8s.example.com')
        ->withToken('fake-token')
        ->withManagerName('shop-tests'));

    Http::preventStrayRequests();
});

it('lists the deployments in a namespace', function (): void {
    Http::fake([
        '*' => Http::response([
            'items' => [
                ['metadata' => ['name' => 'checkout', 'namespace' => 'shop']],
            ],
        ]),
    ]);

    $deployments = Kubernetes::cluster('testing')->deployments()->setNamespace('shop')->get();

    expect($deployments)->toHaveCount(1)
        ->and($deployments->first()->getName())->toBe('checkout');

    Http::assertSent(fn (Request $request): bool => $request->method() === 'GET'
        && $request->url() === 'https://k8s.example.com/apis/apps/v1/namespaces/shop/deployments?pretty=1'
        && $request->hasHeader('Authorization', 'Bearer fake-token'));
});

exec() otvára vlastný WebSocket namiesto HTTP klienta, takže ho Http::fake() nezachytí — nasimulujte ho cez Kubernetes::fake() alebo overte presnú cestu subresource a dotaz, ktoré pre príkaz zostaví getExecPath():

$path = Kubernetes::cluster('testing')->pods()
    ->setNamespace('shop')
    ->withName('checkout-5f7c9d8b6-x2kqj')
    ->getExecPath(['php', 'artisan', 'cache:clear'], container: 'app');

expect($path)
    ->toContain('/api/v1/namespaces/shop/pods/checkout-5f7c9d8b6-x2kqj/exec?')
    ->toContain('container=app')
    ->toContain('command=cache%3Aclear');

Testy samotného balíka

Predvolená sada testov balíka je úplne simulovaná a klaster nepotrebuje. Samostatná, voliteľná sada spúšťa skutočné CRUD, exec a výpisy proti lokálnemu klastru OrbStack; je chránená tak, že sa spustí len pri K8S_INTEGRATION=1, kontexte presne orbstack a hoste apiservera na loopbacku alebo *.orb.local, a každý beh používa dočasný namespace, ktorý sa potom zmaže:

composer test

# Opt-in live suite against a local OrbStack cluster
K8S_INTEGRATION=1 composer test-integration
PremennáPredvolenéÚčel
K8S_INTEGRATIONnenastavenáHodnota 1 (alebo true) sadu zapne; bez nej sa každý integračný test preskočí.
K8S_INTEGRATION_CONTEXTorbstackKontext kubeconfigu, z ktorého sada berie prihlasovacie údaje. Musí to byť orbstack.
K8S_INTEGRATION_NAMESPACEnáhodný k8s-it-…Prepíše dočasný namespace, ktorý sada vytvorí a po sebe zmaže.

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.