@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!

Terraforming your OpsLevel to 11

By Kyle Rockman

Terraforming your OpsLevel to 11

In this talk, I'll cover some of the common problems we see organizations facing with service ownership, how OpsLevel helps them with these problems, and how we use our favorite tool, Terraform, to do it.

  • 338