Skip to content

Commit 414893c

Browse files
feat: Initial ArgoCD PHP Client Structure and Core Services
This commit introduces the initial structure and core functionality for a new PHP client for the ArgoCD API, refactored from the KnpLabs GitHub API client. Key changes and accomplishments so far: 1. **Project Setup:** * Established new directory structure under `argocd-php-client/src/ArgoCD/`. * Initialized `composer.json` with PSR-4 autoloading and base dependencies. 2. **Core Client (`ArgoCD/Client.php`):** * Adapted from the GitHub client. * Handles ArgoCD server URL. * `authenticate()` method implemented to either: * Fetch a bearer token from `/api/v1/session` using username/password via `SessionService`. * Use a provided bearer token directly. * `api()` factory method for accessing service classes. 3. **HTTP Layer:** * `HttpClient/Builder.php` adapted for constructing the HTTP client. * `HttpClient/Plugin/Authentication.php` implemented for bearer token injection. * `HttpClient/Plugin/ArgoCdExceptionThrower.php` for basic error handling. * `Api/AbstractApi.php` provides base HTTP request methods. * `HttpClient/Message/ResponseMediator.php` for processing JSON responses. 4. **Exception Handling:** * Custom exception classes created in `ArgoCD/Exception/`. * `ArgoCdExceptionThrower` parses JSON error responses from ArgoCD. 5. **Models (`ArgoCD/Model/`):** * Initial set of PHP model classes generated based on ArgoCD OpenAPI definitions for session and account services (e.g., `AccountAccount`, `SessionSessionResponse`, `V1Time`). 6. **API Services (`ArgoCD/Api/`):** * `SessionService.php` implemented with `create` (login), `delete` (logout), and `getUserInfo` methods. * `AccountService.php` implemented with methods for account listing, password updates, and token management. * Placeholders for `ApplicationService.php`. This work lays the foundation for a comprehensive ArgoCD PHP client. Further development will involve implementing remaining API services and models, writing unit tests, and updating documentation.
1 parent 663af58 commit 414893c

30 files changed

+1565
-0
lines changed

argocd-php-client/composer.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "your-vendor/argocd-php-client",
3+
"description": "A PHP client for the ArgoCD API",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Your Name",
9+
"email": "your.email@example.com"
10+
}
11+
],
12+
"require": {
13+
"php": ">=7.4 || ^8.0",
14+
"php-http/client-common": "^2.3",
15+
"php-http/discovery": "^1.12",
16+
"psr/http-client-implementation": "^1.0",
17+
"psr/http-factory-implementation": "^1.0",
18+
"psr/http-message": "^1.0 || ^2.0"
19+
},
20+
"require-dev": {
21+
"phpunit/phpunit": "^9.5"
22+
},
23+
"autoload": {
24+
"psr-4": {
25+
"ArgoCD\\": "src/ArgoCD/"
26+
}
27+
},
28+
"autoload-dev": {
29+
"psr-4": {
30+
"ArgoCD\\Tests\\": "tests/"
31+
}
32+
}
33+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php
2+
3+
namespace ArgoCD\Api;
4+
5+
use ArgoCD\Client;
6+
use ArgoCD\HttpClient\Message\ResponseMediator;
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
abstract class AbstractApi
10+
{
11+
/**
12+
* The client instance.
13+
*
14+
* @var Client
15+
*/
16+
protected $client; // Changed visibility to protected to allow access in child classes if needed
17+
18+
/**
19+
* Create a new API instance.
20+
*
21+
* @param Client $client
22+
*
23+
* @return void
24+
*/
25+
public function __construct(Client $client)
26+
{
27+
$this->client = $client;
28+
}
29+
30+
/**
31+
* Get the client instance.
32+
*
33+
* @return Client
34+
*/
35+
protected function getClient(): Client
36+
{
37+
return $this->client;
38+
}
39+
40+
/**
41+
* @return $this
42+
*/
43+
public function configure()
44+
{
45+
// Kept as a no-op or simple method returning $this
46+
return $this;
47+
}
48+
49+
/**
50+
* Send a GET request with query parameters.
51+
*
52+
* @param string $path Request path.
53+
* @param array $parameters GET parameters.
54+
* @param array $requestHeaders Request Headers.
55+
*
56+
* @return array|string
57+
*/
58+
protected function get(string $path, array $parameters = [], array $requestHeaders = [])
59+
{
60+
// Removed $perPage logic
61+
// Removed 'ref' parameter logic as it's GitHub specific
62+
63+
if (count($parameters) > 0) {
64+
$path .= '?'.http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
65+
}
66+
67+
$response = $this->client->getHttpClient()->get($path, $requestHeaders);
68+
69+
return ResponseMediator::getContent($response);
70+
}
71+
72+
/**
73+
* Send a HEAD request with query parameters.
74+
*
75+
* @param string $path Request path.
76+
* @param array $parameters HEAD parameters.
77+
* @param array $requestHeaders Request headers.
78+
*
79+
* @return ResponseInterface
80+
*/
81+
protected function head(string $path, array $parameters = [], array $requestHeaders = []): ResponseInterface
82+
{
83+
// Removed 'ref' parameter logic
84+
$queryString = '';
85+
if (count($parameters) > 0) {
86+
$queryString = '?'.http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
87+
}
88+
return $this->client->getHttpClient()->head($path.$queryString, $requestHeaders);
89+
}
90+
91+
/**
92+
* Send a POST request with JSON-encoded parameters.
93+
*
94+
* @param string $path Request path.
95+
* @param array $parameters POST parameters to be JSON encoded.
96+
* @param array $requestHeaders Request headers.
97+
*
98+
* @return array|string
99+
*/
100+
protected function post(string $path, array $parameters = [], array $requestHeaders = [])
101+
{
102+
return $this->postRaw(
103+
$path,
104+
$this->createJsonBody($parameters),
105+
$requestHeaders
106+
);
107+
}
108+
109+
/**
110+
* Send a POST request with raw data.
111+
*
112+
* @param string $path Request path.
113+
* @param string|null $body Request body.
114+
* @param array $requestHeaders Request headers.
115+
*
116+
* @return array|string
117+
*/
118+
protected function postRaw(string $path, $body, array $requestHeaders = [])
119+
{
120+
$response = $this->client->getHttpClient()->post(
121+
$path,
122+
$requestHeaders,
123+
$body
124+
);
125+
126+
return ResponseMediator::getContent($response);
127+
}
128+
129+
/**
130+
* Send a PATCH request with JSON-encoded parameters.
131+
*
132+
* @param string $path Request path.
133+
* @param array $parameters POST parameters to be JSON encoded.
134+
* @param array $requestHeaders Request headers.
135+
*
136+
* @return array|string
137+
*/
138+
protected function patch(string $path, array $parameters = [], array $requestHeaders = [])
139+
{
140+
$response = $this->client->getHttpClient()->patch(
141+
$path,
142+
$requestHeaders,
143+
$this->createJsonBody($parameters)
144+
);
145+
146+
return ResponseMediator::getContent($response);
147+
}
148+
149+
/**
150+
* Send a PUT request with JSON-encoded parameters.
151+
*
152+
* @param string $path Request path.
153+
* @param array $parameters POST parameters to be JSON encoded.
154+
* @param array $requestHeaders Request headers.
155+
*
156+
* @return array|string
157+
*/
158+
protected function put(string $path, array $parameters = [], array $requestHeaders = [])
159+
{
160+
$response = $this->client->getHttpClient()->put(
161+
$path,
162+
$requestHeaders,
163+
$this->createJsonBody($parameters)
164+
);
165+
166+
return ResponseMediator::getContent($response);
167+
}
168+
169+
/**
170+
* Send a DELETE request with JSON-encoded parameters.
171+
*
172+
* @param string $path Request path.
173+
* @param array $parameters POST parameters to be JSON encoded.
174+
* @param array $requestHeaders Request headers.
175+
*
176+
* @return array|string
177+
*/
178+
protected function delete(string $path, array $parameters = [], array $requestHeaders = [])
179+
{
180+
// ArgoCD DELETE requests might not always have a body.
181+
// If parameters are provided, assume they are for the body.
182+
// If not, send null as the body.
183+
$body = null;
184+
if (count($parameters) > 0) {
185+
$body = $this->createJsonBody($parameters);
186+
}
187+
188+
$response = $this->client->getHttpClient()->delete(
189+
$path,
190+
$requestHeaders,
191+
$body
192+
);
193+
194+
return ResponseMediator::getContent($response);
195+
}
196+
197+
/**
198+
* Create a JSON encoded version of an array of parameters.
199+
*
200+
* @param array $parameters Request parameters
201+
*
202+
* @return string|null
203+
*/
204+
protected function createJsonBody(array $parameters): ?string
205+
{
206+
// Ensure empty array results in null, not "[]" for some ArgoCD endpoints if they expect no body.
207+
// However, for POST/PUT/PATCH, an empty JSON object "{}" might be valid.
208+
// The original behavior is to return null for empty arrays, which is generally fine.
209+
return (count($parameters) === 0) ? null : json_encode($parameters, empty($parameters) ? JSON_FORCE_OBJECT : 0);
210+
}
211+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
namespace ArgoCD\Api;
3+
4+
use ArgoCD\Model\AccountAccount;
5+
use ArgoCD\Model\AccountAccountsList;
6+
use ArgoCD\Model\AccountCanIResponse;
7+
use ArgoCD\Model\AccountCreateTokenRequest;
8+
use ArgoCD\Model\AccountCreateTokenResponse;
9+
use ArgoCD\Model\AccountEmptyResponse;
10+
use ArgoCD\Model\AccountUpdatePasswordRequest;
11+
use ArgoCD\Model\AccountUpdatePasswordResponse;
12+
13+
class AccountService extends AbstractApi
14+
{
15+
/**
16+
* Corresponds to AccountService_ListAccounts
17+
* Lists all accounts.
18+
*
19+
* @return AccountAccountsList
20+
* @throws \ArgoCD\Exception\RuntimeException
21+
*/
22+
public function listAccounts(): AccountAccountsList
23+
{
24+
$responseArray = $this->get('/api/v1/account');
25+
return new AccountAccountsList($responseArray);
26+
}
27+
28+
/**
29+
* Corresponds to AccountService_CanI
30+
* Checks if the current account has permission to perform an action.
31+
*
32+
* @param string $resource
33+
* @param string $action
34+
* @param string $subresource
35+
* @return AccountCanIResponse
36+
* @throws \ArgoCD\Exception\RuntimeException
37+
*/
38+
public function canI(string $resource, string $action, string $subresource): AccountCanIResponse
39+
{
40+
// The response for can-i is typically a raw string "yes" or "no".
41+
// The AbstractApi::get method expects JSON.
42+
// We need to handle this: either get() should allow raw responses,
43+
// or this method needs to handle potential JSON decode errors if the response isn't JSON.
44+
// For now, assuming get() returns the raw string if not JSON,
45+
// and AccountCanIResponse constructor can handle it.
46+
$response = $this->get(sprintf("/api/v1/account/can-i/%s/%s/%s", rawurlencode($resource), rawurlencode($action), rawurlencode($subresource)));
47+
48+
// If $response is a string from get(), AccountCanIResponse constructor is designed to handle it.
49+
// If $response is an array (e.g. {'value': 'yes'}), it also handles it.
50+
return new AccountCanIResponse(is_array($response) ? $response : ['value' => $response]);
51+
}
52+
53+
/**
54+
* Corresponds to AccountService_UpdatePassword
55+
* Updates the password for the current account or a specified account.
56+
*
57+
* @param string $name The name of the account to update. If updating the current user's password, this might be the username.
58+
* @param string $currentPassword The current password.
59+
* @param string $newPassword The new password.
60+
* @return AccountUpdatePasswordResponse
61+
* @throws \ArgoCD\Exception\RuntimeException
62+
*/
63+
public function updatePassword(string $name, string $currentPassword, string $newPassword): AccountUpdatePasswordResponse
64+
{
65+
$requestModel = new AccountUpdatePasswordRequest();
66+
$requestModel->setName($name); // Name of the account being updated
67+
$requestModel->setCurrentPassword($currentPassword);
68+
$requestModel->setNewPassword($newPassword);
69+
70+
$responseArray = $this->put('/api/v1/account/password', $requestModel->toArray());
71+
return new AccountUpdatePasswordResponse($responseArray ?: []); // Response might be empty
72+
}
73+
74+
/**
75+
* Corresponds to AccountService_GetAccount
76+
* Gets information about a specific account.
77+
*
78+
* @param string $name The name of the account.
79+
* @return AccountAccount
80+
* @throws \ArgoCD\Exception\RuntimeException
81+
*/
82+
public function getAccount(string $name): AccountAccount
83+
{
84+
$responseArray = $this->get(sprintf("/api/v1/account/%s", rawurlencode($name)));
85+
return new AccountAccount($responseArray);
86+
}
87+
88+
/**
89+
* Corresponds to AccountService_CreateToken
90+
* Creates a new token for the specified account.
91+
*
92+
* @param string $accountName The name of the account.
93+
* @param string $tokenId The desired ID/name for the token.
94+
* @param string $tokenDescription A description for the token.
95+
* @param string|null $expiresIn Duration string for token expiration (e.g., "30d", "24h", "0" for non-expiring).
96+
* @return AccountCreateTokenResponse
97+
* @throws \ArgoCD\Exception\RuntimeException
98+
*/
99+
public function createToken(string $accountName, string $tokenId, string $tokenDescription, ?string $expiresIn = "0"): AccountCreateTokenResponse
100+
{
101+
$requestModel = new AccountCreateTokenRequest();
102+
$requestModel->setId($tokenId); // This 'id' is the token's identifier
103+
$requestModel->setName($tokenDescription); // This 'name' is the token's description
104+
$requestModel->setExpiresIn($expiresIn);
105+
106+
$responseArray = $this->post(sprintf("/api/v1/account/%s/token", rawurlencode($accountName)), $requestModel->toArray());
107+
return new AccountCreateTokenResponse($responseArray);
108+
}
109+
110+
/**
111+
* Corresponds to AccountService_DeleteToken
112+
* Deletes a token for the specified account.
113+
*
114+
* @param string $accountName The name of the account.
115+
* @param string $tokenId The ID of the token to delete.
116+
* @return AccountEmptyResponse
117+
* @throws \ArgoCD\Exception\RuntimeException
118+
*/
119+
public function deleteToken(string $accountName, string $tokenId): AccountEmptyResponse
120+
{
121+
$responseArray = $this->delete(sprintf("/api/v1/account/%s/token/%s", rawurlencode($accountName), rawurlencode($tokenId)));
122+
return new AccountEmptyResponse($responseArray ?: []); // Response is typically empty
123+
}
124+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
namespace ArgoCD\Api;
3+
4+
class ApplicationService extends AbstractApi
5+
{
6+
// Methods will be added later
7+
}

0 commit comments

Comments
 (0)