@Rocktavious

Today

I'm going to show you

How you can use a "rubric as code" to improve your organization's service ownership and maturity

Who am I

Platform Team @ OpsLevel

Develop Tools & Support our Developers

HashiCorp - Terraform, Vault

Github, Twitter - @Rocktavious

Kyle Rockman

Agenda

Service Ownership

Service Maturity

Overview of Solution

Live Demo

Recap / Conclusion

What does

Service Ownership

really mean?

(And why does it matter?)

"You Build It, You Run It" – Inputs

  • Autonomy: Dev teams control how their services are built and run in production

  • Accountability: All services have ownership. No more pages for orphaned or neglected services

"You Build It, You Run It" – Outputs

  • Speed: Meeting SLOs? Then ship as fast/as often as you want

  • Resiliency: Deep knowledge of a service and pride in ownership

Ok, What's next?

Service Maturity

Let me explain...

Maturity–it's not magc

Continuous Improvement–the setup

  • Set clear standards (minimums) and goals (aspirations)

  • Put those objectives in context–explain why

  • Prioritize ruthlessly–make it simple for owners to know what to focus on 

Continuous Improvement–the action

  • Owners work through maturity tasks and level up their services incrementally 

  • Iterate and adapt–build the muscles so that service owners are ready for anything

  • Reduce friction–give guidance on how, but don't micromanage (& don't forget automation)

Enter

Building a

Terraform Provider

Data, Data, Data

End User Experiance

API with full CRUD

resource "opslevel_filter" "tier1" {
  name = "foo"
  predicate {
    key = "tier_index"
    type = "equals"
    value = "1"
  }
  connective = "and"
}
resource "opslevel_service" "foo" {
  name = "foo"

  description = "foo service"
  framework   = "rails"
  language    = "ruby"

  lifecycle_alias = data.opslevel_lifecycle.beta.alias
  tier_alias = data.opslevel_tier.tier3.alias
  owner_alias = opslevel_team.foo.alias

  aliases = ["bar", "baz"]
  tags = ["foo:bar"]
}
data "opslevel_rubric_category" "security" {
  filter {
    field = "name"
    value = "Security"
  }
}
data "opslevel_rubric_level" "bronze" {
  filter {
    field = "name"
    value = "Bronze"
  }
}
data "opslevel_team" "devs" {
  alias = "developers"
}
data "opslevel_filter" "tier1" {
  filter {
    field = "name"
    value = "Tier 1"
  }
}

resource "opslevel_check_manual" "example" {
  name = "foo"
  enabled = true
  category = data.opslevel_rubric_category.security.id
  level = data.opslevel_rubric_level.bronze.id
  owner = data.opslevel_team.devs.id
  filter = data.opslevel_filter.tier1.id
  update_frequency {
    starting_data = time_static.initial.id
    time_scale = "week"
    value = 1
  }
  update_requires_comment = false
  notes = "Optional additional info"
}
resource "opslevel_service" "foo" {
  name = "foo"

  description = "foo service"
  framework   = "rails"
  language    = "ruby"

  lifecycle_alias = data.opslevel_lifecycle.beta.alias
  tier_alias = data.opslevel_tier.tier3.alias
  owner_alias = opslevel_team.foo.alias

  aliases = ["bar", "baz"]
  // Collapse all tags into a single list
  tags = ["foo:bar", "baz:bar"]
}
resource "opslevel_service" "foo" {
  name = "foo"

  description = "foo service"
  framework   = "rails"
  language    = "ruby"

  lifecycle_alias = data.opslevel_lifecycle.beta.alias
  tier_alias = data.opslevel_tier.tier3.alias
  owner_alias = opslevel_team.foo.alias

  aliases = ["bar", "baz"]
}

resource "opslevel_service_tag" "foo_environment" {
  service = data.opslevel_service.foo.id

  key = "foo"
  value = "bar"
}

resource "opslevel_service_tag" "baz_environment" {
  service = data.opslevel_service.foo.id

  key = "baz"
  value = "bar"
}

https://github.com/hashicorp/terraform-plugin-docs

func datasourceTier() *schema.Resource {
	return &schema.Resource{
		Read: wrap(datasourceTierRead),
		Schema: map[string]*schema.Schema{
			"filter": getDatasourceFilter(true, []string{"alias", "id", "index", "name"}),
			"alias": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"index": {
				Type:     schema.TypeInt,
				Computed: true,
			},
			"name": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func datasourceTierRead(d *schema.ResourceData, client *opslevel.Client) error {
	results, err := client.ListTiers()
	if err != nil {
		return err
	}

	field := d.Get("filter.0.field").(string)
	value := d.Get("filter.0.value").(string)

	item, itemErr := filterTiers(results, field, value)
	if itemErr != nil {
		return itemErr
	}

	d.SetId(item.Id.(string))
	d.Set("alias", item.Alias)
	d.Set("index", item.Index)
	d.Set("name", item.Name)

	return nil
}
func exportServices(c *opslevel.Client, shell *os.File, directory string) {
  services, err := c.ListServices()
  cobra.CheckErr(err)
  for _, service := range services {
    serviceMainAlias := makeTerraformSlug(service.Aliases[0])
    file := newFile(fmt.Sprintf("%s/opslevel_service_%s.tf", directory, serviceMainAlias), false)
    aliases := flattenAliases(service.Aliases)
    if len(aliases) > 0 {
      aliases = fmt.Sprintf("aliases = [\"%s\"]", aliases)
    }
    tags := flattenTags(service.Tags.Nodes)
    if len(tags) > 0 {
      tags = fmt.Sprintf("tags = [\"%s\"]", tags)
    }
    file.WriteString(templateConfig(serviceConfig, serviceMainAlias, service.Name, service.Description, service.Product, service.Framework, service.Language, flattenLifecycle(service.Lifecycle), flattenTier(service.Tier), flattenOwner(service.Owner), aliases, tags))
    shell.WriteString(fmt.Sprintf("# Service: %s\n", serviceMainAlias))
    shell.WriteString(fmt.Sprintf("terraform import opslevel_service.%s %s\n", serviceMainAlias, service.Id))
    for _, tool := range service.Tools.Nodes {
      toolTerraformName := makeTerraformSlug(fmt.Sprintf("%s_%s", serviceMainAlias, getToolTerraformName(tool)))
      file.WriteString(templateConfig(serviceToolConfig, toolTerraformName, serviceMainAlias, tool.DisplayName, tool.Category, tool.Url, tool.Environment))
      shell.WriteString(fmt.Sprintf("terraform import opslevel_service_tool.%s %s:%s\n", toolTerraformName, service.Id, tool.Id))
    }
    for _, edge := range service.Repositories.Edges {
      for _, serviceRepo := range edge.ServiceRepositories {
        repo := serviceRepo.Repository
        repoName := makeTerraformSlug(repo.DefaultAlias)
        serviceRepoTerraformName := fmt.Sprintf("%s_%s", serviceMainAlias, repoName)
        file.WriteString(templateConfig(serviceRepoConfig, serviceRepoTerraformName, serviceMainAlias, repoName, serviceRepo.DisplayName, serviceRepo.BaseDirectory))
        shell.WriteString(fmt.Sprintf("terraform import opslevel_service_repository.%s %s:%s\n", serviceRepoTerraformName, service.Id, serviceRepo.Id))
      }
    }
    file.Close()
  }
}

Build
Terraform
Provider

Live Demo

Recap

Service Ownership

Service Maturity

OpsLevel as a Solution
Custom Terraform Provider

End to End Solution

 

https://www.opslevel.com/request-demo/

 

@Rocktavious

https://slides.com/rocktavious/terraforming-opslevel

Thanks!

Made with Slides.com