├── _config.yml ├── examples ├── java │ ├── .gitignore │ └── README.md ├── neo4j │ ├── .gitignore │ ├── export │ └── README.md ├── dgraph │ ├── .gitignore │ ├── README.md │ ├── export │ └── dgraph ├── javascript │ ├── artifacts │ │ └── deploy.sh │ ├── imports │ │ ├── double.js │ │ ├── define.js │ │ ├── extract.js │ │ └── endpoints.js │ ├── README.md │ ├── define.yaml │ ├── artifacts.yaml │ ├── exec.yaml │ └── constraints.yaml ├── openstack │ ├── profile │ │ ├── capabilities.yaml │ │ ├── relationships.yaml │ │ └── profile.yaml │ └── scripts │ │ ├── build-playbook │ │ └── install-ansible ├── 2.0 │ ├── imports │ │ ├── cloud.yaml │ │ ├── nginx.yaml │ │ ├── mongodb.yaml │ │ └── super-load-balancer.yaml │ ├── dsl-definitions.yaml │ └── copy.yaml ├── ansible │ ├── hosts │ │ ├── inventory.yaml │ │ ├── README.md │ │ ├── playbook.yaml │ │ ├── tosca.yaml │ │ └── ansible.cfg │ ├── tasks │ │ ├── inventory.yaml │ │ ├── README.md │ │ ├── ansible.cfg │ │ └── playbook.yaml │ ├── ansible-playbook │ └── README.md ├── csar │ ├── cloud │ │ └── profile.yaml │ ├── main.yaml │ ├── other 1.yaml │ └── other 2.yaml ├── bpmn │ └── profile │ │ ├── profile.yaml │ │ └── policies.yaml ├── ruby │ ├── README.md │ └── compile.rb ├── python │ ├── README.md │ └── compile.py ├── 1.3 │ ├── imports │ │ ├── nginx.yaml │ │ ├── mongodb.yaml │ │ └── super-load-balancer.yaml │ ├── dsl-definitions.yaml │ ├── unicode.yaml │ └── copy.yaml ├── legacy │ ├── tosca_1_0.yaml │ ├── tosca_1_1.yaml │ └── tosca_1_2.yaml ├── README.md ├── hot │ └── parameters │ │ └── single-server-with-existing-floating-ip.yaml └── cloudify │ └── example.yaml ├── wrappers ├── java │ ├── .gitignore │ ├── java │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── cloud │ │ │ └── puccini │ │ │ └── Problems.java │ ├── pom.xml │ ├── native │ │ └── src │ │ │ └── main │ │ │ └── c │ │ │ └── cloud_puccini_TOSCA.c │ └── README.md ├── ruby │ ├── lib │ │ ├── .gitignore │ │ └── puccini.rb │ ├── puccini.gemspec │ └── README.md ├── python │ ├── MANIFEST.in │ ├── puccini │ │ ├── go.py │ │ └── tosca.py │ ├── README.md │ └── description.md └── ansible │ ├── README.md │ └── ansible_collections │ └── puccini │ └── tosca │ ├── galaxy.yml │ └── README.md ├── normal ├── common.go ├── README.md ├── value.go ├── map.go ├── list.go ├── primitive.go ├── notification.go ├── function-call.go ├── group.go ├── relationship.go └── artifact.go ├── TODO ├── library ├── default.pgo └── main.go ├── assets ├── media │ └── download.png └── tosca │ └── profiles │ ├── simple │ ├── 2.0 │ │ ├── TOSCA.meta │ │ ├── group_types.yaml │ │ ├── profile.yaml │ │ └── policy_types.yaml │ ├── 1.3 │ │ ├── groups.yaml │ │ └── profile.yaml │ ├── 1.0 │ │ ├── groups.yaml │ │ └── profile.yaml │ ├── 1.1 │ │ ├── groups.yaml │ │ └── profile.yaml │ └── 1.2 │ │ ├── groups.yaml │ │ └── profile.yaml │ ├── simple-for-nfv │ └── 1.0 │ │ ├── profile.yaml │ │ └── relationships.yaml │ ├── hot │ └── 1.0 │ │ └── js │ │ ├── constraints │ │ ├── custom_constraint.js │ │ ├── allowed_values.js │ │ ├── allowed_pattern.js │ │ ├── modulo.js │ │ ├── length.js │ │ └── range.js │ │ └── functions │ │ ├── if.js │ │ ├── or.js │ │ ├── and.js │ │ ├── not.js │ │ ├── yaql.js │ │ ├── digest.js │ │ ├── equals.js │ │ ├── filter.js │ │ ├── repeat.js │ │ ├── contains.js │ │ ├── get_attr.js │ │ ├── get_file.js │ │ ├── make_url.js │ │ ├── map_merge.js │ │ ├── str_split.js │ │ ├── get_resource.js │ │ ├── list_concat.js │ │ ├── map_replace.js │ │ ├── str_replace.js │ │ ├── resource_facade.js │ │ ├── list_concat_unique.js │ │ ├── str_replace_strict.js │ │ ├── str_replace_vstrict.js │ │ ├── list_join.js │ │ └── get_param.js │ ├── cloudify │ └── 5.0.5 │ │ ├── js │ │ └── functions │ │ │ ├── concat.js │ │ │ ├── get_input.js │ │ │ ├── get_secret.js │ │ │ ├── get_attribute.js │ │ │ ├── get_capability.js │ │ │ └── get_property.js │ │ └── data.yaml │ ├── implicit │ └── 2.0 │ │ ├── js │ │ ├── constraints │ │ │ ├── schema.js │ │ │ ├── $format.js │ │ │ ├── pattern.js │ │ │ ├── less_than.js │ │ │ ├── greater_or_equal.js │ │ │ ├── greater_than.js │ │ │ ├── less_or_equal.js │ │ │ ├── has_key.js │ │ │ ├── equal.js │ │ │ ├── max_length.js │ │ │ ├── min_length.js │ │ │ └── matches.js │ │ ├── functions │ │ │ ├── get_operation_output.js │ │ │ ├── $get_target_name.js │ │ │ ├── get_property.js │ │ │ ├── join.js │ │ │ ├── get_nodes_of_type.js │ │ │ ├── token.js │ │ │ ├── $node_index.js │ │ │ ├── get_artifact.js │ │ │ ├── length.js │ │ │ ├── get_attribute.js │ │ │ └── union.js │ │ └── comparers │ │ │ └── version.js │ │ └── profile.yaml │ ├── common │ └── 1.0 │ │ └── js │ │ ├── outputs.js │ │ └── coerce.js │ └── profiles.go ├── executables ├── puccini-clout │ ├── default.pgo │ ├── commands │ │ ├── version.go │ │ ├── scriptlet.go │ │ └── scriptlet-get.go │ └── main.go ├── puccini-csar │ ├── default.pgo │ ├── commands │ │ ├── version.go │ │ └── common.go │ └── main.go └── puccini-tosca │ ├── default.pgo │ ├── commands │ └── version.go │ └── main.go ├── clout ├── common.go ├── key.go ├── entity.go ├── util │ ├── list.go │ └── map.go ├── read.go ├── js │ ├── clout-context.go │ └── execution-context.go └── load.go ├── tosca ├── csar │ ├── common.go │ ├── format.go │ └── version.go ├── parsing │ ├── common.go │ ├── rendering.go │ ├── maps.go │ ├── imports.go │ ├── inputs.go │ ├── entity-pointers.go │ ├── grammars.go │ └── function-call.go ├── parser │ ├── phase6-normalization.go │ ├── inputs.go │ ├── phase5-rendering.go │ ├── common.go │ ├── traverse.go │ ├── yaml.go │ ├── parser.go │ ├── gather.go │ └── parse.go └── grammars │ ├── common.go │ ├── tosca_v2_0 │ ├── data-definition.go │ ├── entity.go │ ├── bytes.go │ └── event-filter.go │ ├── cloudify_v1_3 │ ├── type.go │ ├── entity.go │ ├── dsl-resource.go │ ├── node-template-instances.go │ ├── group-policy-trigger.go │ ├── group-policy.go │ ├── policy-type.go │ ├── policy-trigger-type.go │ └── metadata.go │ ├── tosca_v1_0 │ ├── group.go │ ├── node-template.go │ ├── policy-type.go │ ├── service-template.go │ ├── policy.go │ ├── relationship-template.go │ ├── relationship-type.go │ └── capability-definition.go │ ├── tosca_v1_2 │ ├── artifact.go │ ├── group.go │ ├── capability-assignment.go │ ├── group-type.go │ ├── workflow-activity-call-operation.go │ ├── substitution-mappings.go │ ├── artifact-definition.go │ ├── interface-type.go │ ├── interface-assignment.go │ ├── interface-definition.go │ ├── operation-definition.go │ ├── operation-assignment.go │ ├── attribute-definition.go │ ├── schema.go │ ├── relationship-type.go │ ├── capability-definition.go │ ├── service-file.go │ ├── file.go │ └── requirement-assignment.go │ ├── hot │ ├── entity.go │ ├── data.go │ └── parameter-group.go │ ├── tosca_v1_1 │ ├── interface-implementation.go │ ├── substitution-mappings.go │ ├── service-file.go │ ├── file.go │ ├── relationship-type.go │ └── capability-definition.go │ └── tosca_v1_3 │ ├── attribute-definition.go │ ├── requirement-definition.go │ ├── node-template.go │ ├── relationship-type.go │ ├── constraint-clause.go │ ├── value.go │ ├── service-file.go │ ├── file.go │ ├── property-filter.go │ └── schema.go ├── .gitignore └── scripts ├── format ├── test-parse ├── build-wrapper-java ├── test-git-url ├── test-https-url ├── benchmark ├── refresh-mod ├── test-js ├── test-archive-url ├── test-ruby ├── test-csar-http-url ├── test-visualize ├── _env ├── test-archive-url-http ├── _start-http-server ├── test-python ├── test-ansible ├── build-wrapper-ruby ├── test-csar ├── test-formats ├── test ├── test-java ├── test-wasm ├── install-bash-completion ├── build-library ├── build-csars ├── build-windows ├── build-tinygo ├── _functions ├── test-wasi ├── build-wrapper-ansible ├── build-wasm ├── build ├── test-all ├── benchmark-all └── release /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /examples/java/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/neo4j/.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | -------------------------------------------------------------------------------- /wrappers/java/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /wrappers/ruby/lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | -------------------------------------------------------------------------------- /examples/dgraph/.gitignore: -------------------------------------------------------------------------------- 1 | tosca.json 2 | data/ 3 | logs/ 4 | -------------------------------------------------------------------------------- /normal/common.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | const VERSION = "1.0" 4 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Export to GraphML: 2 | http://graphml.graphdrawing.org/ 3 | 4 | -------------------------------------------------------------------------------- /examples/javascript/artifacts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Deploy! 4 | -------------------------------------------------------------------------------- /library/default.pgo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tliron/go-puccini/HEAD/library/default.pgo -------------------------------------------------------------------------------- /assets/media/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tliron/go-puccini/HEAD/assets/media/download.png -------------------------------------------------------------------------------- /executables/puccini-clout/default.pgo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tliron/go-puccini/HEAD/executables/puccini-clout/default.pgo -------------------------------------------------------------------------------- /executables/puccini-csar/default.pgo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tliron/go-puccini/HEAD/executables/puccini-csar/default.pgo -------------------------------------------------------------------------------- /executables/puccini-tosca/default.pgo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tliron/go-puccini/HEAD/executables/puccini-tosca/default.pgo -------------------------------------------------------------------------------- /wrappers/python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | # For sdist 2 | 3 | exclude README.md 4 | include description.md 5 | graft puccini/go-source 6 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/2.0/TOSCA.meta: -------------------------------------------------------------------------------- 1 | CSAR-Version: 2.0 2 | Created-By: OASIS TOSCA TC 3 | Entry-Definitions: profile.yaml 4 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple-for-nfv/1.0/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_2 2 | 3 | imports: 4 | - nodes.yaml 5 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/constraints/custom_constraint.js: -------------------------------------------------------------------------------- 1 | 2 | exports.validate = function() { 3 | // TODO 4 | return true; 5 | }; 6 | -------------------------------------------------------------------------------- /clout/common.go: -------------------------------------------------------------------------------- 1 | package clout 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | var log = commonlog.GetLogger("puccini.clout") 8 | -------------------------------------------------------------------------------- /examples/openstack/profile/capabilities.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | capability_types: 4 | 5 | Resource: {} 6 | -------------------------------------------------------------------------------- /examples/openstack/profile/relationships.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | relationship_types: 4 | 5 | Depends: {} 6 | -------------------------------------------------------------------------------- /tosca/csar/common.go: -------------------------------------------------------------------------------- 1 | package csar 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | var log = commonlog.GetLogger("puccinia.csar") 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .pydevproject 3 | .vscode 4 | 5 | go.work 6 | go.work.sum 7 | 8 | dist/ 9 | docs/html/ 10 | work/ 11 | 12 | __pycache__/ 13 | -------------------------------------------------------------------------------- /tosca/parsing/common.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | var log = commonlog.GetLogger("puccinia.parsing") 8 | -------------------------------------------------------------------------------- /examples/2.0/imports/cloud.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | metadata: 4 | 5 | template_name: Cloud Profile Example 6 | template_author: Puccini 7 | -------------------------------------------------------------------------------- /examples/javascript/imports/double.js: -------------------------------------------------------------------------------- 1 | 2 | exports.evaluate = function(v) { 3 | if (arguments.length !== 1) 4 | throw 'must have 1 argument'; 5 | return v * 2; 6 | }; 7 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | cd "$ROOT" 8 | 9 | go fmt ./... 10 | -------------------------------------------------------------------------------- /examples/ansible/hosts/inventory.yaml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | localhost: 4 | ansible_connection: local 5 | ansible_python_interpreter: "{{ ansible_playbook_python }}" 6 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/if.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#if] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/or.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#or] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /examples/ansible/tasks/inventory.yaml: -------------------------------------------------------------------------------- 1 | all: 2 | hosts: 3 | localhost: 4 | ansible_connection: local 5 | ansible_python_interpreter: "{{ ansible_playbook_python }}" 6 | abc: {} -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/and.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#and] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/not.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#not] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/yaql.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#yaql] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /library/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/util" 5 | 6 | _ "github.com/tliron/commonlog/simple" 7 | ) 8 | 9 | func main() { 10 | util.Exit(0) 11 | } 12 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/digest.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#digest] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/equals.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#equals] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/filter.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#filter] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/repeat.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#repeat] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /clout/key.go: -------------------------------------------------------------------------------- 1 | package clout 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/segmentio/ksuid" 7 | ) 8 | 9 | func NewKey() string { 10 | return fmt.Sprintf("_%s", ksuid.New().String()) 11 | } 12 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/constraints/allowed_values.js: -------------------------------------------------------------------------------- 1 | 2 | exports.validate = function(v) { 3 | let values = Array.prototype.slice.call(arguments, 1); 4 | return values.indexOf(v) !== -1; 5 | }; 6 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/contains.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#contains] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/get_attr.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#get_attr] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/get_file.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#get_file] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/make_url.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#make_url] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/map_merge.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#map_merge] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/str_split.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#str_split] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/get_resource.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#get_resource] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/list_concat.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#list-concat] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/map_replace.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#map_replace] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/str_replace.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#str_replace] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/js/functions/concat.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-intrinsic-functions/] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; 7 | -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/js/functions/get_input.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-intrinsic-functions/] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; 7 | -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/js/functions/get_secret.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-intrinsic-functions/] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; 7 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/resource_facade.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#resource_facade] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/js/functions/get_attribute.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-intrinsic-functions/] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; 7 | -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/js/functions/get_capability.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-intrinsic-functions/] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; 7 | -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/js/functions/get_property.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-intrinsic-functions/] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; 7 | -------------------------------------------------------------------------------- /assets/tosca/profiles/cloudify/5.0.5/data.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_3 2 | 3 | data_types: 4 | 5 | boolean: {} 6 | integer: {} 7 | float: {} 8 | string: {} 9 | list: {} 10 | dict: {} 11 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/list_concat_unique.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#list_concat_unique] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/str_replace_strict.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#str_replace_strict] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/str_replace_vstrict.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#str_replace_vstrict] 3 | 4 | exports.evaluate = function() { 5 | return 'TODO'; 6 | }; -------------------------------------------------------------------------------- /examples/ansible/hosts/README.md: -------------------------------------------------------------------------------- 1 | Ansible Hosts Example 2 | ===================== 3 | 4 | To run, you must change to this directory (so Ansible will pick up our `ansible.cfg` here): 5 | 6 | ../ansible-playbook playbook.yaml 7 | -------------------------------------------------------------------------------- /examples/ansible/tasks/README.md: -------------------------------------------------------------------------------- 1 | Ansible Tasks Example 2 | ===================== 3 | 4 | To run, you must change to this directory (so Ansible will pick up our `ansible.cfg` here): 5 | 6 | ../ansible-playbook playbook.yaml 7 | -------------------------------------------------------------------------------- /executables/puccini-clout/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCommand.AddCommand(cobra.NewVersionCommand(toolName)) 9 | } 10 | -------------------------------------------------------------------------------- /executables/puccini-csar/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCommand.AddCommand(cobra.NewVersionCommand(toolName)) 9 | } 10 | -------------------------------------------------------------------------------- /executables/puccini-tosca/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCommand.AddCommand(cobra.NewVersionCommand(toolName)) 9 | } 10 | -------------------------------------------------------------------------------- /examples/csar/cloud/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: Cloud Profile Example 6 | template_author: Puccini 7 | 8 | node_types: 9 | 10 | Server: {} 11 | -------------------------------------------------------------------------------- /scripts/test-parse: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | puccini-tosca parse "$ROOT/examples/1.3/data-types.yaml" "$@" 10 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/schema.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 4 | 5 | exports.validate = function() { 6 | // TODO 7 | return true; 8 | }; 9 | -------------------------------------------------------------------------------- /clout/entity.go: -------------------------------------------------------------------------------- 1 | package clout 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | ) 6 | 7 | // 8 | // Entity 9 | // 10 | 11 | type Entity interface { 12 | GetMetadata() ard.StringMap 13 | GetProperties() ard.StringMap 14 | } 15 | -------------------------------------------------------------------------------- /examples/ansible/ansible-playbook: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/../../scripts/_env" 6 | 7 | . "$ROOT/dist/python-env/bin/activate" 8 | 9 | ansible-playbook "$@" 10 | -------------------------------------------------------------------------------- /examples/bpmn/profile/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:bpmn.generate: js/generate.js 6 | 7 | namespace: bpmn 8 | 9 | imports: 10 | 11 | - policies.yaml 12 | -------------------------------------------------------------------------------- /examples/openstack/profile/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:openstack.generate: js/generate.js 6 | 7 | namespace: openstack 8 | 9 | imports: 10 | 11 | - nodes.yaml 12 | -------------------------------------------------------------------------------- /examples/ruby/README.md: -------------------------------------------------------------------------------- 1 | Ruby Example 2 | ============ 3 | 4 | This relies on the [Ruby wrapper](../../wrappers/ruby/), so make sure to install that first. 5 | 6 | You can now run the example: 7 | 8 | examples/ruby/compile.rb examples/1.3/data-types.yaml 9 | -------------------------------------------------------------------------------- /assets/tosca/profiles/common/1.0/js/outputs.js: -------------------------------------------------------------------------------- 1 | 2 | const traversal = require('tosca.lib.traversal'); 3 | const tosca = require('tosca.lib.utils'); 4 | 5 | traversal.coerce(); 6 | 7 | if (tosca.isTosca(clout)) 8 | transcribe.output(clout.properties.tosca.outputs); 9 | -------------------------------------------------------------------------------- /examples/python/README.md: -------------------------------------------------------------------------------- 1 | Python Example 2 | ============== 3 | 4 | This relies on the [Python wrapper](../../wrappers/python/), so make sure to install that first. 5 | 6 | You can now run the example: 7 | 8 | examples/python/compile.py examples/1.3/data-types.yaml 9 | -------------------------------------------------------------------------------- /assets/tosca/profiles/common/1.0/js/coerce.js: -------------------------------------------------------------------------------- 1 | 2 | const traversal = require('tosca.lib.traversal'); 3 | const tosca = require('tosca.lib.utils'); 4 | 5 | traversal.coerce(); 6 | if (env.arguments.history !== 'false') 7 | tosca.addHistory('coerce'); 8 | transcribe.output(clout); 9 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/constraints/allowed_pattern.js: -------------------------------------------------------------------------------- 1 | 2 | exports.validate = function(v, re) { 3 | if (arguments.length !== 2) 4 | throw 'must have 1 argument'; 5 | if (v.$string !== undefined) 6 | v = v.$string; 7 | return new RegExp('^' + re + '$').test(v); 8 | }; 9 | -------------------------------------------------------------------------------- /scripts/build-wrapper-java: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-library" 8 | 9 | git_version 10 | 11 | mvn --file "$ROOT/wrappers/java" --define puccini.version=${VERSION:1} 12 | -------------------------------------------------------------------------------- /examples/1.3/imports/nginx.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: NGINX Example 6 | template_author: Puccini 7 | 8 | namespace: nginx 9 | 10 | node_types: 11 | 12 | NginX: 13 | derived_from: tosca:WebServer 14 | -------------------------------------------------------------------------------- /examples/openstack/scripts/build-playbook: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/../../../scripts/_env" 6 | 7 | puccini-tosca compile "$HERE/../hello-world.yaml" --exec=openstack.generate --output="$ROOT/dist/openstack" 8 | -------------------------------------------------------------------------------- /scripts/test-git-url: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | puccini-tosca compile \ 10 | 'git:https://github.com/tliron/go-puccini.git#main!examples/1.3/data-types.yaml' "$@" 11 | -------------------------------------------------------------------------------- /examples/1.3/imports/mongodb.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: MongoDB Example 6 | template_author: Puccini 7 | 8 | namespace: mongodb 9 | 10 | node_types: 11 | 12 | MongoDB: 13 | derived_from: tosca:DBMS 14 | -------------------------------------------------------------------------------- /examples/ansible/hosts/playbook.yaml: -------------------------------------------------------------------------------- 1 | - name: TOSCA hosts 2 | hosts: myservice # matches the service name in "tosca.yaml" 3 | gather_facts: no 4 | tasks: 5 | 6 | - name: Show hosts 7 | debug: 8 | msg: "host: {{ inventory_hostname }}, groups: {{ group_names | join(', ') }}" 9 | -------------------------------------------------------------------------------- /examples/ansible/tasks/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | ansible_managed=Modified by Ansible on %Y-%m-%d %H:%M:%S %Z 3 | inventory=./inventory.yaml 4 | 5 | # This is for testing; usually you would use "ansible-galaxy collection install puccini.tosca" 6 | collections_path=../../../wrappers/ansible 7 | -------------------------------------------------------------------------------- /examples/legacy/tosca_1_0.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_0 2 | 3 | metadata: 4 | 5 | template_name: TOSCA 1.0 Example 6 | template_author: Puccini 7 | 8 | topology_template: 9 | 10 | node_templates: 11 | 12 | main: 13 | type: tosca:Compute 14 | -------------------------------------------------------------------------------- /examples/legacy/tosca_1_1.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_1 2 | 3 | metadata: 4 | 5 | template_name: TOSCA 1.1 Example 6 | template_author: Puccini 7 | 8 | topology_template: 9 | 10 | node_templates: 11 | 12 | main: 13 | type: tosca:Compute 14 | -------------------------------------------------------------------------------- /examples/legacy/tosca_1_2.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_2 2 | 3 | metadata: 4 | 5 | template_name: TOSCA 1.2 Example 6 | template_author: Puccini 7 | 8 | topology_template: 9 | 10 | node_templates: 11 | 12 | main: 13 | type: tosca:Compute 14 | -------------------------------------------------------------------------------- /scripts/test-https-url: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | puccini-tosca compile \ 10 | https://raw.githubusercontent.com/tliron/go-puccini/main/examples/1.3/data-types.yaml "$@" 11 | -------------------------------------------------------------------------------- /scripts/benchmark: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | m 'benchmarking...' 10 | 11 | cd "$ROOT" 12 | 13 | PUCCINI_TEST_ROOT=$ROOT \ 14 | go test -count=10 -run=^$ -benchmem -bench=. "$@" 15 | -------------------------------------------------------------------------------- /scripts/refresh-mod: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | rm --force "$ROOT/go.mod" "$ROOT/go.sum" 8 | 9 | cd "$ROOT" 10 | go mod init "$MODULE" 11 | go mod tidy 12 | 13 | "$HERE/test" 14 | 15 | go mod tidy 16 | -------------------------------------------------------------------------------- /scripts/test-js: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | puccini-tosca compile "$ROOT/examples/1.3/requirements-and-capabilities.yaml" "$@" | \ 10 | puccini-clout scriptlet exec tosca.resolve 11 | -------------------------------------------------------------------------------- /examples/ansible/README.md: -------------------------------------------------------------------------------- 1 | TOSCA Ansible Examples 2 | ====================== 3 | 4 | These rely on the [Ansible wrapper](../../wrappers/ansible/), which in turn relies on the 5 | [Python wrapper](../../wrappers/python/), so make sure to install that first. 6 | 7 | * [Tasks](tasks/) 8 | * [Hosts](hosts/) 9 | -------------------------------------------------------------------------------- /examples/javascript/README.md: -------------------------------------------------------------------------------- 1 | JavaScript Examples 2 | ------------------- 3 | 4 | * [Custom Functions](functions.yaml) 5 | * [Custom Constraints](constraints.yaml) 6 | * [Custom Converters](converters.yaml) 7 | * [Execution](exec.yaml) 8 | * [Redefining Functions](define.yaml) 9 | * [Artifacts](artifacts.yaml) 10 | -------------------------------------------------------------------------------- /scripts/test-archive-url: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-csars" 8 | 9 | puccini-tosca compile "tar:$ROOT/dist/cloud.tar.gz!main.yaml" "$@" 10 | puccini-tosca compile "zip:$ROOT/dist/cloud.csar!main.yaml" "$@" 11 | -------------------------------------------------------------------------------- /executables/puccini-csar/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/util" 5 | "github.com/tliron/go-puccini/executables/puccini-csar/commands" 6 | 7 | _ "github.com/tliron/commonlog/simple" 8 | ) 9 | 10 | func main() { 11 | util.ExitOnSignals() 12 | commands.Execute() 13 | util.Exit(0) 14 | } 15 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/get_operation_output.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.6.1 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 4.6.1 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 4.6.1 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 4.6.1 6 | 7 | exports.evaluate = function() { 8 | return 'TODO'; 9 | }; 10 | -------------------------------------------------------------------------------- /executables/puccini-clout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/util" 5 | "github.com/tliron/go-puccini/executables/puccini-clout/commands" 6 | 7 | _ "github.com/tliron/commonlog/simple" 8 | ) 9 | 10 | func main() { 11 | util.ExitOnSignals() 12 | commands.Execute() 13 | util.Exit(0) 14 | } 15 | -------------------------------------------------------------------------------- /executables/puccini-tosca/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/util" 5 | "github.com/tliron/go-puccini/executables/puccini-tosca/commands" 6 | 7 | _ "github.com/tliron/commonlog/simple" 8 | ) 9 | 10 | func main() { 11 | util.ExitOnSignals() 12 | commands.Execute() 13 | util.Exit(0) 14 | } 15 | -------------------------------------------------------------------------------- /tosca/csar/format.go: -------------------------------------------------------------------------------- 1 | package csar 2 | 3 | import ( 4 | "github.com/tliron/exturl" 5 | ) 6 | 7 | func IsValidFormat(format string) bool { 8 | if exturl.IsValidTarballArchiveFormat(format) { 9 | return true 10 | } 11 | 12 | switch format { 13 | case "zip", "csar": 14 | return true 15 | } 16 | 17 | return false 18 | } 19 | -------------------------------------------------------------------------------- /scripts/test-ruby: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-wrapper-ruby" 8 | 9 | gem install "$ROOT/dist/puccini.gem" 10 | 11 | "$ROOT/examples/ruby/compile.rb" "$ROOT/examples/1.3/inputs-and-outputs.yaml" --input=ram=1gib --input=cores=6 "$@" 12 | -------------------------------------------------------------------------------- /tosca/parser/phase6-normalization.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/normal" 5 | ) 6 | 7 | func (self *Context) Normalize() (*normal.ServiceTemplate, bool) { 8 | self.Parser.lock.Lock() 9 | defer self.Parser.lock.Unlock() 10 | 11 | return normal.NormalizeServiceTemplate(self.Root.EntityPtr) 12 | } 13 | -------------------------------------------------------------------------------- /executables/puccini-clout/commands/scriptlet.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCommand.AddCommand(scriptletCommand) 9 | } 10 | 11 | var scriptletCommand = &cobra.Command{ 12 | Use: "scriptlet", 13 | Short: "Manage and process JavaScript scriptlets for Clout", 14 | } 15 | -------------------------------------------------------------------------------- /scripts/test-csar-http-url: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-csars" 8 | 9 | . "$HERE/_start-http-server" 10 | 11 | puccini-tosca compile "http://localhost:8000/cloud.tar.gz" "$@" 12 | puccini-tosca compile "http://localhost:8000/cloud.csar" "$@" 13 | -------------------------------------------------------------------------------- /tosca/parser/inputs.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | func (self *Context) SetInputs(inputs map[string]ard.Value) { 9 | self.Parser.lock.Lock() 10 | defer self.Parser.lock.Unlock() 11 | 12 | parsing.SetInputs(self.Root.EntityPtr, inputs) 13 | } 14 | -------------------------------------------------------------------------------- /tosca/grammars/common.go: -------------------------------------------------------------------------------- 1 | package grammars 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // Map of keyword -> version -> grammar 8 | var Grammars = make(map[string]map[string]*parsing.Grammar) 9 | 10 | // Map of keyword -> version -> internal URL path 11 | var ImplicitProfilePaths = make(map[string]map[string]string) 12 | -------------------------------------------------------------------------------- /wrappers/java/java/src/main/java/cloud/puccini/Problems.java: -------------------------------------------------------------------------------- 1 | package cloud.puccini; 2 | 3 | import java.util.List; 4 | 5 | public class Problems extends Exception 6 | { 7 | public List problems; 8 | 9 | public Problems( List problems ) 10 | { 11 | super( "problems" ); 12 | this.problems = problems; 13 | } 14 | } -------------------------------------------------------------------------------- /examples/csar/main.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: CSAR Main Example 6 | template_author: Puccini 7 | 8 | imports: 9 | 10 | - namespace_prefix: cloud 11 | file: cloud/profile.yaml 12 | 13 | topology_template: 14 | 15 | node_templates: 16 | 17 | main_server: 18 | type: cloud:Server 19 | -------------------------------------------------------------------------------- /scripts/test-visualize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | puccini-tosca compile "$ROOT/examples/1.3/requirements-and-capabilities.yaml" \ 10 | --exec="$ROOT/assets/profiles/common/1.0/js/visualize.js" > /tmp/puccini.html && \ 11 | xdg-open /tmp/puccini.html 12 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/$get_target_name.js: -------------------------------------------------------------------------------- 1 | 2 | const tosca = require('tosca.lib.utils'); 3 | 4 | exports.evaluate = function() { 5 | if (!this || !this.target) 6 | throw 'TARGET cannot be used in this context'; 7 | if (!tosca.isNodeTemplate(this.target)) 8 | throw 'TARGET is not a node template'; 9 | return this.target.properties.name; 10 | }; 11 | -------------------------------------------------------------------------------- /examples/csar/other 1.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: CSAR Other 1 Example 6 | template_author: Puccini 7 | 8 | imports: 9 | 10 | - namespace_prefix: cloud 11 | file: cloud/profile.yaml 12 | 13 | topology_template: 14 | 15 | node_templates: 16 | 17 | other_server: 18 | type: cloud:Server 19 | -------------------------------------------------------------------------------- /examples/csar/other 2.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: CSAR Other 2 Example 6 | template_author: Puccini 7 | 8 | imports: 9 | 10 | - namespace_prefix: cloud 11 | file: cloud/profile.yaml 12 | 13 | topology_template: 14 | 15 | node_templates: 16 | 17 | other_server: 18 | type: cloud:Server 19 | -------------------------------------------------------------------------------- /scripts/_env: -------------------------------------------------------------------------------- 1 | 2 | _HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 3 | 4 | . "$_HERE/_functions" 5 | 6 | MODULE=github.com/tliron/go-puccini 7 | 8 | ROOT=$(readlink --canonicalize "$_HERE/..") 9 | 10 | GOPATH=${GOPATH:-$HOME/go} 11 | export PATH=$GOPATH/bin:$ROOT:$PATH 12 | 13 | export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java} 14 | 15 | ANSIBLE_VERSION=11.7.0 16 | -------------------------------------------------------------------------------- /scripts/test-archive-url-http: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-csars" 8 | 9 | . "$HERE/_start-http-server" 10 | 11 | puccini-tosca compile "tar:http://localhost:8000/cloud.tar.gz!main.yaml" "$@" 12 | puccini-tosca compile "zip:http://localhost:8000/cloud.csar!main.yaml" "$@" 13 | -------------------------------------------------------------------------------- /tosca/parsing/rendering.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | // 4 | // Renderable 5 | // 6 | 7 | type Renderable interface { 8 | Render() 9 | } 10 | 11 | // From [Renderable] interface 12 | func Render(entityPtr EntityPtr) bool { 13 | if renderable, ok := entityPtr.(Renderable); ok { 14 | renderable.Render() 15 | return true 16 | } else { 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/ansible/hosts/tosca.yaml: -------------------------------------------------------------------------------- 1 | # This file must be named "tosca.yaml" or "tosca.yml" 2 | services: 3 | - name: myservice # the (optional) group name for this service's hosts 4 | template: ../service.yaml 5 | inputs: 6 | ram: 1 gib 7 | # (optional) filtering by node and/or capability types 8 | #node_types: 9 | #- tosca::Abstract.Compute 10 | capability_types: 11 | - tosca::Container 12 | -------------------------------------------------------------------------------- /scripts/_start-http-server: -------------------------------------------------------------------------------- 1 | 2 | pushd "$ROOT/dist" > /dev/null 3 | python3 -m http.server 8000 & 4 | HTTP_SERVER_PID=$! 5 | popd > /dev/null 6 | 7 | sleep 2 8 | if ! kill -0 "$HTTP_SERVER_PID" 2> /dev/null; then 9 | m 'Cannot start web server' "$RED" 10 | exit 1 11 | fi 12 | 13 | function the_end () { 14 | local ERR=$? 15 | kill "$HTTP_SERVER_PID" 16 | exit $ERR 17 | } 18 | 19 | trap the_end EXIT 20 | -------------------------------------------------------------------------------- /scripts/test-python: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | # Test sdist by default 8 | FLAG=${1:--s} 9 | 10 | SUFFIX=$SUFFIX "$HERE/build-wrapper-python" "$FLAG" 11 | . "$ROOT/dist/python-env/bin/activate" 12 | 13 | "$ROOT/examples/python/compile.py" "$ROOT/examples/1.3/inputs-and-outputs.yaml" --input=ram=1gib --input=cores=6 14 | -------------------------------------------------------------------------------- /scripts/test-ansible: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | VENV=$ROOT/dist/python-env 8 | 9 | SUFFIX=$SUFFIX "$HERE/build-wrapper-ansible" 10 | 11 | . "$VENV/bin/activate" 12 | 13 | cd "$ROOT/examples/ansible/tasks" 14 | ansible-playbook playbook.yaml 15 | 16 | # cd "$ROOT/examples/ansible/hosts" 17 | # ansible-playbook playbook.yaml 18 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v2_0/data-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v2_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/normal" 5 | ) 6 | 7 | // 8 | // DataDefinition 9 | // 10 | 11 | type DataDefinition interface { 12 | ToValueMeta() *normal.ValueMeta 13 | GetDescription() string 14 | GetTypeMetadata() Metadata 15 | GetValidationClause() *ValidationClause 16 | GetKeySchema() *Schema 17 | GetEntrySchema() *Schema 18 | } 19 | -------------------------------------------------------------------------------- /scripts/build-wrapper-ruby: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-library" 8 | 9 | cp "$ROOT/dist/libpuccini.so" "$ROOT/wrappers/ruby/lib/" 10 | 11 | git_version 12 | 13 | PUCCINI_VERSION=${SHORT_VERSION:1} \ 14 | gem -C "$ROOT/wrappers/ruby" build \ 15 | --output ../../dist/puccini.gem \ 16 | "$ROOT/wrappers/ruby/puccini.gemspec" 17 | -------------------------------------------------------------------------------- /examples/ansible/hosts/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | ansible_managed=Modified by Ansible on %Y-%m-%d %H:%M:%S %Z 3 | inventory=./inventory.yaml, ./tosca.yaml 4 | 5 | # This is for testing; usually you would use "ansible-galaxy collection install puccini.tosca" 6 | collections_path=../../../wrappers/ansible 7 | 8 | [inventory] 9 | # "puccini.tosca.nodes" should come before "yaml" if you want to include both 10 | enable_plugins=puccini.tosca.nodes, yaml 11 | -------------------------------------------------------------------------------- /examples/2.0/imports/nginx.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | imports: 4 | - url: https://raw.githubusercontent.com/xDaryamo/tosca-community-contributions/refs/heads/master/profiles/org/oasis-open/simple/2.0/profile.yaml 5 | namespace: tosca 6 | 7 | metadata: 8 | 9 | template_name: NGINX Example 10 | template_author: Puccini 11 | 12 | profile: nginx 13 | 14 | node_types: 15 | 16 | NginX: 17 | derived_from: tosca:WebServer 18 | -------------------------------------------------------------------------------- /examples/2.0/imports/mongodb.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | imports: 4 | - url: https://raw.githubusercontent.com/xDaryamo/tosca-community-contributions/refs/heads/master/profiles/org/oasis-open/simple/2.0/profile.yaml 5 | namespace: tosca 6 | 7 | metadata: 8 | 9 | template_name: MongoDB Example 10 | template_author: Puccini 11 | 12 | profile: mongodb 13 | 14 | node_types: 15 | 16 | MongoDB: 17 | derived_from: tosca:DBMS 18 | -------------------------------------------------------------------------------- /scripts/test-csar: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-csars" 8 | 9 | function t () { 10 | puccini-tosca compile "$ROOT/dist/cloud.$1" 11 | puccini-tosca compile "$ROOT/dist/cloud.$1" --template='other 1.yaml' 12 | puccini-tosca compile "$ROOT/dist/cloud.$1" --template=2 13 | puccini-csar meta "$ROOT/dist/cloud.$1" 14 | } 15 | 16 | t tar.gz 17 | t csar 18 | -------------------------------------------------------------------------------- /tosca/parsing/maps.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // 8 | // Mappable 9 | // 10 | 11 | type Mappable interface { 12 | GetKey() string 13 | } 14 | 15 | // From Mappable interface 16 | func GetKey(entityPtr EntityPtr) string { 17 | if mappable, ok := entityPtr.(Mappable); ok { 18 | return mappable.GetKey() 19 | } else { 20 | panic(fmt.Sprintf("entity does not implement \"Mappable\" interface: %T", entityPtr)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/$format.js: -------------------------------------------------------------------------------- 1 | 2 | exports.validate = function(v, format) { 3 | if (arguments.length !== 2) 4 | throw 'must have 1 argument'; 5 | if (!util.isType(v, 'ard.string')) 6 | return 'not a string'; 7 | try { 8 | ard.validateFormat(v, format); 9 | } catch (x) { 10 | if (x.value && x.value.error) 11 | // Unwrap Go error 12 | return x.value.error(); 13 | else 14 | throw x; 15 | } 16 | return true; 17 | }; 18 | -------------------------------------------------------------------------------- /scripts/test-formats: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | function t () { 10 | m "testing format: $1..." 11 | puccini-tosca compile --format="$1" "$ROOT/examples/1.3/data-types.yaml" | 12 | puccini-clout --input-format="$1" --format="$1" scriptlet exec tosca.coerce > /dev/null 13 | } 14 | 15 | t yaml 16 | t json 17 | t xjson 18 | t xml 19 | t cbor 20 | t messagepack 21 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/pattern.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 6 | 7 | exports.validate = function(v, re) { 8 | if (arguments.length !== 2) 9 | throw 'must have 1 argument'; 10 | if (v.$string !== undefined) 11 | v = v.$string; 12 | return new RegExp('^' + re + '$').test(v); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/dgraph/README.md: -------------------------------------------------------------------------------- 1 | Dgraph Example 2 | -------------- 3 | 4 | [Dgraph](https://dgraph.io/) natively uses a variation of the 5 | [RDF N-Quads format](https://www.w3.org/TR/n-quads/), however it also supports 6 | [JSON for mutations](https://dgraph.io/docs/mutations/json-mutation-format/), which is easier 7 | for us to work with in JavaScript. 8 | 9 | You can visualize your results using Ratel, Dgraph's web GUI, at 10 | [`http://localhost:8000/`](http://localhost:8000/). 11 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/type.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // Type 9 | // 10 | 11 | type Type struct { 12 | *Entity `json:"-" yaml:"-"` 13 | Name string `namespace:""` 14 | 15 | ParentName *string `read:"derived_from"` 16 | } 17 | 18 | func NewType(context *parsing.Context) *Type { 19 | return &Type{ 20 | Entity: NewEntity(context), 21 | Name: context.Name, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/get_property.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v2.0] @ 10.2.1.2 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.4.2 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 4.4.2 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 4.4.2 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 4.4.2 6 | 7 | const tosca = require('tosca.lib.utils'); 8 | 9 | exports.evaluate = function() { 10 | return tosca.getNestedValue.call(this, 'property', 'properties', arguments); 11 | }; 12 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/group.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v1_2" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Group 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.5 12 | // 13 | 14 | // ([parsing.Reader] signature) 15 | func ReadGroup(context *parsing.Context) parsing.EntityPtr { 16 | context.SetReadTag("Metadata", "") 17 | 18 | return tosca_v1_2.ReadGroup(context) 19 | } 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.3/groups.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | imports: 4 | 5 | - interfaces.yaml 6 | 7 | group_types: 8 | 9 | tosca.groups.Root: 10 | metadata: 11 | tosca.normative: 'true' 12 | specification.citation: '[TOSCA-Simple-Profile-YAML-v1.3]' 13 | specification.location: 5.10.1 14 | description: >- 15 | This is the default (root) TOSCA Group Type definition that all other TOSCA base Group Types 16 | derive from. 17 | -------------------------------------------------------------------------------- /clout/util/list.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | ) 6 | 7 | func NewList(entryType string, values ard.List) ard.StringMap { 8 | return ard.StringMap{ 9 | "$type": NewListType(entryType), 10 | "$list": values, 11 | } 12 | } 13 | 14 | func NewListType(entryType string) ard.StringMap { 15 | return ard.StringMap{ 16 | "type": ard.StringMap{"name": "list"}, 17 | "entry": ard.StringMap{ 18 | "type": ard.StringMap{"name": entryType}, 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build" 8 | 9 | # -count=1 is the idiomatic way to disable test caching 10 | # GORACE=history_size=7 is the maximum, only affects "-race" 11 | 12 | m 'testing...' 13 | 14 | cd "$ROOT" 15 | 16 | PUCCINI_TEST_ROOT=$ROOT \ 17 | KUTIL_LOCK_DEFAULT=debug \ 18 | KUTIL_LOCK_DEBUG_ORDER_DETECTION=true \ 19 | GORACE=history_size=7 \ 20 | go test -count=1 -timeout=30s . "$@" 21 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/node-template.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v1_3" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // NodeTemplate 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.3 12 | // 13 | 14 | // ([parsing.Reader] signature) 15 | func ReadNodeTemplate(context *parsing.Context) parsing.EntityPtr { 16 | context.SetReadTag("Metadata", "") 17 | 18 | return tosca_v1_3.ReadNodeTemplate(context) 19 | } 20 | -------------------------------------------------------------------------------- /scripts/test-java: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-wrapper-java" 8 | 9 | git_version 10 | 11 | # Build 12 | mvn --file "$ROOT/examples/java" --define puccini.version=${VERSION:1} 13 | 14 | # Run 15 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROOT/dist \ 16 | mvn --quiet --file "$ROOT/examples/java" \ 17 | exec:java --define exec.args="$ROOT/examples/1.3/inputs-and-outputs.yaml --input=ram=1gib --input=cores=6" --errors 18 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/policy-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // PolicyType 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.6.11 12 | // 13 | 14 | // ([parsing.Reader] signature) 15 | func ReadPolicyType(context *parsing.Context) parsing.EntityPtr { 16 | context.SetReadTag("TriggerDefinitions", "") 17 | 18 | return tosca_v2_0.ReadPolicyType(context) 19 | } 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/constraints/modulo.js: -------------------------------------------------------------------------------- 1 | 2 | const tosca = require('tosca.lib.utils'); 3 | 4 | exports.validate = function(v, rules) { 5 | if (arguments.length !== 2) 6 | throw 'must have 1 arguments'; 7 | if ((rules.step === undefined) || (rules.offset === undefined)) 8 | throw 'must provide "step" and "offset"'; 9 | v = tosca.getComparable(v); 10 | let step = tosca.getComparable(rules.step); 11 | let offset = tosca.getComparable(rules.offset); 12 | return value % self.step == self.offset; 13 | }; 14 | -------------------------------------------------------------------------------- /examples/java/README.md: -------------------------------------------------------------------------------- 1 | Java Example 2 | ============ 3 | 4 | This relies on the [Java wrapper](../../wrappers/java/), so make sure to install that first. 5 | 6 | To build this example using [Maven](https://maven.apache.org/): 7 | 8 | mvn --file examples/java 9 | 10 | To run this example using Maven we need to make sure that the JVM process can load the shared 11 | libraries: 12 | 13 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:dist mvn --quiet --file examples/java exec:java --define exec.args=examples/1.3/data-types.yaml 14 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/list_join.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#list_join] 3 | 4 | exports.evaluate = function() { 5 | let length = arguments.length; 6 | if (length < 1) 7 | throw 'must have at least 1 arguments'; 8 | let a = []; 9 | for (let i = 1; i < length; i++) { 10 | let argument = arguments[i]; 11 | if (argument.$string !== undefined) 12 | argument = argument.$string; 13 | a.push(argument); 14 | } 15 | return a.join(arguments[0]); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/bpmn/profile/policies.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | policy_types: 4 | 5 | Base: 6 | description: >- 7 | Root for policies implemented by BPM software. 8 | 9 | Process: 10 | description: >- 11 | Policy implemented by a process defined in BPMN. 12 | derived_from: Base 13 | properties: 14 | bpmn_process_id: 15 | description: >- 16 | Execute this BPMN process when triggered. 17 | type: string 18 | targets: 19 | - tosca:Root 20 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/service-template.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // ServiceTemplate 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.8 12 | // 13 | 14 | // ([parsing.Reader] signature) 15 | func ReadServiceTemplate(context *parsing.Context) parsing.EntityPtr { 16 | context.SetReadTag("WorkflowDefinitions", "") 17 | 18 | return tosca_v2_0.ReadServiceTemplate(context) 19 | } 20 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/artifact.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Artifact 10 | // 11 | 12 | // ([parsing.Reader] signature) 13 | func ReadArtifact(context *parsing.Context) parsing.EntityPtr { 14 | context.SetReadTag("ArtifactVersion", "") 15 | context.SetReadTag("ChecksumAlgorithm", "") 16 | context.SetReadTag("Checksum", "") 17 | 18 | return tosca_v2_0.ReadArtifact(context) 19 | } 20 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/policy.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Policy 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.6 12 | // 13 | 14 | // ([parsing.Reader] signature) 15 | func ReadPolicy(context *parsing.Context) parsing.EntityPtr { 16 | context.SetReadTag("Metadata", "") 17 | context.SetReadTag("TriggerDefinitions", "") 18 | 19 | return tosca_v2_0.ReadPolicy(context) 20 | } 21 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/constraints/length.js: -------------------------------------------------------------------------------- 1 | 2 | exports.validate = function(v, limits) { 3 | if (arguments.length !== 2) 4 | throw 'must have 1 argument'; 5 | if ((limits.min === undefined) && (limits.max === undefined)) 6 | throw 'must provide "min" and/or "max"'; 7 | if (v.$string !== undefined) 8 | v = v.$string; 9 | if (limits.min !== undefined) 10 | if (v.length < limits.min) 11 | return false; 12 | if (limits.max !== undefined) 13 | if (v.length > limits.max) 14 | return false; 15 | return true; 16 | }; 17 | -------------------------------------------------------------------------------- /normal/README.md: -------------------------------------------------------------------------------- 1 | Normal 2 | ====== 3 | 4 | These structs together comprise a "normalized" TOSCA-compatible topology, which is a flat, 5 | serializable data structure. 6 | 7 | They are meant to be generic and independent of TOSCA grammar, especially any specific version of 8 | the TOSCA grammar. 9 | 10 | Though they are usually created by a TOSCA parser as its final result, it's entirely possible to 11 | write code that uses a different approach, for example by translating from a non-TOSCA topology 12 | descriptor into the normal structures. 13 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/relationship-template.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // RelationshipTemplate 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.4 12 | // 13 | 14 | // ([parsing.Reader] signature) 15 | func ReadRelationshipTemplate(context *parsing.Context) parsing.EntityPtr { 16 | context.SetReadTag("Metadata", "") 17 | 18 | return tosca_v2_0.ReadRelationshipTemplate(context) 19 | } 20 | -------------------------------------------------------------------------------- /clout/read.go: -------------------------------------------------------------------------------- 1 | package clout 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/tliron/go-ard" 8 | ) 9 | 10 | func Read(reader io.Reader, format string) (*Clout, error) { 11 | if data, _, err := ard.Read(reader, format, false); err == nil { 12 | if map_, ok := data.(ard.Map); ok { 13 | if clout, err := Unpack(map_); err == nil { 14 | return clout, nil 15 | } else { 16 | return nil, err 17 | } 18 | } else { 19 | return nil, fmt.Errorf("not a map: %T", data) 20 | } 21 | } else { 22 | return nil, err 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tosca/grammars/hot/entity.go: -------------------------------------------------------------------------------- 1 | package hot 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // Entity 11 | // 12 | 13 | type Entity struct { 14 | Context *parsing.Context `traverse:"ignore" json:"-" yaml:"-"` 15 | 16 | renderOnce sync.Once 17 | } 18 | 19 | func NewEntity(context *parsing.Context) *Entity { 20 | return &Entity{ 21 | Context: context, 22 | } 23 | } 24 | 25 | // ([parsing.Contextual] interface) 26 | func (self *Entity) GetContext() *parsing.Context { 27 | return self.Context 28 | } 29 | -------------------------------------------------------------------------------- /clout/js/clout-context.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | import ( 4 | "github.com/tliron/commonjs-goja" 5 | cloutpkg "github.com/tliron/go-puccini/clout" 6 | ) 7 | 8 | // 9 | // CloutContext 10 | // 11 | 12 | type CloutContext struct { 13 | Context *Environment 14 | Clout *cloutpkg.Clout 15 | JSContext *commonjs.Context 16 | } 17 | 18 | func (self *Environment) NewCloutContext(clout *cloutpkg.Clout, jsContext *commonjs.Context) *CloutContext { 19 | return &CloutContext{ 20 | Context: self, 21 | Clout: clout, 22 | JSContext: jsContext, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tosca/grammars/hot/data.go: -------------------------------------------------------------------------------- 1 | package hot 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Data 10 | // 11 | 12 | type Data struct { 13 | *Entity `name:"data"` 14 | 15 | Data ard.Value 16 | } 17 | 18 | func NewData(context *parsing.Context) *Data { 19 | return &Data{ 20 | Entity: NewEntity(context), 21 | Data: context.Data, 22 | } 23 | } 24 | 25 | // ([parsing.Reader] signature) 26 | func ReadData(context *parsing.Context) parsing.EntityPtr { 27 | return NewData(context) 28 | } 29 | -------------------------------------------------------------------------------- /tosca/parser/phase5-rendering.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | func (self *Context) Render() parsing.EntityPtrs { 8 | self.Parser.lock.Lock() 9 | defer self.Parser.lock.Unlock() 10 | 11 | var entityPtrs parsing.EntityPtrs 12 | 13 | self.Parser.renderWork.TraverseEntities(self.Root.EntityPtr, func(entityPtr parsing.EntityPtr) bool { 14 | if parsing.Render(entityPtr) { 15 | entityPtrs = append(entityPtrs, entityPtr) 16 | } 17 | return true 18 | }) 19 | 20 | return entityPtrs 21 | } 22 | -------------------------------------------------------------------------------- /normal/value.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // Value 5 | // 6 | 7 | type Value interface { 8 | SetKey(Value) 9 | SetMeta(*ValueMeta) 10 | } 11 | 12 | // 13 | // Values 14 | // 15 | 16 | type Values map[string]Value 17 | 18 | // 19 | // ValueList 20 | // 21 | 22 | type ValueList []Value 23 | 24 | func (self ValueList) AppendWithKey(key any, value Value) ValueList { 25 | var key_ Value 26 | 27 | var ok bool 28 | if key_, ok = key.(Value); !ok { 29 | key_ = NewPrimitive(key) 30 | } 31 | 32 | value.SetKey(key_) 33 | 34 | return append(self, value) 35 | } 36 | -------------------------------------------------------------------------------- /wrappers/ansible/README.md: -------------------------------------------------------------------------------- 1 | Ansible Wrapper for Puccini 2 | =========================== 3 | 4 | An [Ansible](https://www.ansible.com/) collection, [packaged for Ansible Galaxy](https://galaxy.ansible.com/puccini/tosca). 5 | 6 | This relies on the [Python wrapper](../../wrappers/python/), so make sure to install that first. 7 | 8 | The latest stable version is published on [Ansible Galaxy](https://galaxy.ansible.com/puccini/tosca). To install: 9 | 10 | ansible-galaxy collection install puccini.tosca 11 | 12 | Also see: [Ansible examples](../../examples/ansible/). 13 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v2_0/entity.go: -------------------------------------------------------------------------------- 1 | package tosca_v2_0 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // Entity 11 | // 12 | 13 | type Entity struct { 14 | Context *parsing.Context `traverse:"ignore" json:"-" yaml:"-"` 15 | 16 | renderOnce sync.Once 17 | } 18 | 19 | func NewEntity(context *parsing.Context) *Entity { 20 | return &Entity{ 21 | Context: context, 22 | } 23 | } 24 | 25 | // ([parsing.Contextual] interface) 26 | func (self *Entity) GetContext() *parsing.Context { 27 | return self.Context 28 | } 29 | -------------------------------------------------------------------------------- /wrappers/ansible/ansible_collections/puccini/tosca/galaxy.yml: -------------------------------------------------------------------------------- 1 | # https://docs.ansible.com/ansible/latest/dev_guide/collections_galaxy_meta.html#collections-galaxy-meta 2 | namespace: puccini 3 | name: tosca 4 | version: 0.0.2 5 | readme: README.md 6 | authors: 7 | - Puccini 8 | 9 | license: Apache-2.0 10 | homepage: https://puccini.cloud 11 | repository: https://github.com/tliron/go-puccini 12 | issues: https://github.com/tliron/go-puccini/issues 13 | 14 | description: Deliberately stateless cloud topology management and deployment tools based on TOSCA 15 | tags: 16 | - tosca 17 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/less_than.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function(currentPropertyValue) { 9 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 10 | if (!parsed) { 11 | return false; 12 | } 13 | 14 | return tosca.compare(parsed.val1, parsed.val2) < 0; 15 | }; -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/entity.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // Entity 11 | // 12 | 13 | type Entity struct { 14 | Context *parsing.Context `traverse:"ignore" json:"-" yaml:"-"` 15 | 16 | renderOnce sync.Once 17 | } 18 | 19 | func NewEntity(context *parsing.Context) *Entity { 20 | return &Entity{ 21 | Context: context, 22 | } 23 | } 24 | 25 | // ([parsing.Contextual] interface) 26 | func (self *Entity) GetContext() *parsing.Context { 27 | return self.Context 28 | } 29 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/greater_or_equal.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function(currentPropertyValue) { 9 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 10 | if (!parsed) { 11 | return false; 12 | } 13 | 14 | return tosca.compare(parsed.val1, parsed.val2) >= 0; 15 | }; -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/greater_than.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function(currentPropertyValue) { 9 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 10 | if (!parsed) { 11 | return false; 12 | } 13 | 14 | return tosca.compare(parsed.val1, parsed.val2) > 0; 15 | }; 16 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/less_or_equal.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function(currentPropertyValue) { 9 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 10 | if (!parsed) { 11 | return false; 12 | } 13 | 14 | return tosca.compare(parsed.val1, parsed.val2) <= 0; 15 | }; -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | Grammars 5 | -------- 6 | 7 | * [TOSCA](tosca/) 8 | * [HOT](hot/) 9 | * [Cloudify DSL](cloudify/) 10 | 11 | Profiles 12 | -------- 13 | 14 | * [Kubernetes Profile](kubernetes/) 15 | * [OpenStack Profile](openstack/) 16 | * [BPMN Profile](bpmn/) 17 | 18 | Features 19 | -------- 20 | 21 | * [JavaScript](javascript/) 22 | 23 | Wrappers 24 | -------- 25 | 26 | * [Java](java/) 27 | * [Python](python/) 28 | * [Ruby](ruby/) 29 | * [Ansible](ansible/) 30 | 31 | Storage 32 | ------- 33 | 34 | * [Neo4j](neo4j/) 35 | * [Dgraph](dgraph/) 36 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/functions/get_param.js: -------------------------------------------------------------------------------- 1 | 2 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#get_param] 3 | 4 | const tosca = require('tosca.lib.utils'); 5 | 6 | exports.evaluate = function(input) { 7 | if (arguments.length !== 1) 8 | throw 'must have 1 argument'; 9 | if (!tosca.isTosca(clout)) 10 | throw 'Clout is not TOSCA'; 11 | let inputs = clout.properties.tosca.inputs; 12 | if (!(input in inputs)) 13 | throw util.sprintf('parameter %q not found', input); 14 | let r = inputs[input]; 15 | r = clout.coerce(r); 16 | return r; 17 | }; 18 | -------------------------------------------------------------------------------- /assets/tosca/profiles/hot/1.0/js/constraints/range.js: -------------------------------------------------------------------------------- 1 | 2 | const tosca = require('tosca.lib.utils'); 3 | 4 | exports.validate = function(v, bounds) { 5 | if (arguments.length !== 2) 6 | throw 'must have 1 arguments'; 7 | if ((bounds.min === undefined) && (bounds.max === undefined)) 8 | throw 'must provide "min" and/or "max"'; 9 | v = tosca.getComparable(v); 10 | if (bounds.min !== undefined) 11 | if (tosca.compare(v, bounds.min) < 0) 12 | return false; 13 | if (bounds.max !== undefined) 14 | if (tosca.compare(v, bounds.max) > 0) 15 | return false; 16 | return true; 17 | }; 18 | -------------------------------------------------------------------------------- /tosca/parser/common.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | var log = commonlog.GetLogger("puccini.parser") 8 | var logRead = commonlog.NewScopeLogger(log, "read") 9 | var logNamespaces = commonlog.NewScopeLogger(log, "namespaces") 10 | var logLookup = commonlog.NewScopeLogger(log, "lookup") 11 | var logHierarchies = commonlog.NewScopeLogger(log, "hierarchies") 12 | var logInheritance = commonlog.NewScopeLogger(log, "inheritance") 13 | var logTasks = commonlog.NewScopeLogger(log, "tasks") 14 | var logGather = commonlog.NewScopeLogger(log, "gather") 15 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.0/groups.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_0 2 | 3 | imports: 4 | 5 | - interfaces.yaml 6 | 7 | group_types: 8 | 9 | tosca.groups.Root: 10 | metadata: 11 | tosca.normative: 'true' 12 | specification.citation: '[TOSCA-Simple-Profile-YAML-v1.0]' 13 | specification.location: 5.9.1 14 | description: >- 15 | This is the default (root) TOSCA Group Type definition that all other TOSCA base Group Types 16 | derive from. 17 | interfaces: 18 | Standard: 19 | type: tosca.interfaces.node.lifecycle.Standard 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.1/groups.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_1 2 | 3 | imports: 4 | 5 | - interfaces.yaml 6 | 7 | group_types: 8 | 9 | tosca.groups.Root: 10 | metadata: 11 | tosca.normative: 'true' 12 | specification.citation: '[TOSCA-Simple-Profile-YAML-v1.1]' 13 | specification.location: 5.10.1 14 | description: >- 15 | This is the default (root) TOSCA Group Type definition that all other TOSCA base Group Types 16 | derive from. 17 | interfaces: 18 | Standard: 19 | type: tosca.interfaces.node.lifecycle.Standard 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.2/groups.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_2 2 | 3 | imports: 4 | 5 | - interfaces.yaml 6 | 7 | group_types: 8 | 9 | tosca.groups.Root: 10 | metadata: 11 | tosca.normative: 'true' 12 | specification.citation: '[TOSCA-Simple-Profile-YAML-v1.2]' 13 | specification.location: 5.10.1 14 | description: >- 15 | This is the default (root) TOSCA Group Type definition that all other TOSCA base Group Types 16 | derive from. 17 | interfaces: 18 | Standard: 19 | type: tosca.interfaces.node.lifecycle.Standard 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/join.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.3.2 3 | 4 | exports.evaluate = function() { 5 | let length = arguments.length; 6 | if ((length < 1) || (length > 2)) 7 | throw 'must have 1 or 2 arguments'; 8 | let delimiter = (length == 2) ? arguments[1] : ''; 9 | let args = arguments[0]; 10 | length = args.length; 11 | let a = []; 12 | for (let i = 0; i < length; i++) { 13 | let argument = args[i]; 14 | if (argument.$string !== undefined) 15 | argument = argument.$string; 16 | a.push(argument); 17 | } 18 | return a.join(delimiter); 19 | }; 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:tosca.lib.utils: internal:/profiles/common/1.0/js/lib/utils.js 6 | puccini.scriptlet.import:tosca.lib.traversal: internal:/profiles/common/1.0/js/lib/traversal.js 7 | puccini.scriptlet.import:tosca.coerce: internal:/profiles/common/1.0/js/coerce.js 8 | puccini.scriptlet.import:tosca.outputs: internal:/profiles/common/1.0/js/outputs.js 9 | puccini.scriptlet.import:tosca.resolve: internal:/profiles/common/1.0/js/resolve.js 10 | 11 | imports: 12 | 13 | - data.yaml 14 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/group.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Group 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.8.5 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.7.5 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.5 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadGroup(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("Interfaces", "interfaces,InterfaceAssignment") 19 | 20 | return tosca_v2_0.ReadGroup(context) 21 | } 22 | -------------------------------------------------------------------------------- /wrappers/python/puccini/go.py: -------------------------------------------------------------------------------- 1 | 2 | import ctypes 3 | 4 | # See: https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf 5 | 6 | class GoString(ctypes.Structure): 7 | _fields_ = (('p', ctypes.c_char_p), ('n', ctypes.c_longlong)) 8 | 9 | def __init__(self, s): 10 | self.p = str.encode(s) 11 | self.n = len(s) 12 | 13 | def __str__(self): 14 | return self.p[:self.n].decode() if self.p else '' 15 | 16 | 17 | def to_c_char(v): 18 | return ctypes.c_char(v) 19 | 20 | 21 | def to_c_char_p(s): 22 | return ctypes.c_char_p(str.encode(s)) 23 | -------------------------------------------------------------------------------- /tosca/parsing/imports.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import "github.com/tliron/exturl" 4 | 5 | // 6 | // Importer 7 | // 8 | 9 | type Importer interface { 10 | GetImportSpecs() []*ImportSpec 11 | } 12 | 13 | // From [Importer] interface 14 | func GetImportSpecs(entityPtr EntityPtr) []*ImportSpec { 15 | if importer, ok := entityPtr.(Importer); ok { 16 | return importer.GetImportSpecs() 17 | } else { 18 | return nil 19 | } 20 | } 21 | 22 | // 23 | // ImportSpec 24 | // 25 | 26 | type ImportSpec struct { 27 | URL exturl.URL 28 | NameTransformer NameTransformer 29 | Implicit bool 30 | } 31 | -------------------------------------------------------------------------------- /examples/ansible/tasks/playbook.yaml: -------------------------------------------------------------------------------- 1 | - name: TOSCA tasks 2 | hosts: localhost 3 | gather_facts: no 4 | tasks: 5 | 6 | - name: Compile TOSCA service template 7 | puccini.tosca.compile: 8 | service_template: ../service.yaml 9 | inputs: 10 | ram: 1 gib 11 | register: service 12 | 13 | - name: Show Compute node templates 14 | loop: "{{ service.node_templates }}" 15 | loop_control: 16 | loop_var: node_template 17 | label: "{{ node_template.name }}" 18 | when: "'tosca::Abstract.Compute' in node_template.types" 19 | debug: 20 | msg: "{{ node_template }}" 21 | -------------------------------------------------------------------------------- /examples/openstack/scripts/install-ansible: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Requirements (Fedora) 5 | # sudo dnf install python3-virtualenv python3-libselinux 6 | 7 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 8 | . "$HERE/../../../scripts/_env" 9 | 10 | # virtualenv 11 | # (must use "--system-site-packages" so that Ansible can access localhost on SELinux!) 12 | python3 -m venv --system-site-packages "$ROOT/dist/python-env" 13 | . "$ROOT/dist/python-env/bin/activate" 14 | pip install --upgrade pip 15 | 16 | pip install \ 17 | ansible==3.0.0 \ 18 | os-client-config==2.1.0 \ 19 | rackspaceauth==0.8.1 20 | -------------------------------------------------------------------------------- /scripts/test-wasm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | "$HERE/build-wasm" 8 | 9 | if ! command -v node > /dev/null 2>&1; then 10 | m 'Node.js must be installed' "$RED" 11 | exit 1 12 | fi 13 | 14 | WASM_EXEC=$(go env GOROOT)/lib/wasm/go_js_wasm_exec 15 | 16 | function run () { 17 | local TOOL=$1 18 | env --ignore-environment "$WASM_EXEC" "$ROOT/dist/$TOOL.wasm" "${@:2}" 19 | } 20 | 21 | run puccini-tosca compile \ 22 | "$ROOT/examples/1.3/data-types.yaml" "$@" 23 | 24 | run puccini-tosca compile \ 25 | "$ROOT/dist/cloud.csar" "$@" 26 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/has_key.js: -------------------------------------------------------------------------------- 1 | // TOSCA 2.0 constraint: has_key 2 | 3 | const tosca = require('tosca.lib.utils'); 4 | 5 | exports.validate = function(currentPropertyValue) { 6 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 7 | if (!parsed) { 8 | return false; 9 | } 10 | 11 | const mapValue = parsed.val1; 12 | const keyToFind = parsed.val2; 13 | 14 | if (!mapValue || typeof mapValue !== 'object' || Array.isArray(mapValue)) { 15 | return false; 16 | } 17 | 18 | return mapValue.hasOwnProperty(keyToFind); 19 | }; -------------------------------------------------------------------------------- /examples/dgraph/export: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink -f "$0")") 5 | 6 | function c () { 7 | curl "http://localhost:8080/$1?commitNow=true" \ 8 | --header "Content-Type: application/$2" \ 9 | --request POST \ 10 | --data-binary "$3" \ 11 | --silent | python -m json.tool 12 | } 13 | 14 | puccini-tosca compile "$HERE/../tosca/requirements-and-capabilities.yaml" --exec="$HERE/dgraph.js" --output="$HERE/tosca.json" 15 | 16 | c alter json '{"drop_all": true}' 17 | c mutate json @"$HERE/tosca.json" 18 | c query dql '{ 19 | nodeTemplates(func: has(tosca.name)) { 20 | tosca.name 21 | } 22 | }' 23 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/get_nodes_of_type.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.7.1 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 4.7.1 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 4.7.1 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 4.7.1 6 | 7 | const tosca = require('tosca.lib.utils'); 8 | 9 | exports.evaluate = function(typeName) { 10 | if (arguments.length !== 1) 11 | throw 'must have 1 argument'; 12 | let names = []; 13 | for (let id in clout.vertexes) { 14 | let vertex = clout.vertexes[id]; 15 | if (tosca.isTosca(vertex)) 16 | names.push(vertex.properties.name); 17 | } 18 | return names; 19 | }; 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/token.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.3.3 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 4.3.3 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 4.3.2 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 4.3.2 6 | 7 | exports.evaluate = function(v, separators, index) { 8 | if (arguments.length !== 3) 9 | throw 'must have 3 arguments'; 10 | if (v.$string !== undefined) 11 | v = v.$string; 12 | let s = v.split(new RegExp('[' + escape(separators) + ']')); 13 | return s[index]; 14 | }; 15 | 16 | function escape(s) { 17 | return s.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); 18 | } 19 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/capability-assignment.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // CapabilityAssignment 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.8.1 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.7.1 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.1 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadCapabilityAssignment(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("Occurrences", "") 19 | 20 | return tosca_v2_0.ReadCapabilityAssignment(context) 21 | } 22 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/group-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // GroupType 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.7.11 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.6.11 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.6.10 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadGroupType(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("InterfaceDefinitions", "interfaces,InterfaceDefinition") 19 | 20 | return tosca_v2_0.ReadGroupType(context) 21 | } 22 | -------------------------------------------------------------------------------- /scripts/install-bash-completion: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # See: https://github.com/scop/bash-completion/blob/master/README.md 5 | 6 | USER_DIR=$BASH_COMPLETION_USER_DIR 7 | 8 | if [ -z "$USER_DIR" ]; then 9 | DATA_HOME=$XDG_DATA_HOME 10 | if [ -z "$DATA_HOME" ]; then 11 | DATA_HOME=~/.local/share 12 | fi 13 | USER_DIR=$DATA_HOME/bash-completion 14 | fi 15 | 16 | function c () { 17 | local NAME=$1 18 | "$NAME" completion bash > "$USER_DIR/completions/$NAME" 19 | } 20 | 21 | mkdir --parents "$USER_DIR/completions" 22 | 23 | c puccini-tosca 24 | c puccini-clout 25 | c puccini-csar 26 | 27 | if [ "$1" == -r ]; then 28 | reset 29 | fi 30 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_1/interface-implementation.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_1 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // InterfaceImplementation 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.13.2.3 12 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.13.2.3 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadInterfaceImplementation(context *parsing.Context) parsing.EntityPtr { 17 | context.SetReadTag("Timeout", "") 18 | context.SetReadTag("OperationHost", "") 19 | 20 | return tosca_v2_0.ReadInterfaceImplementation(context) 21 | } 22 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_1/substitution-mappings.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_1 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v1_2" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // SubstitutionMappings 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 2.10, 2.11 12 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 2.10, 2.11 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadSubstitutionMappings(context *parsing.Context) parsing.EntityPtr { 17 | context.SetReadTag("PropertyMappings", "") 18 | context.SetReadTag("InterfaceMappings", "") 19 | 20 | return tosca_v1_2.ReadSubstitutionMappings(context) 21 | } 22 | -------------------------------------------------------------------------------- /scripts/build-library: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$NOBUILD" == true ]; then 5 | exit 6 | fi 7 | 8 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 9 | . "$HERE/_env" 10 | 11 | git_version 12 | 13 | function build () { 14 | local TOOL=$1 15 | pushd "$ROOT/$TOOL" > /dev/null 16 | go build \ 17 | -buildmode=c-shared \ 18 | -o="$ROOT/dist/libpuccini.so" \ 19 | -ldflags " \ 20 | -X 'github.com/tliron/go-kutil/version.GitVersion=$VERSION' \ 21 | -X 'github.com/tliron/go-kutil/version.GitRevision=$REVISION' \ 22 | -X 'github.com/tliron/go-kutil/version.Timestamp=$TIMESTAMP'" 23 | popd > /dev/null 24 | } 25 | 26 | build library 27 | -------------------------------------------------------------------------------- /tosca/parser/traverse.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | "github.com/tliron/go-kutil/reflection" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | func (self *Context) TraverseEntities(log commonlog.Logger, work reflection.EntityWork, traverse reflection.EntityTraverser) { 10 | if work == nil { 11 | work = make(reflection.EntityWork) 12 | } 13 | 14 | // Root 15 | work.TraverseEntities(self.Root.EntityPtr, traverse) 16 | 17 | // Types 18 | self.Root.GetContext().Namespace.Range(func(entityPtr parsing.EntityPtr) bool { 19 | work.TraverseEntities(entityPtr, traverse) 20 | return true 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /clout/load.go: -------------------------------------------------------------------------------- 1 | package clout 2 | 3 | import ( 4 | contextpkg "context" 5 | 6 | "github.com/tliron/commonlog" 7 | "github.com/tliron/exturl" 8 | "github.com/tliron/go-kutil/util" 9 | ) 10 | 11 | func Load(context contextpkg.Context, url exturl.URL, forceFormat string) (*Clout, error) { 12 | if reader, err := url.Open(context); err == nil { 13 | reader = util.NewContextualReadCloser(context, reader) 14 | defer commonlog.CallAndLogWarning(reader.Close, "clout.Load", log) 15 | 16 | format := forceFormat 17 | for format == "" { 18 | format = url.Format() 19 | } 20 | 21 | return Read(reader, format) 22 | } else { 23 | return nil, err 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scripts/build-csars: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$NOBUILD" == true ]; then 5 | exit 6 | fi 7 | 8 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 9 | . "$HERE/_env" 10 | 11 | mkdir --parents "$ROOT/dist" 12 | 13 | puccini-csar create "$ROOT/dist/cloud.tar.gz" "$ROOT/examples/csar" \ 14 | --entry-definitions=main.yaml \ 15 | --other-definitions='other 1.yaml' \ 16 | --other-definitions='other 2.yaml' \ 17 | "$@" 18 | 19 | puccini-csar create "$ROOT/dist/cloud.csar" "$ROOT/examples/csar" \ 20 | --entry-definitions=main.yaml \ 21 | --other-definitions='other 1.yaml' \ 22 | --other-definitions='other 2.yaml' \ 23 | "$@" 24 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.0/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_0 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:tosca.lib.utils: internal:/profiles/common/1.0/js/lib/utils.js 6 | puccini.scriptlet.import:tosca.lib.traversal: internal:/profiles/common/1.0/js/lib/traversal.js 7 | puccini.scriptlet.import:tosca.coerce: internal:/profiles/common/1.0/js/coerce.js 8 | puccini.scriptlet.import:tosca.outputs: internal:/profiles/common/1.0/js/outputs.js 9 | puccini.scriptlet.import:tosca.resolve: internal:/profiles/common/1.0/js/resolve.js 10 | 11 | imports: 12 | 13 | - artifacts.yaml 14 | - groups.yaml 15 | - nodes.yaml 16 | - policies.yaml 17 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.1/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_1 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:tosca.lib.utils: internal:/profiles/common/1.0/js/lib/utils.js 6 | puccini.scriptlet.import:tosca.lib.traversal: internal:/profiles/common/1.0/js/lib/traversal.js 7 | puccini.scriptlet.import:tosca.coerce: internal:/profiles/common/1.0/js/coerce.js 8 | puccini.scriptlet.import:tosca.outputs: internal:/profiles/common/1.0/js/outputs.js 9 | puccini.scriptlet.import:tosca.resolve: internal:/profiles/common/1.0/js/resolve.js 10 | 11 | imports: 12 | 13 | - artifacts.yaml 14 | - groups.yaml 15 | - nodes.yaml 16 | - policies.yaml 17 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.2/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_2 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:tosca.lib.utils: internal:/profiles/common/1.0/js/lib/utils.js 6 | puccini.scriptlet.import:tosca.lib.traversal: internal:/profiles/common/1.0/js/lib/traversal.js 7 | puccini.scriptlet.import:tosca.coerce: internal:/profiles/common/1.0/js/coerce.js 8 | puccini.scriptlet.import:tosca.outputs: internal:/profiles/common/1.0/js/outputs.js 9 | puccini.scriptlet.import:tosca.resolve: internal:/profiles/common/1.0/js/resolve.js 10 | 11 | imports: 12 | 13 | - artifacts.yaml 14 | - groups.yaml 15 | - nodes.yaml 16 | - policies.yaml 17 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/1.3/profile.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | puccini.scriptlet.import:tosca.lib.utils: internal:/profiles/common/1.0/js/lib/utils.js 6 | puccini.scriptlet.import:tosca.lib.traversal: internal:/profiles/common/1.0/js/lib/traversal.js 7 | puccini.scriptlet.import:tosca.coerce: internal:/profiles/common/1.0/js/coerce.js 8 | puccini.scriptlet.import:tosca.outputs: internal:/profiles/common/1.0/js/outputs.js 9 | puccini.scriptlet.import:tosca.resolve: internal:/profiles/common/1.0/js/resolve.js 10 | 11 | imports: 12 | 13 | - artifacts.yaml 14 | - groups.yaml 15 | - nodes.yaml 16 | - policies.yaml 17 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/equal.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function(currentPropertyValue) { 9 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 10 | if (!parsed) { 11 | return false; 12 | } 13 | 14 | const comparable1 = tosca.getComparable(parsed.val1); 15 | const comparable2 = tosca.getComparable(parsed.val2); 16 | 17 | return comparable1 === comparable2; 18 | }; -------------------------------------------------------------------------------- /scripts/build-windows: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | git_version 8 | 9 | function build () { 10 | local TOOL=$1 11 | local EXE=$ROOT/dist/$TOOL.exe 12 | pushd "$ROOT/executables/$TOOL" > /dev/null 13 | GOOS=windows go build \ 14 | -o "$EXE" \ 15 | -ldflags " \ 16 | -X 'github.com/tliron/go-kutil/version.GitVersion=$VERSION' \ 17 | -X 'github.com/tliron/go-kutil/version.GitRevision=$REVISION' \ 18 | -X 'github.com/tliron/go-kutil/version.Timestamp=$TIMESTAMP'" 19 | popd > /dev/null 20 | m "built $EXE" 21 | } 22 | 23 | build puccini-tosca 24 | build puccini-clout 25 | build puccini-csar 26 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/workflow-activity-call-operation.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // WorkflowActivityCallOperation 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.19.2.3 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.17.2.3 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadWorkflowActivityCallOperation(context *parsing.Context) parsing.EntityPtr { 17 | self := tosca_v2_0.NewWorkflowActivityCallOperation(context) 18 | self.InterfaceAndOperation = context.FieldChild("operation", context.Data).ReadString() 19 | return self 20 | } 21 | -------------------------------------------------------------------------------- /examples/dgraph/dgraph: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # See: https://docs.dgraph.io/get-started/#from-installed-binary 5 | 6 | ROOT=/Depot/Applications/Dgraph 7 | 8 | HERE=$(dirname "$(readlink -f "$0")") 9 | 10 | if [ "$1" == start ]; then 11 | 12 | rm --force --recursive "$HERE/logs/" 13 | rm --force --recursive "$HERE/data/" 14 | mkdir --parents "$HERE/logs/" 15 | mkdir --parents "$HERE/data/" 16 | cd "$HERE/data/" 17 | "$ROOT/dgraph" zero --log_dir="$HERE/logs/" & 18 | "$ROOT/dgraph" alpha --log_dir="$HERE/logs/" & 19 | "$ROOT/dgraph" ratel --log_dir="$HERE/logs/" & 20 | xdg-open "http://localhost:8000/?local" 21 | 22 | elif [ "$1" == stop ]; then 23 | 24 | killall dgraph 25 | 26 | fi 27 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/substitution-mappings.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // SubstitutionMappings 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 2.10, 2.11 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 2.10, 2.11 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 2.10, 2.11 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadSubstitutionMappings(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("AttributeMappings", "") 19 | context.SetReadTag("SubstitutionFilter", "") 20 | 21 | return tosca_v2_0.ReadSubstitutionMappings(context) 22 | } 23 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/attribute-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // AttributeDefinition 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.12 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.11 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.10 14 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.10 15 | // 16 | 17 | // ([parsing.Reader] signature) 18 | func ReadAttributeDefinition(context *parsing.Context) parsing.EntityPtr { 19 | context.SetReadTag("Metadata", "") 20 | 21 | return tosca_v2_0.ReadAttributeDefinition(context).(*tosca_v2_0.AttributeDefinition) 22 | } 23 | -------------------------------------------------------------------------------- /scripts/build-tinygo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | git_version 8 | 9 | function build () { 10 | local TOOL=$1 11 | pushd "$ROOT/executables/$TOOL" > /dev/null 12 | tinygo build \ 13 | -o "$ROOT/dist/$TOOL" \ 14 | -ldflags " \ 15 | -X 'github.com/tliron/go-kutil/version.GitVersion=$VERSION' \ 16 | -X 'github.com/tliron/go-kutil/version.GitRevision=$REVISION' \ 17 | -X 'github.com/tliron/go-kutil/version.Timestamp=$TIMESTAMP'" 18 | popd > /dev/null 19 | m "built $GOPATH/bin/$TOOL" 20 | } 21 | 22 | build puccini-tosca 23 | build puccini-clout 24 | build puccini-csar 25 | 26 | rsync "$ROOT/puccini-csar" "$GOPATH/bin/" 27 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/artifact-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // ArtifactDefinition 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.7 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.6 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.6 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadArtifactDefinition(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("ArtifactVersion", "") 19 | context.SetReadTag("ChecksumAlgorithm", "") 20 | context.SetReadTag("Checksum", "") 21 | 22 | return tosca_v2_0.ReadArtifactDefinition(context) 23 | } 24 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/$node_index.js: -------------------------------------------------------------------------------- 1 | const tosca = require('tosca.lib.utils'); 2 | 3 | exports.evaluate = function() { 4 | // Check if we're in a node template context 5 | if (!this || !this.site) 6 | throw '$node_index can only be used in a node template context'; 7 | 8 | // Get the current node template 9 | let nodeTemplate = this.site; 10 | if (!tosca.isNodeTemplate(nodeTemplate)) 11 | throw '$node_index can only be used in a node template context'; 12 | 13 | // Return the node index for this specific instance 14 | // For single nodes (count=1), this will be 0 15 | // For multiple nodes (count>1), this will be 0, 1, 2, etc. 16 | return nodeTemplate.properties.nodeIndex || 0; 17 | }; 18 | -------------------------------------------------------------------------------- /normal/map.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // Map 5 | // 6 | 7 | type Map struct { 8 | Key Value `json:"$key,omitempty" yaml:"$key,omitempty"` 9 | ValueMeta *ValueMeta `json:"$meta,omitempty" yaml:"$meta,omitempty"` 10 | 11 | Entries ValueList `json:"$map" yaml:"$map"` 12 | } 13 | 14 | func NewMap() *Map { 15 | return &Map{Entries: ValueList{}} 16 | } 17 | 18 | // Value interface 19 | func (self *Map) SetKey(key Value) { 20 | self.Key = key 21 | } 22 | 23 | // Value interface 24 | func (self *Map) SetMeta(valueMeta *ValueMeta) { 25 | self.ValueMeta = CopyValueMeta(valueMeta) 26 | } 27 | 28 | func (self *Map) Put(key any, value Value) { 29 | self.Entries = self.Entries.AppendWithKey(key, value) 30 | } 31 | -------------------------------------------------------------------------------- /normal/list.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // List 5 | // 6 | 7 | type List struct { 8 | Key Value `json:"$key,omitempty" yaml:"$key,omitempty"` 9 | ValueMeta *ValueMeta `json:"$meta,omitempty" yaml:"$meta,omitempty"` 10 | 11 | Entries ValueList `json:"$list" yaml:"$list"` 12 | } 13 | 14 | func NewList(length int) *List { 15 | return &List{Entries: make(ValueList, length)} 16 | } 17 | 18 | // Value interface 19 | func (self *List) SetKey(key Value) { 20 | self.Key = key 21 | } 22 | 23 | // Value interface 24 | func (self *List) SetMeta(valueMeta *ValueMeta) { 25 | self.ValueMeta = CopyValueMeta(valueMeta) 26 | } 27 | 28 | func (self *List) Set(index int, value Value) { 29 | self.Entries[index] = value 30 | } 31 | -------------------------------------------------------------------------------- /wrappers/ruby/puccini.gemspec: -------------------------------------------------------------------------------- 1 | version = ENV['PUCCINI_VERSION'] || '0.0.0' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'puccini' 5 | s.version = version 6 | s.required_ruby_version = '>= 3.4' 7 | 8 | s.summary = 'Puccini' 9 | s.description = 'Deliberately stateless cloud topology management and deployment tools based on TOSCA' 10 | s.homepage = 'https://github.com/tliron/go-puccini' 11 | s.license = 'Apache-2.0' 12 | 13 | s.authors = ['Tal Liron'] 14 | s.email = 'tal.liron@gmail.com' 15 | 16 | s.files = [ 17 | 'lib/puccini.rb', 18 | 'lib/libpuccini.so' 19 | ] 20 | end 21 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/max_length.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function() { 9 | // Take the last 2 arguments like other constraints do 10 | if (arguments.length < 2) 11 | throw 'must have at least 2 arguments: value and maximum length'; 12 | 13 | var v = arguments[arguments.length - 2]; // Second-to-last argument 14 | var length = arguments[arguments.length - 1]; // Last argument 15 | 16 | return tosca.getLength(v) <= length; 17 | }; 18 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/min_length.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.validate = function() { 9 | // Take the last 2 arguments like other constraints do 10 | if (arguments.length < 2) 11 | throw 'must have at least 2 arguments: value and minimum length'; 12 | 13 | var v = arguments[arguments.length - 2]; // Second-to-last argument 14 | var length = arguments[arguments.length - 1]; // Last argument 15 | 16 | return tosca.getLength(v) >= length; 17 | }; 18 | -------------------------------------------------------------------------------- /scripts/_functions: -------------------------------------------------------------------------------- 1 | 2 | RED='\033[0;31m' 3 | GREEN='\033[0;32m' 4 | BLUE='\033[0;34m' 5 | CYAN='\033[0;36m' 6 | RESET='\033[0m' 7 | 8 | # Colored messages (blue is the default) 9 | # Examples: 10 | # m "hello world" 11 | # m "hello world" "$GREEN" 12 | function m () { 13 | local COLOR=${2:-$BLUE} 14 | echo -e "$COLOR$1$RESET" 15 | } 16 | 17 | function git_version () { 18 | VERSION=$(git -C "$ROOT" describe --tags --always 2> /dev/null || echo '') 19 | SHORT_VERSION=$(git -C "$ROOT" describe --tags --always --abbrev=0 2> /dev/null || echo '') 20 | REVISION=$(git -C "$ROOT" rev-parse HEAD 2> /dev/null || echo '') 21 | TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S %Z") 22 | GO_VERSION=$(go version | { read _ _ v _; echo ${v#go}; }) 23 | } 24 | -------------------------------------------------------------------------------- /normal/primitive.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | ) 6 | 7 | // 8 | // Primitive 9 | // 10 | 11 | type Primitive struct { 12 | Key Value `json:"$key,omitempty" yaml:"$key,omitempty"` 13 | ValueMeta *ValueMeta `json:"$meta,omitempty" yaml:"$meta,omitempty"` 14 | 15 | Primitive ard.Value `json:"$primitive" yaml:"$primitive"` 16 | } 17 | 18 | func NewPrimitive(primitive ard.Value) *Primitive { 19 | return &Primitive{Primitive: primitive} 20 | } 21 | 22 | // Value interface 23 | func (self *Primitive) SetKey(key Value) { 24 | self.Key = key 25 | } 26 | 27 | // Value interface 28 | func (self *Primitive) SetMeta(valueMeta *ValueMeta) { 29 | self.ValueMeta = CopyValueMeta(valueMeta) 30 | } 31 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/interface-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // InterfaceType 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.7.5 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.6.5 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.6.4 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadInterfaceType(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("OperationDefinitions", "?,OperationDefinition") 19 | context.SetReadTag("NotificationDefinitions", "") 20 | 21 | self := tosca_v2_0.NewInterfaceType(context) 22 | context.ReadFields(self) 23 | return self 24 | } 25 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/2.0/group_types.yaml: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This file contains TOSCA Simple Profile group type definitions. 3 | ########################################################################## 4 | 5 | tosca_definitions_version: tosca_2_0 6 | 7 | description: > 8 | TOSCA Simple Profile group type definitions 9 | 10 | metadata: 11 | template_name: group_types.yaml 12 | template_author: Oasis Open 13 | template_version: "2.0" 14 | 15 | imports: 16 | - node_types.yaml 17 | 18 | group_types: 19 | 20 | Root: 21 | description: > 22 | The TOSCA base group type from which all other normative 23 | TOSCA group types derive. 24 | members: [ Root ] 25 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/interface-assignment.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // InterfaceAssignment 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.16 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.14 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.14 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadInterfaceAssignment(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("Operations", "?,OperationAssignment") 19 | context.SetReadTag("Notifications", "") 20 | 21 | self := tosca_v2_0.NewInterfaceAssignment(context) 22 | context.ReadFields(self) 23 | return self 24 | } 25 | -------------------------------------------------------------------------------- /scripts/test-wasi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | GOOS=wasip1 "$HERE/build-wasm" 8 | 9 | RUNTIME=${RUNTIME:-wasmtime} # TODO: broken with wasmer 10 | 11 | if ! command -v "$RUNTIME" > /dev/null 2>&1; then 12 | m "$RUNTIME must be installed" "$RED" 13 | exit 1 14 | fi 15 | 16 | function run () { 17 | local TOOL=$1 18 | "$RUNTIME" run --dir=/ "$ROOT/dist/$TOOL.wasm" "${@:2}" 19 | } 20 | 21 | run puccini-tosca compile \ 22 | "$ROOT/examples/1.3/data-types.yaml" "$@" 23 | 24 | run puccini-tosca compile \ 25 | "$ROOT/dist/cloud.csar" "$@" 26 | 27 | #run puccini-tosca compile \ 28 | #https://raw.githubusercontent.com/tliron/go-puccini/main/examples/1.3/data-types.yaml "$@" 29 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/2.0/profile.yaml: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This file contains TOSCA Simple Profile type definitions. 3 | ########################################################################## 4 | 5 | tosca_definitions_version: tosca_2_0 6 | 7 | profile: org.oasis-open.simple:2.0 8 | 9 | metadata: 10 | template_name: profile.yaml 11 | template_author: Oasis Open 12 | template_version: "2.0" 13 | 14 | description: TOSCA Simple Profile type definitions 15 | 16 | imports: 17 | - data_types.yaml 18 | - artifact_types.yaml 19 | - capability_types.yaml 20 | - interface_types.yaml 21 | - relationship_types.yaml 22 | - node_types.yaml 23 | - group_types.yaml 24 | - policy_types.yaml 25 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/interface-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // InterfaceDefinition 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.16 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.14 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.14 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadInterfaceDefinition(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("OperationDefinitions", "?,OperationDefinition") 19 | context.SetReadTag("NotificationDefinitions", "") 20 | 21 | self := tosca_v2_0.NewInterfaceDefinition(context) 22 | context.ReadFields(self) 23 | return self 24 | } 25 | -------------------------------------------------------------------------------- /scripts/build-wrapper-ansible: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Requirements (Fedora) 5 | # sudo dnf install python3-libselinux 6 | 7 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 8 | . "$HERE/_env" 9 | 10 | DIST=$ROOT/dist/ansible-galaxy 11 | VENV=$ROOT/dist/python-env 12 | 13 | rm --force --recursive "$DIST" 14 | mkdir --parents "$DIST" 15 | 16 | # We don't need the Python library to build the Ansible Galaxy collection, 17 | # however we do want it installed for testing 18 | SUFFIX=$SUFFIX "$HERE/build-wrapper-python" -e 19 | 20 | . "$VENV/bin/activate" 21 | python -m pip install "ansible==$ANSIBLE_VERSION" 22 | 23 | ansible-galaxy collection build \ 24 | "$ROOT/wrappers/ansible/ansible_collections/puccini/tosca" \ 25 | --output-path "$DIST" \ 26 | --force 27 | -------------------------------------------------------------------------------- /wrappers/ruby/README.md: -------------------------------------------------------------------------------- 1 | Ruby Wrapper for Puccini 2 | ======================== 3 | 4 | This is a Ruby library for calling Puccini. It works by using 5 | [Fiddle](https://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/Fiddle.html) to call a shared library 6 | (.so) built from Puccini's Go code. This is done in-process, so there's no sub-process forking. 7 | 8 | Note that we currently only support installation on 64-bit Linux. 9 | 10 | To build the library and gem: 11 | 12 | scripts/build-library.sh 13 | cp dist/libpuccini.so wrappers/ruby/lib/ 14 | gem build wrappers/ruby/puccini.gemspec -C wrappers/ruby --output ../../dist/puccini.gem 15 | 16 | To install the gem: 17 | 18 | gem install dist/puccini.gem 19 | 20 | Also see: [Ruby examples](../../examples/ruby/). 21 | -------------------------------------------------------------------------------- /assets/tosca/profiles/profiles.go: -------------------------------------------------------------------------------- 1 | package profiles 2 | 3 | import ( 4 | "context" 5 | "embed" 6 | 7 | "github.com/tliron/exturl" 8 | "github.com/tliron/go-kutil/util" 9 | ) 10 | 11 | //go:embed cloudify/* common/* hot/* implicit/* simple/* simple-for-nfv/* 12 | var profiles embed.FS 13 | 14 | func init() { 15 | if err := exturl.ReadToInternalURLsFromFS(context.TODO(), profiles, "", func(path string) (string, bool) { 16 | return "/profiles/" + path, true 17 | }); err != nil { 18 | panic(err) 19 | } 20 | } 21 | 22 | func Get(path string) []byte { 23 | if content, err := profiles.ReadFile(path); err == nil { 24 | return content 25 | } else { 26 | panic(err) 27 | } 28 | } 29 | 30 | func GetString(path string) string { 31 | return util.BytesToString(Get(path)) 32 | } 33 | -------------------------------------------------------------------------------- /scripts/build-wasm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | GOOS=${GOOS:-js} # try wasip1 8 | 9 | git_version 10 | 11 | mkdir --parents "$ROOT/dist" 12 | 13 | function build () { 14 | local TOOL=$1 15 | local WASM=$ROOT/dist/$TOOL.wasm 16 | pushd "$ROOT/executables/$TOOL" > /dev/null 17 | GOOS="$GOOS" GOARCH=wasm go build \ 18 | -o "$WASM" \ 19 | -ldflags " \ 20 | -X 'github.com/tliron/go-kutil/version.GitVersion=$VERSION' \ 21 | -X 'github.com/tliron/go-kutil/version.GitRevision=$REVISION' \ 22 | -X 'github.com/tliron/go-kutil/version.Timestamp=$TIMESTAMP'" 23 | popd > /dev/null 24 | m "built $WASM" 25 | } 26 | 27 | build puccini-tosca 28 | build puccini-clout 29 | build puccini-csar 30 | -------------------------------------------------------------------------------- /wrappers/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | 0.0.0-SNAPSHOT 8 | 9 | 10 | puccini 11 | puccini-parent 12 | ${puccini.version} 13 | 14 | pom 15 | 16 | 17 | java 18 | native 19 | 20 | 21 | 22 | compile native:javah package 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/operation-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // OperationDefinition 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.15 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.13 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.13 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadOperationDefinition(context *parsing.Context) parsing.EntityPtr { 18 | // TOSCA 1.2 doesn't support the "OutputDefinitions" field that exists in TOSCA 2.0 19 | // This disables reading of the OutputDefinitions field 20 | context.SetReadTag("OutputDefinitions", "") 21 | 22 | return tosca_v2_0.ReadOperationDefinition(context) 23 | } 24 | -------------------------------------------------------------------------------- /clout/util/map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | ) 6 | 7 | func NewStringMap(values ard.StringMap, valueType string) ard.StringMap { 8 | entries := make(ard.List, len(values)) 9 | index := 0 10 | for key, value := range values { 11 | entries[index] = NewStringMapEntry(key, value, valueType) 12 | index++ 13 | } 14 | return ard.StringMap{"$map": entries} 15 | } 16 | 17 | func NewStringMapEntry(key string, value ard.Value, valueType string) ard.StringMap { 18 | return ard.StringMap{ 19 | "$type": NewValueType(valueType), 20 | "$key": ard.StringMap{"$value": key}, 21 | "$value": value, 22 | } 23 | } 24 | 25 | func NewValueType(type_ string) ard.StringMap { 26 | return ard.StringMap{ 27 | "type": ard.StringMap{"name": type_}, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tosca/parsing/inputs.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-kutil/reflection" 6 | ) 7 | 8 | // 9 | // HasInputs 10 | // 11 | 12 | type HasInputs interface { 13 | SetInputs(map[string]ard.Value) 14 | } 15 | 16 | // From HasInputs interface 17 | func SetInputs(entityPtr EntityPtr, inputs map[string]ard.Value) bool { 18 | if inputs == nil { 19 | return false 20 | } 21 | 22 | var done bool 23 | 24 | reflection.TraverseEntities(entityPtr, false, func(entityPtr EntityPtr) bool { 25 | if hasInputs, ok := entityPtr.(HasInputs); ok { 26 | hasInputs.SetInputs(inputs) 27 | done = true 28 | 29 | // Only one entity should implement the interface 30 | return false 31 | } 32 | return true 33 | }) 34 | 35 | return done 36 | } 37 | -------------------------------------------------------------------------------- /executables/puccini-csar/commands/common.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | "github.com/tliron/exturl" 6 | "github.com/tliron/go-kutil/util" 7 | "github.com/tliron/go-transcribe" 8 | ) 9 | 10 | const toolName = "puccini-csar" 11 | 12 | var ( 13 | log = commonlog.GetLogger(toolName) 14 | 15 | archiveFormat string 16 | ) 17 | 18 | func Transcriber() *transcribe.Transcriber { 19 | return &transcribe.Transcriber{ 20 | File: output, 21 | Format: format, 22 | ForTerminal: pretty, 23 | Strict: strict, 24 | Base64: base64, 25 | } 26 | } 27 | 28 | func Bases(urlContext *exturl.Context) []exturl.URL { 29 | workingDir, err := urlContext.NewWorkingDirFileURL() 30 | util.FailOnError(err) 31 | return []exturl.URL{workingDir} 32 | } 33 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/operation-assignment.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // OperationAssignment 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.15 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.13 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.13 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadOperationAssignment(context *parsing.Context) parsing.EntityPtr { 18 | // TOSCA 1.2 supports the "Outputs" field in assignments (this is correct) 19 | // No need to disable it here as OperationAssignment in TOSCA 2.0 has "Outputs" field 20 | // context.SetReadTag("Outputs", "") 21 | 22 | return tosca_v2_0.ReadOperationAssignment(context) 23 | } 24 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v2_0/bytes.go: -------------------------------------------------------------------------------- 1 | package tosca_v2_0 2 | 3 | import ( 4 | "github.com/tliron/go-kutil/util" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Bytes 10 | // 11 | // [TOSCA-v2.0] @ ? 12 | // 13 | 14 | type Bytes struct { 15 | OriginalString string `json:"$originalString" yaml:"$originalString"` 16 | 17 | Bytes []byte `json:"bytes" yaml:"bytes"` 18 | } 19 | 20 | // ([parsing.Reader] signature) 21 | func ReadBytes(context *parsing.Context) parsing.EntityPtr { 22 | var self Bytes 23 | 24 | if b64 := context.ReadString(); b64 != nil { 25 | self.OriginalString = *b64 26 | var err error 27 | if self.Bytes, err = util.FromBase64(self.OriginalString); err != nil { 28 | context.ReportValueMalformed("bytes", err.Error()) 29 | } 30 | } 31 | 32 | return &self 33 | } 34 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$NOBUILD" == true ]; then 5 | exit 6 | fi 7 | 8 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 9 | . "$HERE/_env" 10 | 11 | git_version 12 | 13 | function build () { 14 | local TOOL=$1 15 | pushd "$ROOT/executables/$TOOL" > /dev/null 16 | CGO_ENABLED=0 go install \ 17 | -ldflags " \ 18 | -X 'github.com/tliron/go-kutil/version.GitVersion=$VERSION' \ 19 | -X 'github.com/tliron/go-kutil/version.GitRevision=$REVISION' \ 20 | -X 'github.com/tliron/go-kutil/version.Timestamp=$TIMESTAMP'" 21 | if [ -f default.pgo ]; then 22 | m "built ${GOPATH//\\/\\\\}/bin/$TOOL (pgo)" 23 | else 24 | m "built ${GOPATH//\\/\\\\}/bin/$TOOL" 25 | fi 26 | popd > /dev/null 27 | } 28 | 29 | build puccini-tosca 30 | build puccini-clout 31 | build puccini-csar 32 | -------------------------------------------------------------------------------- /tosca/parser/yaml.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tliron/go-kutil/terminal" 7 | "github.com/tliron/yamlkeys" 8 | ) 9 | 10 | // 11 | // YAMLDecodeError 12 | // 13 | 14 | type YAMLDecodeError struct { 15 | DecodeError *yamlkeys.DecodeError 16 | } 17 | 18 | func NewYAMLDecodeError(decodeError *yamlkeys.DecodeError) *YAMLDecodeError { 19 | return &YAMLDecodeError{decodeError} 20 | } 21 | 22 | // (error interface) 23 | func (self *YAMLDecodeError) Error() string { 24 | return self.DecodeError.Error() 25 | } 26 | 27 | // problems.Problematic interface 28 | func (self *YAMLDecodeError) Problem(stylist terminal.Stylist) (string, string, string, int, int) { 29 | return "", "", fmt.Sprintf("malformed YAML, %s", self.DecodeError.Message), self.DecodeError.Line, self.DecodeError.Column 30 | } 31 | -------------------------------------------------------------------------------- /examples/neo4j/export: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink -f "$0")") 5 | 6 | function c () { 7 | curl "http://localhost:7474/db/data/transaction/commit" \ 8 | --silent \ 9 | --request POST \ 10 | --user "neo4j:admin" \ 11 | --header 'Accept: application/json; charset=UTF-8' \ 12 | --header 'Content-Type: application/json' \ 13 | --header 'X-Stream: true' \ 14 | "$@" | python -m json.tool 15 | } 16 | 17 | mkdir --parents "$HERE/data/" 18 | 19 | puccini-tosca compile "$HERE/../tosca/requirements-and-capabilities.yaml" | \ 20 | puccini-clout scriptlet exec "$HERE/neo4j.js" \ 21 | > "$HERE/data/tosca.json" 22 | 23 | c --data '{"statements": [{"statement": "MATCH (n) DETACH DELETE n"}]}' 24 | c --data-binary @"$HERE/data/tosca.json" 25 | c --data '{"statements": [{"statement": "MATCH (n) RETURN n"}]}' 26 | -------------------------------------------------------------------------------- /examples/neo4j/README.md: -------------------------------------------------------------------------------- 1 | Neo4j Example 2 | ------------- 3 | 4 | There are several ways to get data into [Neo4j](https://neo4j.com/). 5 | 6 | We have here chosen to use the [HTTP API](https://neo4j.com/docs/http-api/3.5/) to send a series 7 | of [Cypher CREATE](https://neo4j.com/docs/cypher-manual/3.5/clauses/create/) commands packed 8 | into a JSON POST, which would create the data in a single transaction. 9 | 10 | (Note that Neo4j's [RESTful API](https://neo4j.com/docs/rest-docs/3.5/) is being deprecated and 11 | should not be used.) 12 | 13 | Another approach could be to use the `neo4j-admin` tool to 14 | [import data](https://neo4j.com/docs/operations-manual/3.5/tools/import/) in CSV format. 15 | 16 | You can visualize your results using Bolt, Neo4j's web GUI, at 17 | [`http://localhost:7474/browser/`](http://localhost:7474/browser/). 18 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/requirement-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // RequirementDefinition 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.7.3 13 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.7.3 14 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.6.3 15 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.6.2 16 | // 17 | 18 | // ([parsing.Reader] signature) 19 | func ReadRequirementDefinition(context *parsing.Context) parsing.EntityPtr { 20 | context.SetReadTag("CountRange", "occurrences,RangeEntity") 21 | 22 | self := tosca_v2_0.ReadRequirementDefinition(context).(*tosca_v2_0.RequirementDefinition) 23 | self.DefaultCountRange = ard.List{1, 1} 24 | return self 25 | } 26 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/node-template.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // NodeTemplate 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.8.3 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.8.3 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.7.3 14 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.3 15 | // 16 | 17 | // ([parsing.Reader] signature) 18 | func ReadNodeTemplate(context *parsing.Context) parsing.EntityPtr { 19 | self := tosca_v2_0.NewNodeTemplate(context) 20 | context.ValidateUnsupportedFields(context.ReadFields(self)) 21 | switch self.Name { 22 | case "SELF", "SOURCE", "TARGET", "HOST": 23 | context.Clone(self.Name).ReportValueInvalid("node template name", "reserved") 24 | } 25 | return self 26 | } 27 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/attribute-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v1_3" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // AttributeDefinition 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.11 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.10 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.10 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadAttributeDefinition(context *parsing.Context) parsing.EntityPtr { 18 | // TOSCA 1.2 doesn't support the "KeySchema" field (introduced in TOSCA 1.3) 19 | context.SetReadTag("KeySchema", "") 20 | // TOSCA 1.2 doesn't support the "ValidationClause" field (introduced in TOSCA 2.0) 21 | context.SetReadTag("ValidationClause", "") 22 | 23 | return tosca_v1_3.ReadAttributeDefinition(context) 24 | } 25 | -------------------------------------------------------------------------------- /examples/1.3/dsl-definitions.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: DSL Definitions Example 6 | template_author: Puccini 7 | 8 | dsl_definitions: 9 | 10 | # This area is allowed but otherwise ignored by TOSCA 11 | # Its intended use is as a scratch space for YAML anchors 12 | # See: http://yaml.org/spec/1.2/spec.html#id2785586 13 | 14 | # (Note that the key name doesn't matter here 15 | # It's just a placeholder) 16 | port: &PORT 17 | properties: 18 | protocol: udp 19 | port: 9100 20 | 21 | topology_template: 22 | 23 | node_templates: 24 | 25 | web: 26 | type: tosca:WebServer 27 | capabilities: 28 | # Here we'll use the anchor 29 | data_endpoint: *PORT 30 | admin_endpoint: *PORT 31 | 32 | host: 33 | type: tosca:Compute 34 | -------------------------------------------------------------------------------- /tosca/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/tliron/go-kutil/reflection" 7 | "github.com/tliron/go-kutil/util" 8 | ) 9 | 10 | // 11 | // Parser 12 | // 13 | 14 | type Parser struct { 15 | readCache sync.Map // entityPtr or Promise 16 | lookupFieldsWork reflection.EntityWork 17 | addHierarchyWork reflection.EntityWork 18 | getInheritTaskWork reflection.EntityWork 19 | renderWork reflection.EntityWork 20 | lock util.RWLocker 21 | } 22 | 23 | func NewParser() *Parser { 24 | return &Parser{ 25 | lookupFieldsWork: make(reflection.EntityWork), 26 | addHierarchyWork: make(reflection.EntityWork), 27 | getInheritTaskWork: make(reflection.EntityWork), 28 | renderWork: make(reflection.EntityWork), 29 | lock: util.NewDefaultRWLocker(), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/relationship-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // RelationshipType 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.7.10 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.7.10 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.6.10 14 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.6.9 15 | // 16 | 17 | // ([parsing.Reader] signature) 18 | func ReadRelationshipType(context *parsing.Context) parsing.EntityPtr { 19 | // TOSCA 1.3 uses "valid_target_types" while TOSCA 2.0 uses "valid_capability_types" 20 | // We need to map the v1.3 field name to what the v2.0 reader expects 21 | context.SetReadTag("ValidCapabilityTypeNames", "valid_target_types") 22 | 23 | return tosca_v2_0.ReadRelationshipType(context) 24 | } 25 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/get_artifact.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.8.1 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 4.8.1 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 4.8.1 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 4.8.1 6 | 7 | const tosca = require('tosca.lib.utils'); 8 | 9 | exports.evaluate = function(entity, artifactName, location, remove) { 10 | if (arguments.length < 2) 11 | throw 'must have at least 2 arguments'; 12 | let nodeTemplate = tosca.getModelableEntity.call(this, entity).properties; 13 | if (!nodeTemplate.artifacts || !(artifactName in nodeTemplate.artifacts)) 14 | throw util.sprintf('artifact %q not found in %q', artifactName, nodeTemplate.name); 15 | let artifact = nodeTemplate.artifacts[artifactName]; 16 | if (artifact.$artifact === undefined) 17 | return artifact.sourcePath; 18 | return artifact.$artifact; 19 | }; 20 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/length.js: -------------------------------------------------------------------------------- 1 | // TOSCA $length function 2 | // [TOSCA-v2.0] @ 10.2.3.1 3 | 4 | exports.evaluate = function(arg) { 5 | if (arguments.length !== 1) { 6 | throw 'The $length function expects exactly one argument.'; 7 | } 8 | 9 | if (arg === null || arg === undefined) { 10 | throw 'The $length function argument cannot be null or undefined.'; 11 | } 12 | 13 | if (typeof arg === 'string') { 14 | return arg.length; 15 | } 16 | 17 | if (Array.isArray(arg)) { 18 | return arg.length; 19 | } 20 | 21 | // In Goja, ard.Map becomes a JS object 22 | if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) { 23 | return Object.keys(arg).length; 24 | } 25 | 26 | throw 'The $length function argument must be a string, list, or map; got ' + (typeof arg); 27 | }; -------------------------------------------------------------------------------- /clout/js/execution-context.go: -------------------------------------------------------------------------------- 1 | package js 2 | 3 | // 4 | // ExecutionContext 5 | // 6 | 7 | type ExecutionContext struct { 8 | CloutContext *CloutContext 9 | Site any 10 | Source any 11 | Target any 12 | } 13 | 14 | func (self *CloutContext) NewExecutionContext(site any, source any, target any) *ExecutionContext { 15 | return &ExecutionContext{ 16 | CloutContext: self, 17 | Site: site, 18 | Source: source, 19 | Target: target, 20 | } 21 | } 22 | 23 | func (self *ExecutionContext) Call(scriptletName string, functionName string, arguments ...any) (any, error) { 24 | if exports, err := self.CloutContext.JSContext.Environment.Require(scriptletName, true, nil); err == nil { 25 | return self.CloutContext.JSContext.Environment.GetAndCall(exports, functionName, self, arguments...) 26 | } else { 27 | return nil, err 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/comparers/version.js: -------------------------------------------------------------------------------- 1 | 2 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.2.2 3 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.2.2 4 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.2.2 5 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.2.2 6 | 7 | exports.compare = function(a, b) { 8 | if (a.$comparer !== b.$comparer) 9 | throw 'both values must be of type "version"'; 10 | if (a.major !== b.major) 11 | return a.major < b.major ? -1 : 1; 12 | if (a.minor !== b.minor) 13 | return a.minor < b.minor ? -1 : 1; 14 | if (a.fix !== b.fix) 15 | return a.fix < b.fix ? -1 : 1; 16 | let aq = a.qualifier.toLowerCase(); 17 | let bq = b.qualifier.toLowerCase(); 18 | if (aq !== bq) // note: the qualifier is compared alphabetically, *not* semantically 19 | return aq < bq ? -1 : 1; 20 | if (a.build !== b.build) 21 | return a.build < b.build ? -1 : 1; 22 | return 0; 23 | }; 24 | -------------------------------------------------------------------------------- /wrappers/python/README.md: -------------------------------------------------------------------------------- 1 | Python Wrapper for Puccini 2 | ========================== 3 | 4 | This is a Python library for calling Puccini. It works by using 5 | [ctypes](https://docs.python.org/3/library/ctypes.html) to call a shared library (.so) built from 6 | Puccini's Go code. This is done in-process, so there's no sub-process forking. 7 | 8 | Note that we currently only support installation on 64-bit Linux. 9 | 10 | The latest stable version is published on [PyPI](https://pypi.org/project/puccini/). To install: 11 | 12 | pip install puccini 13 | 14 | To install from source: 15 | 16 | git clone https://github.com/tliron/go-puccini.git 17 | cd puccini 18 | scripts/build-wrapper-python -e 19 | 20 | This will create a [virtualenv](https://virtualenv.pypa.io/). To use it: 21 | 22 | . dist/python-env/bin/activate 23 | 24 | Also see: [Python examples](../../examples/python/). 25 | -------------------------------------------------------------------------------- /examples/javascript/imports/define.js: -------------------------------------------------------------------------------- 1 | 2 | const traversal = require('tosca.lib.traversal'); 3 | 4 | // The "clout.define" API accepts the scriptlet source code as text 5 | clout.define('tosca.function.get_input', "\n\ 6 | const tosca = require('tosca.lib.utils');\n\ 7 | \n\ 8 | // This is a copy of the built-in get_input function source\n\ 9 | // Except that we added a '* 2' to the returned result\n\ 10 | exports.evaluate = function(input) {\n\ 11 | if (arguments.length !== 1)\n\ 12 | throw 'must have 1 argument';\n\ 13 | if (!tosca.isTosca(clout))\n\ 14 | throw 'Clout is not TOSCA';\n\ 15 | let inputs = clout.properties.tosca.inputs;\n\ 16 | if (!(input in inputs))\n\ 17 | throw puccini.sprintf('input \"%s\" not found', input);\n\ 18 | let r = inputs[input];\n\ 19 | r = clout.coerce(r);\n\ 20 | return r * 2;\n\ 21 | };\n\ 22 | "); 23 | 24 | traversal.coerce(); 25 | 26 | puccini.write(clout); 27 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/schema.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // Schema 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ ? 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ ? 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ ? 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadSchema(context *parsing.Context) parsing.EntityPtr { 18 | // TOSCA 1.2 doesn't support the "ValidationClause" field (introduced in TOSCA 2.0) 19 | context.SetReadTag("ValidationClause", "") 20 | // TOSCA 1.2 doesn't support the "KeySchema" field (introduced in TOSCA 1.3) 21 | context.SetReadTag("KeySchema", "") 22 | // TOSCA 1.2 doesn't support the "Metadata" field (introduced in TOSCA 1.3) 23 | context.SetReadTag("Metadata", "") 24 | 25 | return tosca_v2_0.ReadSchema(context) 26 | } 27 | -------------------------------------------------------------------------------- /wrappers/java/native/src/main/c/cloud_puccini_TOSCA.c: -------------------------------------------------------------------------------- 1 | #include "cloud_puccini_TOSCA.h" 2 | #include "libpuccini.h" 3 | #include 4 | 5 | JNIEXPORT jstring JNICALL Java_cloud_puccini_TOSCA__1Compile 6 | (JNIEnv *env, jclass cls, jstring url, jstring inputs, jstring quirks, jboolean resolve, jboolean coerce) 7 | { 8 | const char *url_ = (*env)->GetStringUTFChars(env, url, 0); 9 | const char *inputs_ = (*env)->GetStringUTFChars(env, inputs, 0); 10 | const char *quirks_ = (*env)->GetStringUTFChars(env, quirks, 0); 11 | 12 | char *result = Compile((char *) url_, (char *) inputs_, (char *) quirks_, resolve, coerce); 13 | 14 | (*env)->ReleaseStringUTFChars(env, url, url_); 15 | (*env)->ReleaseStringUTFChars(env, inputs, inputs_); 16 | (*env)->ReleaseStringUTFChars(env, quirks, quirks_); 17 | 18 | jstring result_ = (*env)->NewStringUTF(env, result); 19 | free(result); 20 | return result_; 21 | } 22 | -------------------------------------------------------------------------------- /examples/1.3/imports/super-load-balancer.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | metadata: 4 | 5 | template_name: Super Load Balancer Example 6 | template_author: Puccini 7 | 8 | node_types: 9 | 10 | # We're going to allow this single node to be substituted by an entire service 11 | SuperLoadBalancer: 12 | derived_from: tosca:LoadBalancer 13 | properties: 14 | redundancy: 15 | type: integer 16 | required: false 17 | attributes: 18 | admin_state: 19 | type: string 20 | db_state: 21 | type: string 22 | requirements: 23 | # We require hosts for our internal components 24 | - admin_host: 25 | capability: tosca:Compute 26 | node: tosca:Compute 27 | relationship: tosca:HostedOn 28 | - db_host: 29 | capability: tosca:Compute 30 | node: tosca:Compute 31 | relationship: tosca:HostedOn 32 | -------------------------------------------------------------------------------- /examples/javascript/imports/extract.js: -------------------------------------------------------------------------------- 1 | // This scriptlet extracts all artifacts to the output directory 2 | 3 | const traversal = require('tosca.lib.traversal'); 4 | const tosca = require('tosca.lib.utils'); 5 | 6 | traversal.coerce(); 7 | 8 | for (let vertexId in clout.vertexes) { 9 | let vertex = clout.vertexes[vertexId]; 10 | if (!tosca.isNodeTemplate(vertex)) 11 | continue; 12 | let nodeTemplate = vertex.properties; 13 | 14 | for (let key in nodeTemplate.artifacts) { 15 | let artifact = nodeTemplate.artifacts[key]; 16 | 17 | // If 'puccini.output' is empty, this will be relative to current directory 18 | let targetPath = puccini.joinFilePath(puccini.output, artifact.filename); 19 | 20 | puccini.log.noticef('extracting "%s" to "%s"', artifact.sourcePath, targetPath); 21 | puccini.download(artifact.sourcePath, targetPath); 22 | 23 | //puccini.log.noticef('%s', puccini.exec('cat', targetPath)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/dsl-resource.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // DSLResource 9 | // 10 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-upload-resources/] 11 | // 12 | 13 | type DSLResource struct { 14 | *Entity `name:"DSL resource"` 15 | 16 | SourcePath *string `read:"source_path" mandatory:""` 17 | DestinationPath *string `read:"destination_path" mandatory:""` 18 | } 19 | 20 | func NewDSLResource(context *parsing.Context) *DSLResource { 21 | return &DSLResource{Entity: NewEntity(context)} 22 | } 23 | 24 | // ([parsing.Reader] signature) 25 | func ReadDSLResource(context *parsing.Context) parsing.EntityPtr { 26 | self := NewDSLResource(context) 27 | context.ValidateUnsupportedFields(context.ReadFields(self)) 28 | return self 29 | } 30 | 31 | // 32 | // DSLResources 33 | // 34 | 35 | type DSLResources []*DSLResource 36 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_1/service-file.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_1 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // ServiceFile 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.9 12 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.9 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadServiceFile(context *parsing.Context) parsing.EntityPtr { 17 | context.SetReadTag("ServiceTemplate", "topology_template,ServiceTemplate") 18 | context.SetReadTag("Profile", "") 19 | 20 | self := tosca_v2_0.NewServiceFile(context) 21 | context.ScriptletNamespace.Merge(DefaultScriptletNamespace) 22 | ignore := []string{"dsl_definitions"} 23 | if context.HasQuirk(parsing.QuirkAnnotationsIgnore) { 24 | ignore = append(ignore, "annotation_types") 25 | } 26 | context.ValidateUnsupportedFields(append(context.ReadFields(self), ignore...)) 27 | return self 28 | } 29 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/node-template-instances.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // NodeTemplateInstances 9 | // 10 | // [https://cloudify.co/guide/3.2/dsl-spec-node-templates.html] 11 | // 12 | 13 | type NodeTemplateInstances struct { 14 | *Entity `name:"node template instances"` 15 | 16 | Deploy *int64 `read:"deploy"` 17 | } 18 | 19 | func NewNodeTemplateInstances(context *parsing.Context) *NodeTemplateInstances { 20 | return &NodeTemplateInstances{Entity: NewEntity(context)} 21 | } 22 | 23 | // ([parsing.Reader] signature) 24 | func ReadNodeTemplateInstances(context *parsing.Context) parsing.EntityPtr { 25 | self := NewNodeTemplateInstances(context) 26 | 27 | context.ValidateUnsupportedFields(context.ReadFields(self)) 28 | 29 | if self.Deploy == nil { 30 | // Default to 1 31 | var deploy int64 = 1 32 | self.Deploy = &deploy 33 | } 34 | 35 | return self 36 | } 37 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_1/file.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_1 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // File 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.9 12 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.9 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadFile(context *parsing.Context) parsing.EntityPtr { 17 | context.SetReadTag("Profile", "") 18 | 19 | self := tosca_v2_0.NewFile(context) 20 | context.ScriptletNamespace.Merge(DefaultScriptletNamespace) 21 | ignore := []string{"dsl_definitions"} 22 | if context.HasQuirk(parsing.QuirkImportsTopologyTemplateIgnore) { 23 | ignore = append(ignore, "topology_template") 24 | } 25 | if context.HasQuirk(parsing.QuirkAnnotationsIgnore) { 26 | ignore = append(ignore, "annotation_types") 27 | } 28 | context.ValidateUnsupportedFields(append(context.ReadFields(self), ignore...)) 29 | return self 30 | } 31 | -------------------------------------------------------------------------------- /tosca/parser/gather.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/tliron/go-puccini/tosca/parsing" 8 | ) 9 | 10 | func (self *Context) Gather(pattern string) parsing.EntityPtrs { 11 | var entityPtrs parsing.EntityPtrs 12 | 13 | re := compileGatherPattern(pattern) 14 | 15 | self.TraverseEntities(logGather, nil, func(entityPtr parsing.EntityPtr) bool { 16 | context := parsing.GetContext(entityPtr) 17 | 18 | if re.MatchString(context.Path.String()) { 19 | entityPtrs = append(entityPtrs, entityPtr) 20 | } 21 | 22 | return true 23 | }) 24 | 25 | return entityPtrs 26 | } 27 | 28 | func compileGatherPattern(pattern string) *regexp.Regexp { 29 | split := strings.Split(pattern, "*") 30 | last := len(split) - 1 31 | var reString string 32 | for index, s := range split { 33 | reString += regexp.QuoteMeta(s) 34 | if index != last { 35 | reString += `.*` 36 | } 37 | } 38 | return regexp.MustCompile(reString) 39 | } 40 | -------------------------------------------------------------------------------- /examples/hot/parameters/single-server-with-existing-floating-ip.yaml: -------------------------------------------------------------------------------- 1 | #parameters: 2 | public_network: ext-net 3 | floating_ip: ID_OF_FLOATING_IP_GOES_HERE 4 | image: Ubuntu 16.04 LTS sys11 optimized 2018.03.21 5 | flavor: m1.micro 6 | ssh_keys: 7 | - 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDN7WgiiXGQju8PlZ2IVoQygxeLzxvt2baZx9Q1JmfHed6Pxz+yiibbWiZIMDYiu76FSf6SENoUdSb56jcuFv4CCfu1lnLa3p/si/ic7BlDeIs6754cqdQTMlHShPw9z69cJKdd0qsA5KPBL7tEzCUWrRDidsiHza9cj/mlZ7w5X6+BUhXYa/0UK6cjkYD/T7qHhgYAGCalhwRIIPdoFhllkGaoO5r0gUgvkv1PFpK7psNfuxbuA4th0gU4Qhgj8hTpmcRFceneIwG9ZpEIbhbfyQcA3pPSZFDGsdcnDhHMHXHZsjGLca1lXDh7izn3t8fHLXAjwMnw5OdsNu6vARk+JZsZprrwSi++WWd43KUeGNdr5KLgHQDaNiLhgBKwZk+4ZpK1BAl4PZidX6P+idu4qWHNEv49yzxfI+puPbwhNBtWIrehZVKSah9/ALYtDyYBtMRyF9i9fW4O17Ov5dR10vrq4Mm6NlBJynCFjMY6z8hZFZCHc3QnPLCwfIeRH2PRMJonF5+wsyc4kxCwqK1HLsvwSFcAVdsEtFeppEEq5/WjDiv6sb62h+lckL/hXm+Y3rKULpEoHuVl/BX/rwCI1c6ES6asLQ3ZkEYo/0s3FnzZlu2qEYERddUAmzunAPx3fzcMjNUsEmpZS1uXUOFIenL+rj1kSmDfb90fQQRnnQ== dschwabe@dschwabe' 8 | -------------------------------------------------------------------------------- /tosca/grammars/hot/parameter-group.go: -------------------------------------------------------------------------------- 1 | package hot 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // ParameterGroup 9 | // 10 | // [https://docs.openstack.org/heat/wallaby/template_guide/hot_spec.html#parameter-groups-section] 11 | // 12 | 13 | type ParameterGroup struct { 14 | *Entity `name:"parameter group"` 15 | 16 | Label *string `read:"label"` 17 | Description *string `read:"description"` 18 | Parameters []*string `read:"parameters"` 19 | } 20 | 21 | func NewParameterGroup(context *parsing.Context) *ParameterGroup { 22 | return &ParameterGroup{Entity: NewEntity(context)} 23 | } 24 | 25 | // ([parsing.Reader] signature) 26 | func ReadParameterGroup(context *parsing.Context) parsing.EntityPtr { 27 | self := NewParameterGroup(context) 28 | context.ValidateUnsupportedFields(context.ReadFields(self)) 29 | return self 30 | } 31 | 32 | // 33 | // ParameterGroups 34 | // 35 | 36 | type ParameterGroups []*ParameterGroup 37 | -------------------------------------------------------------------------------- /wrappers/ansible/ansible_collections/puccini/tosca/README.md: -------------------------------------------------------------------------------- 1 | Puccini TOSCA Collection for Ansible 2 | ==================================== 3 | 4 | Enables [TOSCA](https://www.oasis-open.org/committees/tosca/) support for Ansible. 5 | 6 | Part of the [Puccini](https://puccini.cloud) project. 7 | 8 | This is a work in progress. 9 | 10 | Installation 11 | ------------ 12 | 13 | Requires the [Puccini Python library](https://pypi.org/project/puccini/). 14 | 15 | Often this should be enough to get up and running: 16 | 17 | pip install puccini 18 | ansible-galaxy collection install puccini.tosca 19 | 20 | Usage 21 | ----- 22 | 23 | We currently enable two ways to consume TOSCA in Ansible: 24 | 25 | * Iterate over arbitrary TOSCA nodes using our custom task `puccini.tosca.compile` 26 | * Use TOSCA nodes as an Ansible inventory, via our custom inventory plugin `puccini.tosca.nodes` 27 | 28 | See the [examples](https://github.com/tliron/go-puccini/tree/main/examples/ansible). 29 | -------------------------------------------------------------------------------- /scripts/test-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | m 'test...' 8 | "$HERE/test" 9 | m 'build-library...' 10 | "$HERE/build-library" 11 | m 'build-csars...' 12 | "$HERE/build-csars" 13 | 14 | export NOBUILD=true 15 | 16 | m 'test-parse...' 17 | "$HERE/test-parse" 18 | 19 | m 'test-formats...' 20 | #"$HERE/test-formats" 21 | 22 | m 'test-csar...' 23 | "$HERE/test-csar" 24 | 25 | m 'test-https-url...' 26 | "$HERE/test-https-url" 27 | m 'test-git-url...' 28 | "$HERE/test-git-url" 29 | m 'test-csar-http-url...' 30 | "$HERE/test-csar-http-url" 31 | m 'test-archive-url...' 32 | "$HERE/test-archive-url" 33 | m 'test-archive-url-http...' 34 | "$HERE/test-archive-url-http" 35 | 36 | m 'test-wasm...' 37 | "$HERE/test-wasm" 38 | m 'test-wasi...' 39 | "$HERE/test-wasi" 40 | m 'test-java...' 41 | "$HERE/test-java" 42 | m 'test-python...' 43 | "$HERE/test-python" 44 | m 'test-ruby...' 45 | "$HERE/test-ruby" 46 | 47 | m done! 48 | -------------------------------------------------------------------------------- /tosca/parsing/entity-pointers.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // 8 | // EntityPtr 9 | // 10 | 11 | type EntityPtr = any 12 | 13 | // 14 | // EntityPtrs 15 | // 16 | 17 | type EntityPtrs []EntityPtr 18 | 19 | // ([sort.Interface]) 20 | func (self EntityPtrs) Len() int { 21 | return len(self) 22 | } 23 | 24 | // ([sort.Interface]) 25 | func (self EntityPtrs) Swap(i, j int) { 26 | self[i], self[j] = self[j], self[i] 27 | } 28 | 29 | // ([sort.Interface]) 30 | func (self EntityPtrs) Less(i, j int) bool { 31 | iName := GetContext(self[i]).Path.String() 32 | jName := GetContext(self[j]).Path.String() 33 | return strings.Compare(iName, jName) < 0 34 | } 35 | 36 | // 37 | // EntityPtrSet 38 | // 39 | 40 | type EntityPtrSet map[EntityPtr]struct{} 41 | 42 | func (self EntityPtrSet) Add(entityPtr EntityPtr) { 43 | self[entityPtr] = struct{}{} 44 | } 45 | 46 | func (self EntityPtrSet) Contains(entityPtr EntityPtr) bool { 47 | _, ok := self[entityPtr] 48 | return ok 49 | } 50 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/relationship-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // RelationshipType 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.6.6 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadRelationshipType(context *parsing.Context) parsing.EntityPtr { 17 | // Convert "valid_target_types" (1.0) to "valid_target_node_types" (2.0) before calling v2.0 reader 18 | if context.Is(ard.TypeMap) { 19 | if m, ok := context.Data.(ard.Map); ok { 20 | if validTargetTypes, ok := m["valid_target_types"]; ok { 21 | // In TOSCA 1.0, valid_target_types refers to capability types 22 | // In TOSCA 2.0, this maps to valid_capability_types 23 | m["valid_capability_types"] = validTargetTypes 24 | delete(m, "valid_target_types") 25 | } 26 | } 27 | } 28 | 29 | return tosca_v2_0.ReadRelationshipType(context) 30 | } 31 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_1/relationship-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_1 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // RelationshipType 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.6.6 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadRelationshipType(context *parsing.Context) parsing.EntityPtr { 17 | // Convert "valid_target_types" (1.1) to "valid_capability_types" (2.0) before calling v2.0 reader 18 | if context.Is(ard.TypeMap) { 19 | if m, ok := context.Data.(ard.Map); ok { 20 | if validTargetTypes, ok := m["valid_target_types"]; ok { 21 | // In TOSCA 1.1, valid_target_types refers to capability types 22 | // In TOSCA 2.0, this maps to valid_capability_types 23 | m["valid_capability_types"] = validTargetTypes 24 | delete(m, "valid_target_types") 25 | } 26 | } 27 | } 28 | 29 | return tosca_v2_0.ReadRelationshipType(context) 30 | } 31 | -------------------------------------------------------------------------------- /wrappers/python/puccini/tosca.py: -------------------------------------------------------------------------------- 1 | 2 | import pathlib, ctypes, ard 3 | from . import go 4 | 5 | library_path = pathlib.Path(__file__).parents[0] / 'libpuccini.so' 6 | library = ctypes.cdll.LoadLibrary(library_path) 7 | 8 | library.Compile.argtypes = (ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char, ctypes.c_char) 9 | library.Compile.restype = ctypes.c_char_p 10 | 11 | 12 | class Problems(Exception): 13 | def __init__(self, problems): 14 | self.message = 'problems' 15 | self.problems = problems 16 | 17 | 18 | def compile(url, inputs=None, quirks=None, resolve=True, coerce=True): 19 | inputs = ard.encode(inputs or {}) 20 | quirks = ard.encode(quirks or []) 21 | result = ard.read(library.Compile(go.to_c_char_p(url), go.to_c_char_p(inputs), go.to_c_char_p(quirks), go.to_c_char(resolve), go.to_c_char(coerce))) 22 | if 'problems' in result: 23 | raise Problems(result['problems']) 24 | elif 'error' in result: 25 | raise Exception(result['error']) 26 | return result['clout'] 27 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/relationship-type.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // RelationshipType 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.6 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadRelationshipTypeWithConstraints(context *parsing.Context) parsing.EntityPtr { 17 | // Convert "valid_target_types" (1.2) to "valid_capability_types" (2.0) before calling v2.0 reader 18 | if context.Is(ard.TypeMap) { 19 | if m, ok := context.Data.(ard.Map); ok { 20 | if validTargetTypes, ok := m["valid_target_types"]; ok { 21 | // In TOSCA 1.2, valid_target_types refers to capability types 22 | // In TOSCA 2.0, this maps to valid_capability_types 23 | m["valid_capability_types"] = validTargetTypes 24 | delete(m, "valid_target_types") 25 | } 26 | } 27 | } 28 | 29 | return tosca_v2_0.ReadRelationshipType(context) 30 | } 31 | -------------------------------------------------------------------------------- /wrappers/ruby/lib/puccini.rb: -------------------------------------------------------------------------------- 1 | require 'fiddle' 2 | require 'fiddle/import' 3 | require 'yaml' 4 | 5 | module Puccini 6 | extend Fiddle::Importer 7 | dlload File.join(__dir__, 'libpuccini.so') 8 | extern 'char *Compile(char *, char *, char *, char, char)' 9 | 10 | module TOSCA 11 | extend self 12 | 13 | class Problems < StandardError 14 | def initialize(problems) 15 | @problems = problems 16 | end 17 | attr_reader :problems 18 | end 19 | 20 | def compile(url, inputs=nil, quirks=nil, resolve=true, coerce=true) 21 | inputs = YAML.dump (inputs || {}) 22 | quirks = YAML.dump (quirks || []) 23 | result = YAML.unsafe_load Puccini::Compile(url, inputs, quirks, resolve ? 1 : 0, coerce ? 1 : 0).to_s 24 | if result.key? 'problems' 25 | raise Problems.new result['problems'] 26 | elsif result.key? 'error' 27 | raise StandardError.new result['error'] 28 | else 29 | return result['clout'] 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /examples/javascript/define.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | # Compare these two runs: 4 | # puccini-tosca compile examples/javascript/define.yaml --coerce 5 | # puccini-tosca compile examples/javascript/define.yaml --exec=define 6 | 7 | # Also see: exec.yaml, functions.yaml 8 | 9 | metadata: 10 | 11 | template_name: JavaScript Define Example 12 | template_author: Puccini 13 | 14 | # In the functions.yaml example we saw how to define functions declaratively 15 | # Here we will do it programmatically 16 | puccini.scriptlet.import:define: imports/define.js 17 | 18 | node_types: 19 | 20 | Switch: 21 | properties: 22 | port: 23 | type: integer 24 | 25 | topology_template: 26 | 27 | inputs: 28 | 29 | port: 30 | type: integer 31 | default: 80 32 | 33 | node_templates: 34 | 35 | host: 36 | type: Switch 37 | properties: 38 | # We will be redefining this function in define.js to double the value 39 | port: { get_input: port } 40 | -------------------------------------------------------------------------------- /wrappers/java/README.md: -------------------------------------------------------------------------------- 1 | Java Wrapper for Puccini 2 | ======================== 3 | 4 | This is a Java library for calling Puccini. It works by using a JNI shim library to call a shared 5 | library (.so) built from Puccini's Go code. This is done in-process, so there's no sub-process 6 | forking. 7 | 8 | Note that we currently only support installation on 64-bit Linux. 9 | 10 | The build requirements are [Maven](https://maven.apache.org/), gcc, and a full JDK. To install them 11 | on Fedora: 12 | 13 | sudo dnf install mvn gcc java-11-openjdk-devel 14 | 15 | To build the libraries: 16 | 17 | scripts/build-library.sh 18 | mvn --file wrappers/java 19 | 20 | The Puccini shared library as well as the JNI shim shared library will both be in the `dist/` 21 | subdirectory. To use them you can either copy them into your operating system's standard library 22 | path, or else set the path before running the JVM, e.g.: 23 | 24 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:dist java ... 25 | 26 | Also see: [Java examples](../../examples/java/). 27 | -------------------------------------------------------------------------------- /examples/2.0/dsl-definitions.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | imports: 4 | - url: https://raw.githubusercontent.com/xDaryamo/tosca-community-contributions/refs/heads/master/profiles/org/oasis-open/simple/2.0/profile.yaml 5 | namespace: tosca 6 | 7 | metadata: 8 | 9 | template_name: DSL Definitions Example 10 | template_author: Puccini 11 | 12 | dsl_definitions: 13 | 14 | # This area is allowed but otherwise ignored by TOSCA 15 | # Its intended use is as a scratch space for YAML anchors 16 | # See: http://yaml.org/spec/1.2/spec.html#id2785586 17 | 18 | # (Note that the key name doesn't matter here 19 | # It's just a placeholder) 20 | port: &PORT 21 | properties: 22 | protocol: udp 23 | port: 9100 24 | 25 | service_template: 26 | 27 | node_templates: 28 | 29 | web: 30 | type: tosca:WebServer 31 | capabilities: 32 | # Here we'll use the anchor 33 | data_endpoint: *PORT 34 | admin_endpoint: *PORT 35 | 36 | host: 37 | type: tosca:Compute 38 | -------------------------------------------------------------------------------- /normal/notification.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // Notification 5 | // 6 | 7 | type Notification struct { 8 | Interface *Interface `json:"-" yaml:"-"` 9 | Name string `json:"-" yaml:"-"` 10 | 11 | Description string `json:"description" yaml:"description"` 12 | Implementation string `json:"implementation" yaml:"implementation"` 13 | Dependencies []string `json:"dependencies" yaml:"dependencies"` 14 | Timeout int64 `json:"timeout" yaml:"timeout"` 15 | Host string `json:"host,omitempty" yaml:"host,omitempty"` 16 | Outputs Values `json:"outputs" yaml:"outputs"` 17 | } 18 | 19 | func (self *Interface) NewNotification(name string) *Notification { 20 | notification := &Notification{ 21 | Interface: self, 22 | Name: name, 23 | Dependencies: make([]string, 0), 24 | Outputs: make(Values), 25 | } 26 | self.Notifications[name] = notification 27 | return notification 28 | } 29 | 30 | // 31 | // Notifications 32 | // 33 | 34 | type Notifications map[string]*Notification 35 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/constraint-clause.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // Instead of creating remapped scriptlets, use the original validation scriptlets directly 9 | // This ensures the keys match what tosca_v2_0.ReadValidationClause expects 10 | 11 | var ConstraintClauseScriptlets = tosca_v2_0.ValidationClauseScriptlets 12 | var ConstraintClauseNativeArgumentIndexes = tosca_v2_0.ValidationClauseNativeArgumentIndexes 13 | 14 | // Override the reader to ensure it has access to the correct operators 15 | func ReadConstraintClause(context *parsing.Context) parsing.EntityPtr { 16 | // Create a new context with the right scriptlet namespace 17 | contextWithScriptlets := context.Clone(context.Data) 18 | contextWithScriptlets.ScriptletNamespace = DefaultScriptletNamespace 19 | 20 | // Call the v2.0 implementation with our enhanced context 21 | return tosca_v2_0.ReadValidationClause(contextWithScriptlets) 22 | } 23 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_0/capability-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_0 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // CapabilityDefinition 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.1 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadCapabilityDefinition(context *parsing.Context) parsing.EntityPtr { 17 | // Handle TOSCA 1.0 specific fields before calling v2.0 reader 18 | if context.Is(ard.TypeMap) { 19 | if m, ok := context.Data.(ard.Map); ok { 20 | // TOSCA 1.0 uses "valid_source_types" which doesn't exist in TOSCA 2.0 21 | if _, ok := m["valid_source_types"]; ok { 22 | delete(m, "valid_source_types") 23 | } 24 | 25 | // TOSCA 1.0 uses "occurrences" which doesn't exist in TOSCA 2.0 CapabilityDefinition 26 | if _, ok := m["occurrences"]; ok { 27 | delete(m, "occurrences") 28 | } 29 | } 30 | } 31 | 32 | return tosca_v2_0.ReadCapabilityDefinition(context) 33 | } 34 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_1/capability-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_1 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // CapabilityDefinition 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.7.1 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadCapabilityDefinition(context *parsing.Context) parsing.EntityPtr { 17 | // Handle TOSCA 1.1 specific fields before calling v2.0 reader 18 | if context.Is(ard.TypeMap) { 19 | if m, ok := context.Data.(ard.Map); ok { 20 | // TOSCA 1.1 uses "valid_source_types" which doesn't exist in TOSCA 2.0 21 | if _, ok := m["valid_source_types"]; ok { 22 | delete(m, "valid_source_types") 23 | } 24 | 25 | // TOSCA 1.1 uses "occurrences" which doesn't exist in TOSCA 2.0 CapabilityDefinition 26 | if _, ok := m["occurrences"]; ok { 27 | delete(m, "occurrences") 28 | } 29 | } 30 | } 31 | 32 | return tosca_v2_0.ReadCapabilityDefinition(context) 33 | } 34 | -------------------------------------------------------------------------------- /examples/cloudify/example.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_3 2 | 3 | node_templates: 4 | 5 | host: 6 | type: cloudify.nodes.Compute 7 | 8 | db: 9 | type: cloudify.nodes.DBMS 10 | relationships: 11 | - type: cloudify.relationships.contained_in 12 | target: host 13 | 14 | groups: 15 | 16 | host_and_db: 17 | members: [ host, db ] 18 | 19 | policies: 20 | 21 | scaling: 22 | type: cloudify.policies.types.threshold 23 | properties: 24 | default_instances: 2 25 | targets: [ host_and_db ] 26 | 27 | plugins: 28 | 29 | plugin_with_args: 30 | executor: central_deployment_agent 31 | source: http://www.example.com/path/to/plugin.tar.gz 32 | install_arguments: -r requirements.txt 33 | 34 | upload_resources: 35 | 36 | plugin_resources: 37 | - http://www.my-plugin.com/path/to/plugin.wgn 38 | dsl_resources: 39 | - source_path: http://www.my-plugin.com/path/to/plugin.yaml 40 | destination_path: /opt/cfy/plugins/my-plugin-name/plugin.yaml 41 | parameters: 42 | fetch_timeout: 20 43 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/capability-definition.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // CapabilityDefinition 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.7.1 13 | // 14 | 15 | // ([parsing.Reader] signature) 16 | func ReadCapabilityDefinitionWithConstraints(context *parsing.Context) parsing.EntityPtr { 17 | // Handle TOSCA 1.2 specific fields before calling v2.0 reader 18 | if context.Is(ard.TypeMap) { 19 | if m, ok := context.Data.(ard.Map); ok { 20 | // TOSCA 1.2 uses "valid_source_types" which doesn't exist in TOSCA 2.0 21 | if _, ok := m["valid_source_types"]; ok { 22 | delete(m, "valid_source_types") 23 | } 24 | 25 | // TOSCA 1.2 uses "occurrences" which doesn't exist in TOSCA 2.0 CapabilityDefinition 26 | if _, ok := m["occurrences"]; ok { 27 | delete(m, "occurrences") 28 | } 29 | } 30 | } 31 | 32 | return tosca_v2_0.ReadCapabilityDefinition(context) 33 | } 34 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/value.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // ([parsing.Reader] signature) 10 | func ReadAttributeValue(context *parsing.Context) parsing.EntityPtr { 11 | self := tosca_v2_0.NewValue(context) 12 | 13 | // Unpack long notation (only for attributes) 14 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.13.2.2 15 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.12.2.2 16 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.11.2.2 17 | // [TOSCA-Simple-Profile-YAML-v1.0] @3.5.11.2.2 18 | if context.Is(ard.TypeMap) { 19 | map_ := context.Data.(ard.Map) 20 | if len(map_) == 2 { 21 | if description, ok := map_["description"]; ok { 22 | if value, ok := map_["value"]; ok { 23 | self.Description = context.FieldChild("description", description).ReadString() 24 | context.Data = value 25 | } 26 | } 27 | } 28 | } 29 | 30 | tosca_v2_0.ParseFunctionCall(context) 31 | 32 | return self 33 | } 34 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/service-file.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // ServiceFile 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.10 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.9 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.9 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadServiceFile(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("ServiceTemplate", "topology_template,ServiceTemplate") 19 | context.SetReadTag("Profile", "namespace") 20 | 21 | self := tosca_v2_0.NewServiceFile(context) 22 | context.ScriptletNamespace.Merge(DefaultScriptletNamespace) 23 | ignore := []string{"dsl_definitions"} 24 | if context.HasQuirk(parsing.QuirkAnnotationsIgnore) { 25 | ignore = append(ignore, "annotation_types") 26 | } 27 | context.ValidateUnsupportedFields(append(context.ReadFields(self), ignore...)) 28 | if self.Profile != nil { 29 | context.CanonicalNamespace = self.Profile 30 | } 31 | return self 32 | } 33 | -------------------------------------------------------------------------------- /examples/javascript/artifacts.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | # To execute the scriptlet and extract artifacts to a specific directory: 4 | # puccini-tosca compile examples/javascript/artifacts.yaml --exec=extract --output=work 5 | 6 | # See also: examples/1.3/artifacts.yaml 7 | 8 | metadata: 9 | 10 | template_name: JavaScript Artifacts Example 11 | template_author: Puccini 12 | 13 | puccini.scriptlet.import:extract: imports/extract.js 14 | 15 | topology_template: 16 | 17 | node_templates: 18 | 19 | host: 20 | type: tosca:Compute 21 | artifacts: 22 | deploy: 23 | type: tosca:Implementation.Bash 24 | # Path is relative to this file's location, even if it's within a CSAR 25 | # (Note that extraction will even work from within a CSAR! 26 | # This works by unzipping the archive if it's a local CSAR, or by downloading 27 | # to a temporary file and then unzipping if it's a remote CSAR) 28 | file: artifacts/deploy.sh 29 | deploy_path: /var/lib/orchestration/images/ 30 | -------------------------------------------------------------------------------- /examples/2.0/imports/super-load-balancer.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | imports: 4 | - url: https://raw.githubusercontent.com/xDaryamo/tosca-community-contributions/refs/heads/master/profiles/org/oasis-open/simple/2.0/profile.yaml 5 | namespace: tosca 6 | 7 | metadata: 8 | 9 | template_name: Super Load Balancer Example 10 | template_author: Puccini 11 | 12 | node_types: 13 | 14 | # We're going to allow this single node to be substituted by an entire service 15 | SuperLoadBalancer: 16 | derived_from: tosca:LoadBalancer 17 | properties: 18 | redundancy: 19 | type: integer 20 | required: false 21 | attributes: 22 | admin_state: 23 | type: string 24 | db_state: 25 | type: string 26 | requirements: 27 | # We require hosts for our internal components 28 | - admin_host: 29 | capability: tosca:Compute 30 | node: tosca:Compute 31 | relationship: tosca:HostedOn 32 | - db_host: 33 | capability: tosca:Compute 34 | node: tosca:Compute 35 | relationship: tosca:HostedOn 36 | -------------------------------------------------------------------------------- /examples/ruby/compile.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require 'puccini' 5 | require 'yaml' 6 | 7 | inputs = {} 8 | quirks = [] 9 | resolve = true 10 | coerce = true 11 | 12 | option_parser = OptionParser.new do |opts| 13 | opts.on '-i', '--input=INPUT', 'specify input (format is name=value)' do |i| 14 | k, v = i.split('=') 15 | inputs[k] = YAML.load(v) 16 | end 17 | opts.on '-q', '--quirk=QUIRK', 'specify quirk' do |q| 18 | quirks.push(q) 19 | end 20 | opts.on '-r', '--resolve=FLAG', 'whether to resolve' do |r| 21 | resolve = (r == 'true') 22 | end 23 | opts.on '-c', '--coerce=FLAG', 'whether to coerce' do |c| 24 | coerce = (c == 'true') 25 | end 26 | end 27 | 28 | option_parser.parse! 29 | 30 | if ARGV.length == 0 31 | puts 'no URL provided' 32 | exit 1 33 | end 34 | 35 | begin 36 | clout = Puccini::TOSCA.compile(ARGV[0], inputs, quirks, resolve, coerce) 37 | puts YAML.dump(clout) 38 | rescue Puccini::TOSCA::Problems => e 39 | puts 'Problems:' 40 | for problem in e.problems 41 | puts YAML.dump(problem) 42 | end 43 | exit 1 44 | end 45 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/file.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // File 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.10 12 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.9 13 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.9 14 | // 15 | 16 | // ([parsing.Reader] signature) 17 | func ReadFile(context *parsing.Context) parsing.EntityPtr { 18 | context.SetReadTag("Profile", "namespace") 19 | 20 | self := tosca_v2_0.NewFile(context) 21 | context.ScriptletNamespace.Merge(DefaultScriptletNamespace) 22 | ignore := []string{"dsl_definitions"} 23 | if context.HasQuirk(parsing.QuirkImportsTopologyTemplateIgnore) { 24 | ignore = append(ignore, "topology_template") 25 | } 26 | if context.HasQuirk(parsing.QuirkAnnotationsIgnore) { 27 | ignore = append(ignore, "annotation_types") 28 | } 29 | context.ValidateUnsupportedFields(append(context.ReadFields(self), ignore...)) 30 | if self.Profile != nil { 31 | context.CanonicalNamespace = self.Profile 32 | } 33 | return self 34 | } 35 | -------------------------------------------------------------------------------- /normal/function-call.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // FunctionCall 9 | // 10 | 11 | type FunctionCall struct { 12 | Key Value `json:"$key,omitempty" yaml:"$key,omitempty"` 13 | ValueMeta *ValueMeta `json:"$meta,omitempty" yaml:"$meta,omitempty"` 14 | 15 | FunctionCall *parsing.FunctionCall `json:"$functionCall" yaml:"$functionCall"` 16 | } 17 | 18 | func NewFunctionCall(functionCall *parsing.FunctionCall) *FunctionCall { 19 | return &FunctionCall{FunctionCall: functionCall} 20 | } 21 | 22 | // Value interface 23 | func (self *FunctionCall) SetKey(key Value) { 24 | self.Key = key 25 | } 26 | 27 | // Value interface 28 | func (self *FunctionCall) SetMeta(valueMeta *ValueMeta) { 29 | self.ValueMeta = CopyValueMeta(valueMeta) 30 | } 31 | 32 | // 33 | // FunctionCalls 34 | // 35 | 36 | type FunctionCalls []*FunctionCall 37 | 38 | // 39 | // FunctionCallMap 40 | // 41 | 42 | type FunctionCallMap map[string]FunctionCalls 43 | 44 | // 45 | // FunctionCallMapMap 46 | // 47 | 48 | type FunctionCallMapMap map[string]FunctionCallMap 49 | -------------------------------------------------------------------------------- /tosca/parsing/grammars.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | // 4 | // GrammarVersion 5 | // 6 | 7 | type GrammarVersion struct { 8 | Version string 9 | ImplicitProfilePath string 10 | } 11 | 12 | type GrammarVersions map[string][]GrammarVersion 13 | 14 | func (self GrammarVersions) Add(keyword string, version string, implicitProfilePath string) { 15 | grammarVersion := GrammarVersion{version, implicitProfilePath} 16 | self[keyword] = append(self[keyword], grammarVersion) 17 | } 18 | 19 | // 20 | // Grammar 21 | // 22 | 23 | type Grammar struct { 24 | Versions GrammarVersions 25 | Readers Readers 26 | InvalidNamespaceCharacters string 27 | } 28 | 29 | func NewGrammar() Grammar { 30 | return Grammar{ 31 | Versions: make(GrammarVersions), 32 | Readers: make(Readers), 33 | } 34 | } 35 | 36 | func (self *Grammar) RegisterVersion(keyword string, version string, implicitProfilePath string) { 37 | self.Versions.Add(keyword, version, implicitProfilePath) 38 | } 39 | 40 | func (self *Grammar) RegisterReader(name string, reader Reader) { 41 | self.Readers[name] = reader 42 | } 43 | -------------------------------------------------------------------------------- /normal/group.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // Group 5 | // 6 | 7 | type Group struct { 8 | ServiceTemplate *ServiceTemplate `json:"-" yaml:"-"` 9 | Name string `json:"-" yaml:"-"` 10 | 11 | Metadata map[string]string `json:"metadata" yaml:"metadata"` 12 | Description string `json:"description" yaml:"description"` 13 | Types EntityTypes `json:"types" yaml:"types"` 14 | Properties Values `json:"properties" yaml:"properties"` 15 | Interfaces Interfaces `json:"interfaces" yaml:"interfaces"` 16 | 17 | Members []*NodeTemplate `json:"-" yaml:"-"` 18 | } 19 | 20 | func (self *ServiceTemplate) NewGroup(name string) *Group { 21 | group := &Group{ 22 | ServiceTemplate: self, 23 | Name: name, 24 | Metadata: make(map[string]string), 25 | Types: make(EntityTypes), 26 | Properties: make(Values), 27 | Interfaces: make(Interfaces), 28 | Members: make([]*NodeTemplate, 0), 29 | } 30 | self.Groups[name] = group 31 | return group 32 | } 33 | 34 | // 35 | // Groups 36 | // 37 | 38 | type Groups map[string]*Group 39 | -------------------------------------------------------------------------------- /normal/relationship.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // Relationship 5 | // 6 | 7 | type Relationship struct { 8 | Requirement *Requirement `json:"-" yaml:"-"` 9 | Name string `json:"-" yaml:"-"` 10 | 11 | Metadata map[string]string `json:"metadata" yaml:"metadata"` 12 | Description string `json:"description" yaml:"description"` 13 | Types EntityTypes `json:"types" yaml:"types"` 14 | Properties Values `json:"properties" yaml:"properties"` 15 | Attributes Values `json:"attributes" yaml:"attributes"` 16 | Interfaces Interfaces `json:"interfaces" yaml:"interfaces"` 17 | } 18 | 19 | func (self *Requirement) NewRelationship() *Relationship { 20 | relationship := &Relationship{ 21 | Requirement: self, 22 | Metadata: make(map[string]string), 23 | Types: make(EntityTypes), 24 | Properties: make(Values), 25 | Attributes: make(Values), 26 | Interfaces: make(Interfaces), 27 | } 28 | self.Relationship = relationship 29 | return relationship 30 | } 31 | 32 | // 33 | // Relationships 34 | // 35 | 36 | type Relationships []*Relationship 37 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/service-file.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // ServiceFile 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.10 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.10 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.9 14 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.9 15 | // 16 | 17 | // ([parsing.Reader] signature) 18 | func ReadServiceFile(context *parsing.Context) parsing.EntityPtr { 19 | context.SetReadTag("ServiceTemplate", "topology_template,ServiceTemplate") 20 | context.SetReadTag("Profile", "namespace") 21 | 22 | self := tosca_v2_0.NewServiceFile(context) 23 | context.ScriptletNamespace.Merge(DefaultScriptletNamespace) 24 | ignore := []string{"dsl_definitions"} 25 | if context.HasQuirk(parsing.QuirkAnnotationsIgnore) { 26 | ignore = append(ignore, "annotation_types") 27 | } 28 | context.ValidateUnsupportedFields(append(context.ReadFields(self), ignore...)) 29 | if self.Profile != nil { 30 | context.CanonicalNamespace = self.Profile 31 | } 32 | return self 33 | } 34 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_2/requirement-assignment.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_2 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // 10 | // RequirementAssignment 11 | // 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.8.2 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.7.2 14 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.7.2 15 | // 16 | 17 | // ([parsing.Reader] signature) 18 | func ReadRequirementAssignment(context *parsing.Context) parsing.EntityPtr { 19 | context.SetReadTag("Count", "") 20 | context.SetReadTag("Directives", "") 21 | context.SetReadTag("Optional", "") 22 | //context.SetReadTag("Allocation", "") 23 | 24 | self := tosca_v2_0.NewRequirementAssignment(context) 25 | 26 | if context.Is(ard.TypeMap) { 27 | // Long notation 28 | context.ValidateUnsupportedFields(append(context.ReadFields(self))) 29 | } else if context.ValidateType(ard.TypeMap, ard.TypeString) { 30 | // Short notation 31 | self.TargetNodeTemplateNameOrTypeName = context.FieldChild("node", context.Data).ReadString() 32 | } 33 | 34 | return self 35 | } 36 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/get_attribute.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 4.5.1 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 4.5.1 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 4.5.1 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 4.5.1 5 | 6 | const tosca = require('tosca.lib.utils'); 7 | 8 | exports.evaluate = function(entity, first) { 9 | // Validate relationship attributes during evaluation 10 | const args = Array.prototype.slice.call(arguments); 11 | 12 | if (args.length >= 4) { 13 | // This looks like a relationship attribute access: [node, requirement, attribute, index] 14 | const vertex = tosca.getModelableEntity.call(this, args[0]); 15 | const requirementName = args[1]; 16 | const attributeName = args[2]; 17 | const relationshipIndex = args[3] || 0; 18 | 19 | // Validate that the relationship and attribute exist, and index is valid 20 | tosca.validateRelationshipAttributeAccess(vertex, requirementName, attributeName, relationshipIndex); 21 | } 22 | 23 | return tosca.getNestedValue.call(this, 'attribute', 'attributes', arguments); 24 | }; 25 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/file.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 5 | "github.com/tliron/go-puccini/tosca/parsing" 6 | ) 7 | 8 | // 9 | // File 10 | // 11 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.10 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.10 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.9 14 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.9 15 | // 16 | 17 | // ([parsing.Reader] signature) 18 | func ReadFile(context *parsing.Context) parsing.EntityPtr { 19 | context.SetReadTag("Profile", "namespace") 20 | 21 | self := tosca_v2_0.NewFile(context) 22 | context.ScriptletNamespace.Merge(DefaultScriptletNamespace) 23 | ignore := []string{"dsl_definitions"} 24 | if context.HasQuirk(parsing.QuirkImportsTopologyTemplateIgnore) { 25 | ignore = append(ignore, "topology_template") 26 | } 27 | if context.HasQuirk(parsing.QuirkAnnotationsIgnore) { 28 | ignore = append(ignore, "annotation_types") 29 | } 30 | context.ValidateUnsupportedFields(append(context.ReadFields(self), ignore...)) 31 | if self.Profile != nil { 32 | context.CanonicalNamespace = self.Profile 33 | } 34 | return self 35 | } 36 | -------------------------------------------------------------------------------- /tosca/parsing/function-call.go: -------------------------------------------------------------------------------- 1 | package parsing 2 | 3 | // Note: This is conceptually part of the "tosca.normal" package, but must be separated to 4 | // avoid circular imports. 5 | 6 | // 7 | // FunctionCall 8 | // 9 | 10 | type FunctionCall struct { 11 | Name string `json:"name" yaml:"name"` 12 | Arguments []any `json:"arguments" yaml:"arguments"` 13 | URL string `json:"url,omitempty" yaml:"url,omitempty"` 14 | Row int `json:"row,omitempty" yaml:"row,omitempty"` 15 | Column int `json:"column,omitempty" yaml:"column,omitempty"` 16 | Path string `json:"path,omitempty" yaml:"path,omitempty"` 17 | } 18 | 19 | func NewFunctionCall(name string, arguments []any, url string, row int, column int, path string) *FunctionCall { 20 | return &FunctionCall{ 21 | Name: name, 22 | Arguments: arguments, 23 | URL: url, 24 | Row: row, 25 | Column: column, 26 | Path: path, 27 | } 28 | } 29 | 30 | func (self *Context) NewFunctionCall(name string, arguments []any) *FunctionCall { 31 | row, column := self.GetLocation() 32 | return NewFunctionCall(name, arguments, self.URL.String(), row, column, self.Path.String()) 33 | } 34 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/functions/union.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-v2.0] @ 10.2.4.1 2 | 3 | exports.evaluate = function() { 4 | if (arguments.length === 0) { 5 | throw 'The $union function expects at least one argument.'; 6 | } 7 | 8 | let result = []; 9 | let seen = new Set(); 10 | 11 | // Process each list argument 12 | for (let i = 0; i < arguments.length; i++) { 13 | let arg = arguments[i]; 14 | 15 | // Validate that the argument is a list/array 16 | if (!Array.isArray(arg)) { 17 | throw 'The $union function argument ' + i + ' must be a list; got ' + (typeof arg); 18 | } 19 | 20 | // Add unique elements to result 21 | for (let j = 0; j < arg.length; j++) { 22 | let element = arg[j]; 23 | 24 | // Create a string key for comparison (handles objects and primitives) 25 | let key = JSON.stringify(element); 26 | 27 | if (!seen.has(key)) { 28 | seen.add(key); 29 | result.push(element); 30 | } 31 | } 32 | } 33 | 34 | return result; 35 | }; -------------------------------------------------------------------------------- /tosca/grammars/tosca_v2_0/event-filter.go: -------------------------------------------------------------------------------- 1 | package tosca_v2_0 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // EventFilter 9 | // 10 | // [TOSCA-v2.0] @ ? 11 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.21 12 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.17 13 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.15 14 | // 15 | 16 | type EventFilter struct { 17 | *Entity `name:"event filter" json:"-" yaml:"-"` 18 | 19 | NodeTemplateNameOrTypeName *string `read:"node"` 20 | RequirementName *string `read:"requirement"` 21 | CapabilityName *string `read:"capability"` 22 | 23 | NodeTemplate *NodeTemplate `lookup:"node,NodeTemplateNameOrTypeName" traverse:"ignore" json:"-" yaml:"-"` 24 | NodeType *NodeType `lookup:"node,NodeTemplateNameOrTypeName" traverse:"ignore" json:"-" yaml:"-"` 25 | } 26 | 27 | func NewEventFilter(context *parsing.Context) *EventFilter { 28 | return &EventFilter{Entity: NewEntity(context)} 29 | } 30 | 31 | // ([parsing.Reader] signature) 32 | func ReadEventFilter(context *parsing.Context) parsing.EntityPtr { 33 | self := NewEventFilter(context) 34 | context.ReadFields(self) 35 | return self 36 | } 37 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/group-policy-trigger.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // GroupPolicyTriggerTrigger 9 | // 10 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-groups/] 11 | // 12 | 13 | type GroupPolicyTrigger struct { 14 | *Entity `name:"group policy trigger"` 15 | 16 | PolicyTriggerTypeName *string `read:"type" mandatory:""` 17 | Parameters Values `read:"parameters,Value"` 18 | 19 | PolicyTriggerType *PolicyTriggerType `lookup:"type,PolicyTriggerTypeName" traverse:"ignore" json:"-" yaml:"-"` 20 | } 21 | 22 | func NewGroupPolicyTrigger(context *parsing.Context) *GroupPolicyTrigger { 23 | return &GroupPolicyTrigger{ 24 | Entity: NewEntity(context), 25 | Parameters: make(Values), 26 | } 27 | } 28 | 29 | // ([parsing.Reader] signature) 30 | func ReadGroupPolicyTrigger(context *parsing.Context) parsing.EntityPtr { 31 | self := NewGroupPolicyTrigger(context) 32 | context.ValidateUnsupportedFields(context.ReadFields(self)) 33 | return self 34 | } 35 | 36 | // 37 | // GroupPolicyTriggers 38 | // 39 | 40 | type GroupPolicyTriggers map[string]*GroupPolicyTrigger 41 | -------------------------------------------------------------------------------- /tosca/parser/parse.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | contextpkg "context" 5 | "errors" 6 | 7 | "github.com/tliron/go-puccini/normal" 8 | ) 9 | 10 | func (self *Context) Parse(context contextpkg.Context) (*normal.ServiceTemplate, error) { 11 | // Phase 1: Read 12 | ok := self.ReadRoot(context, self.URL, self.Bases, "") 13 | self.MergeProblems() 14 | problems := self.GetProblems() 15 | 16 | if !ok { 17 | return nil, errors.New("read error") 18 | } 19 | 20 | if !problems.Empty() { 21 | return nil, errors.New("read problems") 22 | } 23 | 24 | // Phase 2: Namespaces 25 | self.AddNamespaces() 26 | self.LookupNames() 27 | 28 | // Phase 3: Hierarchies 29 | self.AddHierarchies() 30 | 31 | // Phase 4: Inheritance 32 | self.Inherit(nil) 33 | 34 | self.SetInputs(self.Inputs) 35 | 36 | // Phase 5: Rendering 37 | self.Render() 38 | self.MergeProblems() 39 | if !problems.Empty() { 40 | return nil, errors.New("parsing problems") 41 | } 42 | 43 | // Phase 6: Normalization 44 | normalServiceTemplate, ok := self.Normalize() 45 | if !ok || !problems.Empty() { 46 | return nil, errors.New("normalization") 47 | } 48 | 49 | return normalServiceTemplate, nil 50 | } 51 | -------------------------------------------------------------------------------- /scripts/benchmark-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | COUNT=${1:-50} 8 | 9 | mkdir --parents "$ROOT/dist" 10 | 11 | function run () { 12 | puccini-csar create "$ROOT/dist/cloud.tar.gz" "$ROOT/examples/csar" \ 13 | --entry-definitions=main.yaml \ 14 | --other-definitions='other 1.yaml' \ 15 | --other-definitions='other 2.yaml' > /dev/null 2>&1 16 | 17 | puccini-csar create "$ROOT/dist/cloud.csar" "$ROOT/examples/csar" \ 18 | --entry-definitions=main.yaml \ 19 | --other-definitions='other 1.yaml' \ 20 | --other-definitions='other 2.yaml' > /dev/null 2>&1 21 | 22 | puccini-tosca compile "$ROOT/dist/cloud.tar.gz" --coerce > /dev/null 2>&1 23 | 24 | puccini-tosca compile "$ROOT/dist/cloud.csar" --coerce > /dev/null 2>&1 25 | 26 | puccini-tosca compile "$ROOT/dist/cloud.tar.gz" | \ 27 | puccini-clout scriptlet exec tosca.resolve > /dev/null 2>&1 28 | } 29 | 30 | m "running $COUNT times..." 31 | START=$(date +%s.%N) 32 | for (( i = 0; i < "$COUNT"; i++)); do 33 | run 34 | done 35 | END=$(date +%s.%N) 36 | TIME=$(printf %.2f $(bc <<< "$END - $START")) 37 | m "seconds: $TIME" 38 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/group-policy.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // GroupPolicy 9 | // 10 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-groups/] 11 | // 12 | 13 | type GroupPolicy struct { 14 | *Entity `name:"group policy"` 15 | 16 | PolicyTypeName *string `read:"type" mandatory:""` 17 | Properties Values `read:"properties,Value"` 18 | Triggers GroupPolicyTriggers `read:"triggers,GroupPolicyTrigger"` 19 | 20 | PolicyType *PolicyType `lookup:"type,PolicyTypeName" traverse:"ignore" json:"-" yaml:"-"` 21 | } 22 | 23 | func NewGroupPolicy(context *parsing.Context) *GroupPolicy { 24 | return &GroupPolicy{ 25 | Entity: NewEntity(context), 26 | Properties: make(Values), 27 | Triggers: make(GroupPolicyTriggers), 28 | } 29 | } 30 | 31 | // ([parsing.Reader] signature) 32 | func ReadGroupPolicy(context *parsing.Context) parsing.EntityPtr { 33 | self := NewGroupPolicy(context) 34 | context.ValidateUnsupportedFields(context.ReadFields(self)) 35 | return self 36 | } 37 | 38 | // 39 | // GroupPolicies 40 | // 41 | 42 | type GroupPolicies map[string]*GroupPolicy 43 | -------------------------------------------------------------------------------- /scripts/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | #curl --silent --fail --location https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | \ 8 | #sh -s -- -b "$GOPATH/bin" 9 | go install github.com/goreleaser/goreleaser@latest 10 | 11 | # Uses the current tag for the release version 12 | # (So you likely want to tag before running this) 13 | 14 | WORK=$(mktemp --directory) 15 | 16 | function the_end () { 17 | local ERR=$? 18 | rm --recursive --force "$WORK" 19 | exit $ERR 20 | } 21 | 22 | trap the_end EXIT 23 | 24 | # Create a clean copy of our repo 25 | rsync --recursive "$ROOT/" "$WORK" 26 | cd "$WORK" 27 | git clean -xdf 28 | find . -type f -name .gitignore -exec rm {} \; 29 | 30 | git_version 31 | 32 | ARGS= 33 | if [ "$1" == -t ]; then 34 | ARGS='--snapshot --skip=publish' 35 | fi 36 | 37 | VERSION=$VERSION REVISION=$REVISION TIMESTAMP=$TIMESTAMP \ 38 | "$GOPATH/bin/goreleaser" \ 39 | --parallelism=$(nproc) \ 40 | --skip=validate $ARGS 41 | 42 | # Copy releases back here 43 | rm --recursive --force "$ROOT/dist/release" 44 | mkdir --parents "$ROOT/dist" 45 | rsync --recursive "$WORK/dist/" "$ROOT/dist/release" 46 | -------------------------------------------------------------------------------- /tosca/csar/version.go: -------------------------------------------------------------------------------- 1 | package csar 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // 10 | // Version 11 | // 12 | 13 | type Version struct { 14 | Major uint8 `json:"major" yaml:"major"` 15 | Minor uint8 `json:"minor" yaml:"minor"` 16 | } 17 | 18 | func ParseVersion(value string) (*Version, error) { 19 | split := strings.Split(value, ".") 20 | if len(split) != 2 { 21 | return nil, fmt.Errorf("malformed version in TOSCA.meta: %s", value) 22 | } 23 | 24 | major, err := parseDigit(value, split[0]) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | minor, err := parseDigit(value, split[1]) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return &Version{major, minor}, nil 35 | } 36 | 37 | // ([fmt.Stringer] interface) 38 | func (self *Version) String() string { 39 | return fmt.Sprintf("%d.%d", self.Major, self.Minor) 40 | } 41 | 42 | func parseDigit(value string, digit string) (uint8, error) { 43 | d, err := strconv.ParseUint(digit, 10, 8) 44 | 45 | // Seriously, the spec says this should be a single digit (?!) 46 | if (err != nil) || (d > 9) { 47 | return 0, fmt.Errorf("malformed version in TOSCA.meta: %s", value) 48 | } 49 | 50 | return uint8(d), nil 51 | } 52 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple/2.0/policy_types.yaml: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This file contains TOSCA Simple Profile policy type 3 | # definitions. 4 | ########################################################################## 5 | 6 | tosca_definitions_version: tosca_2_0 7 | 8 | metadata: 9 | template_name: policy_types.yaml 10 | template_author: Oasis Open 11 | template_version: "2.0" 12 | 13 | description: > 14 | TOSCA Simple Profile Version policy type definitions 15 | 16 | policy_types: 17 | 18 | Placement: 19 | description: >- 20 | The TOSCA Policy Type definition that is used to govern 21 | placement of TOSCA nodes or groups of nodes. 22 | 23 | Scaling: 24 | description: >- 25 | The TOSCA Policy Type definition that is used to govern scaling 26 | of TOSCA nodes or groups of nodes. 27 | 28 | Update: 29 | description: >- 30 | The TOSCA Policy Type definition that is used to govern update 31 | of TOSCA nodes or groups of nodes. 32 | 33 | Performance: 34 | description: >- 35 | The TOSCA Policy Type definition that is used to declare 36 | performance requirements for TOSCA nodes or groups of nodes. 37 | -------------------------------------------------------------------------------- /examples/1.3/unicode.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | # Puccini supports Unicode everywhere 4 | 5 | metadata: 6 | 7 | template_name: Unicode Example 8 | template_author: Puccini 9 | 10 | node_types: 11 | 12 | 燈泡: 13 | requirements: 14 | - 插座: 插座 15 | 16 | 電源面板: 17 | properties: 18 | 年齡: 19 | type: scalar-unit.time 20 | capabilities: 21 | 主要: 插座 22 | 23 | capability_types: 24 | 25 | 插座: 26 | properties: 27 | 標準: 28 | type: string 29 | 電壓: 30 | type: float 31 | 電頻: 32 | type: scalar-unit.frequency 33 | 34 | relationship_types: 35 | 36 | 插頭: 37 | properties: 38 | 供應商: 39 | type: string 40 | attributes: 41 | IP地址: 42 | type: string 43 | 44 | topology_template: 45 | 46 | node_templates: 47 | 48 | 燈: 49 | type: 燈泡 50 | requirements: 51 | - 插座: 52 | relationship: 53 | type: 插頭 54 | properties: 55 | 供應商: 智能家電行業 56 | 57 | 主面板: 58 | type: 電源面板 59 | properties: 60 | 年齡: 5 d 61 | capabilities: 62 | 主要: 63 | properties: 64 | 標準: 高頻 65 | 電壓: 110.0 66 | 電頻: 120 hz 67 | -------------------------------------------------------------------------------- /examples/2.0/copy.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_2_0 2 | 3 | # "copy" is a convience feature that works like a YAML copy-and-merge 4 | # It is available for both node templates and relationship templates 5 | # The YAML is copied from the source to the target *before* applying the changes 6 | # Note that during the merge of values lists are replaced, not appended to 7 | # This can happen recursively, 8 | # though Puccini does make sure to report a problem if you are copying in a loop 9 | 10 | metadata: 11 | 12 | template_name: Copy Example 13 | template_author: Puccini 14 | 15 | node_types: 16 | 17 | Machine: 18 | properties: 19 | cpu: 20 | type: CPU 21 | 22 | data_types: 23 | 24 | CPU: 25 | properties: 26 | architecture: 27 | type: string 28 | cores: 29 | type: integer 30 | 31 | service_template: 32 | 33 | node_templates: 34 | 35 | server1: 36 | type: Machine 37 | properties: 38 | cpu: 39 | architecture: x86 40 | cores: 4 41 | 42 | server2: 43 | copy: server1 44 | properties: 45 | cpu: 46 | cores: 8 47 | 48 | client: 49 | copy: server2 50 | properties: 51 | cpu: 52 | architecture: ARM 53 | -------------------------------------------------------------------------------- /examples/javascript/imports/endpoints.js: -------------------------------------------------------------------------------- 1 | // This scriptlet gathers all endpoint capabilities and generates a report 2 | 3 | const traversal = require('tosca.lib.traversal'); 4 | const tosca = require('tosca.lib.utils'); 5 | 6 | // "traversal.coerce" calls all intrinsic functions and validates all constraints 7 | traversal.coerce(); 8 | 9 | let endpoints = []; 10 | 11 | for (let v in clout.vertexes) { 12 | let vertex = clout.vertexes[v]; 13 | 14 | // We'll skip vertexes that are not TOSCA node templates 15 | if (!tosca.isNodeTemplate(vertex)) 16 | continue; 17 | 18 | let nodeTemplate = vertex.properties; 19 | 20 | for (let c in nodeTemplate.capabilities) { 21 | let capability = nodeTemplate.capabilities[c]; 22 | 23 | // We'll skip capabilities that do not inherit from Endpoint 24 | if (!('tosca::Endpoint' in capability.types)) 25 | continue; 26 | 27 | // Add endpoint to the report 28 | endpoints.push({ 29 | name: nodeTemplate.name + '.' + c, 30 | protocol: capability.properties.protocol, 31 | port: capability.properties.port, 32 | }); 33 | } 34 | } 35 | 36 | // "puccini.write" will use either YAML (the default), JSON, or XML according to the format selected 37 | // in the command line (use --format to change it) 38 | puccini.write(endpoints); 39 | -------------------------------------------------------------------------------- /examples/python/compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Note that installing `puccini` will also install `ard` 4 | 5 | import sys, argparse, puccini.tosca, ard 6 | 7 | parser = argparse.ArgumentParser(description='Compile TOSCA') 8 | parser.add_argument('url', help='URL to TOSCA file or CSAR') 9 | parser.add_argument('-i', '--input', dest='inputs', nargs='*', action='extend', help='specify input (format is name=value)') 10 | parser.add_argument('-q', '--quirk', dest='quirks', nargs='*', action='extend', help='specify quirk') 11 | parser.add_argument('-r', '--resolve', dest='resolve', default='true', help='whether to resolve') 12 | parser.add_argument('-c', '--coerce', dest='coerce', default='true', help='whether to coerce') 13 | 14 | args = parser.parse_args() 15 | 16 | if args.inputs: 17 | inputs = {} 18 | for i in args.inputs: 19 | k, v = i.split('=') 20 | inputs[k] = ard.decode_yaml(v) 21 | args.inputs = inputs 22 | 23 | try: 24 | clout = puccini.tosca.compile(args.url, args.inputs, args.quirks, args.resolve == 'true', args.coerce == 'true') 25 | ard.write(clout, sys.stdout) 26 | except puccini.tosca.Problems as e: 27 | print('Problems:', file=sys.stderr) 28 | for problem in e.problems: 29 | ard.write(problem, sys.stderr) 30 | sys.exit(1) 31 | -------------------------------------------------------------------------------- /assets/tosca/profiles/implicit/2.0/js/constraints/matches.js: -------------------------------------------------------------------------------- 1 | // [TOSCA-Simple-Profile-YAML-v1.3] @ 3.6.3 2 | // [TOSCA-Simple-Profile-YAML-v1.2] @ 3.6.3 3 | // [TOSCA-Simple-Profile-YAML-v1.1] @ 3.5.2 4 | // [TOSCA-Simple-Profile-YAML-v1.0] @ 3.5.2 5 | 6 | // TOSCA 2.0 operator: matches 7 | const tosca = require('tosca.lib.utils'); 8 | 9 | exports.validate = function(currentPropertyValue) { 10 | const parsed = tosca.parseComparisonArguments(currentPropertyValue, arguments); 11 | if (!parsed) { 12 | return false; 13 | } 14 | 15 | const stringToTest = parsed.val1; 16 | const regexPattern = parsed.val2; 17 | 18 | // Validate arguments 19 | if (stringToTest === undefined || stringToTest === null) { 20 | return false; 21 | } 22 | 23 | if (regexPattern === undefined || regexPattern === null) { 24 | return false; 25 | } 26 | 27 | // Both arguments must be strings 28 | if (typeof stringToTest !== 'string' || typeof regexPattern !== 'string') { 29 | return false; 30 | } 31 | 32 | try { 33 | const regex = new RegExp(regexPattern); 34 | return regex.test(stringToTest); 35 | } catch (e) { 36 | // Invalid regex pattern 37 | return false; 38 | } 39 | }; -------------------------------------------------------------------------------- /examples/1.3/copy.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | # "copy" is a convience feature that works like a YAML copy-and-merge 4 | # It is available for both node templates and relationship templates 5 | # The YAML is copied from the source to the target *before* applying the changes 6 | # Note that during the merge of values lists are replaced, not appended to 7 | # This can happen recursively, 8 | # though Puccini does make sure to report a problem if you are copying in a loop 9 | 10 | metadata: 11 | 12 | template_name: Copy Example 13 | template_author: Puccini 14 | 15 | node_types: 16 | 17 | Machine: 18 | properties: 19 | cpu: 20 | type: CPU 21 | 22 | data_types: 23 | 24 | CPU: 25 | properties: 26 | architecture: 27 | type: string 28 | cores: 29 | type: integer 30 | 31 | topology_template: 32 | 33 | node_templates: 34 | 35 | server1: 36 | type: Machine 37 | properties: 38 | cpu: 39 | architecture: x86 40 | cores: 4 41 | 42 | server2: 43 | copy: server1 44 | properties: 45 | cpu: 46 | cores: 8 47 | 48 | client: 49 | copy: server2 50 | properties: 51 | cpu: 52 | architecture: ARM 53 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/policy-type.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // PolicyType 9 | // 10 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-policy-types/] 11 | // 12 | 13 | type PolicyType struct { 14 | *Entity `name:"policy type"` 15 | Name string `namespace:""` 16 | 17 | Source *string `read:"source" mandatory:""` 18 | PropertyDefinitions PropertyDefinitions `read:"properties,PropertyDefinition"` 19 | } 20 | 21 | func NewPolicyType(context *parsing.Context) *PolicyType { 22 | return &PolicyType{ 23 | Entity: NewEntity(context), 24 | Name: context.Name, 25 | PropertyDefinitions: make(PropertyDefinitions), 26 | } 27 | } 28 | 29 | // ([parsing.Reader] signature) 30 | func ReadPolicyType(context *parsing.Context) parsing.EntityPtr { 31 | self := NewPolicyType(context) 32 | context.ValidateUnsupportedFields(context.ReadFields(self)) 33 | return self 34 | } 35 | 36 | var policyTypeRoot *PolicyType 37 | 38 | // ([parsing.Hierarchical] interface) 39 | func (self *PolicyType) GetParent() parsing.EntityPtr { 40 | return policyTypeRoot 41 | } 42 | 43 | // 44 | // PolicyTypes 45 | // 46 | 47 | type PolicyTypes []*PolicyType 48 | -------------------------------------------------------------------------------- /assets/tosca/profiles/simple-for-nfv/1.0/relationships.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_2 2 | 3 | imports: 4 | - capabilities.yaml 5 | 6 | relationship_types: 7 | 8 | tosca.relationships.nfv.VirtualBindsTo: 9 | metadata: 10 | tosca.normative: 'true' 11 | specification.citation: '[TOSCA-Simple-Profile-NFV-v1.0-csd04]' 12 | specification.location: 5.7.1 13 | description: >- 14 | This relationship type represents an association relationship between VDU and CP node types. 15 | derived_from: tosca.relationships.DependsOn 16 | valid_target_types: [ tosca.capabilities.nfv.VirtualBindable ] 17 | 18 | # ERRATUM: csd04 lacks the definition of tosca.relationships.nfv.Monitor (the derived_from and 19 | # valid_target_types), so we are using the definition in csd03 section 8.4.2. 20 | tosca.relationships.nfv.Monitor: 21 | metadata: 22 | tosca.normative: 'true' 23 | specification.citation: '[TOSCA-Simple-Profile-NFV-v1.0-csd04]' 24 | specification.location: 5.7.2 25 | description: >- 26 | This relationship type represents an association relationship to the Metric capability of VDU 27 | node types. 28 | derived_from: tosca.relationships.ConnectsTo 29 | valid_target_types: [ tosca.capabilities.nfv.Metric ] 30 | -------------------------------------------------------------------------------- /examples/javascript/exec.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | # To execute the scriptlet run: 4 | # puccini-tosca compile examples/javascript/exec.yaml --exec=endpoints 5 | 6 | # Also see: functions.yaml 7 | 8 | metadata: 9 | 10 | template_name: JavaScript Exec Example 11 | template_author: Puccini 12 | 13 | puccini.scriptlet.import:endpoints: imports/endpoints.js 14 | 15 | topology_template: 16 | 17 | node_templates: 18 | 19 | frontend: 20 | type: tosca:LoadBalancer 21 | capabilities: 22 | client: 23 | properties: 24 | port: 80 25 | requirements: 26 | - application: application 27 | 28 | application: 29 | type: tosca:WebApplication 30 | capabilities: 31 | app_endpoint: 32 | properties: 33 | protocol: http 34 | port: 8080 35 | 36 | node_js: 37 | type: tosca:WebServer 38 | capabilities: 39 | admin_endpoint: 40 | properties: 41 | protocol: http 42 | port: 8081 43 | data_endpoint: 44 | properties: 45 | port: 9000 46 | 47 | host: 48 | type: tosca:Compute 49 | capabilities: 50 | endpoint: 51 | properties: 52 | port: 9001 53 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/property-filter.go: -------------------------------------------------------------------------------- 1 | // tosca_v1_3/property_filter_adapter.go 2 | package tosca_v1_3 3 | 4 | import ( 5 | "github.com/tliron/go-ard" 6 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 7 | "github.com/tliron/go-puccini/tosca/parsing" 8 | ) 9 | 10 | // Inherits from tosca_v2_0.PropertyFilter and provides legacy "Constraint..." support 11 | type PropertyFilter struct { 12 | *tosca_v2_0.PropertyFilter 13 | } 14 | 15 | func (p *PropertyFilter) GetConstraintClauses() ConstraintClauses { 16 | // Converts ValidationClauses to ConstraintClauses for backward compatibility 17 | cc := make(ConstraintClauses, len(p.ValidationClauses)) 18 | for i, vc := range p.ValidationClauses { 19 | cc[i] = (*ConstraintClause)(vc) 20 | } 21 | return cc 22 | } 23 | 24 | // Reader override for TOSCA 1.3 grammar 25 | func ReadPropertyFilter(ctx *parsing.Context) parsing.EntityPtr { 26 | 27 | // Rename "constraints" to "validation" for compatibility with 2.0 28 | if m, ok := ctx.Data.(ard.Map); ok { 29 | if c, ok := m["constraints"]; ok { 30 | m["validation"] = c 31 | delete(m, "constraints") 32 | } 33 | } 34 | 35 | v2pf := tosca_v2_0.ReadPropertyFilter(ctx).(*tosca_v2_0.PropertyFilter) 36 | 37 | // Return the 1.3 compatible wrapper 38 | return &PropertyFilter{v2pf} 39 | } 40 | -------------------------------------------------------------------------------- /normal/artifact.go: -------------------------------------------------------------------------------- 1 | package normal 2 | 3 | // 4 | // Artifact 5 | // 6 | 7 | type Artifact struct { 8 | NodeTemplate *NodeTemplate `json:"-" yaml:"-"` 9 | Name string `json:"-" yaml:"-"` 10 | 11 | Description string `json:"description" yaml:"description"` 12 | Types EntityTypes `json:"types" yaml:"types"` 13 | Properties Values `json:"properties" yaml:"properties"` 14 | Filename string `json:"filename" yaml:"filename"` 15 | SourcePath string `json:"sourcePath" yaml:"sourcePath"` 16 | TargetPath string `json:"targetPath" yaml:"targetPath"` 17 | Version string `json:"version" yaml:"version"` 18 | ChecksumAlgorithm string `json:"checksumAlgorithm" yaml:"checksumAlgorithm"` 19 | Checksum string `json:"checksum" yaml:"checksum"` 20 | Credential Value `json:"credential" yaml:"credential"` 21 | } 22 | 23 | func (self *NodeTemplate) NewArtifact(name string) *Artifact { 24 | artifact := &Artifact{ 25 | NodeTemplate: self, 26 | Name: name, 27 | Types: make(EntityTypes), 28 | Properties: make(Values), 29 | } 30 | self.Artifacts[name] = artifact 31 | return artifact 32 | } 33 | 34 | // 35 | // Artifacts 36 | // 37 | 38 | type Artifacts map[string]*Artifact 39 | -------------------------------------------------------------------------------- /wrappers/python/description.md: -------------------------------------------------------------------------------- 1 | Puccini 2 | ======= 3 | 4 | Parse and compile [TOSCA](https://www.oasis-open.org/committees/tosca/) 5 | to [Clout](https://puccini.cloud/clout/). 6 | 7 | Part of the [Puccini](https://puccini.cloud) project. 8 | 9 | This is a work in progress. 10 | 11 | 12 | Installation 13 | ------------ 14 | 15 | At this time only Linux x86_64 platforms are supported. We will update this page 16 | as more platforms are added. In most cases this should work: 17 | 18 | pip install puccini 19 | 20 | If you are using Python 3.11 on most Linuxes then you will get our pre-built binaries 21 | when installing. 22 | 23 | For other environments, Puccini will be built from source. Though Puccini is written 24 | in Go, you do not need Go tooling installed, as it will be downloaded on-demand by 25 | our installer. The only requirement is that your operating system have the `curl` 26 | and `tar` tools. 27 | 28 | 29 | Usage 30 | ----- 31 | 32 | Example: 33 | 34 | ```python 35 | import sys, puccini.tosca, ard 36 | 37 | try: 38 | clout = puccini.tosca.compile('/path/to/my-tosca-service.csar') # can also be a URL 39 | ard.write(clout, sys.stdout) 40 | except puccini.tosca.Problems as e: 41 | print('Problems:', file=sys.stderr) 42 | for problem in e.problems: 43 | ard.write(problem, sys.stderr) 44 | ``` 45 | -------------------------------------------------------------------------------- /examples/javascript/constraints.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: tosca_simple_yaml_1_3 2 | 3 | # To apply the constraints run: 4 | # puccini-tosca compile --coerce examples/javascript/constraints.yaml 5 | 6 | # Also see: functions.yaml 7 | 8 | metadata: 9 | 10 | template_name: JavaScript Constraints Example 11 | template_author: Puccini 12 | 13 | # Use metadata prefixed with "puccini.scriptlet:tosca.constraint." 14 | # Your scriptlet must export a function named "validate" 15 | # The first argument is always the value to validate 16 | # Return "true" if valid 17 | # If invalid, can return "false" or a string explaining the reason it is invalid 18 | # (Note that this is different from "throw", which should be used for errors) 19 | puccini.scriptlet:tosca.constraint.multiple_of: |- 20 | exports.validate = function(value, multiple) { 21 | if (arguments.length !== 2) 22 | throw 'must have 1 argument'; 23 | return (value / multiple) % 1 == 0; 24 | }; 25 | 26 | node_types: 27 | 28 | Rack: 29 | properties: 30 | slots: 31 | type: integer 32 | constraints: 33 | # Our custom constraint 34 | - multiple_of: 4 35 | 36 | topology_template: 37 | 38 | node_templates: 39 | 40 | rack: 41 | type: Rack 42 | properties: 43 | slots: 8 44 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/policy-trigger-type.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-puccini/tosca/parsing" 5 | ) 6 | 7 | // 8 | // PolicyTriggerType 9 | // 10 | // [https://docs.cloudify.co/5.0.5/developer/blueprints/spec-policy-triggers/] 11 | // 12 | 13 | type PolicyTriggerType struct { 14 | *Entity `name:"policy trigger type"` 15 | Name string `namespace:""` 16 | 17 | Source *string `read:"source" mandatory:""` 18 | Parameters PropertyDefinitions `read:"parameters,PropertyDefinition"` 19 | } 20 | 21 | func NewPolicyTriggerType(context *parsing.Context) *PolicyTriggerType { 22 | return &PolicyTriggerType{ 23 | Entity: NewEntity(context), 24 | Name: context.Name, 25 | Parameters: make(PropertyDefinitions), 26 | } 27 | } 28 | 29 | // ([parsing.Reader] signature) 30 | func ReadPolicyTriggerType(context *parsing.Context) parsing.EntityPtr { 31 | self := NewPolicyTriggerType(context) 32 | context.ValidateUnsupportedFields(context.ReadFields(self)) 33 | return self 34 | } 35 | 36 | var policyTriggerTypeRoot *PolicyTriggerType 37 | 38 | // ([parsing.Hierarchical] interface) 39 | func (self *PolicyTriggerType) GetParent() parsing.EntityPtr { 40 | return policyTriggerTypeRoot 41 | } 42 | 43 | // 44 | // PolicyTriggerTypes 45 | // 46 | 47 | type PolicyTriggerTypes []*PolicyTriggerType 48 | -------------------------------------------------------------------------------- /tosca/grammars/tosca_v1_3/schema.go: -------------------------------------------------------------------------------- 1 | package tosca_v1_3 2 | 3 | import ( 4 | "github.com/tliron/go-ard" 5 | "github.com/tliron/go-puccini/tosca/grammars/tosca_v2_0" 6 | "github.com/tliron/go-puccini/tosca/parsing" 7 | ) 8 | 9 | // Embeds tosca_v2_0.Schema for compatibility 10 | type Schema struct{ *tosca_v2_0.Schema } 11 | 12 | // Allows old code to call this method 13 | func (s *Schema) GetConstraintClauses() ConstraintClauses { 14 | if s.ValidationClause == nil { 15 | return nil 16 | } 17 | return ConstraintClauses{s.ValidationClause} 18 | } 19 | 20 | // Reader for TOSCA 1.3 schema 21 | func ReadSchema(ctx *parsing.Context) parsing.EntityPtr { 22 | 23 | // Convert "constraints" to "validation" for compatibility with 2.0 24 | if m, ok := ctx.Data.(ard.Map); ok { 25 | if c, ok := m["constraints"].(ard.List); ok && len(c) > 0 { 26 | // Convert TOSCA 1.3 constraints list to TOSCA 2.0 validation clause 27 | if len(c) == 1 { 28 | // Single constraint: use it directly 29 | m["validation"] = c[0] 30 | } else { 31 | // Multiple constraints: wrap in $and 32 | m["validation"] = ard.Map{"$and": c} 33 | } 34 | delete(m, "constraints") 35 | } 36 | } 37 | 38 | // Delegate to the 2.0 reader 39 | v2s := tosca_v2_0.ReadSchema(ctx).(*tosca_v2_0.Schema) 40 | 41 | // Return the 2.0 entity for compatibility 42 | return v2s 43 | } 44 | -------------------------------------------------------------------------------- /executables/puccini-clout/commands/scriptlet-get.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | contextpkg "context" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/tliron/exturl" 9 | "github.com/tliron/go-kutil/terminal" 10 | "github.com/tliron/go-kutil/util" 11 | "github.com/tliron/go-puccini/clout/js" 12 | ) 13 | 14 | func init() { 15 | scriptletCommand.AddCommand(getCommand) 16 | getCommand.Flags().StringVarP(&output, "output", "o", "", "output to file (default is stdout)") 17 | } 18 | 19 | var getCommand = &cobra.Command{ 20 | Use: "get [NAME] [[Clout PATH or URL]]", 21 | Short: "Get JavaScript scriptlet from Clout", 22 | Long: ``, 23 | Args: cobra.RangeArgs(1, 2), 24 | Run: func(cmd *cobra.Command, args []string) { 25 | scriptletName := args[0] 26 | 27 | var url string 28 | if len(args) == 2 { 29 | url = args[1] 30 | } 31 | 32 | urlContext := exturl.NewContext() 33 | util.OnExitError(urlContext.Release) 34 | 35 | context, cancel := contextpkg.WithTimeout(contextpkg.Background(), time.Duration(timeout*float64(time.Second))) 36 | util.OnExit(cancel) 37 | 38 | clout := LoadClout(context, url, urlContext) 39 | 40 | scriptlet, err := js.GetScriptlet(scriptletName, clout) 41 | util.FailOnError(err) 42 | 43 | if !terminal.Quiet { 44 | err = Transcriber().Write(scriptlet) 45 | util.FailOnError(err) 46 | } 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /tosca/grammars/cloudify_v1_3/metadata.go: -------------------------------------------------------------------------------- 1 | package cloudify_v1_3 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/tliron/go-ard" 7 | "github.com/tliron/go-puccini/tosca/parsing" 8 | ) 9 | 10 | // 11 | // Metadata 12 | // 13 | // Note: not in spec 14 | // 15 | 16 | type Metadata map[string]ard.Value 17 | 18 | // ([parsing.Reader] signature) 19 | func ReadMetadata(context *parsing.Context) parsing.EntityPtr { 20 | var self map[string]ard.Value 21 | 22 | if context.ValidateType(ard.TypeMap) { 23 | metadata := context.ReadStringMap() 24 | if metadata != nil { 25 | self = *metadata 26 | } 27 | } 28 | 29 | if self != nil { 30 | for key, value := range self { 31 | if strings.HasPrefix(key, parsing.MetadataScriptletImportPrefix) { 32 | if v, ok := value.(string); ok { 33 | context.ImportScriptlet(key[len(parsing.MetadataScriptletImportPrefix):], v) 34 | delete(self, key) 35 | } else { 36 | context.MapChild(key, value).ReportValueWrongType(ard.TypeString) 37 | } 38 | } else if strings.HasPrefix(key, parsing.MetadataScriptletPrefix) { 39 | if v, ok := value.(string); ok { 40 | context.EmbedScriptlet(key[len(parsing.MetadataScriptletPrefix):], v) 41 | delete(self, key) 42 | } else { 43 | context.MapChild(key, value).ReportValueWrongType(ard.TypeString) 44 | } 45 | } 46 | } 47 | } 48 | 49 | return self 50 | } 51 | --------------------------------------------------------------------------------