blueprints: better OCI support in UI (#4263)
use oci:// prefix to detect oci blueprint, add UI support Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -92,7 +92,7 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel): | |||||||
|         if ":" in url.path: |         if ":" in url.path: | ||||||
|             path, _, ref = path.partition(":") |             path, _, ref = path.partition(":") | ||||||
|         client = NewClient( |         client = NewClient( | ||||||
|             f"{url.scheme}://{url.hostname}", |             f"https://{url.hostname}", | ||||||
|             WithUserAgent(authentik_user_agent()), |             WithUserAgent(authentik_user_agent()), | ||||||
|             WithUsernamePassword(url.username, url.password), |             WithUsernamePassword(url.username, url.password), | ||||||
|             WithDefaultName(path), |             WithDefaultName(path), | ||||||
| @ -135,12 +135,11 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel): | |||||||
|  |  | ||||||
|     def retrieve(self) -> str: |     def retrieve(self) -> str: | ||||||
|         """Retrieve blueprint contents""" |         """Retrieve blueprint contents""" | ||||||
|  |         if self.path.startswith("oci://"): | ||||||
|  |             return self.retrieve_oci() | ||||||
|         full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(self.path)) |         full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(self.path)) | ||||||
|         if full_path.exists(): |  | ||||||
|             LOGGER.debug("Blueprint path exists locally", instance=self) |  | ||||||
|         with full_path.open("r", encoding="utf-8") as _file: |         with full_path.open("r", encoding="utf-8") as _file: | ||||||
|             return _file.read() |             return _file.read() | ||||||
|         return self.retrieve_oci() |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def serializer(self) -> Serializer: |     def serializer(self) -> Serializer: | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
|  | import { docLink } from "@goauthentik/common/global"; | ||||||
| import { first } from "@goauthentik/common/utils"; | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/CodeMirror"; | import "@goauthentik/elements/CodeMirror"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -8,18 +9,36 @@ import YAML from "yaml"; | |||||||
|  |  | ||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
|  |  | ||||||
| import { TemplateResult, html } from "lit"; | import { CSSResult, TemplateResult, css, html } from "lit"; | ||||||
| import { customElement } from "lit/decorators.js"; | import { customElement, state } from "lit/decorators.js"; | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; | import { ifDefined } from "lit/directives/if-defined.js"; | ||||||
| import { until } from "lit/directives/until.js"; | import { until } from "lit/directives/until.js"; | ||||||
|  |  | ||||||
|  | import PFContent from "@patternfly/patternfly/components/Content/content.css"; | ||||||
|  | import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css"; | ||||||
|  |  | ||||||
| import { BlueprintInstance, ManagedApi } from "@goauthentik/api"; | import { BlueprintInstance, ManagedApi } from "@goauthentik/api"; | ||||||
|  |  | ||||||
|  | enum blueprintSource { | ||||||
|  |     local, | ||||||
|  |     oci, | ||||||
|  | } | ||||||
|  |  | ||||||
| @customElement("ak-blueprint-form") | @customElement("ak-blueprint-form") | ||||||
| export class BlueprintForm extends ModelForm<BlueprintInstance, string> { | export class BlueprintForm extends ModelForm<BlueprintInstance, string> { | ||||||
|  |     @state() | ||||||
|  |     source: blueprintSource = blueprintSource.local; | ||||||
|  |  | ||||||
|     loadInstance(pk: string): Promise<BlueprintInstance> { |     loadInstance(pk: string): Promise<BlueprintInstance> { | ||||||
|         return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsRetrieve({ |         return new ManagedApi(DEFAULT_CONFIG) | ||||||
|  |             .managedBlueprintsRetrieve({ | ||||||
|                 instanceUuid: pk, |                 instanceUuid: pk, | ||||||
|  |             }) | ||||||
|  |             .then((inst) => { | ||||||
|  |                 if (inst.path.startsWith("oci://")) { | ||||||
|  |                     this.source = blueprintSource.oci; | ||||||
|  |                 } | ||||||
|  |                 return inst; | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -31,6 +50,18 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     static get styles(): CSSResult[] { | ||||||
|  |         return super.styles.concat( | ||||||
|  |             PFToggleGroup, | ||||||
|  |             PFContent, | ||||||
|  |             css` | ||||||
|  |                 .pf-c-toggle-group { | ||||||
|  |                     justify-content: center; | ||||||
|  |                 } | ||||||
|  |             `, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     send = (data: BlueprintInstance): Promise<BlueprintInstance> => { |     send = (data: BlueprintInstance): Promise<BlueprintInstance> => { | ||||||
|         if (this.instance?.pk) { |         if (this.instance?.pk) { | ||||||
|             return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsUpdate({ |             return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsUpdate({ | ||||||
| @ -65,7 +96,43 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> { | |||||||
|                 </div> |                 </div> | ||||||
|                 <p class="pf-c-form__helper-text">${t`Disabled blueprints are never applied.`}</p> |                 <p class="pf-c-form__helper-text">${t`Disabled blueprints are never applied.`}</p> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|             <ak-form-element-horizontal label=${t`Path`} name="path"> |             <div class="pf-c-card pf-m-selectable pf-m-selected"> | ||||||
|  |                 <div class="pf-c-card__body"> | ||||||
|  |                     <div class="pf-c-toggle-group"> | ||||||
|  |                         <div class="pf-c-toggle-group__item"> | ||||||
|  |                             <button | ||||||
|  |                                 class="pf-c-toggle-group__button ${this.source === | ||||||
|  |                                 blueprintSource.local | ||||||
|  |                                     ? "pf-m-selected" | ||||||
|  |                                     : ""}" | ||||||
|  |                                 type="button" | ||||||
|  |                                 @click=${() => { | ||||||
|  |                                     this.source = blueprintSource.local; | ||||||
|  |                                 }} | ||||||
|  |                             > | ||||||
|  |                                 <span class="pf-c-toggle-group__text">${t`Local path`}</span> | ||||||
|  |                             </button> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="pf-c-divider pf-m-vertical" role="separator"></div> | ||||||
|  |                         <div class="pf-c-toggle-group__item"> | ||||||
|  |                             <button | ||||||
|  |                                 class="pf-c-toggle-group__button ${this.source === | ||||||
|  |                                 blueprintSource.oci | ||||||
|  |                                     ? "pf-m-selected" | ||||||
|  |                                     : ""}" | ||||||
|  |                                 type="button" | ||||||
|  |                                 @click=${() => { | ||||||
|  |                                     this.source = blueprintSource.oci; | ||||||
|  |                                 }} | ||||||
|  |                             > | ||||||
|  |                                 <span class="pf-c-toggle-group__text">${t`OCI Registry`}</span> | ||||||
|  |                             </button> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="pf-c-card__footer"> | ||||||
|  |                     ${this.source === blueprintSource.local | ||||||
|  |                         ? html`<ak-form-element-horizontal label=${t`Path`} name="path"> | ||||||
|                               <select class="pf-c-form-control"> |                               <select class="pf-c-form-control"> | ||||||
|                                   ${until( |                                   ${until( | ||||||
|                                       new ManagedApi(DEFAULT_CONFIG) |                                       new ManagedApi(DEFAULT_CONFIG) | ||||||
| @ -76,16 +143,47 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> { | |||||||
|                                                   if (file.meta && file.meta.name) { |                                                   if (file.meta && file.meta.name) { | ||||||
|                                                       name = `${name} (${file.meta.name})`; |                                                       name = `${name} (${file.meta.name})`; | ||||||
|                                                   } |                                                   } | ||||||
|                                     const selected = file.path === this.instance?.path; |                                                   const selected = | ||||||
|                                     return html`<option ?selected=${selected} value=${file.path}> |                                                       file.path === this.instance?.path; | ||||||
|  |                                                   return html`<option | ||||||
|  |                                                       ?selected=${selected} | ||||||
|  |                                                       value=${file.path} | ||||||
|  |                                                   > | ||||||
|                                                       ${name} |                                                       ${name} | ||||||
|                                                   </option>`; |                                                   </option>`; | ||||||
|                                               }); |                                               }); | ||||||
|                                           }), |                                           }), | ||||||
|                                       html`<option>${t`Loading...`}</option>`, |                                       html`<option>${t`Loading...`}</option>`, | ||||||
|                                   )} |                                   )} | ||||||
|                 </select> |                               </select></ak-form-element-horizontal | ||||||
|             </ak-form-element-horizontal> |                           >` | ||||||
|  |                         : html``} | ||||||
|  |                     ${this.source === blueprintSource.oci | ||||||
|  |                         ? html`<ak-form-element-horizontal label=${t`URL`} name="path"> | ||||||
|  |                               <input | ||||||
|  |                                   type="text" | ||||||
|  |                                   value="${ifDefined(this.instance?.path)}" | ||||||
|  |                                   class="pf-c-form-control" | ||||||
|  |                                   required | ||||||
|  |                               /> | ||||||
|  |                               <p class="pf-c-form__helper-text"> | ||||||
|  |                                   ${t`OCI URL, in the format of oci://registry.domain.tld/path/to/manifest.`} | ||||||
|  |                               </p> | ||||||
|  |                               <p class="pf-c-form__helper-text"> | ||||||
|  |                                   ${t`See more about OCI support here:`}  | ||||||
|  |                                   <a | ||||||
|  |                                       target="_blank" | ||||||
|  |                                       href="${docLink( | ||||||
|  |                                           "/developer-docs/blueprints/?utm_source=authentik#storage---oci", | ||||||
|  |                                       )}" | ||||||
|  |                                       >${t`Documentation`}</a | ||||||
|  |                                   > | ||||||
|  |                               </p> | ||||||
|  |                           </ak-form-element-horizontal>` | ||||||
|  |                         : html``} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|             <ak-form-group> |             <ak-form-group> | ||||||
|                 <span slot="header">${t`Additional settings`}</span> |                 <span slot="header">${t`Additional settings`}</span> | ||||||
|                 <div slot="body" class="pf-c-form"> |                 <div slot="body" class="pf-c-form"> | ||||||
|  | |||||||
| @ -33,7 +33,7 @@ To disable existing blueprints, an empty file can be mounted over the existing b | |||||||
|  |  | ||||||
| Blueprints can also be stored in remote [OCI](https://opencontainers.org/) compliant registries. This includes GitHub Container Registry, Docker hub and many other registries. | Blueprints can also be stored in remote [OCI](https://opencontainers.org/) compliant registries. This includes GitHub Container Registry, Docker hub and many other registries. | ||||||
|  |  | ||||||
| To download a blueprint via OCI, set the path to `https://ghcr.io/<username>/<package-name>:<ref>`. This will fetch the blueprint from an OCI package hosted on GHCR. | To download a blueprint via OCI, set the path to `oci://ghcr.io/<username>/<package-name>:<ref>`. This will fetch the blueprint from an OCI package hosted on GHCR. | ||||||
|  |  | ||||||
| To fetch blueprints from a private registry with authentication, credentials can be embedded into the URL. | To fetch blueprints from a private registry with authentication, credentials can be embedded into the URL. | ||||||
|  |  | ||||||
|  | |||||||
| @ -3,6 +3,12 @@ title: Release 2022.12 | |||||||
| slug: "2022.12" | slug: "2022.12" | ||||||
| --- | --- | ||||||
|  |  | ||||||
|  | ## Breaking changes | ||||||
|  |  | ||||||
|  | -   Blueprints fetched via OCI require oci:// schema | ||||||
|  |  | ||||||
|  |     To better detect if a blueprint should be fetched locally or via OCI, all OCI sourced blueprints require an `oci://` protocol. | ||||||
|  |  | ||||||
| ## New features | ## New features | ||||||
|  |  | ||||||
| -   Bundled GeoIP City database | -   Bundled GeoIP City database | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L