Skip to content

Commit

Permalink
Add initial APIClient generator.
Browse files Browse the repository at this point in the history
This is just a really simple client generator that does a GET against a
configured URL on a regular interval.
  • Loading branch information
bigkevmcd committed Feb 28, 2023
1 parent 48390e1 commit d9ca30f
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 3 deletions.
22 changes: 22 additions & 0 deletions api/v1alpha1/gitopsset_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ type PullRequestGenerator struct {
Forks bool `json:"forks,omitempty"`
}

// APIClientGenerator defines a generator that queries an API endpoint and uses
// that to generate data.
type APIClientGenerator struct {
// The interval at which to poll the API endpoint.
// +required
Interval metav1.Duration `json:"interval"`

// This is the API endpoint to use.
// +kubebuilder:validation:Pattern="^https://"
// +optional
Endpoint string `json:"endpoint,omitempty"`

// JSONPath is string that is used to modify the result of the API
// call.
//
// This can be used to extract a repeating element from a response.
// https://kubernetes.io/docs/reference/kubectl/jsonpath/
JSONPath string `json:"jsonPath,omitempty"`
}

// GitRepositoryGeneratorFileItem defines a path to a file to be parsed when generating.
type GitRepositoryGeneratorFileItem struct {
// Path is the name of a file to read and generate from can be JSON or YAML.
Expand Down Expand Up @@ -107,6 +127,7 @@ type GitOpsSetNestedGenerator struct {
GitRepository *GitRepositoryGenerator `json:"gitRepository,omitempty"`
PullRequests *PullRequestGenerator `json:"pullRequests,omitempty"`
Cluster *ClusterGenerator `json:"cluster,omitempty"`
APIClient *APIClientGenerator `json:"apiClient,omitempty"`
}

// GitOpsSetGenerator is the top-level set of generators for this GitOpsSet.
Expand All @@ -116,6 +137,7 @@ type GitOpsSetGenerator struct {
GitRepository *GitRepositoryGenerator `json:"gitRepository,omitempty"`
Matrix *MatrixGenerator `json:"matrix,omitempty"`
Cluster *ClusterGenerator `json:"cluster,omitempty"`
APIClient *APIClientGenerator `json:"apiClient,omitempty"`
}

// GitOpsSetSpec defines the desired state of GitOpsSet
Expand Down
26 changes: 26 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions config/crd/bases/templates.weave.works_gitopssets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ spec:
description: GitOpsSetGenerator is the top-level set of generators
for this GitOpsSet.
properties:
apiClient:
description: APIClientGenerator defines a generator that queries
an API endpoint and uses that to generate data.
properties:
endpoint:
description: This is the API endpoint to use.
pattern: ^https://
type: string
interval:
description: The interval at which to poll the API endpoint.
type: string
jsonPath:
description: "JSONPath is string that is used to modify
the result of the API call. \n This can be used to extract
a repeating element from a response. https://kubernetes.io/docs/reference/kubectl/jsonpath/"
type: string
required:
- interval
type: object
cluster:
description: ClusterGenerator defines a generator that queries
the cluster API for relevant clusters.
Expand Down Expand Up @@ -169,6 +188,28 @@ spec:
generators allowed by the GitOpsSetGenerator because
the CRD format doesn't support recursive declarations.
properties:
apiClient:
description: APIClientGenerator defines a generator
that queries an API endpoint and uses that to generate
data.
properties:
endpoint:
description: This is the API endpoint to use.
pattern: ^https://
type: string
interval:
description: The interval at which to poll the
API endpoint.
type: string
jsonPath:
description: "JSONPath is string that is used
to modify the result of the API call. \n This
can be used to extract a repeating element from
a response. https://kubernetes.io/docs/reference/kubectl/jsonpath/"
type: string
required:
- interval
type: object
cluster:
description: ClusterGenerator defines a generator
that queries the cluster API for relevant clusters.
Expand Down
98 changes: 98 additions & 0 deletions controllers/templates/generators/apiclient/api_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package apiclient

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"

"github.com/go-logr/logr"
templatesv1 "github.com/weaveworks/gitopssets-controller/api/v1alpha1"
"github.com/weaveworks/gitopssets-controller/controllers/templates/generators"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// GeneratorFactory is a function for creating per-reconciliation generators for
// the MatrixGenerator.
func GeneratorFactory(httpClient *http.Client) generators.GeneratorFactory {
return func(l logr.Logger, c client.Client) generators.Generator {
return NewGenerator(l, c, httpClient)
}
}

// APIClientGenerator generates from an API endpoint.
type APIClientGenerator struct {
client.Client
HTTPClient *http.Client
logr.Logger
}

// NewGenerator creates and returns a new API client generator.
func NewGenerator(l logr.Logger, c client.Client, httpClient *http.Client) *APIClientGenerator {
return &APIClientGenerator{
Client: c,
Logger: l,
HTTPClient: httpClient,
}
}

func (g *APIClientGenerator) Generate(ctx context.Context, sg *templatesv1.GitOpsSetGenerator, ks *templatesv1.GitOpsSet) ([]map[string]any, error) {
if sg == nil {
g.Logger.Info("no generator provided")
return nil, generators.ErrEmptyGitOpsSet
}

if sg.APIClient == nil {
g.Logger.Info("API client info is nil")
return nil, nil
}

g.Logger.Info("generating params frou APIClient generator", "endpoint", sg.APIClient.Endpoint)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, sg.APIClient.Endpoint, nil)
if err != nil {
g.Logger.Error(err, "failed to create request", "endpoint", sg.APIClient.Endpoint)
return nil, err
}

resp, err := g.HTTPClient.Do(req)
if err != nil {
g.Logger.Error(err, "failed to fetch endpoint", "endpoint", sg.APIClient.Endpoint)
return nil, err
}
defer resp.Body.Close()

// Anything 400+ is an error?
if resp.StatusCode >= http.StatusBadRequest {
// TODO: this should read the body and insert it?
g.Logger.Info("failed to fetch endpoint", "endpoint", sg.APIClient.Endpoint, "statusCode", resp.StatusCode)
return nil, fmt.Errorf("got %d response from endpoint %s", resp.StatusCode, sg.APIClient.Endpoint)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
g.Logger.Error(err, "failed to read response", "endpoint", sg.APIClient.Endpoint)
return nil, err
}
// TODO: check that the response status code is right

var result []map[string]any
if err := json.Unmarshal(body, &result); err != nil {
g.Logger.Error(err, "failed to unmarshal JSON response", "endpoint", sg.APIClient.Endpoint)
return nil, fmt.Errorf("failed to unmarshal JSON response from endpoint %s", sg.APIClient.Endpoint)
}

res := []map[string]any{}
for _, v := range result {
res = append(res, v)
}

return res, nil
}

// Interval is an implementation of the Generator interface.
func (g *APIClientGenerator) Interval(sg *templatesv1.GitOpsSetGenerator) time.Duration {
return sg.APIClient.Interval.Duration
}
Loading

0 comments on commit d9ca30f

Please sign in to comment.