From acf139c36301a745c8c9d940c2900e06c302268c Mon Sep 17 00:00:00 2001 From: Marc Campbell <marc.e.campbell@gmail.com> Date: Thu, 3 Sep 2020 14:56:39 -0700 Subject: [PATCH] Initial commit --- CONTRIBUTING.md | 33 ++ Makefile | 11 + README.md | 64 +++ cmd/integration/main.go | 7 + .../crds/v1/crds.kubeflare.io_apitokens.yaml | 81 ++++ config/crds/v1/crds.kubeflare.io_tokens.yaml | 78 ++++ config/crds/v1/crds.kubeflare.io_zones.yaml | 99 +++++ .../v1beta1/crds.kubeflare.io_apitokens.yaml | 81 ++++ .../v1beta1/crds.kubeflare.io_tokens.yaml | 78 ++++ .../crds/v1beta1/crds.kubeflare.io_zones.yaml | 99 +++++ go.mod | 4 +- go.sum | 21 + integration/.gitignore | 1 + integration/Makefile | 12 + .../tests/always-use-https-off/Makefile | 5 + .../tests/always-use-https-off/after.sh | 6 + .../tests/always-use-https-off/before.sh | 7 + .../tests/always-use-https-off/expected.txt | 1 + .../tests/always-use-https-off/spec.yaml | 8 + .../tests/always-use-https-off/verify.sh | 3 + .../tests/always-use-https-on/Makefile | 6 + .../tests/always-use-https-on/after.sh | 6 + .../tests/always-use-https-on/before.sh | 7 + .../tests/always-use-https-on/expected.txt | 1 + .../tests/always-use-https-on/spec.yaml | 8 + .../tests/always-use-https-on/verify.sh | 3 + integration/tests/browser-cache-ttl/Makefile | 6 + integration/tests/browser-cache-ttl/after.sh | 6 + integration/tests/browser-cache-ttl/before.sh | 7 + .../tests/browser-cache-ttl/expected.txt | 1 + integration/tests/browser-cache-ttl/spec.yaml | 8 + integration/tests/browser-cache-ttl/verify.sh | 3 + integration/tests/cache-level/Makefile | 6 + integration/tests/cache-level/after.sh | 6 + integration/tests/cache-level/before.sh | 7 + integration/tests/cache-level/expected.txt | 1 + integration/tests/cache-level/spec.yaml | 8 + integration/tests/cache-level/verify.sh | 3 + integration/tests/common.mk | 15 + integration/tests/minify/Makefile | 6 + integration/tests/minify/after.sh | 6 + integration/tests/minify/before.sh | 7 + integration/tests/minify/expected.txt | 5 + integration/tests/minify/spec.yaml | 11 + integration/tests/minify/verify.sh | 3 + integration/tests/mobile-redirect/Makefile | 6 + integration/tests/mobile-redirect/after.sh | 6 + integration/tests/mobile-redirect/before.sh | 7 + .../tests/mobile-redirect/expected.txt | 5 + integration/tests/mobile-redirect/spec.yaml | 11 + integration/tests/mobile-redirect/verify.sh | 3 + pkg/apis/crds/v1alpha1/apitoken_types.go | 98 +++++ pkg/apis/crds/v1alpha1/zone_types.go | 54 +++ .../crds/v1alpha1/zz_generated.deepcopy.go | 368 +++++++++++++++++- pkg/cli/integrationcli/root.go | 47 +++ pkg/cli/integrationcli/run.go | 68 ++++ pkg/cli/managercli/run.go | 11 +- .../typed/crds/v1alpha1/apitoken.go | 195 ++++++++++ .../typed/crds/v1alpha1/crds_client.go | 5 + .../typed/crds/v1alpha1/fake/fake_apitoken.go | 142 +++++++ .../crds/v1alpha1/fake/fake_crds_client.go | 4 + .../crds/v1alpha1/generated_expansion.go | 2 + pkg/controller/zone/api.go | 47 +++ pkg/controller/zone/dns_records.go | 136 +++++++ pkg/controller/zone/settings.go | 324 +++++++++++++++ pkg/controller/zone/settings_test.go | 63 +++ pkg/controller/zone/zone_controller.go | 26 ++ 67 files changed, 2461 insertions(+), 12 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 cmd/integration/main.go create mode 100644 config/crds/v1/crds.kubeflare.io_apitokens.yaml create mode 100644 config/crds/v1/crds.kubeflare.io_tokens.yaml create mode 100644 config/crds/v1beta1/crds.kubeflare.io_apitokens.yaml create mode 100644 config/crds/v1beta1/crds.kubeflare.io_tokens.yaml create mode 100644 integration/.gitignore create mode 100644 integration/Makefile create mode 100644 integration/tests/always-use-https-off/Makefile create mode 100755 integration/tests/always-use-https-off/after.sh create mode 100755 integration/tests/always-use-https-off/before.sh create mode 100644 integration/tests/always-use-https-off/expected.txt create mode 100644 integration/tests/always-use-https-off/spec.yaml create mode 100755 integration/tests/always-use-https-off/verify.sh create mode 100644 integration/tests/always-use-https-on/Makefile create mode 100755 integration/tests/always-use-https-on/after.sh create mode 100755 integration/tests/always-use-https-on/before.sh create mode 100644 integration/tests/always-use-https-on/expected.txt create mode 100644 integration/tests/always-use-https-on/spec.yaml create mode 100755 integration/tests/always-use-https-on/verify.sh create mode 100644 integration/tests/browser-cache-ttl/Makefile create mode 100755 integration/tests/browser-cache-ttl/after.sh create mode 100755 integration/tests/browser-cache-ttl/before.sh create mode 100644 integration/tests/browser-cache-ttl/expected.txt create mode 100644 integration/tests/browser-cache-ttl/spec.yaml create mode 100755 integration/tests/browser-cache-ttl/verify.sh create mode 100644 integration/tests/cache-level/Makefile create mode 100755 integration/tests/cache-level/after.sh create mode 100755 integration/tests/cache-level/before.sh create mode 100644 integration/tests/cache-level/expected.txt create mode 100644 integration/tests/cache-level/spec.yaml create mode 100755 integration/tests/cache-level/verify.sh create mode 100644 integration/tests/common.mk create mode 100644 integration/tests/minify/Makefile create mode 100755 integration/tests/minify/after.sh create mode 100755 integration/tests/minify/before.sh create mode 100644 integration/tests/minify/expected.txt create mode 100644 integration/tests/minify/spec.yaml create mode 100755 integration/tests/minify/verify.sh create mode 100644 integration/tests/mobile-redirect/Makefile create mode 100755 integration/tests/mobile-redirect/after.sh create mode 100755 integration/tests/mobile-redirect/before.sh create mode 100644 integration/tests/mobile-redirect/expected.txt create mode 100644 integration/tests/mobile-redirect/spec.yaml create mode 100755 integration/tests/mobile-redirect/verify.sh create mode 100644 pkg/apis/crds/v1alpha1/apitoken_types.go create mode 100644 pkg/cli/integrationcli/root.go create mode 100644 pkg/cli/integrationcli/run.go create mode 100644 pkg/client/kubeflareclientset/typed/crds/v1alpha1/apitoken.go create mode 100644 pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_apitoken.go create mode 100644 pkg/controller/zone/api.go create mode 100644 pkg/controller/zone/dns_records.go create mode 100644 pkg/controller/zone/settings.go create mode 100644 pkg/controller/zone/settings_test.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fbf816b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contribuing Guide + + +### Integration tests + +These require a domain and API token from Cloudflare. +There's a test domain in the GitHub repo and GitHub Actions uses it when running. + +To execute locally: + +``` +export CF_API_EMAIL=you@you.com +export CF_API_KEY=1234 +export CF_ZONE_ID=12345 +export CF_ZONE_NAME=domain.com + +make integration +``` + +To run a single integration test (useful when adding a new api call): + +``` +export CF_API_EMAIL=you@you.com +export CF_API_KEY=1234 +export CF_ZONE_ID=12345 +export CF_ZONE_NAME=domain.com + +make -C integration/tests/cache-level run +``` + +## Setup + +- You should create an A record (proxied) for "mobile". diff --git a/Makefile b/Makefile index 7d416a2..afbe651 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,17 @@ clean-and-tidy: @go clean -modcache ||: @go mod tidy ||: +.PHONY: integration +integration: integration-bin + make -C integration run + +.PHONY: integration-bin +integration-bin: generate fmt vet manifests + go build \ + ${LDFLAGS} \ + -i \ + -o bin/integration \ + ./cmd/integration .PHONY: test test: generate fmt vet manifests diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c0601a --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# Kubeflare + +Kuebflare is a Kubernetes cluster add-on (Operator) that allows you to manage your Cloudflare settings using a Kubernetes declarative API. + +After installing the Kubeflare Operator to your Kubernetes cluster, some new custom types will be created in the cluster. These types allow you to define a Cloudflare Zone (domain) and specify the settings and DNS records to create. When this YAML is deployed to the cluster, the Kubeflare Operator will reconcile this with the Cloudflare API to deploy the settings requested in the YAML to the Cloudflare account. + +## Motiviation + +This project was created at [Replicated](https://www.replicated.com) to manage Cloudflare settings using our GitOps workflow. We wanted a way for a devleoper to commit their DNS records and other Cloudflare settings to be reviewed and deployed with their code, as a single deployment. This tightly couples the infrastructure changes with the application changes, and makes deploying new services easier and more transparant. + +## Examples + +Below is an example of a Kubernetes manifest that we deploy for a domain (with some information redacted): + +```yaml +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: domainname.io +spec: + apiToken: redacted + settings: + alwaysUseHttps: true + alwaysOnline: true + minify: + css: true + dnsRecords: + - type: "A" + name: "domainname.io" + content: "1.1.1.1" + proxied: true + ttl: 3600 + - type: "MX" + name: "domainname.io" + content: "aspmx.l.google.com" + priority: 1 + - type: "MX" + name: "domainname.io" + content: "alt1.aspmx.l.google.com" + priority: 5 + - type: "MX" + name: "domainname.io" + content: "alt2.aspmx.l.google.com" + priority: 5 + - type: "MX" + name: "domainname.io" + content: "alt3.aspmx.l.google.com" + proxied: false + priority: 10 + - type: "MX" + name: "domainname.io" + content: "alt4.aspmx.l.google.com" + priority: 10 +``` + +## Settings supported + +The Cloudflare API is large and supports many settings. Kubeflare doesn't support all yet, but the current release of Kubeflare supports the following settings: + +- All [zone settings](https://api.cloudflare.com/#zone-settings-get-all-zone-settings) (all settings listed under "Zone Settings") +- DNS Records + + +This project is independent of Cloudflare and built using their public APIs. This is not a Cloudflare project. diff --git a/cmd/integration/main.go b/cmd/integration/main.go new file mode 100644 index 0000000..43fd967 --- /dev/null +++ b/cmd/integration/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/replicatedhq/kubeflare/pkg/cli/integrationcli" + +func main() { + integrationcli.InitAndExecute() +} diff --git a/config/crds/v1/crds.kubeflare.io_apitokens.yaml b/config/crds/v1/crds.kubeflare.io_apitokens.yaml new file mode 100644 index 0000000..f26f668 --- /dev/null +++ b/config/crds/v1/crds.kubeflare.io_apitokens.yaml @@ -0,0 +1,81 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.8 + creationTimestamp: null + name: apitokens.crds.kubeflare.io +spec: + group: crds.kubeflare.io + names: + kind: APIToken + listKind: APITokenList + plural: apitokens + singular: apitoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: APIToken is the Schema for the APITokens API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APITokenSpec defines the desired state of APIToken + properties: + email: + type: string + name: + type: string + value: + type: string + valueFrom: + properties: + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be + a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be + defined + type: boolean + required: + - key + type: object + type: object + required: + - email + - name + type: object + status: + description: APITokenStatus defines the observed state of APIToken + type: object + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crds/v1/crds.kubeflare.io_tokens.yaml b/config/crds/v1/crds.kubeflare.io_tokens.yaml new file mode 100644 index 0000000..5dbd625 --- /dev/null +++ b/config/crds/v1/crds.kubeflare.io_tokens.yaml @@ -0,0 +1,78 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.8 + creationTimestamp: null + name: tokens.crds.kubeflare.io +spec: + group: crds.kubeflare.io + names: + kind: Token + listKind: TokenList + plural: tokens + singular: token + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Token is the Schema for the Tokens API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TokenSpec defines the desired state of Token + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be + a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be + defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + status: + description: TokenStatus defines the observed state of Token + type: object + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crds/v1/crds.kubeflare.io_zones.yaml b/config/crds/v1/crds.kubeflare.io_zones.yaml index e1ad836..0333b8a 100644 --- a/config/crds/v1/crds.kubeflare.io_zones.yaml +++ b/config/crds/v1/crds.kubeflare.io_zones.yaml @@ -35,6 +35,103 @@ spec: type: object spec: description: ZoneSpec defines the desired state of Zone + properties: + apiToken: + type: string + dnsRecords: + items: + properties: + content: + type: string + name: + type: string + priority: + type: integer + proxied: + type: boolean + ttl: + type: integer + type: + type: string + required: + - content + - name + - type + type: object + type: array + settings: + properties: + advancedDDOS: + type: boolean + alwaysOnline: + type: boolean + alwaysUseHttps: + type: boolean + automaticHTTSRewrites: + type: boolean + brotli: + type: boolean + browserCacheTTL: + type: integer + browserCheck: + type: boolean + cacheLevel: + type: string + challengeTTL: + type: integer + developmentMode: + type: boolean + emailObfuscation: + type: boolean + hotlinkProtection: + type: boolean + ipGeolocation: + type: boolean + ipv6: + type: boolean + minify: + properties: + css: + type: boolean + html: + type: boolean + js: + type: boolean + type: object + mirage: + type: boolean + mobileRedirect: + properties: + mobileSubdomain: + type: string + status: + type: boolean + stripURI: + type: boolean + required: + - status + type: object + opportunisticEncryption: + type: boolean + opportunisticOnion: + type: boolean + originErrorPagePassThru: + type: boolean + polish: + type: boolean + prefetchPreload: + type: boolean + privacyPass: + type: boolean + responseBuffering: + type: boolean + rocketLoader: + type: boolean + webp: + type: boolean + type: object + required: + - apiToken type: object status: description: ZoneStatus defines the observed state of Zone @@ -42,6 +139,8 @@ spec: type: object served: true storage: true + subresources: + status: {} status: acceptedNames: kind: "" diff --git a/config/crds/v1beta1/crds.kubeflare.io_apitokens.yaml b/config/crds/v1beta1/crds.kubeflare.io_apitokens.yaml new file mode 100644 index 0000000..0b8281b --- /dev/null +++ b/config/crds/v1beta1/crds.kubeflare.io_apitokens.yaml @@ -0,0 +1,81 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.8 + creationTimestamp: null + name: apitokens.crds.kubeflare.io +spec: + group: crds.kubeflare.io + names: + kind: APIToken + listKind: APITokenList + plural: apitokens + singular: apitoken + scope: Namespaced + validation: + openAPIV3Schema: + description: APIToken is the Schema for the APITokens API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: APITokenSpec defines the desired state of APIToken + properties: + email: + type: string + name: + type: string + value: + type: string + valueFrom: + properties: + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be + a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - email + - name + type: object + status: + description: APITokenStatus defines the observed state of APIToken + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crds/v1beta1/crds.kubeflare.io_tokens.yaml b/config/crds/v1beta1/crds.kubeflare.io_tokens.yaml new file mode 100644 index 0000000..9e189d3 --- /dev/null +++ b/config/crds/v1beta1/crds.kubeflare.io_tokens.yaml @@ -0,0 +1,78 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.8 + creationTimestamp: null + name: tokens.crds.kubeflare.io +spec: + group: crds.kubeflare.io + names: + kind: Token + listKind: TokenList + plural: tokens + singular: token + scope: Namespaced + validation: + openAPIV3Schema: + description: Token is the Schema for the Tokens API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TokenSpec defines the desired state of Token + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must be + a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + status: + description: TokenStatus defines the observed state of Token + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crds/v1beta1/crds.kubeflare.io_zones.yaml b/config/crds/v1beta1/crds.kubeflare.io_zones.yaml index a181720..d248801 100644 --- a/config/crds/v1beta1/crds.kubeflare.io_zones.yaml +++ b/config/crds/v1beta1/crds.kubeflare.io_zones.yaml @@ -15,6 +15,8 @@ spec: plural: zones singular: zone scope: Namespaced + subresources: + status: {} validation: openAPIV3Schema: description: Zone is the Schema for the zones API @@ -33,6 +35,103 @@ spec: type: object spec: description: ZoneSpec defines the desired state of Zone + properties: + apiToken: + type: string + dnsRecords: + items: + properties: + content: + type: string + name: + type: string + priority: + type: integer + proxied: + type: boolean + ttl: + type: integer + type: + type: string + required: + - content + - name + - type + type: object + type: array + settings: + properties: + advancedDDOS: + type: boolean + alwaysOnline: + type: boolean + alwaysUseHttps: + type: boolean + automaticHTTSRewrites: + type: boolean + brotli: + type: boolean + browserCacheTTL: + type: integer + browserCheck: + type: boolean + cacheLevel: + type: string + challengeTTL: + type: integer + developmentMode: + type: boolean + emailObfuscation: + type: boolean + hotlinkProtection: + type: boolean + ipGeolocation: + type: boolean + ipv6: + type: boolean + minify: + properties: + css: + type: boolean + html: + type: boolean + js: + type: boolean + type: object + mirage: + type: boolean + mobileRedirect: + properties: + mobileSubdomain: + type: string + status: + type: boolean + stripURI: + type: boolean + required: + - status + type: object + opportunisticEncryption: + type: boolean + opportunisticOnion: + type: boolean + originErrorPagePassThru: + type: boolean + polish: + type: boolean + prefetchPreload: + type: boolean + privacyPass: + type: boolean + responseBuffering: + type: boolean + rocketLoader: + type: boolean + webp: + type: boolean + type: object + required: + - apiToken type: object status: description: ZoneStatus defines the observed state of Zone diff --git a/go.mod b/go.mod index f6e16ae..6d61f66 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/aws/aws-sdk-go-v2 v0.23.0 github.com/blang/semver v3.5.1+incompatible + github.com/cloudflare/cloudflare-go v0.13.2 github.com/go-sql-driver/mysql v1.5.0 github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect @@ -13,9 +14,10 @@ require ( github.com/onsi/gomega v1.8.1 github.com/pkg/errors v0.9.1 github.com/prometheus/common v0.4.1 + github.com/r3labs/diff v1.1.0 github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.4.0 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3 go.uber.org/zap v1.10.0 gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index 6a47f7c..ad845d8 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A= +github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -53,6 +55,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -294,6 +297,7 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -312,6 +316,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -329,6 +334,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -349,6 +355,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/r3labs/diff v1.1.0 h1:V53xhrbTHrWFWq3gI4b94AjgEJOerO1+1l0xyHOBi8M= +github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -356,10 +364,12 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -391,12 +401,15 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xo/dburl v0.0.0-20200124232849-e9ec94f52bc3/go.mod h1:A47W3pdWONaZmXuLZgfKLAVgUY0qvfTRM5vVDKS40S4= @@ -466,6 +479,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -498,6 +513,8 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -508,6 +525,8 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -575,6 +594,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/integration/.gitignore b/integration/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/integration/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/integration/Makefile b/integration/Makefile new file mode 100644 index 0000000..cbd80a1 --- /dev/null +++ b/integration/Makefile @@ -0,0 +1,12 @@ +SHELL := /bin/bash + +all: run + +.PHONY: run +run: + make -C tests/always-use-https-on run + make -C tests/always-use-https-off run + make -C tests/browser-cache-ttl run + make -C tests/cache-level run + make -C tests/minify run + make -C tests/mobile-redirect run \ No newline at end of file diff --git a/integration/tests/always-use-https-off/Makefile b/integration/tests/always-use-https-off/Makefile new file mode 100644 index 0000000..282e527 --- /dev/null +++ b/integration/tests/always-use-https-off/Makefile @@ -0,0 +1,5 @@ +include ../common.mk + +.PHONY: run +run: commonrun + diff -B ./expected.txt ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/always-use-https-off/after.sh b/integration/tests/always-use-https-off/after.sh new file mode 100755 index 0000000..1fe0c5a --- /dev/null +++ b/integration/tests/always-use-https-off/after.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/always_use_https" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ No newline at end of file diff --git a/integration/tests/always-use-https-off/before.sh b/integration/tests/always-use-https-off/before.sh new file mode 100755 index 0000000..76e2a09 --- /dev/null +++ b/integration/tests/always-use-https-off/before.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/always_use_https" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"value":"on"}' diff --git a/integration/tests/always-use-https-off/expected.txt b/integration/tests/always-use-https-off/expected.txt new file mode 100644 index 0000000..c323644 --- /dev/null +++ b/integration/tests/always-use-https-off/expected.txt @@ -0,0 +1 @@ +"off" diff --git a/integration/tests/always-use-https-off/spec.yaml b/integration/tests/always-use-https-off/spec.yaml new file mode 100644 index 0000000..e321ebd --- /dev/null +++ b/integration/tests/always-use-https-off/spec.yaml @@ -0,0 +1,8 @@ +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: always-use-https +spec: + apiToken: UNUSED + settings: + alwaysUseHttps: false \ No newline at end of file diff --git a/integration/tests/always-use-https-off/verify.sh b/integration/tests/always-use-https-off/verify.sh new file mode 100755 index 0000000..6a4b8a4 --- /dev/null +++ b/integration/tests/always-use-https-off/verify.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./out/result.json | jq '.result.value' > ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/always-use-https-on/Makefile b/integration/tests/always-use-https-on/Makefile new file mode 100644 index 0000000..93d7d6c --- /dev/null +++ b/integration/tests/always-use-https-on/Makefile @@ -0,0 +1,6 @@ +include ../common.mk + + +.PHONY: run +run: commonrun + diff -B ./expected.txt ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/always-use-https-on/after.sh b/integration/tests/always-use-https-on/after.sh new file mode 100755 index 0000000..1fe0c5a --- /dev/null +++ b/integration/tests/always-use-https-on/after.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/always_use_https" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ No newline at end of file diff --git a/integration/tests/always-use-https-on/before.sh b/integration/tests/always-use-https-on/before.sh new file mode 100755 index 0000000..ea95a3e --- /dev/null +++ b/integration/tests/always-use-https-on/before.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/always_use_https" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"value":"off"}' diff --git a/integration/tests/always-use-https-on/expected.txt b/integration/tests/always-use-https-on/expected.txt new file mode 100644 index 0000000..75e6408 --- /dev/null +++ b/integration/tests/always-use-https-on/expected.txt @@ -0,0 +1 @@ +"on" diff --git a/integration/tests/always-use-https-on/spec.yaml b/integration/tests/always-use-https-on/spec.yaml new file mode 100644 index 0000000..40dd43c --- /dev/null +++ b/integration/tests/always-use-https-on/spec.yaml @@ -0,0 +1,8 @@ +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: always-use-https +spec: + apiToken: UNUSED + settings: + alwaysUseHttps: true \ No newline at end of file diff --git a/integration/tests/always-use-https-on/verify.sh b/integration/tests/always-use-https-on/verify.sh new file mode 100755 index 0000000..6a4b8a4 --- /dev/null +++ b/integration/tests/always-use-https-on/verify.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./out/result.json | jq '.result.value' > ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/browser-cache-ttl/Makefile b/integration/tests/browser-cache-ttl/Makefile new file mode 100644 index 0000000..93d7d6c --- /dev/null +++ b/integration/tests/browser-cache-ttl/Makefile @@ -0,0 +1,6 @@ +include ../common.mk + + +.PHONY: run +run: commonrun + diff -B ./expected.txt ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/browser-cache-ttl/after.sh b/integration/tests/browser-cache-ttl/after.sh new file mode 100755 index 0000000..985324f --- /dev/null +++ b/integration/tests/browser-cache-ttl/after.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/browser_cache_ttl" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ No newline at end of file diff --git a/integration/tests/browser-cache-ttl/before.sh b/integration/tests/browser-cache-ttl/before.sh new file mode 100755 index 0000000..b85f1c4 --- /dev/null +++ b/integration/tests/browser-cache-ttl/before.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/browser_cache_ttl" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"value" 14400}' diff --git a/integration/tests/browser-cache-ttl/expected.txt b/integration/tests/browser-cache-ttl/expected.txt new file mode 100644 index 0000000..f3317a6 --- /dev/null +++ b/integration/tests/browser-cache-ttl/expected.txt @@ -0,0 +1 @@ +86400 diff --git a/integration/tests/browser-cache-ttl/spec.yaml b/integration/tests/browser-cache-ttl/spec.yaml new file mode 100644 index 0000000..f4b8369 --- /dev/null +++ b/integration/tests/browser-cache-ttl/spec.yaml @@ -0,0 +1,8 @@ +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: always-use-https +spec: + apiToken: UNUSED + settings: + browserCacheTTL: 86400 \ No newline at end of file diff --git a/integration/tests/browser-cache-ttl/verify.sh b/integration/tests/browser-cache-ttl/verify.sh new file mode 100755 index 0000000..6a4b8a4 --- /dev/null +++ b/integration/tests/browser-cache-ttl/verify.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./out/result.json | jq '.result.value' > ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/cache-level/Makefile b/integration/tests/cache-level/Makefile new file mode 100644 index 0000000..93d7d6c --- /dev/null +++ b/integration/tests/cache-level/Makefile @@ -0,0 +1,6 @@ +include ../common.mk + + +.PHONY: run +run: commonrun + diff -B ./expected.txt ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/cache-level/after.sh b/integration/tests/cache-level/after.sh new file mode 100755 index 0000000..63d5f4f --- /dev/null +++ b/integration/tests/cache-level/after.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/cache_level" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ No newline at end of file diff --git a/integration/tests/cache-level/before.sh b/integration/tests/cache-level/before.sh new file mode 100755 index 0000000..b976333 --- /dev/null +++ b/integration/tests/cache-level/before.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/cache_level" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"value":"aggressive"}' diff --git a/integration/tests/cache-level/expected.txt b/integration/tests/cache-level/expected.txt new file mode 100644 index 0000000..c05a929 --- /dev/null +++ b/integration/tests/cache-level/expected.txt @@ -0,0 +1 @@ +"basic" diff --git a/integration/tests/cache-level/spec.yaml b/integration/tests/cache-level/spec.yaml new file mode 100644 index 0000000..396efdc --- /dev/null +++ b/integration/tests/cache-level/spec.yaml @@ -0,0 +1,8 @@ +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: always-use-https +spec: + apiToken: UNUSED + settings: + cacheLevel: basic \ No newline at end of file diff --git a/integration/tests/cache-level/verify.sh b/integration/tests/cache-level/verify.sh new file mode 100755 index 0000000..6a4b8a4 --- /dev/null +++ b/integration/tests/cache-level/verify.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./out/result.json | jq '.result.value' > ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/common.mk b/integration/tests/common.mk new file mode 100644 index 0000000..0c5d919 --- /dev/null +++ b/integration/tests/common.mk @@ -0,0 +1,15 @@ +SHELL := /bin/bash + +.PHONY: commonrun +commonrun: + ./before.sh + ../../../bin/integration run \ + --spec ./spec.yaml \ + --email $(CF_API_EMAIL) \ + --key $(CF_API_KEY) \ + --zone-name $(CF_ZONE_NAME) + rm -rf out + mkdir -p out + ./after.sh > out/result.json + ./verify.sh + \ No newline at end of file diff --git a/integration/tests/minify/Makefile b/integration/tests/minify/Makefile new file mode 100644 index 0000000..93d7d6c --- /dev/null +++ b/integration/tests/minify/Makefile @@ -0,0 +1,6 @@ +include ../common.mk + + +.PHONY: run +run: commonrun + diff -B ./expected.txt ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/minify/after.sh b/integration/tests/minify/after.sh new file mode 100755 index 0000000..a917d13 --- /dev/null +++ b/integration/tests/minify/after.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/minify" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ No newline at end of file diff --git a/integration/tests/minify/before.sh b/integration/tests/minify/before.sh new file mode 100755 index 0000000..9d9a19b --- /dev/null +++ b/integration/tests/minify/before.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/minify" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"value":{"css": "off", "js": "off", "html": "off"}}' diff --git a/integration/tests/minify/expected.txt b/integration/tests/minify/expected.txt new file mode 100644 index 0000000..16ea088 --- /dev/null +++ b/integration/tests/minify/expected.txt @@ -0,0 +1,5 @@ +{ + "js": "on", + "css": "on", + "html": "off" +} diff --git a/integration/tests/minify/spec.yaml b/integration/tests/minify/spec.yaml new file mode 100644 index 0000000..551e267 --- /dev/null +++ b/integration/tests/minify/spec.yaml @@ -0,0 +1,11 @@ +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: always-use-https +spec: + apiToken: UNUSED + settings: + minify: + css: on + js: on + html: off \ No newline at end of file diff --git a/integration/tests/minify/verify.sh b/integration/tests/minify/verify.sh new file mode 100755 index 0000000..6a4b8a4 --- /dev/null +++ b/integration/tests/minify/verify.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./out/result.json | jq '.result.value' > ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/mobile-redirect/Makefile b/integration/tests/mobile-redirect/Makefile new file mode 100644 index 0000000..93d7d6c --- /dev/null +++ b/integration/tests/mobile-redirect/Makefile @@ -0,0 +1,6 @@ +include ../common.mk + + +.PHONY: run +run: commonrun + diff -B ./expected.txt ./out/actual.txt \ No newline at end of file diff --git a/integration/tests/mobile-redirect/after.sh b/integration/tests/mobile-redirect/after.sh new file mode 100755 index 0000000..c8dccce --- /dev/null +++ b/integration/tests/mobile-redirect/after.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +curl -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/mobile_redirect" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ No newline at end of file diff --git a/integration/tests/mobile-redirect/before.sh b/integration/tests/mobile-redirect/before.sh new file mode 100755 index 0000000..d7ebfa0 --- /dev/null +++ b/integration/tests/mobile-redirect/before.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/settings/mobile_redirect" \ + -H "X-Auth-Email: ${CF_API_EMAIL}" \ + -H "X-Auth-Key: ${CF_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{"value":{"status": "off"}}' diff --git a/integration/tests/mobile-redirect/expected.txt b/integration/tests/mobile-redirect/expected.txt new file mode 100644 index 0000000..79fb35e --- /dev/null +++ b/integration/tests/mobile-redirect/expected.txt @@ -0,0 +1,5 @@ +{ + "status": "on", + "mobile_subdomain": "mobile", + "strip_uri": true +} diff --git a/integration/tests/mobile-redirect/spec.yaml b/integration/tests/mobile-redirect/spec.yaml new file mode 100644 index 0000000..f7692ba --- /dev/null +++ b/integration/tests/mobile-redirect/spec.yaml @@ -0,0 +1,11 @@ +apiVersion: crds.kubeflare.io/v1alpha1 +kind: Zone +metadata: + name: always-use-https +spec: + apiToken: UNUSED + settings: + mobileRedirect: + status: true + mobileSubdomain: "mobile" + stripURI: true \ No newline at end of file diff --git a/integration/tests/mobile-redirect/verify.sh b/integration/tests/mobile-redirect/verify.sh new file mode 100755 index 0000000..6a4b8a4 --- /dev/null +++ b/integration/tests/mobile-redirect/verify.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cat ./out/result.json | jq '.result.value' > ./out/actual.txt \ No newline at end of file diff --git a/pkg/apis/crds/v1alpha1/apitoken_types.go b/pkg/apis/crds/v1alpha1/apitoken_types.go new file mode 100644 index 0000000..263ee47 --- /dev/null +++ b/pkg/apis/crds/v1alpha1/apitoken_types.go @@ -0,0 +1,98 @@ +/* +Copyright 2019 Replicated, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +func (a APIToken) GetTokenValue(ctx context.Context) (string, error) { + if a.Spec.Value != "" { + return a.Spec.Value, nil + } + + if a.Spec.ValueFrom != nil { + cfg, err := config.GetConfig() + if err != nil { + return "", errors.Wrap(err, "failed to get config") + } + + clientset, err := kubernetes.NewForConfig(cfg) + if err != nil { + return "", errors.Wrap(err, "failed to get clientset") + } + + if a.Spec.ValueFrom.SecretKeyRef != nil { + secret, err := clientset.CoreV1().Secrets(a.Namespace).Get(ctx, a.Spec.ValueFrom.SecretKeyRef.Name, metav1.GetOptions{}) + if err != nil { + return "", errors.Wrap(err, "failed to get secret") + } + + return string(secret.Data[a.Spec.ValueFrom.SecretKeyRef.Key]), nil + } + } + + return "", errors.New("unable to read api token") +} + +type ValueFrom struct { + SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` +} + +// APITokenSpec defines the desired state of APIToken +type APITokenSpec struct { + Name string `json:"name"` + Email string `json:"email"` + Value string `json:"value,omitempty"` + ValueFrom *ValueFrom `json:"valueFrom,omitempty"` +} + +// APITokenStatus defines the observed state of APIToken +type APITokenStatus struct { +} + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIToken is the Schema for the APITokens API +// +k8s:openapi-gen=true +type APIToken struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec APITokenSpec `json:"spec,omitempty"` + Status APITokenStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APITokenList contains a list of APIToken +type APITokenList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []APIToken `json:"items"` +} + +func init() { + SchemeBuilder.Register(&APIToken{}, &APITokenList{}) +} diff --git a/pkg/apis/crds/v1alpha1/zone_types.go b/pkg/apis/crds/v1alpha1/zone_types.go index 6e7193f..af9170b 100644 --- a/pkg/apis/crds/v1alpha1/zone_types.go +++ b/pkg/apis/crds/v1alpha1/zone_types.go @@ -20,8 +20,61 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type DNSRecord struct { + Type string `json:"type"` + Name string `json:"name"` + Content string `json:"content"` + TTL *int `json:"ttl,omitempty"` + Priority *int `json:"priority,omitempty"` + Proxied *bool `json:"proxied,omitempty"` +} + +type MobileRedirect struct { + Status *bool `json:"status,omi2tempty"` + MobileSubdomain *string `json:"mobileSubdomain,omitempty"` + StripURI *bool `json:"stripURI,omitempty"` +} + +type MinifySetting struct { + CSS *bool `json:"css,omitempty"` + HTML *bool `json:"html,omitempty"` + JS *bool `json:"js,omitempty"` +} + +type ZoneSettings struct { + AdvancedDDOS *bool `json:"advancedDDOS,omitempty"` + AlwaysOnline *bool `json:"alwaysOnline,omitempty"` + AlwaysUseHTTPS *bool `json:"alwaysUseHttps,omitempty"` + OpportunisticOnion *bool `json:"opportunisticOnion,omitempty"` + AutomaticHTTPSRewrites *bool `json:"automaticHTTSRewrites,omitempty"` + BrowserCacheTTL *int `json:"browserCacheTTL,omitempty"` + BrowserCheck *bool `json:"browserCheck,omitempty"` + CacheLevel *string `json:"cacheLevel,omitempty"` + ChallengeTTL *int `json:"challengeTTL,omitempty"` + DevelopmentMode *bool `json:"developmentMode,omitempty"` + EmailObfuscation *bool `json:"emailObfuscation,omitempty"` + HotlinkProtection *bool `json:"hotlinkProtection,omitempty"` + IPGeolocation *bool `json:"ipGeolocation,omitempty"` + IPV6 *bool `json:"ipv6,omitempty"` + Minify *MinifySetting `json:"minify,omitempty"` + MobileRedirect *MobileRedirect `json:"mobileRedirect,omitempty"` + Mirage *bool `json:"mirage,omitempty"` + OriginErrorPagePassThru *bool `json:"originErrorPagePassThru,omitempty"` + OpportunisticEncryption *bool `json:"opportunisticEncryption,omitempty"` + Polish *bool `json:"polish,omitempty"` + WebP *bool `json:"webp,omitempty"` + Brotli *bool `json:"brotli,omitempty"` + PrefetchPreload *bool `json:"prefetchPreload,omitempty"` + PrivacyPass *bool `json:"privacyPass,omitempty"` + ResponseBuffering *bool `json:"responseBuffering,omitempty"` + RocketLoader *bool `json:"rocketLoader,omitempty"` +} + // ZoneSpec defines the desired state of Zone type ZoneSpec struct { + APIToken string `json:"apiToken"` + Settings *ZoneSettings `json:"settings,omitempty"` + DNSRecords []*DNSRecord `json:"dnsRecords,omitempty"` } // ZoneStatus defines the observed state of Zone @@ -33,6 +86,7 @@ type ZoneStatus struct { // Zone is the Schema for the zones API // +k8s:openapi-gen=true +// +kubebuilder:subresource:status type Zone struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/crds/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/crds/v1alpha1/zz_generated.deepcopy.go index b7782ed..aa16c54 100644 --- a/pkg/apis/crds/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/crds/v1alpha1/zz_generated.deepcopy.go @@ -21,15 +21,220 @@ limitations under the License. package v1alpha1 import ( + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIToken) DeepCopyInto(out *APIToken) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIToken. +func (in *APIToken) DeepCopy() *APIToken { + if in == nil { + return nil + } + out := new(APIToken) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APIToken) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APITokenList) DeepCopyInto(out *APITokenList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]APIToken, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APITokenList. +func (in *APITokenList) DeepCopy() *APITokenList { + if in == nil { + return nil + } + out := new(APITokenList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *APITokenList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APITokenSpec) DeepCopyInto(out *APITokenSpec) { + *out = *in + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(ValueFrom) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APITokenSpec. +func (in *APITokenSpec) DeepCopy() *APITokenSpec { + if in == nil { + return nil + } + out := new(APITokenSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APITokenStatus) DeepCopyInto(out *APITokenStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APITokenStatus. +func (in *APITokenStatus) DeepCopy() *APITokenStatus { + if in == nil { + return nil + } + out := new(APITokenStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRecord) DeepCopyInto(out *DNSRecord) { + *out = *in + if in.TTL != nil { + in, out := &in.TTL, &out.TTL + *out = new(int) + **out = **in + } + if in.Priority != nil { + in, out := &in.Priority, &out.Priority + *out = new(int) + **out = **in + } + if in.Proxied != nil { + in, out := &in.Proxied, &out.Proxied + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRecord. +func (in *DNSRecord) DeepCopy() *DNSRecord { + if in == nil { + return nil + } + out := new(DNSRecord) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MinifySetting) DeepCopyInto(out *MinifySetting) { + *out = *in + if in.CSS != nil { + in, out := &in.CSS, &out.CSS + *out = new(bool) + **out = **in + } + if in.HTML != nil { + in, out := &in.HTML, &out.HTML + *out = new(bool) + **out = **in + } + if in.JS != nil { + in, out := &in.JS, &out.JS + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MinifySetting. +func (in *MinifySetting) DeepCopy() *MinifySetting { + if in == nil { + return nil + } + out := new(MinifySetting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MobileRedirect) DeepCopyInto(out *MobileRedirect) { + *out = *in + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(bool) + **out = **in + } + if in.MobileSubdomain != nil { + in, out := &in.MobileSubdomain, &out.MobileSubdomain + *out = new(string) + **out = **in + } + if in.StripURI != nil { + in, out := &in.StripURI, &out.StripURI + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MobileRedirect. +func (in *MobileRedirect) DeepCopy() *MobileRedirect { + if in == nil { + return nil + } + out := new(MobileRedirect) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValueFrom) DeepCopyInto(out *ValueFrom) { + *out = *in + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValueFrom. +func (in *ValueFrom) DeepCopy() *ValueFrom { + if in == nil { + return nil + } + out := new(ValueFrom) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Zone) DeepCopyInto(out *Zone) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -83,9 +288,170 @@ func (in *ZoneList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ZoneSettings) DeepCopyInto(out *ZoneSettings) { + *out = *in + if in.AdvancedDDOS != nil { + in, out := &in.AdvancedDDOS, &out.AdvancedDDOS + *out = new(bool) + **out = **in + } + if in.AlwaysOnline != nil { + in, out := &in.AlwaysOnline, &out.AlwaysOnline + *out = new(bool) + **out = **in + } + if in.AlwaysUseHTTPS != nil { + in, out := &in.AlwaysUseHTTPS, &out.AlwaysUseHTTPS + *out = new(bool) + **out = **in + } + if in.OpportunisticOnion != nil { + in, out := &in.OpportunisticOnion, &out.OpportunisticOnion + *out = new(bool) + **out = **in + } + if in.AutomaticHTTPSRewrites != nil { + in, out := &in.AutomaticHTTPSRewrites, &out.AutomaticHTTPSRewrites + *out = new(bool) + **out = **in + } + if in.BrowserCacheTTL != nil { + in, out := &in.BrowserCacheTTL, &out.BrowserCacheTTL + *out = new(int) + **out = **in + } + if in.BrowserCheck != nil { + in, out := &in.BrowserCheck, &out.BrowserCheck + *out = new(bool) + **out = **in + } + if in.CacheLevel != nil { + in, out := &in.CacheLevel, &out.CacheLevel + *out = new(string) + **out = **in + } + if in.ChallengeTTL != nil { + in, out := &in.ChallengeTTL, &out.ChallengeTTL + *out = new(int) + **out = **in + } + if in.DevelopmentMode != nil { + in, out := &in.DevelopmentMode, &out.DevelopmentMode + *out = new(bool) + **out = **in + } + if in.EmailObfuscation != nil { + in, out := &in.EmailObfuscation, &out.EmailObfuscation + *out = new(bool) + **out = **in + } + if in.HotlinkProtection != nil { + in, out := &in.HotlinkProtection, &out.HotlinkProtection + *out = new(bool) + **out = **in + } + if in.IPGeolocation != nil { + in, out := &in.IPGeolocation, &out.IPGeolocation + *out = new(bool) + **out = **in + } + if in.IPV6 != nil { + in, out := &in.IPV6, &out.IPV6 + *out = new(bool) + **out = **in + } + if in.Minify != nil { + in, out := &in.Minify, &out.Minify + *out = new(MinifySetting) + (*in).DeepCopyInto(*out) + } + if in.MobileRedirect != nil { + in, out := &in.MobileRedirect, &out.MobileRedirect + *out = new(MobileRedirect) + (*in).DeepCopyInto(*out) + } + if in.Mirage != nil { + in, out := &in.Mirage, &out.Mirage + *out = new(bool) + **out = **in + } + if in.OriginErrorPagePassThru != nil { + in, out := &in.OriginErrorPagePassThru, &out.OriginErrorPagePassThru + *out = new(bool) + **out = **in + } + if in.OpportunisticEncryption != nil { + in, out := &in.OpportunisticEncryption, &out.OpportunisticEncryption + *out = new(bool) + **out = **in + } + if in.Polish != nil { + in, out := &in.Polish, &out.Polish + *out = new(bool) + **out = **in + } + if in.WebP != nil { + in, out := &in.WebP, &out.WebP + *out = new(bool) + **out = **in + } + if in.Brotli != nil { + in, out := &in.Brotli, &out.Brotli + *out = new(bool) + **out = **in + } + if in.PrefetchPreload != nil { + in, out := &in.PrefetchPreload, &out.PrefetchPreload + *out = new(bool) + **out = **in + } + if in.PrivacyPass != nil { + in, out := &in.PrivacyPass, &out.PrivacyPass + *out = new(bool) + **out = **in + } + if in.ResponseBuffering != nil { + in, out := &in.ResponseBuffering, &out.ResponseBuffering + *out = new(bool) + **out = **in + } + if in.RocketLoader != nil { + in, out := &in.RocketLoader, &out.RocketLoader + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZoneSettings. +func (in *ZoneSettings) DeepCopy() *ZoneSettings { + if in == nil { + return nil + } + out := new(ZoneSettings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ZoneSpec) DeepCopyInto(out *ZoneSpec) { *out = *in + if in.Settings != nil { + in, out := &in.Settings, &out.Settings + *out = new(ZoneSettings) + (*in).DeepCopyInto(*out) + } + if in.DNSRecords != nil { + in, out := &in.DNSRecords, &out.DNSRecords + *out = make([]*DNSRecord, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(DNSRecord) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZoneSpec. diff --git a/pkg/cli/integrationcli/root.go b/pkg/cli/integrationcli/root.go new file mode 100644 index 0000000..8b9056a --- /dev/null +++ b/pkg/cli/integrationcli/root.go @@ -0,0 +1,47 @@ +package integrationcli + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func RootCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "integration", + Short: "this is the out-of-k8s integration and e2e tests of kubeflare", + Long: `...`, + PreRun: func(cmd *cobra.Command, args []string) { + viper.BindPFlags(cmd.Flags()) + }, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + os.Exit(1) + }, + } + + cobra.OnInitialize(initConfig) + + cmd.AddCommand(RunCmd()) + + viper.BindPFlags(cmd.Flags()) + + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + + return cmd +} + +func InitAndExecute() { + if err := RootCmd().Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func initConfig() { + viper.SetEnvPrefix("KUBEFLARE") + viper.AutomaticEnv() +} diff --git a/pkg/cli/integrationcli/run.go b/pkg/cli/integrationcli/run.go new file mode 100644 index 0000000..980daff --- /dev/null +++ b/pkg/cli/integrationcli/run.go @@ -0,0 +1,68 @@ +package integrationcli + +import ( + "context" + "io/ioutil" + + "github.com/cloudflare/cloudflare-go" + crdsv1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + kubeflarescheme "github.com/replicatedhq/kubeflare/pkg/client/kubeflareclientset/scheme" + "github.com/replicatedhq/kubeflare/pkg/controller/zone" + "github.com/spf13/cobra" + "github.com/spf13/viper" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" +) + +func RunCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "runs a single integration test from a definition", + Long: `...`, + SilenceErrors: true, + SilenceUsage: true, + PreRun: func(cmd *cobra.Command, args []string) { + viper.BindPFlags(cmd.Flags()) + }, + RunE: func(cmd *cobra.Command, args []string) error { + v := viper.GetViper() + + b, err := ioutil.ReadFile(v.GetString("spec")) + if err != nil { + return err + } + + decode := kubeflarescheme.Codecs.UniversalDeserializer().Decode + obj, _, err := decode(b, nil, nil) + if err != nil { + return err + } + instance := obj.(*crdsv1alpha1.Zone) + instance.Name = v.GetString("zone-name") + + ctx := context.TODO() + + cf, err := cloudflare.New(v.GetString("key"), v.GetString("email")) + if err != nil { + return err + } + + err = zone.ReconcileSettings(ctx, *instance, cf) + if err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().String("spec", "", "the spec file to apply") + cmd.MarkFlagRequired("spec") + cmd.Flags().String("email", "", "email to use with cloudflare") + cmd.MarkFlagRequired("email") + cmd.Flags().String("key", "", "cloudflare api key") + cmd.MarkFlagRequired("key") + cmd.Flags().String("zone-name", "", "zone name (domain) to use") + cmd.MarkFlagRequired("zone-name") + + return cmd +} diff --git a/pkg/cli/managercli/run.go b/pkg/cli/managercli/run.go index 910aae4..2df12a2 100644 --- a/pkg/cli/managercli/run.go +++ b/pkg/cli/managercli/run.go @@ -2,7 +2,6 @@ package managercli import ( "os" - "strings" "github.com/replicatedhq/kubeflare/pkg/apis" zonecontroller "github.com/replicatedhq/kubeflare/pkg/controller/zone" @@ -33,6 +32,7 @@ func RunCmd() *cobra.Command { v := viper.GetViper() if v.GetString("log-level") == "debug" { + logger.Info("setting log level to debug") logger.SetDebug() } @@ -84,12 +84,3 @@ func RunCmd() *cobra.Command { return cmd } - -func defaultManagerTag() string { - tag := version.Version() - if strings.HasPrefix(tag, "v") { - tag = strings.TrimPrefix(tag, "v") - } - - return tag -} diff --git a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/apitoken.go b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/apitoken.go new file mode 100644 index 0000000..b7c835f --- /dev/null +++ b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/apitoken.go @@ -0,0 +1,195 @@ +/* +Copyright 2020 Replicated, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + scheme "github.com/replicatedhq/kubeflare/pkg/client/kubeflareclientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// APITokensGetter has a method to return a APITokenInterface. +// A group's client should implement this interface. +type APITokensGetter interface { + APITokens(namespace string) APITokenInterface +} + +// APITokenInterface has methods to work with APIToken resources. +type APITokenInterface interface { + Create(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.CreateOptions) (*v1alpha1.APIToken, error) + Update(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.UpdateOptions) (*v1alpha1.APIToken, error) + UpdateStatus(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.UpdateOptions) (*v1alpha1.APIToken, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.APIToken, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.APITokenList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.APIToken, err error) + APITokenExpansion +} + +// aPITokens implements APITokenInterface +type aPITokens struct { + client rest.Interface + ns string +} + +// newAPITokens returns a APITokens +func newAPITokens(c *CrdsV1alpha1Client, namespace string) *aPITokens { + return &aPITokens{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the aPIToken, and returns the corresponding aPIToken object, and an error if there is any. +func (c *aPITokens) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.APIToken, err error) { + result = &v1alpha1.APIToken{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apitokens"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of APITokens that match those selectors. +func (c *aPITokens) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.APITokenList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.APITokenList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apitokens"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested aPITokens. +func (c *aPITokens) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("apitokens"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a aPIToken and creates it. Returns the server's representation of the aPIToken, and an error, if there is any. +func (c *aPITokens) Create(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.CreateOptions) (result *v1alpha1.APIToken, err error) { + result = &v1alpha1.APIToken{} + err = c.client.Post(). + Namespace(c.ns). + Resource("apitokens"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIToken). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a aPIToken and updates it. Returns the server's representation of the aPIToken, and an error, if there is any. +func (c *aPITokens) Update(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.UpdateOptions) (result *v1alpha1.APIToken, err error) { + result = &v1alpha1.APIToken{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apitokens"). + Name(aPIToken.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIToken). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *aPITokens) UpdateStatus(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.UpdateOptions) (result *v1alpha1.APIToken, err error) { + result = &v1alpha1.APIToken{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apitokens"). + Name(aPIToken.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIToken). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the aPIToken and deletes it. Returns an error if one occurs. +func (c *aPITokens) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("apitokens"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *aPITokens) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("apitokens"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched aPIToken. +func (c *aPITokens) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.APIToken, err error) { + result = &v1alpha1.APIToken{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("apitokens"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/crds_client.go b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/crds_client.go index ab3f633..07c2a6e 100644 --- a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/crds_client.go +++ b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/crds_client.go @@ -26,6 +26,7 @@ import ( type CrdsV1alpha1Interface interface { RESTClient() rest.Interface + APITokensGetter ZonesGetter } @@ -34,6 +35,10 @@ type CrdsV1alpha1Client struct { restClient rest.Interface } +func (c *CrdsV1alpha1Client) APITokens(namespace string) APITokenInterface { + return newAPITokens(c, namespace) +} + func (c *CrdsV1alpha1Client) Zones(namespace string) ZoneInterface { return newZones(c, namespace) } diff --git a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_apitoken.go b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_apitoken.go new file mode 100644 index 0000000..df15b20 --- /dev/null +++ b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_apitoken.go @@ -0,0 +1,142 @@ +/* +Copyright 2020 Replicated, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeAPITokens implements APITokenInterface +type FakeAPITokens struct { + Fake *FakeCrdsV1alpha1 + ns string +} + +var apitokensResource = schema.GroupVersionResource{Group: "crds.kubeflare.io", Version: "v1alpha1", Resource: "apitokens"} + +var apitokensKind = schema.GroupVersionKind{Group: "crds.kubeflare.io", Version: "v1alpha1", Kind: "APIToken"} + +// Get takes name of the aPIToken, and returns the corresponding aPIToken object, and an error if there is any. +func (c *FakeAPITokens) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.APIToken, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(apitokensResource, c.ns, name), &v1alpha1.APIToken{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIToken), err +} + +// List takes label and field selectors, and returns the list of APITokens that match those selectors. +func (c *FakeAPITokens) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.APITokenList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(apitokensResource, apitokensKind, c.ns, opts), &v1alpha1.APITokenList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.APITokenList{ListMeta: obj.(*v1alpha1.APITokenList).ListMeta} + for _, item := range obj.(*v1alpha1.APITokenList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested aPITokens. +func (c *FakeAPITokens) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(apitokensResource, c.ns, opts)) + +} + +// Create takes the representation of a aPIToken and creates it. Returns the server's representation of the aPIToken, and an error, if there is any. +func (c *FakeAPITokens) Create(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.CreateOptions) (result *v1alpha1.APIToken, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(apitokensResource, c.ns, aPIToken), &v1alpha1.APIToken{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIToken), err +} + +// Update takes the representation of a aPIToken and updates it. Returns the server's representation of the aPIToken, and an error, if there is any. +func (c *FakeAPITokens) Update(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.UpdateOptions) (result *v1alpha1.APIToken, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(apitokensResource, c.ns, aPIToken), &v1alpha1.APIToken{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIToken), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeAPITokens) UpdateStatus(ctx context.Context, aPIToken *v1alpha1.APIToken, opts v1.UpdateOptions) (*v1alpha1.APIToken, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(apitokensResource, "status", c.ns, aPIToken), &v1alpha1.APIToken{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIToken), err +} + +// Delete takes name of the aPIToken and deletes it. Returns an error if one occurs. +func (c *FakeAPITokens) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(apitokensResource, c.ns, name), &v1alpha1.APIToken{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeAPITokens) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(apitokensResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.APITokenList{}) + return err +} + +// Patch applies the patch and returns the patched aPIToken. +func (c *FakeAPITokens) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.APIToken, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(apitokensResource, c.ns, name, pt, data, subresources...), &v1alpha1.APIToken{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIToken), err +} diff --git a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_crds_client.go b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_crds_client.go index c65a9ab..5bb4b4a 100644 --- a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_crds_client.go +++ b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/fake/fake_crds_client.go @@ -28,6 +28,10 @@ type FakeCrdsV1alpha1 struct { *testing.Fake } +func (c *FakeCrdsV1alpha1) APITokens(namespace string) v1alpha1.APITokenInterface { + return &FakeAPITokens{c, namespace} +} + func (c *FakeCrdsV1alpha1) Zones(namespace string) v1alpha1.ZoneInterface { return &FakeZones{c, namespace} } diff --git a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/generated_expansion.go b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/generated_expansion.go index eb8645b..22a3a39 100644 --- a/pkg/client/kubeflareclientset/typed/crds/v1alpha1/generated_expansion.go +++ b/pkg/client/kubeflareclientset/typed/crds/v1alpha1/generated_expansion.go @@ -18,4 +18,6 @@ limitations under the License. package v1alpha1 +type APITokenExpansion interface{} + type ZoneExpansion interface{} diff --git a/pkg/controller/zone/api.go b/pkg/controller/zone/api.go new file mode 100644 index 0000000..6cc98ec --- /dev/null +++ b/pkg/controller/zone/api.go @@ -0,0 +1,47 @@ +package zone + +import ( + "context" + + "github.com/cloudflare/cloudflare-go" + "github.com/pkg/errors" + crdsv1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + crdsclientv1alpha1 "github.com/replicatedhq/kubeflare/pkg/client/kubeflareclientset/typed/crds/v1alpha1" + "github.com/replicatedhq/kubeflare/pkg/logger" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +func (r *ReconcileZone) getCloudflareAPI(ctx context.Context, instance crdsv1alpha1.Zone) (*cloudflare.API, error) { + cfg, err := config.GetConfig() + if err != nil { + return nil, errors.Wrap(err, "failed to get config") + } + + crdsClient, err := crdsclientv1alpha1.NewForConfig(cfg) + if err != nil { + return nil, errors.Wrap(err, "failed to create crds client") + } + + apiToken, err := crdsClient.APITokens(instance.Namespace).Get(ctx, instance.Spec.APIToken, metav1.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to get api token") + } + + tokenValue, err := apiToken.GetTokenValue(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get token value") + } + + logger.Debug("creating cloudflare api object", + zap.String("email", apiToken.Spec.Email), + zap.Int("tokenLength", len(tokenValue))) + + api, err := cloudflare.New(tokenValue, apiToken.Spec.Email) + if err != nil { + return nil, errors.Wrap(err, "failed to create cloudflare api instance") + } + + return api, nil +} diff --git a/pkg/controller/zone/dns_records.go b/pkg/controller/zone/dns_records.go new file mode 100644 index 0000000..6cb5eb1 --- /dev/null +++ b/pkg/controller/zone/dns_records.go @@ -0,0 +1,136 @@ +package zone + +import ( + "context" + + "github.com/cloudflare/cloudflare-go" + "github.com/pkg/errors" + crdsv1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + "github.com/replicatedhq/kubeflare/pkg/logger" + "go.uber.org/zap" +) + +func (r *ReconcileZone) reconcileDNSRecords(ctx context.Context, instance crdsv1alpha1.Zone) error { + logger.Debug("reconcileDNSRecords for zone", zap.String("zoneName", instance.Name)) + + api, err := r.getCloudflareAPI(ctx, instance) + if err != nil { + return errors.Wrap(err, "failed to get cloudflare api") + } + + zoneID, err := api.ZoneIDByName(instance.Name) + if err != nil { + return errors.Wrap(err, "failed to get zone id") + } + + existingRecords, err := api.DNSRecords(zoneID, cloudflare.DNSRecord{}) + if err != nil { + return errors.Wrap(err, "failed to list dns records") + } + + recordsToCreate := []cloudflare.DNSRecord{} + recordsToUpdate := []cloudflare.DNSRecord{} + // recordsToDelete := []cloudflare.DNSRecord{} + + for _, existingRecord := range existingRecords { + found := false + for _, desiredRecord := range instance.Spec.DNSRecords { + if desiredRecord.Name == existingRecord.Name && desiredRecord.Type == existingRecord.Type { + found = true + isChanged := false + + if desiredRecord.Content != existingRecord.Content { + isChanged = true + existingRecord.Content = desiredRecord.Content + } + desiredTTL := 1 + if desiredRecord.TTL != nil { + desiredTTL = *desiredRecord.TTL + } + if desiredTTL != existingRecord.TTL { + isChanged = true + existingRecord.TTL = desiredTTL + } + if desiredRecord.Priority != nil { + if *desiredRecord.Priority != existingRecord.Priority { + isChanged = true + existingRecord.Priority = *desiredRecord.Priority + } + } + if desiredRecord.Proxied != nil { + if *desiredRecord.Proxied != existingRecord.Proxied { + isChanged = true + existingRecord.Proxied = *desiredRecord.Proxied + } + } + + if isChanged { + recordsToUpdate = append(recordsToUpdate, existingRecord) + } + } + } + if !found { + // TODO this feels dangerous, how can we opt-in to delete somehow to avoid erasing all records + // recordsToDelete = append(recordsToDelete, existingRecord) + } + } + + for _, desiredRecord := range instance.Spec.DNSRecords { + found := false + for _, existingRecord := range existingRecords { + if existingRecord.Type == desiredRecord.Type && existingRecord.Name == desiredRecord.Name { + found = true + goto Found + } + } + Found: + if !found { + recordToCreate := cloudflare.DNSRecord{ + Type: desiredRecord.Type, + Name: desiredRecord.Name, + Content: desiredRecord.Content, + } + if desiredRecord.TTL != nil { + recordToCreate.TTL = *desiredRecord.TTL + } else { + recordToCreate.TTL = 1 + } + + if desiredRecord.Priority != nil { + recordToCreate.Priority = *desiredRecord.Priority + } + if desiredRecord.Proxied != nil { + recordToCreate.Proxied = *desiredRecord.Proxied + } + recordsToCreate = append(recordsToCreate, recordToCreate) + } + } + + for _, recordToCreate := range recordsToCreate { + response, err := api.CreateDNSRecord(zoneID, recordToCreate) + if err != nil { + return errors.Wrap(err, "failed to create dns record") + } + + if !response.Success { + return errors.New("non success when creating dns record") + } + } + + for _, recordToUpdate := range recordsToUpdate { + rr := cloudflare.DNSRecord{ + Type: recordToUpdate.Type, + Name: recordToUpdate.Name, + Content: recordToUpdate.Content, + TTL: recordToUpdate.TTL, + Proxied: recordToUpdate.Proxied, + } + + err := api.UpdateDNSRecord(zoneID, recordToUpdate.ID, rr) + if err != nil { + return errors.Wrap(err, "failed to update dns record") + } + } + + return nil +} diff --git a/pkg/controller/zone/settings.go b/pkg/controller/zone/settings.go new file mode 100644 index 0000000..b3cc862 --- /dev/null +++ b/pkg/controller/zone/settings.go @@ -0,0 +1,324 @@ +package zone + +import ( + "context" + + "github.com/cloudflare/cloudflare-go" + "github.com/pkg/errors" + crdsv1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + "github.com/replicatedhq/kubeflare/pkg/logger" + "go.uber.org/zap" +) + +func ReconcileSettings(ctx context.Context, instance crdsv1alpha1.Zone, cf *cloudflare.API) error { + logger.Debug("reconcileSettings for zone", zap.String("zoneName", instance.Name)) + + if instance.Spec.Settings == nil { + logger.Debug("instance does not contain settings to reconcile") + return nil + } + + zoneID, err := cf.ZoneIDByName(instance.Name) + if err != nil { + return errors.Wrap(err, "failed to get zone id") + } + + zoneSettingsResponse, err := cf.ZoneSettings(zoneID) + if err != nil { + return errors.Wrap(err, "failed to get zone settings") + } + + updatedZoneSettings := []cloudflare.ZoneSetting{} + + for _, zoneSetting := range zoneSettingsResponse.Result { + switch zoneSetting.ID { + case "advanced_ddos": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.AdvancedDDOS) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "always_use_https": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.AlwaysUseHTTPS) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "always_online": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.AlwaysOnline) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "opportunistic_onion": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.OpportunisticOnion) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "automatic_https_rewrites": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.AutomaticHTTPSRewrites) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "browser_cache_ttl": + needsUpdate := compareAndUpdateIntZoneSetting(&zoneSetting, instance.Spec.Settings.BrowserCacheTTL) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "browser_check": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.BrowserCheck) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "cache_level": + needsUpdate := compareAndUpdateStringZoneSetting(&zoneSetting, instance.Spec.Settings.CacheLevel) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "challenge_ttl": + needsUpdate := compareAndUpdateIntZoneSetting(&zoneSetting, instance.Spec.Settings.ChallengeTTL) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "development_mode": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.DevelopmentMode) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "email_obfuscation": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.EmailObfuscation) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "hotlink_protection": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.HotlinkProtection) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "ip_geolocation": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.IPGeolocation) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "ipv6": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.IPV6) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "minify": + needsUpdate := compareAndUpdateMinifyZoneSetting(&zoneSetting, instance.Spec.Settings.Minify) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "mobile_redirect": + needsUpdate := compareAndUpdateMobileRedirectZoneSetting(&zoneSetting, instance.Spec.Settings.MobileRedirect) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "mirage": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.Mirage) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "origin_error_page_pass_thru": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.OriginErrorPagePassThru) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "opportunistic_encryption": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.OpportunisticEncryption) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "polish": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.Polish) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "webp": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.WebP) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "brotli": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.Brotli) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "prefetch_preload": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.PrefetchPreload) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "privacy_pass": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.PrivacyPass) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "response_buffering": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.ResponseBuffering) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + case "rocket_loader": + needsUpdate := compareAndUpdateBoolZoneSetting(&zoneSetting, instance.Spec.Settings.RocketLoader) + if needsUpdate { + updatedZoneSettings = append(updatedZoneSettings, zoneSetting) + } + } + + } + + if len(updatedZoneSettings) == 0 { + logger.Debug("no setting was changed in zone", zap.String("zoneName", instance.Name)) + return nil + } + + logger.Debug("updating zone settings", + zap.String("zoneName", instance.Name), + zap.Any("updatedSettings", updatedZoneSettings)) + + updateResponse, err := cf.UpdateZoneSettings(zoneID, updatedZoneSettings) + if err != nil { + return errors.Wrap(err, "failed to update zone settings") + } + + if !updateResponse.Success { + return errors.New("unsuccessful response from cloudflare api") + } + + return nil +} + +func compareAndUpdateBoolZoneSetting(zoneSetting *cloudflare.ZoneSetting, desiredValue *bool) bool { + if desiredValue == nil { + return false + } + + currentValue := zoneSetting.Value == "on" + if currentValue != *desiredValue { + if *desiredValue { + zoneSetting.Value = "on" + } else { + zoneSetting.Value = "off" + } + return true + } + + return false +} + +func compareAndUpdateIntZoneSetting(zoneSetting *cloudflare.ZoneSetting, desiredValue *int) bool { + if desiredValue == nil { + return false + } + + currentValue := int(zoneSetting.Value.(float64)) + if currentValue != *desiredValue { + zoneSetting.Value = *desiredValue + return true + } + + return false +} + +func compareAndUpdateStringZoneSetting(zoneSetting *cloudflare.ZoneSetting, desiredValue *string) bool { + if desiredValue == nil { + return false + } + + currentValue := zoneSetting.Value.(string) + if currentValue != *desiredValue { + zoneSetting.Value = *desiredValue + return true + } + + return false +} + +func compareAndUpdateMinifyZoneSetting(zoneSetting *cloudflare.ZoneSetting, desiredValue *crdsv1alpha1.MinifySetting) bool { + if desiredValue == nil { + return false + } + + isChanged := true + currentValue := zoneSetting.Value.(map[string]interface{}) + + if desiredValue.CSS != nil { + currentCSS := currentValue["css"] == "on" + if currentCSS != *desiredValue.CSS { + if *desiredValue.CSS { + zoneSetting.Value.(map[string]interface{})["css"] = "on" + } else { + zoneSetting.Value.(map[string]interface{})["css"] = "off" + } + isChanged = true + } + } + + if desiredValue.HTML != nil { + currentCSS := currentValue["html"] == "on" + if currentCSS != *desiredValue.HTML { + if *desiredValue.HTML { + zoneSetting.Value.(map[string]interface{})["html"] = "on" + } else { + zoneSetting.Value.(map[string]interface{})["html"] = "off" + } + isChanged = true + } + } + + if desiredValue.JS != nil { + currentCSS := currentValue["js"] == "on" + if currentCSS != *desiredValue.JS { + if *desiredValue.JS { + zoneSetting.Value.(map[string]interface{})["js"] = "on" + } else { + zoneSetting.Value.(map[string]interface{})["js"] = "off" + } + isChanged = true + } + } + + return isChanged +} + +func compareAndUpdateMobileRedirectZoneSetting(zoneSetting *cloudflare.ZoneSetting, desiredValue *crdsv1alpha1.MobileRedirect) bool { + if desiredValue == nil { + return false + } + + hasChanged := false + + if desiredValue.Status != nil { + currentStatus := zoneSetting.Value.(map[string]interface{})["status"].(string) == "on" + if *desiredValue.Status != currentStatus { + if *desiredValue.Status { + zoneSetting.Value.(map[string]interface{})["status"] = "on" + } else { + zoneSetting.Value.(map[string]interface{})["status"] = "off" + } + hasChanged = true + } + } + + if desiredValue.MobileSubdomain != nil { + if zoneSetting.Value.(map[string]interface{})["mobile_subdomain"] == nil { + zoneSetting.Value.(map[string]interface{})["mobile_subdomain"] = *desiredValue.MobileSubdomain + hasChanged = true + } else { + currentMobileSubdomain := zoneSetting.Value.(map[string]interface{})["mobile_subdomain"].(string) + if *desiredValue.MobileSubdomain != currentMobileSubdomain { + zoneSetting.Value.(map[string]interface{})["mobile_subdomain"] = *desiredValue.MobileSubdomain + hasChanged = true + } + } + } + + if desiredValue.StripURI != nil { + currentStripURI := zoneSetting.Value.(map[string]interface{})["strip_uri"].(bool) + if *desiredValue.StripURI != currentStripURI { + zoneSetting.Value.(map[string]interface{})["strip_uri"] = *desiredValue.StripURI + hasChanged = true + } + } + + return hasChanged +} diff --git a/pkg/controller/zone/settings_test.go b/pkg/controller/zone/settings_test.go new file mode 100644 index 0000000..d8f3587 --- /dev/null +++ b/pkg/controller/zone/settings_test.go @@ -0,0 +1,63 @@ +package zone + +import ( + "testing" + + "github.com/cloudflare/cloudflare-go" + crdsv1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + "github.com/stretchr/testify/assert" +) + +var ( + trueValue = true + falseValue = false + stringM = "m" +) + +func Test_compareAndUpdateMobileRedirectZoneSetting(t *testing.T) { + tests := []struct { + name string + zoneSetting cloudflare.ZoneSetting + desiredValue *crdsv1alpha1.MobileRedirect + expected bool + }{ + { + name: "no change", + zoneSetting: cloudflare.ZoneSetting{ + Value: map[string]interface{}{ + "status": "on", + "mobile_subdomain": "m", + "strip_uri": false, + }, + }, + desiredValue: &crdsv1alpha1.MobileRedirect{ + Status: &trueValue, + MobileSubdomain: &stringM, + StripURI: &falseValue, + }, + expected: false, + }, + { + name: "changed subdomin only", + zoneSetting: cloudflare.ZoneSetting{ + Value: map[string]interface{}{ + "status": "on", + "mobile_subdomain": "mm", + "strip_uri": false, + }, + }, + desiredValue: &crdsv1alpha1.MobileRedirect{ + Status: &trueValue, + MobileSubdomain: &stringM, + StripURI: &falseValue, + }, + expected: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := compareAndUpdateMobileRedirectZoneSetting(&test.zoneSetting, test.desiredValue) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/pkg/controller/zone/zone_controller.go b/pkg/controller/zone/zone_controller.go index 0f9ef48..68b749d 100644 --- a/pkg/controller/zone/zone_controller.go +++ b/pkg/controller/zone/zone_controller.go @@ -17,10 +17,12 @@ limitations under the License. package zone import ( + "context" "time" "github.com/pkg/errors" crdsv1alpha1 "github.com/replicatedhq/kubeflare/pkg/apis/crds/v1alpha1" + "github.com/replicatedhq/kubeflare/pkg/logger" "k8s.io/apimachinery/pkg/runtime" kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -91,5 +93,29 @@ type ReconcileZone struct { func (r *ReconcileZone) Reconcile(request reconcile.Request) (reconcile.Result, error) { // This reconcile loop will be called for all Zone objects // because of the informer that we have set up + ctx := context.Background() + instance := crdsv1alpha1.Zone{} + err := r.Get(ctx, request.NamespacedName, &instance) + if err != nil { + logger.Error(err) + return reconcile.Result{}, err + } + + cf, err := r.getCloudflareAPI(ctx, instance) + if err != nil { + logger.Error(err) + return reconcile.Result{}, err + } + + if err := ReconcileSettings(ctx, instance, cf); err != nil { + logger.Error(err) + return reconcile.Result{}, err + } + + if err := r.reconcileDNSRecords(ctx, instance); err != nil { + logger.Error(err) + return reconcile.Result{}, err + } + return reconcile.Result{}, nil } -- GitLab