Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions doc/12-icinga2-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,30 @@ was malformed.
A status in the range of 500 generally means that there was a server-side problem
and Icinga 2 is unable to process your request.

Starting with Icinga 2 v2.16.0 we introduced HTTP chunked transfer encoding for some of the API endpoints.
As a consequence, the `/v1/actions/` and `/v1/objects/` endpoints will always return a `202 Accepted` status code as a
general HTTP status, but the status of each individual operation remains the same as before, and will continue to report
`2xx`, `4xx` or `5xx` status codes. For example, you may see such a response when trying to delete multiple objects.

```json
{
"results": [
{
"code": 200,
"status": "Object was deleted."
},
{
"code": 500,
"status": "Object could not be deleted.",
}
]
}
```

In such cases, you should always check the individual result entries for their status code and not rely solely on the
overall HTTP status code. There are also a number of other endpoints which use chunked transfer encoding, but have no
behavioral difference in terms of the overall HTTP status code and will continue to return `200` for successful requests.

### Security <a id="icinga2-api-security"></a>

* HTTPS only.
Expand Down Expand Up @@ -2742,7 +2766,7 @@ r = requests.post(request_url,
print "Request URL: " + str(r.url)
print "Status code: " + str(r.status_code)

if (r.status_code == 200):
if (r.status_code == 202):
print "Result: " + json.dumps(r.json())
else:
print r.text
Expand Down Expand Up @@ -2791,7 +2815,7 @@ rescue => e
end

puts "Status: " + response.code.to_s
if response.code == 200
if response.code == 202
puts "Result: " + (JSON.pretty_generate JSON.parse(response.body))
else
puts "Error: " + response
Expand Down Expand Up @@ -2846,7 +2870,7 @@ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
print "Status: " . $code . "\n";

if ($code == 200) {
if ($code == 202) {
$response = json_decode($response, true);
print_r($response);
}
Expand Down Expand Up @@ -2898,7 +2922,7 @@ $client->POST("/v1/objects/services", $data);
my $status = $client->responseCode();
print "Status: " . $status . "\n";
my $response = $client->responseContent();
if ($status == 200) {
if ($status == 202) {
print "Result: " . Dumper(decode_json($response)) . "\n";
} else {
print "Error: " . $response . "\n";
Expand All @@ -2921,14 +2945,13 @@ import (
"bytes"
"crypto/tls"
"log"
"io/ioutil"
"net/http"
)

func main() {
var urlBase= "https://localhost:5665"
var apiUser= "root"
var apiPass= "icinga"
var urlBase = "https://localhost:5665"
var apiUser = "root"
var apiPass = "icinga"

urlEndpoint := urlBase + "/v1/objects/services"

Expand All @@ -2943,7 +2966,7 @@ func main() {
"filter": "match(\"ping*\", service.name)"
}`)

req, err := http.NewRequest("POST", urlEndpoint, bytes.NewBuffer(requestBody))
req, err := http.NewRequest("POST", urlEndpoint, bytes.NewReader(requestBody))
req.Header.Set("Accept", "application/json")
req.Header.Set("X-HTTP-Method-Override", "GET")

Expand All @@ -2958,22 +2981,24 @@ func main() {

log.Print("Response status:", resp.Status)

bodyBytes, _ := ioutil.ReadAll(resp.Body)
bodyString := string(bodyBytes)
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(resp.Body); err != nil {
log.Fatal("Read error:", err)
return
}

if resp.StatusCode == http.StatusOK {
log.Print("Result: " + bodyString)
if resp.StatusCode == http.StatusAccepted {
log.Print("Result: " + buf.String())
} else {
log.Fatal(bodyString)
log.Fatal(buf.String())
}
}
```

Build the binary:

```bash
go build icinga.go
./icinga
go run ./icinga.go
```

#### Example API Client in Powershell <a id="icinga2-api-clients-programmatic-examples-powershell"></a>
Expand Down
79 changes: 63 additions & 16 deletions lib/base/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,86 @@ namespace icinga
{

/**
* ValueGenerator is a class that defines a generator function type for producing Values on demand.
* Abstract base class for generators that produce a sequence of Values.
*
* This class is used to create generator functions that can yield any values that can be represented by the
* Icinga Value type. The generator function is exhausted when it returns `std::nullopt`, indicating that there
* are no more values to produce. Subsequent calls to `Next()` will always return `std::nullopt` after exhaustion.
* @note Any instance of a Generator should be treated as an @c Array -like object that produces its elements
* on-the-fly.
*
* @ingroup base
*/
class ValueGenerator final : public Object
class Generator : public Object
{
public:
DECLARE_PTR_TYPEDEFS(ValueGenerator);
DECLARE_PTR_TYPEDEFS(Generator);

/**
* Generates a Value using the provided generator function.
* Produces the next Value in the sequence.
*
* This method returns the next Value produced by the generator. If the generator is exhausted,
* it returns std::nullopt for all subsequent calls to this method.
*
* The generator function should return an `std::optional<Value>` which contains the produced Value or
* `std::nullopt` when there are no more values to produce. After the generator function returns `std::nullopt`,
* the generator is considered exhausted, and further calls to `Next()` will always return `std::nullopt`.
* @return The next Value in the sequence, or std::nullopt if the generator is exhausted.
*/
using GenFunc = std::function<std::optional<Value>()>;
virtual std::optional<Value> Next() = 0;
};

explicit ValueGenerator(GenFunc generator): m_Generator(std::move(generator))
/**
* A generator that transforms elements of a container into Values using a provided transformation function.
*
* This class takes a container and a transformation function as input. It uses the transformation function
* to convert each element of the container into a Value. The generator produces Values on-the-fly as they
* are requested via the `Next()` method. If the transformation function returns `std::nullopt` for an element,
* even though there are more elements in the container, the generator will yield that `std::nullopt` value like
* any other Value and continue to the next element on subsequent calls to @c Next().
*
* @tparam Container The type of the container holding the elements to be transformed.
*
* @ingroup base
*/
template<typename Container>
class ValueGenerator final : public Generator
{
public:
DECLARE_PTR_TYPEDEFS(ValueGenerator);

// The type of elements in the container and the type to be passed to the transformation function.
using ValueType = typename Container::value_type;

// The type of the transformation function that takes a ValueType and returns an optional Value.
using TransFormFunc = std::function<std::optional<Value> (const ValueType&)>;

/**
* Constructs a ValueGenerator with the given container and transformation function.
*
* The generator will iterate over the elements of the container, applying the transformation function
* to each element to produce values on-the-fly. You must ensure that the container remains valid for
* the lifetime of the generator.
*
* @param container The container holding the elements to be transformed.
* @param generator The transformation function to convert elements into Values.
*/
ValueGenerator(Container& container, TransFormFunc generator)
: m_It{container.begin()}, m_End{container.end()}, m_Func{std::move(generator)}
{
}

std::optional<Value> Next() const
std::optional<Value> Next() override
{
return m_Generator();
if (m_It == m_End) {
return std::nullopt;
}

auto next = m_Func(*m_It);
++m_It;
return next;
}

private:
GenFunc m_Generator; // The generator function that produces Values.
using Iterator = typename Container::iterator;
Iterator m_It; // Current iterator position.
Iterator m_End; // End iterator position.

TransFormFunc m_Func; // The transformation function.
};

}
} // namespace icinga
4 changes: 2 additions & 2 deletions lib/base/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc)
EncodeObject(static_pointer_cast<Dictionary>(obj), extractor, yc);
} else if (type == Array::TypeInstance) {
EncodeArray(static_pointer_cast<Array>(obj), yc);
} else if (auto gen(dynamic_pointer_cast<ValueGenerator>(obj)); gen) {
} else if (auto gen(dynamic_pointer_cast<Generator>(obj)); gen) {
EncodeValueGenerator(gen, yc);
} else {
// Some other non-serializable object type!
Expand Down Expand Up @@ -126,7 +126,7 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_contex
* @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder
* to flush the output stream safely when it has not acquired any object lock on the parent containers.
*/
void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc)
void JsonEncoder::EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc)
{
BeginContainer('[');
bool isEmpty = true;
Expand Down
2 changes: 1 addition & 1 deletion lib/base/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class JsonEncoder

private:
void EncodeArray(const Array::Ptr& array, boost::asio::yield_context* yc);
void EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc);
void EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc);

template<typename Iterable, typename ValExtractor>
void EncodeObject(const Iterable& container, const ValExtractor& extractor, boost::asio::yield_context* yc);
Expand Down
Loading
Loading