forked from softwarefactory-project/sf-operator
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRenderSchemas.hs
More file actions
129 lines (117 loc) · 5.46 KB
/
RenderSchemas.hs
File metadata and controls
129 lines (117 loc) · 5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Char qualified as Char
import Data.Foldable (traverse_)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
import Data.Yaml qualified as Yaml
import Dhall.Core (pretty)
import Dhall.Core qualified as Dhall
import Dhall.Kubernetes.Convert qualified as Convert
import Dhall.Kubernetes.Types (Definition (typ), Expr, ModelName (..))
import Dhall.Map qualified as DMap
import Lens.Micro qualified as Lens
main :: IO ()
main = do
-- Read the CRD schemas
crd <- Yaml.decodeFileThrow "config/crd/bases/sf.softwarefactory-project.io_softwarefactories.yaml"
-- Convert into dhall schemas
case Convert.toDefinition crd of
Left err -> error $ "Couldn't parse the crd: " <> Text.unpack err
Right result -> traverse_ writeDhall $ generateSchemas $ Map.fromList [result]
putStrLn "Done!"
where
writeDhall (fp, content) = Text.writeFile ("schemas/" <> fp <> ".dhall") $ pretty content
-- | Generate one schema per model name, along with a global package.dhall import.
generateSchemas :: Map ModelName Definition -> [(FilePath, Expr)]
generateSchemas defs = [("package", package)] <> map toDhallFiles (Map.toList types)
where
package = getPackage $ Map.keys types
types = getTypes defs
defaults = getDefaults types
toDhallFiles (model, typeExpr) =
let defExpr = case Map.lookup model defaults of
Just x -> x
Nothing -> Dhall.RecordLit mempty
schemaExpr =
Dhall.RecordLit $
DMap.fromList
[ ("Type", Dhall.makeRecordField $ adjustImport typeExpr)
, ("default", Dhall.makeRecordField defExpr)
]
in (Text.unpack $ unModelName model, schemaExpr)
-- | Returns the Dhall type expr for the CRD.
getTypes :: Map ModelName Definition -> Map ModelName Expr
getTypes = fixModels . Convert.toTypes mempty splitModels True []
where
fixModels = Map.map fixEmptyType . Map.filterWithKey removeTop
-- Remove the non-spec part of the CRD
removeTop k _v = case unModelName k of
"io.k8s.apimachinery.pkg.util.intstr.NatOrString" -> False
"sf.softwarefactory-project.io.SoftwareFactory" -> False
_ -> True
-- Attribute like cpu, memory or storage size are undefined, this make them Text
fixEmptyType :: Expr -> Expr
fixEmptyType = Lens.transformOf Dhall.subExpressions emptyTypeToText
emptyTypeToText expr = case expr of
Dhall.Record m | m == mempty -> Dhall.Text
_ -> expr
-- | Split the schemas into logical units
splitModels :: [ModelName] -> Definition -> Maybe ModelName
splitModels hierarchy def
| -- We only split object schema, not values like array or strings
typ def /= Just "object" =
Nothing
| otherwise = case hierarchy of
-- The top level .spec attribute is moved into SoftwareFactorySpec
[ModelName "sf.softwarefactory-project.io.SoftwareFactory", ModelName "spec"] ->
Just $ ModelName "Spec"
-- Adapt LogJuicer storage
[ModelName "Spec", ModelName "logjuicer"] -> Just $ ModelName "Storage"
-- Spec attributes are moved into dedicated models
[ModelName "Spec", ModelName attr] ->
Just $ ModelName $ adjustName attr
-- Zuul and Nodepool attributes are moved into dedicated models
[ModelName "Zuul", ModelName x]
| "conns" `Text.isSuffixOf` x -> Just $ ModelName $ adjustName $ Text.replace "conns" "Conn" x
| otherwise -> Just $ ModelName $ "Zuul" <> adjustName x
[ModelName "Nodepool", ModelName x] ->
Just $ ModelName $ "Nodepool" <> adjustName x
-- Adapt MariaDB attributes
[_, ModelName "logStorage"] -> Just $ ModelName "Storage"
[_, ModelName "dbStorage"] -> Just $ ModelName "Storage"
-- Move limits and storage attribute into dedicated models
_ -> case last hierarchy of
ModelName "limits" -> Just $ ModelName "Limits"
ModelName "storage" -> Just $ ModelName "Storage"
_ -> Nothing
-- | Convert model name to PascalCase
adjustName :: Text -> Text
adjustName = Text.filter (/= ' ') . Text.unwords . map toTitle . Text.words . Text.replace "-" " "
where
toTitle s = case Text.uncons s of
Just (c, rest) -> Text.cons (Char.toUpper c) rest
Nothing -> s
-- | Returns the Dhall default expr.
getDefaults :: Map ModelName Expr -> Map ModelName Expr
getDefaults = fmap adjustImport . Map.mapMaybeWithKey (Convert.toDefault mempty mempty)
{- | Adjust the import path because the default dhall-openapi converter generates multiple files
for the type and default while we want to keep them in a single schema file.
-}
adjustImport :: Expr -> Expr
adjustImport = Lens.transformOf Dhall.subExpressions toLocalType
where
toLocalType expr = case expr of
Dhall.Embed (Dhall.Import (Dhall.ImportHashed _ (Dhall.Local _ (Dhall.File _ f))) _) ->
Dhall.Field (mkImport f) $ Dhall.makeFieldSelection "Type"
_ -> expr
mkImport :: Text -> Expr
mkImport = Dhall.Embed . Convert.mkImport mempty []
-- | Return a Dhall expression for the package
getPackage :: [ModelName] -> Expr
getPackage = Dhall.RecordLit . DMap.fromList . map toRecordField
where
toRecordField (ModelName name) = (name, Dhall.makeRecordField $ mkImport (name <> ".dhall"))