Hugo docs site (#1883)
We should present the docs in a format that they are nice to read on GitHub Pages. ![preview image of website](https://user-images.githubusercontent.com/12575/137663893-17a89af8-5713-4a7b-81d7-b71ef49ee7b4.png) ### TODO list (after merge) - [x] ~~Note that build is expected to fail at the moment until links are fixed.~~ - [ ] Set up GitHub Pages. - [ ] Publish to GitHub Pages on master merge. - [ ] Get a better design. ### Open questions - Do we want to include design docs/decision records? (I think yes, I have put them at the bottom of the list) - Do we want to move the README/setup instructions to the site and have the GitHub README be more minimal? (probably?)
|
@ -58,6 +58,10 @@ go get \
|
|||
sigs.k8s.io/kind@v0.11.1 \
|
||||
sigs.k8s.io/kustomize/kustomize/v4@v4.2.0
|
||||
|
||||
# for docs site
|
||||
go install -tags extended github.com/gohugoio/hugo@v0.88.1
|
||||
go install github.com/wjdp/htmltest@v0.15.0
|
||||
|
||||
if [ "$1" != "devcontainer" ]; then
|
||||
echo "Installing golangci-lint…"
|
||||
# golangci-lint is provided by base image if in devcontainer
|
||||
|
|
|
@ -55,6 +55,11 @@ jobs:
|
|||
docker start "$container_id"
|
||||
echo "::set-output name=id::$container_id"
|
||||
|
||||
- name: Build & validate docs site
|
||||
run: |
|
||||
container_id=${{steps.devcontainer.outputs.id}}
|
||||
docker exec "$container_id" task build-docs-site
|
||||
|
||||
- name: Run CI tasks
|
||||
run: |
|
||||
container_id=${{steps.devcontainer.outputs.id}}
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
[submodule "hack/generator/specs/azure-resource-manager-schemas"]
|
||||
path = v2/specs/azure-resource-manager-schemas
|
||||
url = https://github.com/Azure/azure-resource-manager-schemas
|
||||
[submodule "docs/hugo/themes/book"]
|
||||
path = docs/hugo/themes/book
|
||||
url = https://github.com/alex-shpak/hugo-book
|
||||
|
|
10
Taskfile.yml
|
@ -62,6 +62,12 @@ tasks:
|
|||
cmds:
|
||||
- gofmt -l -s -w .
|
||||
|
||||
build-docs-site:
|
||||
dir: docs/hugo
|
||||
cmds:
|
||||
- hugo
|
||||
- htmltest
|
||||
|
||||
############### Generator targets ###############
|
||||
basic-checks:
|
||||
deps: [header-check, specifier-check]
|
||||
|
@ -116,8 +122,8 @@ tasks:
|
|||
|
||||
generator:diagrams:
|
||||
desc: Regenerate all GraphViz diagrams
|
||||
dir: "./docs/design/images"
|
||||
sources: ["*/*.dot"]
|
||||
dir: "./docs/hugo/static/images"
|
||||
sources: ["**/*.dot"]
|
||||
cmds:
|
||||
- "for f in **/*.dot; do dot -Tpng -o${f%.dot}.png $f; done"
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
public/
|
||||
resources/_gen/
|
||||
tmp/
|
|
@ -0,0 +1,3 @@
|
|||
DirectoryPath: public
|
||||
CheckExternal: false
|
||||
IgnoreAltMissing: true
|
|
@ -0,0 +1,9 @@
|
|||
# Docs
|
||||
|
||||
The documentation site is generated by Hugo. To run Hugo locally, install it, then run:
|
||||
|
||||
```sh
|
||||
hugo server
|
||||
```
|
||||
|
||||
Markdown files go under `/content/`, static files like images under `/static/`.
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
baseURL = ''
|
||||
languageCode = 'en-us'
|
||||
title = 'Azure Service Operator'
|
||||
theme = 'book'
|
||||
|
||||
# theme-specific settings
|
||||
[params]
|
||||
BookSection = "*"
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
# Azure Service Operator
|
|
@ -1,3 +1,8 @@
|
|||
---
|
||||
title: '2020-04: Why Code Generation?'
|
||||
date: 2020-04-01
|
||||
---
|
||||
|
||||
# Why Code Generation?
|
||||
|
||||
## Context
|
||||
|
@ -41,4 +46,3 @@ Migration of the code generator into the ASO repo occurred in [PR #1427](https:/
|
|||
## References
|
||||
|
||||
None.
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
---
|
||||
title: '2020-07: Pipeline Architecture'
|
||||
date: 2020-07-01
|
||||
---
|
||||
|
||||
# Pipeline architecture
|
||||
|
||||
## Context
|
|
@ -1,3 +1,8 @@
|
|||
---
|
||||
title: '2020-11: AST Library Choice'
|
||||
date: 2020-11-01
|
||||
---
|
||||
|
||||
# Abstract Syntax Tree Library Choice
|
||||
|
||||
## Context
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
|
||||
# Architecture Decision Records
|
||||
|
||||
This folder documents some of the architecturally significant decisions we've made during the Azure Service Operator project.
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: "Design"
|
||||
weight: 2 # want this rendered second
|
||||
---
|
|
@ -8,9 +8,9 @@ Sometimes, in addition to structural changes, there are behaviour changes betwee
|
|||
|
||||
### Example
|
||||
|
||||
Revisting the CRM example from the [Versioning](versioning.md) specification, consider what happens if we have two available versions of the resource `Person`, lets call them **v1** and **v2**. In **v2** the new properties `PostalAddress` and `ResidentialAddress` are mandatory, requiring that everyone have a both a mailing address and a home.
|
||||
Revisting the CRM example from the [Versioning](../versioning/) specification, consider what happens if we have two available versions of the resource `Person`, lets call them **v1** and **v2**. In **v2** the new properties `PostalAddress` and `ResidentialAddress` are mandatory, requiring that everyone have a both a mailing address and a home.
|
||||
|
||||
![example](images/api-versions/example.png)
|
||||
![example](/images/api-versions/example.png)
|
||||
|
||||
If we have a valid **v1** `Person`, trying to submit that through the **v2** ARM API will fail because it's missing these addresses.
|
||||
|
||||
|
@ -24,7 +24,7 @@ When generating storage variants, we'll inject a new `OriginalVersion` property
|
|||
|
||||
To populate the `OriginalVersion` property on each storage spec, we'll inject an `OriginalVersion()` method (returning **string**) into the API variant of each spec.
|
||||
|
||||
![preservation](images/api-versions/preservation.png)
|
||||
![preservation](/images/api-versions/preservation.png)
|
||||
|
||||
API version shown on the left, corresponding Storage version shown on the right.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
title: Case Studies
|
||||
---
|
|
@ -1,4 +1,7 @@
|
|||
<!-- omit in toc -->
|
||||
---
|
||||
title: Chained Storage Versions
|
||||
---
|
||||
|
||||
# Case Study - Chained Storage Versions
|
||||
|
||||
This case study explores the alternative solution of using a *chained storage versions*. We update the storage schema of each resource each release of the service operator. We'll keep the storage version up to date with the latest GA release of each resource. Older storage versions are retained, both as intermediate steps in the hub-and-spoke conversions, and to allow upgrades.
|
||||
|
@ -7,43 +10,6 @@ For the purposes of discussion, we'll be following the version by version evolut
|
|||
|
||||
Examples shown are deliberately simplified in order to focus, and therefore minutiae should be considered motivational, not binding. Reference the formal specification for precise details.
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [Version 2011-01-01 - Initial Release](#version-2011-01-01---initial-release)
|
||||
- [Storage Conversion](#storage-conversion)
|
||||
- [Version Map](#version-map)
|
||||
- [Version 2012-02-02 - No Change](#version-2012-02-02---no-change)
|
||||
- [Storage Conversion](#storage-conversion-1)
|
||||
- [Version Map](#version-map-1)
|
||||
- [Version 2013-03-03 - New Property](#version-2013-03-03---new-property)
|
||||
- [Storage Conversions](#storage-conversions)
|
||||
- [Version Map](#version-map-2)
|
||||
- [How often are new properties added?](#how-often-are-new-properties-added)
|
||||
- [Version 2014-04-04 Preview - Schema Change](#version-2014-04-04-preview---schema-change)
|
||||
- [Storage Conversion](#storage-conversion-2)
|
||||
- [Version Map](#version-map-3)
|
||||
- [Version 2014-04-04 - Schema Change](#version-2014-04-04---schema-change)
|
||||
- [Storage Conversion](#storage-conversion-3)
|
||||
- [Version Map](#version-map-4)
|
||||
- [Version 2015-05-05 - Property Rename](#version-2015-05-05---property-rename)
|
||||
- [Storage Conversion](#storage-conversion-4)
|
||||
- [Version Map](#version-map-5)
|
||||
- [How often do property renames happen?](#how-often-do-property-renames-happen)
|
||||
- [Version 2016-06-06 - Complex Properties](#version-2016-06-06---complex-properties)
|
||||
- [Storage Conversion](#storage-conversion-5)
|
||||
- [Version Map](#version-map-6)
|
||||
- [Version 2017-07-07 - Optionality changes](#version-2017-07-07---optionality-changes)
|
||||
- [Storage Conversion](#storage-conversion-6)
|
||||
- [Version Map](#version-map-7)
|
||||
- [How often does optionality change?](#how-often-does-optionality-change)
|
||||
- [Version 2018-08-08 - Extending nested properties](#version-2018-08-08---extending-nested-properties)
|
||||
- [Version Map](#version-map-8)
|
||||
- [Version 2019-09-09 - Changing types](#version-2019-09-09---changing-types)
|
||||
- [Storage Conversion](#storage-conversion-7)
|
||||
- [Version Map](#version-map-9)
|
||||
- [How often do properties change their type?](#how-often-do-properties-change-their-type)
|
||||
|
||||
# Version 2011-01-01 - Initial Release
|
||||
|
||||
The initial release of the CRM includes a simple definition to capture information about a particular person:
|
||||
|
@ -129,7 +95,7 @@ These methods will be automatically generated in order to handle the majority of
|
|||
|
||||
With only two classes, our version map is simple and straightforward.
|
||||
|
||||
![](images/case-study-chained-storage/2011-01-01.png)
|
||||
![](/images/case-study-chained-storage/2011-01-01.png)
|
||||
|
||||
|
||||
# Version 2012-02-02 - No Change
|
||||
|
@ -158,7 +124,7 @@ An additional bidirectional conversion between `v20110101storage` and `v20120202
|
|||
|
||||
Our version map diagram is becoming useful for seeing the relationship between versions:
|
||||
|
||||
![](images/case-study-chained-storage/2012-02-02.png)
|
||||
![](/images/case-study-chained-storage/2012-02-02.png)
|
||||
|
||||
Observe that the prior storage version is still shown, with a bidirectional conversion with the current storage version. Existing users who upgrade their service operator will have their storage upgraded using this conversion. The conversion between storage versions will be generated with the same approach, and with the same structure, as all our other conversions.
|
||||
|
||||
|
@ -264,7 +230,7 @@ func (person *Person) ConvertFromStorage(vnext storage.Person) error {
|
|||
|
||||
A graph of our conversions now starts to show the chaining between storage versions that gives the name to this approach. Bidirectional conversions to and from earlier versions of storage allow conversion between any pairs of API versions.
|
||||
|
||||
![](images/case-study-chained-storage/2013-03-03.png)
|
||||
![](/images/case-study-chained-storage/2013-03-03.png)
|
||||
|
||||
## How often are new properties added?
|
||||
|
||||
|
@ -404,7 +370,7 @@ Preview releases, by definition, include unstable changes that may differ once t
|
|||
|
||||
We don't want to make changes to our storage versions based on these speculative changes, so we handle persistence of the preview release with the existing storage version, by way of a down-conversion to `v20130303storage`:
|
||||
|
||||
![](images/case-study-chained-storage/2014-04-04-preview.png)
|
||||
![](/images/case-study-chained-storage/2014-04-04-preview.png)
|
||||
|
||||
|
||||
# Version 2014-04-04 - Schema Change
|
||||
|
@ -546,7 +512,7 @@ For each property we need to consider that it might have already been populated
|
|||
|
||||
We can see in our version map that the preview release is still supported, but the associated storage version is not in the main chain of interconvertible versions.
|
||||
|
||||
![](images/case-study-chained-storage/2014-04-04.png)
|
||||
![](/images/case-study-chained-storage/2014-04-04.png)
|
||||
|
||||
# Version 2015-05-05 - Property Rename
|
||||
|
||||
|
@ -628,13 +594,13 @@ While `SortKey` appears at the end of the list of assignments in the first metho
|
|||
|
||||
Here we see our horizon policy coming into effect, with support for version 2011-01-01 being dropped in this release:
|
||||
|
||||
![](images/case-study-chained-storage/2015-05-05.png)
|
||||
![](/images/case-study-chained-storage/2015-05-05.png)
|
||||
|
||||
For users staying up to date with releases of the service operator, this will likely have no effect - but users still using the original release (storage version `v2011-01-01storage`) will need to update to an intermediate release before adopting this version.
|
||||
|
||||
An alternative approach would be to always support conversion from every storage version, even if the related API version has been dropped:
|
||||
|
||||
![](images/case-study-chained-storage/2015-05-05-alternate.png)
|
||||
![](/images/case-study-chained-storage/2015-05-05-alternate.png)
|
||||
|
||||
This would allow users to upgrade from almost any older version of the service operator. ("Almost" because we would still have older versions drop off when they are retired by ARM.)
|
||||
|
||||
|
@ -787,7 +753,7 @@ We're recursively applying the same conversion pattern to `Address` as we have a
|
|||
|
||||
Again we see the oldest version drop out, allowing users of the three prior versions of the service operator to upgrade cleanly:
|
||||
|
||||
![](images/case-study-chained-storage/2016-06-06.png)
|
||||
![](/images/case-study-chained-storage/2016-06-06.png)
|
||||
|
||||
# Version 2017-07-07 - Optionality changes
|
||||
|
||||
|
@ -858,7 +824,7 @@ If we instead had an _optional_ field that became _required_ in a later version
|
|||
|
||||
## Version Map
|
||||
|
||||
![](images/case-study-chained-storage/2017-07-07.png)
|
||||
![](/images/case-study-chained-storage/2017-07-07.png)
|
||||
|
||||
## How often does optionality change?
|
||||
|
||||
|
@ -900,7 +866,7 @@ These changes are entirely similar to those previously covered in version 2014-0
|
|||
|
||||
In this release, we see that support for both `2014-04-04` and the preview version `2014-04-04preview` has been dropped:
|
||||
|
||||
![](images/case-study-chained-storage/2018-08-08.png)
|
||||
![](/images/case-study-chained-storage/2018-08-08.png)
|
||||
|
||||
Users still running earlier releases of the service operator that are using `2014-04-04` or earlier will need to install an intermediate release in order to upgrade to this one.
|
||||
|
||||
|
@ -994,7 +960,7 @@ If we don't include metadata to capture type renames, the conversion can be manu
|
|||
|
||||
## Version Map
|
||||
|
||||
![](images/case-study-chained-storage/2019-09-09.png)
|
||||
![](/images/case-study-chained-storage/2019-09-09.png)
|
||||
|
||||
## How often do properties change their type?
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
---
|
||||
title: Fixed Storage Version
|
||||
---
|
||||
|
||||
<!-- omit in toc -->
|
||||
# Case Study - Fixed Storage Version
|
||||
|
||||
|
@ -7,45 +11,6 @@ For the purposes of discussion, we'll be following the version by version evolut
|
|||
|
||||
Examples shown are deliberately simplified in order to focus on specific details, and therefore minutiae should be considered motivational, not binding. Reference the formal specification for precise details.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Version 2011-01-01 - Initial Release](#version-2011-01-01---initial-release)
|
||||
- [Storage Conversion](#storage-conversion)
|
||||
- [Version Map](#version-map)
|
||||
- [Version 2012-02-02 - No Change](#version-2012-02-02---no-change)
|
||||
- [Storage Conversion](#storage-conversion-1)
|
||||
- [Version Map](#version-map-1)
|
||||
- [Version 2013-03-03 - New Property](#version-2013-03-03---new-property)
|
||||
- [Storage Conversions](#storage-conversions)
|
||||
- [Version Map](#version-map-2)
|
||||
- [How often are new properties added?](#how-often-are-new-properties-added)
|
||||
- [Version 2014-04-04 Preview - Schema Change](#version-2014-04-04-preview---schema-change)
|
||||
- [Storage Conversion](#storage-conversion-2)
|
||||
- [Version Map](#version-map-3)
|
||||
- [Version 2014-04-04 - Schema Change](#version-2014-04-04---schema-change)
|
||||
- [Issue: Property Bloat](#issue-property-bloat)
|
||||
- [Storage Conversion](#storage-conversion-3)
|
||||
- [Version Map](#version-map-4)
|
||||
- [Version 2015-05-05 - Property Rename](#version-2015-05-05---property-rename)
|
||||
- [Storage Conversion](#storage-conversion-4)
|
||||
- [Version Map](#version-map-5)
|
||||
- [How often do property renames happen?](#how-often-do-property-renames-happen)
|
||||
- [Version 2016-06-06 - Complex Properties](#version-2016-06-06---complex-properties)
|
||||
- [Storage Conversion](#storage-conversion-5)
|
||||
- [Version Map](#version-map-6)
|
||||
- [Version 2017-07-07 - Optionality changes](#version-2017-07-07---optionality-changes)
|
||||
- [Storage Conversion](#storage-conversion-6)
|
||||
- [Version Map](#version-map-7)
|
||||
- [How often do optionality changes happen?](#how-often-do-optionality-changes-happen)
|
||||
- [Issue: Property Amnesia](#issue-property-amnesia)
|
||||
- [Version 2018-08-08 - Extending nested properties](#version-2018-08-08---extending-nested-properties)
|
||||
- [Version Map](#version-map-8)
|
||||
- [Version 2019-09-09 - Changing types](#version-2019-09-09---changing-types)
|
||||
- [Issue: Type collision](#issue-type-collision)
|
||||
- [Storage Conversion](#storage-conversion-7)
|
||||
- [Version Map](#version-map-9)
|
||||
- [How often do properties change their type?](#how-often-do-properties-change-their-type)
|
||||
|
||||
# Version 2011-01-01 - Initial Release
|
||||
|
||||
The initial release of the CRM includes a simple definition to capture information about a particular person:
|
||||
|
@ -133,7 +98,7 @@ Since they never change, the `ConvertTo()` and `ConvertFrom()` methods are omitt
|
|||
|
||||
With only two classes, our version map doesn't look much like the traditional hub and spoke model, but this will change as we work through this case study:
|
||||
|
||||
![](images/case-study-fixed-storage/2011-01-01.png)
|
||||
![](/images/case-study-fixed-storage/2011-01-01.png)
|
||||
|
||||
# Version 2012-02-02 - No Change
|
||||
|
||||
|
@ -157,7 +122,7 @@ Conversions between version `v20120202` and the `v1` storage version will be ide
|
|||
|
||||
Our hub and spoke diagram is becoming useful for seeing the relationship between versions:
|
||||
|
||||
![](images/case-study-fixed-storage/2012-02-02.png)
|
||||
![](/images/case-study-fixed-storage/2012-02-02.png)
|
||||
|
||||
# Version 2013-03-03 - New Property
|
||||
|
||||
|
@ -228,7 +193,7 @@ Conversion methods for earlier API versions of `Person` are essentially unchange
|
|||
|
||||
A graph of our conversions now starts to show the expected hub and spoke structure:
|
||||
|
||||
![](images/case-study-fixed-storage/2013-03-03.png)
|
||||
![](/images/case-study-fixed-storage/2013-03-03.png)
|
||||
|
||||
## How often are new properties added?
|
||||
|
||||
|
@ -364,7 +329,7 @@ Implementations of these interfaces are called *after* the generated boilerplate
|
|||
|
||||
The preview version just appears as another version in our hub and spoke diagram:
|
||||
|
||||
![](images/case-study-fixed-storage/2014-04-04-preview.png)
|
||||
![](/images/case-study-fixed-storage/2014-04-04-preview.png)
|
||||
|
||||
|
||||
# Version 2014-04-04 - Schema Change
|
||||
|
@ -515,7 +480,7 @@ func (person *Person) AssignTo(dest v1.Person) error {
|
|||
|
||||
We can see in our version map that the preview release is still supported:
|
||||
|
||||
![](images/case-study-fixed-storage/2014-04-04.png)
|
||||
![](/images/case-study-fixed-storage/2014-04-04.png)
|
||||
|
||||
# Version 2015-05-05 - Property Rename
|
||||
|
||||
|
@ -645,7 +610,7 @@ Here we can see the `2015-05-05` version of `ConvertToStorage()` populates `Alph
|
|||
|
||||
Here we see our horizon policy coming into effect, with support for version 2011-01-01 being dropped in this release:
|
||||
|
||||
![](images/case-study-fixed-storage/2015-05-05.png)
|
||||
![](/images/case-study-fixed-storage/2015-05-05.png)
|
||||
|
||||
## How often do property renames happen?
|
||||
|
||||
|
@ -802,7 +767,7 @@ We're recursively applying the same conversion pattern to `Address` as we have a
|
|||
|
||||
Again we see the oldest version (`2012-02-02`) drop out:
|
||||
|
||||
![](images/case-study-fixed-storage/2016-06-06.png)
|
||||
![](/images/case-study-fixed-storage/2016-06-06.png)
|
||||
|
||||
|
||||
# Version 2017-07-07 - Optionality changes
|
||||
|
@ -876,7 +841,7 @@ If we instead had an _optional_ property that became _required_ in a later versi
|
|||
|
||||
Note that the 2013-03-03 version has now dropped out:
|
||||
|
||||
![](images/case-study-fixed-storage/2017-07-07.png)
|
||||
![](/images/case-study-fixed-storage/2017-07-07.png)
|
||||
|
||||
## How often do optionality changes happen?
|
||||
|
||||
|
@ -970,7 +935,7 @@ These changes are entirely similar to those previously covered in version `2014-
|
|||
|
||||
In this release, we see that support for both `2014-04-04` and the preview version `2014-04-04preview` has been dropped:
|
||||
|
||||
![](images/case-study-fixed-storage/2018-08-08.png)
|
||||
![](/images/case-study-fixed-storage/2018-08-08.png)
|
||||
|
||||
Dropping those releases triggers a reccurrance of the ***Property Amnesia*** issue discussed above - the `FullName` property (only included in the `2014-04-04preview` release) has been forgotten.
|
||||
|
||||
|
@ -1138,7 +1103,7 @@ If we don't include metadata to capture type renames, the conversion can be manu
|
|||
|
||||
## Version Map
|
||||
|
||||
![](images/case-study-fixed-storage/2019-09-09.png)
|
||||
![](/images/case-study-fixed-storage/2019-09-09.png)
|
||||
|
||||
## How often do properties change their type?
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
<!-- omit in toc -->
|
||||
---
|
||||
title: Rolling Storage Versions
|
||||
---
|
||||
# Case Study - Rolling Storage Versions
|
||||
|
||||
This case study explores the recommended solution of using a *rolling storage version* where we update the storage schema of each resource each release of the service operator. We'll keep the storage version up to date with the latest GA release of each resource.
|
||||
|
@ -7,43 +9,6 @@ For the purposes of discussion, we'll be following the version by version evolut
|
|||
|
||||
Examples shown are deliberately simplified in order to focus, and therefore minutiae should be considered motivational, not binding. Reference the formal specification for precise details.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Version 2011-01-01 - Initial Release](#version-2011-01-01---initial-release)
|
||||
- [Storage Conversion](#storage-conversion)
|
||||
- [Version Map](#version-map)
|
||||
- [Version 2012-02-02 - No Change](#version-2012-02-02---no-change)
|
||||
- [Storage Conversion](#storage-conversion-1)
|
||||
- [Version Map](#version-map-1)
|
||||
- [Version 2013-03-03 - New Property](#version-2013-03-03---new-property)
|
||||
- [Storage Conversions](#storage-conversions)
|
||||
- [Version Map](#version-map-2)
|
||||
- [How often are new properties added?](#how-often-are-new-properties-added)
|
||||
- [Version 2014-04-04 Preview - Schema Change](#version-2014-04-04-preview---schema-change)
|
||||
- [Storage Conversion](#storage-conversion-2)
|
||||
- [Version Map](#version-map-3)
|
||||
- [Version 2014-04-04 - Schema Change](#version-2014-04-04---schema-change)
|
||||
- [Storage Conversion](#storage-conversion-3)
|
||||
- [Version Map](#version-map-4)
|
||||
- [Version 2015-05-05 - Property Rename](#version-2015-05-05---property-rename)
|
||||
- [Storage Conversion](#storage-conversion-4)
|
||||
- [Issue: Instability of manual conversions](#issue-instability-of-manual-conversions)
|
||||
- [Version Map](#version-map-5)
|
||||
- [How often do property renames happen?](#how-often-do-property-renames-happen)
|
||||
- [Version 2016-06-06 - Complex Properties](#version-2016-06-06---complex-properties)
|
||||
- [Storage Conversion](#storage-conversion-5)
|
||||
- [Version Map](#version-map-6)
|
||||
- [Version 2017-07-07 - Optionality changes](#version-2017-07-07---optionality-changes)
|
||||
- [Storage Conversion](#storage-conversion-6)
|
||||
- [Version Map](#version-map-7)
|
||||
- [How often does optionality change?](#how-often-does-optionality-change)
|
||||
- [Version 2018-08-08 - Extending nested properties](#version-2018-08-08---extending-nested-properties)
|
||||
- [Version Map](#version-map-8)
|
||||
- [Version 2019-09-09 - Changing types](#version-2019-09-09---changing-types)
|
||||
- [Storage Conversion](#storage-conversion-7)
|
||||
- [Version Map](#version-map-9)
|
||||
- [How often do properties change their type?](#how-often-do-properties-change-their-type)
|
||||
|
||||
# Version 2011-01-01 - Initial Release
|
||||
|
||||
The initial release of the CRM includes a simple definition to capture information about a particular person:
|
||||
|
@ -129,7 +94,7 @@ These methods will be automatically generated in order to handle the majority of
|
|||
|
||||
With only two classes, our version map doesn't look much like the traditional hub and spoke model, but this will change as we work through this case study:
|
||||
|
||||
![](images/case-study-rolling-storage/2011-01-01.png)
|
||||
![](/images/case-study-rolling-storage/2011-01-01.png)
|
||||
|
||||
|
||||
# Version 2012-02-02 - No Change
|
||||
|
@ -155,7 +120,7 @@ Conversions with the upgraded storage version will need to be trivially modified
|
|||
|
||||
Our hub and spoke diagram is becoming useful for seeing the relationship between versions:
|
||||
|
||||
![](images/case-study-rolling-storage/2012-02-02.png)
|
||||
![](/images/case-study-rolling-storage/2012-02-02.png)
|
||||
|
||||
Observe that the prior storage version is still shown, with a one way conversion to the current storage version. Existing users who upgrade their service operator will have their storage upgraded using this conversion. The conversion between storage versions will be generated with the same approach, and with the same structure, as all our other conversions.
|
||||
|
||||
|
@ -229,7 +194,7 @@ Conversion methods for earlier API versions of `Person` are essentially unchange
|
|||
|
||||
A graph of our conversions now starts to show the expected hub and spoke structure, with conversions from earlier versions of storage allowing easy upgrades for existing users of the service operator.
|
||||
|
||||
![](images/case-study-rolling-storage/2013-03-03.png)
|
||||
![](/images/case-study-rolling-storage/2013-03-03.png)
|
||||
|
||||
## How often are new properties added?
|
||||
|
||||
|
@ -351,7 +316,7 @@ Preview releases, by definition, include unstable changes that may differ once t
|
|||
|
||||
We don't want to make changes to our storage versions based on these speculative changes, so we handle persistence of the preview release with the existing storage version:
|
||||
|
||||
![](images/case-study-rolling-storage/2014-04-04-preview.png)
|
||||
![](/images/case-study-rolling-storage/2014-04-04-preview.png)
|
||||
|
||||
|
||||
# Version 2014-04-04 - Schema Change
|
||||
|
@ -523,7 +488,7 @@ func (person *Person) AssignTo(dest storage.Person) error {
|
|||
|
||||
We can see in our version map that the preview release is still supported, but is now backed by the GA release of the version:
|
||||
|
||||
![](images/case-study-rolling-storage/2014-04-04.png)
|
||||
![](/images/case-study-rolling-storage/2014-04-04.png)
|
||||
|
||||
# Version 2015-05-05 - Property Rename
|
||||
|
||||
|
@ -622,7 +587,7 @@ We also have the issue seen above where introduction of a change requires additi
|
|||
|
||||
Here we see our horizon policy coming into effect, with support for version 2011-01-01 being dropped in this release:
|
||||
|
||||
![](images/case-study-rolling-storage/2015-05-05.png)
|
||||
![](/images/case-study-rolling-storage/2015-05-05.png)
|
||||
|
||||
For users staying up to date with releases of the service operator, this will likely have no effect - but users still using the original release (storage version `v2011-01-01storage`) will need to update to an intermediate release before adopting this version.
|
||||
|
||||
|
@ -777,7 +742,7 @@ We're recursively applying the same conversion pattern to `Address` as we have a
|
|||
|
||||
Again we see the oldest version drop out, allowing users of the three prior versions of the service operator to upgrade cleanly:
|
||||
|
||||
![](images/case-study-rolling-storage/2016-06-06.png)
|
||||
![](/images/case-study-rolling-storage/2016-06-06.png)
|
||||
|
||||
# Version 2017-07-07 - Optionality changes
|
||||
|
||||
|
@ -848,7 +813,7 @@ If we instead had an _optional_ field that became _required_ in a later version
|
|||
|
||||
## Version Map
|
||||
|
||||
![](images/case-study-rolling-storage/2017-07-07.png)
|
||||
![](/images/case-study-rolling-storage/2017-07-07.png)
|
||||
|
||||
## How often does optionality change?
|
||||
|
||||
|
@ -890,7 +855,7 @@ These changes are entirely similar to those previously covered in version 2014-0
|
|||
|
||||
In this release, we see that support for both `2014-04-04` and the preview version `2014-04-04preview` has been dropped:
|
||||
|
||||
![](images/case-study-rolling-storage/2018-08-08.png)
|
||||
![](/images/case-study-rolling-storage/2018-08-08.png)
|
||||
|
||||
Users still running earlier releases of the service operator that are using `2014-04-04` or earlier will need to install an intermediate release in order to upgrade to this one.
|
||||
|
||||
|
@ -984,7 +949,7 @@ If we don't include metadata to capture type renames, the conversion can be manu
|
|||
|
||||
## Version Map
|
||||
|
||||
![](images/case-study-rolling-storage/2019-09-09.png)
|
||||
![](/images/case-study-rolling-storage/2019-09-09.png)
|
||||
|
||||
## How often do properties change their type?
|
||||
|
|
@ -207,7 +207,7 @@ There are no failure conditions (`Severity = Error`) specified here because curr
|
|||
Open questions which have since been answered are below.
|
||||
|
||||
**Question:** Will this work with [kstatus](https://github.com/kubernetes-sigs/cli-utils/blob/master/pkg/kstatus/README.md), which recommends negative polarity conditions?
|
||||
**Answer:** Yes, kstatus supports the `Ready` condition as well, with a [few caveats](https://github.com/kubernetes-sigs/cli-utils/blob/master/pkg/kstatus/README.md#the-ready-condition). Anecdotally, we feel that positive polarity conditions (like `Ready`) are more clear. As mentioned [above](examining-other-projects-like-aso), there are many operators that follow this `Ready` pattern including Crossplane and CAPI. If need-be, we can work around the major problem with kstatus and `Ready` by providing a webhook that automatically includes it on all resource creations.
|
||||
**Answer:** Yes, kstatus supports the `Ready` condition as well, with a [few caveats](https://github.com/kubernetes-sigs/cli-utils/blob/master/pkg/kstatus/README.md#the-ready-condition). Anecdotally, we feel that positive polarity conditions (like `Ready`) are more clear. As mentioned [above](#examining-other-projects-like-aso), there are many operators that follow this `Ready` pattern including Crossplane and CAPI. If need-be, we can work around the major problem with kstatus and `Ready` by providing a webhook that automatically includes it on all resource creations.
|
||||
|
||||
**Question:** The KEP for `Condition` says that `LastTransitionTime` should be updated any time the condition changes. The CAPI proposal says it should change when `Status` changes, but the actual CAPI implementation changes it any time the `Condition` changes.Which behavior do we want?
|
||||
**Answer:** We will follow the KEPs definition (and CAPI's actual implementation) and update `LastTransitionTime` any time a `Condition` changes, even if that change is from `Status=False` to `Status=False`.
|
|
@ -283,7 +283,7 @@ This may require some manual work. One thing we can investigate doing long term
|
|||
is see if there's a way to get teams to annotate "links" in their Swagger somehow.
|
||||
|
||||
**For dependent resources** we must identify all of the owner to dependent relationships between resources.
|
||||
As discussed in [what ARM does](#What-ARM-does), this can be done using the `resources` property in the ARM deployment templates.
|
||||
As discussed in what ARM does, this can be done using the `resources` property in the ARM deployment templates.
|
||||
These are much easier to automatically detect than related resources as the dependent types are called out in the `resources` property explicitly.
|
||||
|
||||
### How to choose the right reference type (ResourceReference vs KnownResourceReference) at generation time
|
|
@ -1,40 +1,9 @@
|
|||
<!-- omit in toc -->
|
||||
# Versioning
|
||||
|
||||
|
||||
Specification for how storage versioning will operate for code generated CRD definitions.
|
||||
|
||||
We're generating a large number of CRD definitions based on the JSON schema definitions published for Azure Resource Manager use.
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [Goals](#goals)
|
||||
- [Non-Goals](#non-goals)
|
||||
- [Other Constraints](#other-constraints)
|
||||
- [Case Studies](#case-studies)
|
||||
- [Proposed Solution](#proposed-solution)
|
||||
- [Defining Storage Versions](#defining-storage-versions)
|
||||
- [Generated conversion methods](#generated-conversion-methods)
|
||||
- [External Metadata for common changes](#external-metadata-for-common-changes)
|
||||
- [Standard extension points](#standard-extension-points)
|
||||
- [Testing](#testing)
|
||||
- [Round Trip Testing](#round-trip-testing)
|
||||
- [Relibility Testing](#relibility-testing)
|
||||
- [Golden Tests](#golden-tests)
|
||||
- [Conversion Flow](#conversion-flow)
|
||||
- [Direct conversion to storage type](#direct-conversion-to-storage-type)
|
||||
- [Two step conversion to storage type](#two-step-conversion-to-storage-type)
|
||||
- [Multiple step conversion to storage type](#multiple-step-conversion-to-storage-type)
|
||||
- [Two step conversion from storage type](#two-step-conversion-from-storage-type)
|
||||
- [Alternative Solutions](#alternative-solutions)
|
||||
- [Alternative: Fixed storage version](#alternative-fixed-storage-version)
|
||||
- [Alternative: Use the latest API version](#alternative-use-the-latest-api-version)
|
||||
- [Metadata Design](#metadata-design)
|
||||
- [Outstanding Issues](#outstanding-issues)
|
||||
- [Service Operator Upgrades](#service-operator-upgrades)
|
||||
- [See Also](#see-also)
|
||||
|
||||
## Goals
|
||||
|
||||
**Principle of Least Surprise:** The goal of the service operator is to allow users to consume Azure resources without having to leave the tooling they are familiar with. We therefore want to do things in the idiomatic Kubernetes fashion, so that they don't experience any nasty surprises.
|
||||
|
@ -64,11 +33,11 @@ Unlike the typical situation with a hand written service operator, we don't have
|
|||
|
||||
There are three case studies that accompany this specification, each one walking through one possible solution and showing how it will perform as a synthetic ARM style API evolves over time.
|
||||
|
||||
The [**Chained Versions**](case-study-chained-storage-versions.md) case study shows how the preferred solution adapts to changes as the API is modified.
|
||||
The [**Chained Versions**](/design/case-studies/case-study-chained-storage-versions/) case study shows how the preferred solution adapts to changes as the API is modified.
|
||||
|
||||
The [**Rolling Versions**](case-study-rolling-storage-versions.md) case study shows an alternative that works well but falls down when hand coded conversions are introduced between versions.
|
||||
The [**Rolling Versions**](/design/case-studies/case-study-rolling-storage-versions/) case study shows an alternative that works well but falls down when hand coded conversions are introduced between versions.
|
||||
|
||||
The [**Fixed Version**](case-study-fixed-storage-version.md) case study shows how a popular alternative would fare, calling out some specific problems that will occur.
|
||||
The [**Fixed Version**](/design/case-studies/case-study-fixed-storage-version/) case study shows how a popular alternative would fare, calling out some specific problems that will occur.
|
||||
|
||||
**TL;DR:** Using a *fixed storage version* appears simpler at first, and works well as long as the changes from version to version are simple. However, when the changes become complex (as they are bound to do over time), this approach starts to break down. While there is up front complexity to address with *chained storage versions*, the approach doesn't break down over time and we can generate useful automated tests for verification. The *rolling storage version* approach is viable, but requires additional ongoing maintenance when manual conversions are introduced between versions.
|
||||
|
||||
|
@ -157,7 +126,7 @@ The `ConvertToStorage()` method is responsible for copying all of the properties
|
|||
|
||||
Each property defined in the API type is considered in turn, and will require different handling based on its type and whether a suitable match is found on the storage type:
|
||||
|
||||
![](images/versioning/property-mapping-flowchart.png)
|
||||
![](/images/versioning/property-mapping-flowchart.png)
|
||||
|
||||
**For properties with a primitive type** a matching property must have the same name and the identical type. If found, a simple assignment will copy the value over. If not found, the value will be stashed-in/recalled-from the property bag present on the storage type.
|
||||
|
||||
|
@ -267,7 +236,7 @@ We'll generate two golden tests for each type in each API type, one to test veri
|
|||
|
||||
**Testing conversion to the latest version** will check that an instance of a older version of the API can be up-converted to the latest version:
|
||||
|
||||
![](images/versioning/golden-tests-to-latest.png)
|
||||
![](/images/versioning/golden-tests-to-latest.png)
|
||||
|
||||
The test will involve these steps:
|
||||
|
||||
|
@ -284,7 +253,7 @@ If neither rule is satisfied, the test will silently null out.
|
|||
|
||||
**Testing conversion from the latest version** will check that an instance of the latest version of the API can be down-converted to an older version.
|
||||
|
||||
![](images/versioning/golden-tests-from-latest.png)
|
||||
![](/images/versioning/golden-tests-from-latest.png)
|
||||
|
||||
* Create an exemplar instance of the latest API type
|
||||
* Convert it to the storage type using `ConvertToStorage()`
|
||||
|
@ -301,7 +270,7 @@ If neither rule is satisfied, the test will silently null out.
|
|||
|
||||
To illustrate the operation of conversions, consider the following graph of related versions of `Person`:
|
||||
|
||||
![](images/versioning/conversions.png)
|
||||
![](/images/versioning/conversions.png)
|
||||
|
||||
API versions are shown across the top, with the associated storage versions directly below. The arrows show the direction of references between the packages, with a package at the start of the arrow importing the package at the end. For example, package `v3` imports `v3storage` and can access the types within.
|
||||
|
||||
|
@ -311,27 +280,27 @@ The highlighted storage version **v4storage** is the currently nominated hub ver
|
|||
|
||||
The simplest case is a conversion directly between **v4** and **v4storage**, which simply involves copying properties across:
|
||||
|
||||
![](images/versioning/direct-conversion.png)
|
||||
![](/images/versioning/direct-conversion.png)
|
||||
|
||||
|
||||
### Two step conversion to storage type
|
||||
|
||||
There's no direct conversion between a **v3.Person** and a **v4storage.Person**, so an intermediate step is required: we convert first to a **v3storage.Person**, and then to the final type:
|
||||
|
||||
![](images/versioning/two-step-conversion.png)
|
||||
![](/images/versioning/two-step-conversion.png)
|
||||
|
||||
|
||||
### Multiple step conversion to storage type
|
||||
|
||||
The approach generalizes - at each stage, an intermediate instance is created, one step closer to the current hub type, and the properties are copied across:
|
||||
|
||||
![](images/versioning/multiple-step-conversion.png)
|
||||
![](/images/versioning/multiple-step-conversion.png)
|
||||
|
||||
### Two step conversion from storage type
|
||||
|
||||
When converting in the other direction, the process is similar - we show here just the two step case to illustrate.
|
||||
|
||||
![](images/versioning/two-step-reverse-conversion.png)
|
||||
![](/images/versioning/two-step-reverse-conversion.png)
|
||||
|
||||
|
||||
## Alternative Solutions
|
||||
|
@ -344,7 +313,7 @@ The "v1" storage version of each supported resource type will be created by merg
|
|||
|
||||
To maintain backward compatibility as Azure APIs evolve over time, we will include properties across all versions of the API, even for versions we are not currently generating as output. This ensures that properties in use by older APIs are still present and available for forward conversion to newer APIs, even as those older APIs age out of use.
|
||||
|
||||
This approach has a number of issues that are called out in detail in the [fixed storage version case study](case-study-fixed-storage-version.md).
|
||||
This approach has a number of issues that are called out in detail in the [fixed storage version case study](/design/case-studies/case-study-fixed-storage-version/).
|
||||
|
||||
**Property Bloat**: As our API evolves over time, our storage version is accumulating all the properties that have ever existed, bloating the storage version with obsolete properties that are seldom (if ever) used. Even properties that only ever existed on a single preview release of an ARM API need to be correctly managed for the lifetime of the service operator.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
weight: 1 # want this rendered first
|
||||
---
|
|
@ -27,7 +27,7 @@ There are three key pieces of information required before adding a resource to t
|
|||
This is usually a date, sometimes with a `-preview` suffix. In our example entry from above, this is `2020-11-01`.
|
||||
|
||||
## Adding the resource to the code generation configuration file
|
||||
The code generation configuration file is located [here](../../v2/azure-arm.yaml). To add a new resource to this file, find the `exportFilters` section of the file and scroll down until you get to a block of `exportFilters` for individual resources.
|
||||
The code generation configuration file is located [here](https://github.com/Azure/azure-service-operator/blob/master/v2/azure-arm.yaml). To add a new resource to this file, find the `exportFilters` section of the file and scroll down until you get to a block of `exportFilters` for individual resources.
|
||||
|
||||
Add a new `exportFilter` of kind `include-transitive` at the end of that block, right _above_ this section:
|
||||
```yaml
|
||||
|
@ -56,7 +56,7 @@ In the case of our example above, that ends up being:
|
|||
|
||||
## Run the code generator
|
||||
|
||||
Follow the steps in the [contributing guide](/v2/CONTRIBUTING.md) to set up your development environment.
|
||||
Follow the steps in the [contributing guide](https://github.com/Azure/azure-service-operator/blob/master/v2/CONTRIBUTING.md) to set up your development environment.
|
||||
Once you have a working development environment, run the `task` command to run the code generator.
|
||||
|
||||
## Fix any errors raised by the code generator
|
||||
|
@ -68,7 +68,7 @@ Example:
|
|||
> It might need to be manually added to `newKnownReferencesMap`,
|
||||
|
||||
To fix this error, determine whether the property in question is an ARM ID or not, and then update the `newKnownReferencesMap` function
|
||||
in [add_cross_resource_references.go](/v2/internal/generator/codegen/pipeline/add_cross_resource_references.go#L185).
|
||||
in [add_cross_resource_references.go](https://github.com/Azure/azure-service-operator/blob/master/v2/internal/generator/codegen/pipeline/add_cross_resource_references.go#:~:text=func-,newknownreferencesmap,-(configuration%20*config.Configuration).
|
||||
|
||||
If the property is an ARM ID, update `newKnownReferencesMap` to flag that property as a reference:
|
||||
```go
|
||||
|
@ -89,7 +89,7 @@ If the property is not an ARM ID, update `newKnownReferencesMap` to indicate tha
|
|||
TODO: expand on other common errors
|
||||
|
||||
## Examine the generated resource
|
||||
After running the generator, the new resource you added should be in the [apis](/v2/api/) directory.
|
||||
After running the generator, the new resource you added should be in the [apis](https://github.com/Azure/azure-service-operator/blob/master/v2/api/) directory.
|
||||
|
||||
Have a look through the files in the directory named after the `group` and `version` of the resource that was added.
|
||||
In our `NetworkSecurityGroups` example, the best place to start is `/v2/api/microsoft.network/v1alpha1api20201101/network_security_group_types_gen.go`
|
||||
|
@ -121,13 +121,13 @@ If you do identify properties which should be removed or changed, you can make c
|
|||
2. `# Deal with properties that should have been marked readOnly but weren't`
|
||||
|
||||
## Write a CRUD test for the resource
|
||||
The best way to do this is to start from an [existing test](/v2/internal/controller/controllers/crd_cosmosdb_databaseaccount_test.go) and modify it to work for your resource. It can also be helpful to refer to examples in the [ARM templates GitHub repo](https://github.com/Azure/azure-quickstart-templates).
|
||||
The best way to do this is to start from an [existing test](https://github.com/Azure/azure-service-operator/blob/master/v2/internal/controller/controllers/crd_cosmosdb_databaseaccount_test.go) and modify it to work for your resource. It can also be helpful to refer to examples in the [ARM templates GitHub repo](https://github.com/Azure/azure-quickstart-templates).
|
||||
|
||||
## Run the CRUD test for the resource and commit the recording
|
||||
See [the code generator README](/v2/CONTRIBUTING.md#running-integration-tests) for how to run recording tests.
|
||||
See [the code generator README](https://github.com/Azure/azure-service-operator/blob/master/v2/CONTRIBUTING.md#running-integration-tests) for how to run recording tests.
|
||||
|
||||
## Add a new sample
|
||||
The samples are located in the [samples directory](/v2/config/samples). There should be at least one sample for each kind of supported resource. These currently need to be added manually. It's possible in the future we will automatically generate samples similar to how we automatically generate CRDs and types, but that doesn't happen today.
|
||||
The samples are located in the [samples directory](https://github.com/Azure/azure-service-operator/blob/master/v2/config/samples). There should be at least one sample for each kind of supported resource. These currently need to be added manually. It's possible in the future we will automatically generate samples similar to how we automatically generate CRDs and types, but that doesn't happen today.
|
||||
|
||||
## Send a PR
|
||||
You're all done!
|
До Ширина: | Высота: | Размер: 31 KiB После Ширина: | Высота: | Размер: 31 KiB |
До Ширина: | Высота: | Размер: 29 KiB После Ширина: | Высота: | Размер: 29 KiB |
До Ширина: | Высота: | Размер: 168 KiB После Ширина: | Высота: | Размер: 168 KiB |
До Ширина: | Высота: | Размер: 286 KiB После Ширина: | Высота: | Размер: 286 KiB |
До Ширина: | Высота: | Размер: 118 KiB После Ширина: | Высота: | Размер: 118 KiB |
До Ширина: | Высота: | Размер: 11 KiB После Ширина: | Высота: | Размер: 11 KiB |
До Ширина: | Высота: | Размер: 20 KiB После Ширина: | Высота: | Размер: 20 KiB |
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 28 KiB |
До Ширина: | Высота: | Размер: 38 KiB После Ширина: | Высота: | Размер: 38 KiB |
До Ширина: | Высота: | Размер: 51 KiB После Ширина: | Высота: | Размер: 51 KiB |
До Ширина: | Высота: | Размер: 54 KiB После Ширина: | Высота: | Размер: 54 KiB |
До Ширина: | Высота: | Размер: 49 KiB После Ширина: | Высота: | Размер: 49 KiB |
До Ширина: | Высота: | Размер: 48 KiB После Ширина: | Высота: | Размер: 48 KiB |
До Ширина: | Высота: | Размер: 52 KiB После Ширина: | Высота: | Размер: 52 KiB |
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 33 KiB |
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 33 KiB |
До Ширина: | Высота: | Размер: 7.7 KiB После Ширина: | Высота: | Размер: 7.7 KiB |
До Ширина: | Высота: | Размер: 9.8 KiB После Ширина: | Высота: | Размер: 9.8 KiB |
До Ширина: | Высота: | Размер: 12 KiB После Ширина: | Высота: | Размер: 12 KiB |
До Ширина: | Высота: | Размер: 19 KiB После Ширина: | Высота: | Размер: 19 KiB |
До Ширина: | Высота: | Размер: 25 KiB После Ширина: | Высота: | Размер: 25 KiB |
До Ширина: | Высота: | Размер: 26 KiB После Ширина: | Высота: | Размер: 26 KiB |
До Ширина: | Высота: | Размер: 25 KiB После Ширина: | Высота: | Размер: 25 KiB |
До Ширина: | Высота: | Размер: 21 KiB После Ширина: | Высота: | Размер: 21 KiB |
До Ширина: | Высота: | Размер: 14 KiB После Ширина: | Высота: | Размер: 14 KiB |
До Ширина: | Высота: | Размер: 14 KiB После Ширина: | Высота: | Размер: 14 KiB |
До Ширина: | Высота: | Размер: 12 KiB После Ширина: | Высота: | Размер: 12 KiB |
До Ширина: | Высота: | Размер: 19 KiB После Ширина: | Высота: | Размер: 19 KiB |
До Ширина: | Высота: | Размер: 26 KiB После Ширина: | Высота: | Размер: 26 KiB |
До Ширина: | Высота: | Размер: 34 KiB После Ширина: | Высота: | Размер: 34 KiB |
До Ширина: | Высота: | Размер: 43 KiB После Ширина: | Высота: | Размер: 43 KiB |
До Ширина: | Высота: | Размер: 44 KiB После Ширина: | Высота: | Размер: 44 KiB |
До Ширина: | Высота: | Размер: 43 KiB После Ширина: | Высота: | Размер: 43 KiB |
До Ширина: | Высота: | Размер: 40 KiB После Ширина: | Высота: | Размер: 40 KiB |
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 33 KiB |
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 33 KiB |